From 73b387c0343ab799e3abdfc49b0cd564043c57c0 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Fri, 2 Oct 2026 22:00:19 -0700 Subject: [PATCH] fix: the font size box takes any size typed and tags the default size and font Clover: show which size is the default and allow typing any number. A number typed in Font Size offers that size alone, cut to the half point below, or else says the range OneNote 2010 takes (6 to 130, as its own box refuses others in the lab); Enter on a menu offering nothing for what was typed leaves the size as it was. The size and font the paragraph's style gives, or else the Default font's, read Default in the lists. Assisted-by: claude-opus-5.5 --- crates/canvas/src/editor/format.rs | 25 ++++++++++++++++ crates/snowbound/src/main.rs | 48 ++++++++++++++++++++++-------- crates/ui/src/popup.rs | 4 +++ crates/ui/src/tests.rs | 7 +++-- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/crates/canvas/src/editor/format.rs b/crates/canvas/src/editor/format.rs index 2b3d917895cadb7914a8aab82dc8c834084decb7..a3754e6f87c8e9efa45d11c422078b2cd89000be 100644 --- a/crates/canvas/src/editor/format.rs +++ b/crates/canvas/src/editor/format.rs @@ -356,6 +356,10 @@ pub struct FormatState { pub tags: Vec<(NoteTag, u16)>, /// The stored name of the paragraph style every selected paragraph shares. pub style: Option, + /// The font and size the selected paragraphs' style gives, or else the Default font, + /// where they share one: what their text is in without formatting of its own. + pub style_font: Option, + pub style_size: Option, } impl Toggle { @@ -703,6 +707,11 @@ impl CanvasEditor { .iter() .all(|node| self.list(node).as_ref() == Some(&kind)) }; + let styled = |node: &&PageParagraph| { + (node.style.as_ref()) + .and_then(|style| self.definitions.get(style)) + .map(|definition| &definition.format) + }; Ok(FormatState { toggles: Toggle::ALL .into_iter() @@ -754,6 +763,20 @@ impl CanvasEditor { _ => None, } })), + style_font: common(paragraphs.iter().map(|node| { + Some( + styled(node) + .and_then(|format| format.font.clone()) + .unwrap_or_else(|| self.default_font.face.clone()), + ) + })), + style_size: common(paragraphs.iter().map(|node| { + Some( + styled(node) + .and_then(|format| format.font_size) + .unwrap_or(self.default_font.size), + ) + })), }) } @@ -2665,6 +2688,8 @@ mod tests { (NoteTag::defaults()[2].clone(), 2) ], style: state.style.clone(), + style_font: state.style_font.clone(), + style_size: state.style_size, } ); // Format Painter, a gallery tag and an inserted table write as well. diff --git a/crates/snowbound/src/main.rs b/crates/snowbound/src/main.rs index 7d8de1ba75ebea9f3bbdfaf9c56c0ee4716ad693..e625cc549a14b556fe701d5ee97368bbef18e445 100644 --- a/crates/snowbound/src/main.rs +++ b/crates/snowbound/src/main.rs @@ -2331,6 +2331,11 @@ impl State { current: *name == Some(font.as_str()), heading: name.is_none(), separated: *separated, + shortcut: if name.is_some() && *name == state.style_font.as_deref() { + "Default" + } else { + "" + }, ..Default::default() }) .collect(); @@ -2344,26 +2349,45 @@ impl State { let combo = ui.id("size"); let menu = toolbar_popup("size"); ui::shell::combo(ui, "size", "Font Size", &size, 44.0, menu, fonts_apply); - // A size typed in the field joins the list, in half points as stored. - let mut sizes = SIZES.to_vec(); - if let Some(typed) = ui::popup::query(ui, toolbar_popup("size")) - .and_then(|query| query.trim().parse::().ok()) - .map(|typed| (typed * 2.0).round() / 2.0) - .filter(|typed| onestore::FONT_SIZES.contains(typed) && !sizes.contains(typed)) - { - let at = sizes.partition_point(|size| *size < typed); - sizes.insert(at, typed); - } + // A number typed offers that size alone, cut to the half point below, or else the + // range OneNote 2010 takes, as the lab's OneNote 2010 does. + let typed = ui::popup::query(ui, toolbar_popup("size")).map(str::trim); + let number = typed.and_then(|typed| typed.parse::().ok()); + let sizes = match number.map(|typed| (typed * 2.0).floor() / 2.0) { + Some(typed) if onestore::FONT_SIZES.contains(&typed) => vec![typed], + Some(_) => Vec::new(), + None => SIZES.to_vec(), + }; let labels: Vec<_> = sizes.iter().map(|size| format!("{size}")).collect(); - let items: Vec<_> = labels + let mut items: Vec<_> = labels .iter() - .map(|label| ui::popup::Item { + .zip(&sizes) + .map(|(label, points)| ui::popup::Item { text: label, checked: Some(*label == size), current: *label == size, + shortcut: if state.style_size == Some(*points) { + "Default" + } else { + "" + }, + fallback: number.is_some(), ..Default::default() }) .collect(); + let range = format!( + "Type a size from {} to {}", + onestore::FONT_SIZES.start(), + onestore::FONT_SIZES.end() + ); + if items.is_empty() || typed.is_some_and(|typed| !typed.is_empty()) && number.is_none() { + items.push(ui::popup::Item { + text: &range, + disabled: true, + fallback: true, + ..Default::default() + }); + } let anchor = ui::Anchor::Over(ui.rect(combo).unwrap_or_default()); if let Some(index) = ui::popup::menu(ui, toolbar_popup("size"), anchor, &items, Some(&size)) { diff --git a/crates/ui/src/popup.rs b/crates/ui/src/popup.rs index 5761bb746725c2847967ff3cccd20dfd9b38038b..b34f0e8ace9a93e4de8e325a1220bb3819d9dc0a 100644 --- a/crates/ui/src/popup.rs +++ b/crates/ui/src/popup.rs @@ -579,6 +579,10 @@ fn choose( return Some((mode, index)); } state(ui, id).submenu = Some((matches.key(row), Some(ui.now))); + } else if role == Role::Menu && !typed.is_empty() && keys.contains(&NamedKey::Enter) { + // Enter on a menu offering nothing for what was typed leaves things as they were. + ui.close_popup(id); + return None; } let popup = state(ui, id); popup.query = query; diff --git a/crates/ui/src/tests.rs b/crates/ui/src/tests.rs index c6fe77fc019438f20938cf220ed57ad705efc26b..e5420a71ddd52cfe711b1d50a5fcf9ed5a58491f 100644 --- a/crates/ui/src/tests.rs +++ b/crates/ui/src/tests.rs @@ -1545,15 +1545,18 @@ fn typing_filters_to_the_best_matches_first() { } #[test] -fn nothing_matching_leaves_nothing_to_choose() { +fn nothing_matching_leaves_nothing_to_choose_and_enter_leaves_the_menu() { let mut ui = open_menu(Some("Filter")); ui.event(typed("z")); menu_frame(&mut ui, BELOW, Some("Filter")); ui.event(key(NamedKey::ArrowDown)); - ui.event(key(NamedKey::Enter)); let (_, chosen) = menu_frame(&mut ui, BELOW, Some("Filter")); assert_eq!(chosen, None); assert!(ui.popup_open(menu_id())); + ui.event(key(NamedKey::Enter)); + let (_, chosen) = menu_frame(&mut ui, BELOW, Some("Filter")); + assert_eq!(chosen, None); + assert!(!ui.popup_open(menu_id())); } #[test] -- 2.54.0