Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/boot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,8 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
.menu_style
.unwrap_or(config.options.menu_style);

// The graphical menu only uses the mouse when it is enabled in the configuration.
let graphical_mouse = config.menu_styles.graphical.enable_mouse;
// The settings of the graphical menu, which only uses the mouse when it is enabled.
let graphical = &config.menu_styles.graphical;

// Find the forced boot entry, unless the boot menu is forced.
// The oneshot entry from the bootloader interface is forced by id, and it is used instead of
Expand Down Expand Up @@ -672,7 +672,7 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
menu_timeout,
menu_disabled,
menu_style,
graphical_mouse,
graphical,
&entries,
)
.context("unable to select entry via boot menu")?,
Expand Down
8 changes: 5 additions & 3 deletions crates/boot/src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alloc::vec;
use alloc::vec::Vec;
use anyhow::{Context, Result};
use core::time::Duration;
use edera_sprout_config::MenuStyle;
use edera_sprout_config::{GraphicalMenuConfiguration, MenuStyle};
use eficore::bootloader_interface::BootloaderInterface;
use eficore::platform::timer::PlatformTimer;
use log::warn;
Expand Down Expand Up @@ -177,7 +177,7 @@ pub fn select<'live>(
timeout: Option<Duration>,
menu_disabled: bool,
style: MenuStyle,
graphical_mouse: bool,
graphical: &GraphicalMenuConfiguration,
entries: &'live [BootableEntry],
) -> Result<&'live BootableEntry> {
// A hidden menu gives the user a moment to ask for the menu with a key press.
Expand Down Expand Up @@ -210,7 +210,9 @@ pub fn select<'live>(
// The graphical menu needs a graphics output, which not every machine has.
// If it fails, the simple menu is used instead, which falls back to the basic menu.
MenuStyle::Graphical => GraphicalMenu {
enable_mouse: graphical_mouse,
enable_mouse: graphical.enable_mouse,
enable_logo: graphical.enable_logo,
enable_animation: graphical.enable_animation,
}
.select(timeout, entries)
.or_else(|error| {
Expand Down
42 changes: 27 additions & 15 deletions crates/boot/src/menu/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ const CURSOR: [&str; 16] = [
pub struct GraphicalMenu {
/// Whether the mouse is used when the firmware has a pointing device.
pub enable_mouse: bool,
/// Whether the logo is drawn next to the name.
pub enable_logo: bool,
/// Whether the logo bounces.
pub enable_animation: bool,
}

/// The pointing devices of the firmware.
Expand Down Expand Up @@ -231,8 +235,8 @@ struct Layout {
columns: usize,
/// The row of the line between the entries and the status line.
footer: usize,
/// The column, row, width and height of the logo when it is on the ground.
logo: (usize, usize, usize, usize),
/// The column, row, width and height of the logo when it is on the ground, if it is shown.
logo: Option<(usize, usize, usize, usize)>,
/// How many pixels the logo rises while it bounces.
bounce: usize,
/// The column, row and scale of the name, if there is room for it.
Expand All @@ -243,7 +247,7 @@ struct Layout {

impl Layout {
/// Calculate the layout of `entries` on a framebuffer of `screen` width and height.
fn new(screen: (usize, usize), entries: &[BootableEntry]) -> Self {
fn new(screen: (usize, usize), entries: &[BootableEntry], logo: bool, animate: bool) -> Self {
let (width, height) = screen;

// Large screens get larger text so that it stays readable, but not so large that the
Expand Down Expand Up @@ -286,16 +290,18 @@ impl Layout {
(header, visible) = fit(logo_height);
}
let logo_width = logo_height * LOGO_WIDTH / LOGO_HEIGHT;
let bounce = logo_height / 16;
let bounce = if animate { logo_height / 16 } else { 0 };

// The name is next to the logo, if the two fit together.
let title_scale = (logo_height / 32).clamp(scale, scale * 3);
let title_width = TITLE.len() * GLYPH_SIZE * title_scale;
let fits = logo_width + cell + title_width <= width.saturating_sub(margin * 2);
let group = if fits {
logo_width + cell + title_width
} else {
logo_width
// Without the logo, the name is alone and takes the place of the group.
let shown = if logo { logo_width + cell } else { 0 };
let fits = shown + title_width <= width.saturating_sub(margin * 2);
let group = match (logo, fits) {
(true, true) => shown + title_width,
(true, false) => logo_width,
(false, _) => title_width,
};
let group_x = width.saturating_sub(group) / 2;

Expand Down Expand Up @@ -325,10 +331,10 @@ impl Layout {
text_x: panel_x + padding + cell,
columns: list_width.saturating_sub(cell * 2) / cell,
footer: list_y + list + cell,
logo: (group_x, top + bounce, logo_width, logo_height),
logo: logo.then_some((group_x, top + bounce, logo_width, logo_height)),
bounce,
title: fits.then_some((
group_x + logo_width + cell,
group_x + shown,
top + bounce + logo_height.saturating_sub(GLYPH_SIZE * title_scale) / 2,
title_scale,
)),
Expand Down Expand Up @@ -375,7 +381,11 @@ impl Screen {
/// Open every display that the firmware has, or fail if it has none.
/// The console splitter has a graphics output that draws to every display. It has no device
/// path, so it is only used if there are no outputs for the displays themselves.
fn open_all(entries: &[BootableEntry]) -> Result<Vec<Self>> {
fn open_all(
entries: &[BootableEntry],
enable_logo: bool,
enable_animation: bool,
) -> Result<Vec<Self>> {
let handles = uefi::boot::find_handles::<GraphicsOutput>()
.context("unable to find a graphics output")?;
let has_device_path = |handle: &Handle| {
Expand Down Expand Up @@ -412,7 +422,7 @@ impl Screen {
) else {
continue;
};
let layout = Layout::new((width, height), entries);
let layout = Layout::new((width, height), entries, enable_logo, enable_animation);
render_base(&mut base, &layout);
screens.push(Self {
gop,
Expand Down Expand Up @@ -604,7 +614,9 @@ fn draw_centered(

/// Draw the logo above the entries, bouncing a little with each of `ticks`.
fn draw_logo(fb: &mut Framebuffer, layout: &Layout, ticks: usize) {
let (x, floor, width, height) = layout.logo;
let Some((x, floor, width, height)) = layout.logo else {
return;
};

// The logo rises and falls by up to `bounce` pixels.
let phase = ticks % BOUNCE_TICKS;
Expand Down Expand Up @@ -930,7 +942,7 @@ impl BootMenu for GraphicalMenu {
return Ok(&entries[default]);
}

let mut screens = Screen::open_all(entries)?;
let mut screens = Screen::open_all(entries, self.enable_logo, self.enable_animation)?;
let mut mouse = self.enable_mouse.then(Mouse::open).flatten();

let result = uefi::system::with_stdin(|input| {
Expand Down
37 changes: 36 additions & 1 deletion crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,37 @@ pub struct MenuStylesConfiguration {
}

/// Settings for the graphical boot menu.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GraphicalMenuConfiguration {
/// Enables selecting entries with the mouse, and shows the cursor. When disabled, or when
/// the firmware has no pointing device, entries are selected with the keyboard only.
#[serde(rename = "enable-mouse", default)]
pub enable_mouse: bool,

/// Shows the Sprout logo next to the name above the entries. When disabled, only the name is
/// shown.
#[serde(rename = "enable-logo", default = "default_true")]
pub enable_logo: bool,

/// Animates the logo, which bounces. When disabled, the logo stays still.
#[serde(rename = "enable-animation", default = "default_true")]
pub enable_animation: bool,
}

/// The default graphical menu settings, which match an empty graphical section.
impl Default for GraphicalMenuConfiguration {
fn default() -> Self {
Self {
enable_mouse: false,
enable_logo: true,
enable_animation: true,
}
}
}

/// The default for settings that are on unless they are turned off.
fn default_true() -> bool {
true
}

/// The default configuration, which matches an empty configuration file.
Expand Down Expand Up @@ -203,6 +228,16 @@ mod tests {
assert!(!empty().menu_styles.graphical.enable_mouse);
}

#[test]
fn graphical_logo_and_animation_are_on_by_default() {
let graphical = RootConfiguration::default().menu_styles.graphical;
assert!(graphical.enable_logo);
assert!(graphical.enable_animation);
let graphical = empty().menu_styles.graphical;
assert!(graphical.enable_logo);
assert!(graphical.enable_animation);
}

#[test]
fn bls_strict_mode_is_off_by_default() {
assert!(!RootConfiguration::default().options.bls_strict_mode);
Expand Down
10 changes: 7 additions & 3 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,15 @@ Settings for a particular menu style. Only the graphical menu has any.
```toml
[menu-styles.graphical]
enable-mouse = true
enable-logo = false
enable-animation = false
```

| Key | Type | Default | Meaning |
|----------------|---------|---------|----------------------------------------------------------------------|
| `enable-mouse` | boolean | `false` | Select entries with the mouse and show the cursor. Without it, or when the firmware has no pointing device, the menu takes the keyboard only. |
| Key | Type | Default | Meaning |
|--------------------|---------|---------|------------------------------------------------------------------|
| `enable-mouse` | boolean | `false` | Select entries with the mouse and show the cursor. Without it, or when the firmware has no pointing device, the menu takes the keyboard only. |
| `enable-logo` | boolean | `true` | Show the Sprout logo next to the name. Without it, only the name is shown. |
| `enable-animation` | boolean | `true` | Bounce the logo. Without it, the logo stays still. |

## `drivers`

Expand Down
Loading