diff --git a/crates/ui-egui/src/floating.rs b/crates/ui-egui/src/floating.rs index 80942dd..ddc3e27 100644 --- a/crates/ui-egui/src/floating.rs +++ b/crates/ui-egui/src/floating.rs @@ -106,9 +106,25 @@ pub struct BarButton { pub act: Act, } +/// The font name and font size boxes shown on a mini toolbar, as on the ribbon. The ribbon keeps +/// these up to date in egui memory under [`FONT_BOXES_KEY`], and the page menu reads them. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct FontBoxes { + pub family: String, + pub size: String, + pub families: Vec, + pub families_note: Option, + pub sizes: Vec, +} + +/// The egui memory key under which the ribbon stores the [`FontBoxes`] for this frame. +pub const FONT_BOXES_KEY: &str = "notecraft-font-boxes"; + /// A mini toolbar: rows of buttons. Plain data, so any place can build its own. #[derive(Clone, Debug, Default, PartialEq)] pub struct Bar { + /// The font boxes, drawn in a row above the buttons, when there are any. + pub fonts: Option, pub rows: Vec>, } @@ -124,6 +140,7 @@ pub fn text_bar() -> Bar { let m = Glyph::Mark; let i = Glyph::Icon; Bar { + fonts: None, rows: vec![ vec![ button(m(Mark::Bold), "Bold", "Bold (Ctrl+B)", Act::Mark("bold")), @@ -145,6 +162,11 @@ pub fn text_bar() -> Bar { } } +/// The mini toolbar with the font boxes on top, as on the ribbon. +pub fn text_bar_with_fonts(fonts: FontBoxes) -> Bar { + Bar { fonts: Some(fonts), ..text_bar() } +} + const BTN: f32 = 26.0; /// What drawing a bar did. @@ -159,7 +181,7 @@ pub struct BarOut { /// above, and pushes the act of a pressed button into `acts`. `id` must differ between bars that /// can be open at once. pub fn show_bar(ctx: &Context, id: Id, anchor: Rect, bar: &Bar, t: &Tokens, acts: &mut Vec) -> BarOut { - let rows = bar.rows.len() as f32; + let rows = bar.rows.len() as f32 + if bar.fonts.is_some() { 1.0 } else { 0.0 }; let room_above = anchor.min.y - ctx.content_rect().min.y; let want = rows * (BTN + 2.0) + 14.0; let (pivot, at) = if room_above >= want + 4.0 { @@ -171,6 +193,46 @@ pub fn show_bar(ctx: &Context, id: Id, anchor: Rect, bar: &Bar, t: &Tokens, acts let area = Area::new(id).order(Order::Foreground).pivot(pivot).fixed_pos(at).constrain(true).show(ctx, |ui| { Frame::popup(ui.style()).inner_margin(egui::Margin::same(4)).show(ui, |ui| { ui.spacing_mut().item_spacing = vec2(2.0, 2.0); + if let Some(fonts) = &bar.fonts { + ui.horizontal(|ui| { + let families = crate::combo::show( + ui, + t.text, + crate::combo::Spec { + id: id.with("font-family"), + box_label: crate::tl_id!("ui.font_family_box"), + arrow_label: crate::tl_id!("ui.font_family"), + shown: &fonts.family, + width: 96.0, + items: &fonts.families, + empty_note: fonts.families_note.as_deref(), + accept: &|typed| notecraft_formats::markdown::valid_font_family(typed), + }, + ); + if let Some(name) = families.chosen { + acts.push(Act::FontFamily(name)); + picked = true; + } + let size = crate::combo::show( + ui, + t.text, + crate::combo::Spec { + id: id.with("font-size"), + box_label: crate::tl_id!("ui.font_size_box"), + arrow_label: crate::tl_id!("ui.font_size"), + shown: &fonts.size, + width: 38.0, + items: &fonts.sizes, + empty_note: None, + accept: &|typed| typed.parse::().is_ok_and(|size| crate::ribbon::FONT_SIZES.contains(&size)), + }, + ); + if let Some(chosen) = size.chosen.and_then(|typed| typed.parse::().ok()) { + acts.push(Act::FontSize(chosen)); + picked = true; + } + }); + } for row in &bar.rows { ui.horizontal(|ui| { for b in row { @@ -224,7 +286,7 @@ mod tests { #[test] fn a_bar_can_be_built_from_data_and_drawn_anywhere() { let t = Theme::DEFAULT.tokens(); - let bar = Bar { rows: vec![vec![button(Glyph::Mark(Mark::Cut), "Cut", "Cut", Act::Clipboard("cut"))]] }; + let bar = Bar { fonts: None, rows: vec![vec![button(Glyph::Mark(Mark::Cut), "Cut", "Cut", Act::Clipboard("cut"))]] }; let ctx = Context::default(); let mut acts = Vec::new(); let mut out = None; diff --git a/crates/ui-egui/src/pagemenu.rs b/crates/ui-egui/src/pagemenu.rs index 5506517..bc961c8 100644 --- a/crates/ui-egui/src/pagemenu.rs +++ b/crates/ui-egui/src/pagemenu.rs @@ -56,7 +56,8 @@ pub fn show(ui: &mut Ui, body: egui::Rect, acts: &mut Vec, t: &Tokens) { }) .response; // The mini toolbar floats over the menu, as it does in OneNote. - let bar = floating::show_bar(ui.ctx(), id().with("bar"), area.rect, &floating::text_bar(), t, acts); + let fonts = ui.ctx().data(|d| d.get_temp::(egui::Id::new(floating::FONT_BOXES_KEY))).unwrap_or_default(); + let bar = floating::show_bar(ui.ctx(), id().with("bar"), area.rect, &floating::text_bar_with_fonts(fonts), t, acts); picked |= bar.picked; let away = |p: egui::Pos2| !area.rect.contains(p) && !bar.rect.contains(p); diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs index 5aae0e6..b29934f 100644 --- a/crates/ui-egui/src/ribbon.rs +++ b/crates/ui-egui/src/ribbon.rs @@ -910,6 +910,14 @@ fn home_tab(ui: &mut Ui, view: &View<'_>, t: &Tokens, acts: &mut Vec) { accept: &|typed| typed.parse::().is_ok_and(|size| FONT_SIZES.contains(&size)), }, ); + let boxes = crate::floating::FontBoxes { + family: view.font_family.unwrap_or("").to_owned(), + size: shown.to_owned(), + families: families.clone(), + families_note: families_note.clone(), + sizes: sizes.clone(), + }; + ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new(crate::floating::FONT_BOXES_KEY), boxes)); if let Some(size) = size_out.chosen.and_then(|typed| typed.parse::().ok()) { acts.push(Act::FontSize(size)); } diff --git a/crates/ui-egui/tests/ui.rs b/crates/ui-egui/tests/ui.rs index 9f815e5..a24be1c 100644 --- a/crates/ui-egui/tests/ui.rs +++ b/crates/ui-egui/tests/ui.rs @@ -5129,6 +5129,14 @@ fn right_click_on_selected_words_keeps_them_selected_and_shows_the_mini_toolbar( } } +/// The floating toolbar has the same font name and font size boxes as the ribbon (owner, 2026-11-12). +#[test] +fn the_floating_toolbar_has_the_font_boxes() { + let (_t, h) = right_click_on_selected_words("ui-floating-fonts"); + assert!(h.query_all_by_label("Font size").count() >= 2, "the size box is on the ribbon and on the floating toolbar"); + assert!(h.query_all_by_label("Font name").count() >= 2, "the font name box is on the ribbon and on the floating toolbar"); +} + /// A button on the toolbar acts on the words that were selected when the menu was opened. #[test] fn bold_on_the_mini_toolbar_wraps_the_selected_words() {