diff --git a/crates/boot/src/main.rs b/crates/boot/src/main.rs index e78d18c..643e1a0 100644 --- a/crates/boot/src/main.rs +++ b/crates/boot/src/main.rs @@ -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 @@ -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")?, diff --git a/crates/boot/src/menu.rs b/crates/boot/src/menu.rs index be03e42..8c61bb0 100644 --- a/crates/boot/src/menu.rs +++ b/crates/boot/src/menu.rs @@ -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; @@ -177,7 +177,7 @@ pub fn select<'live>( timeout: Option, 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. @@ -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| { diff --git a/crates/boot/src/menu/graphical.rs b/crates/boot/src/menu/graphical.rs index 319ba3b..a2f86ed 100644 --- a/crates/boot/src/menu/graphical.rs +++ b/crates/boot/src/menu/graphical.rs @@ -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. @@ -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. @@ -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 @@ -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; @@ -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, )), @@ -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> { + fn open_all( + entries: &[BootableEntry], + enable_logo: bool, + enable_animation: bool, + ) -> Result> { let handles = uefi::boot::find_handles::() .context("unable to find a graphics output")?; let has_device_path = |handle: &Handle| { @@ -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, @@ -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; @@ -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| { diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d8f9f3d..8456961 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -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. @@ -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); diff --git a/docs/config.md b/docs/config.md index 5ab7485..d9fa75c 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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`