authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 21:40:11-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-03 01:50:33-07:00
log0fe456a8909d9b9991afbe28c28f915d031ca44f
tree2a4f6a8e8278499eb545761682cebed6895d558b
parenta4a0ccf055b14aa599b027e96a679054fea6b5a2
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: the Themes dialog and its entry points are Customize Font Styles, with the Styles icon

Clover: the right-click menus' Theme, the Format menu's and palette's Customize Theme and the Styles gallery's Customize all open the same dialog, so they and the dialog share one name, and the Styles button's Aa icon; scoped, the dialog reads Customize Font Styles for This Section. In the gallery it sits under a rule, where OneNote 2010 keeps Clear Formatting, and in context menus it comes last, under a rule. A gallery group can start under a rule, which More Symbols gains too. Assisted-by: claude-opus-5.5

9 files changed, 55 insertions(+), 16 deletions(-)

crates/snowbound/src/art.rs-2
...@@ -110,7 +110,6 @@ pub const CLOSE_NOTEBOOK: &[&str] = art!(...@@ -110,7 +110,6 @@ pub const CLOSE_NOTEBOOK: &[&str] = art!(
110pub const CODE: &[&str] = art!("icons/code");110pub const CODE: &[&str] = art!("icons/code");
111pub const COMMAND_PALETTE: &[&str] = art!("icons/palette", "icons/badge-command");111pub const COMMAND_PALETTE: &[&str] = art!("icons/palette", "icons/badge-command");
112pub const COPY_LINK: &[&str] = art!("icons/page", "icons/badge-link");112pub const COPY_LINK: &[&str] = art!("icons/page", "icons/badge-link");
113pub const CUSTOMIZE_THEME: &[&str] = art!("icons/theme", "icons/badge-edit");
114pub const CUSTOMIZE_TAGS: &[&str] = art!("icons/tag", "icons/badge-edit");113pub const CUSTOMIZE_TAGS: &[&str] = art!("icons/tag", "icons/badge-edit");
115pub const DELETE: &[&str] = art!("icons/delete");114pub const DELETE: &[&str] = art!("icons/delete");
116pub const EMPTY_RECYCLE_BIN: &[&str] = art!("icons/recycle-bin", "icons/badge-remove");115pub const EMPTY_RECYCLE_BIN: &[&str] = art!("icons/recycle-bin", "icons/badge-remove");
...@@ -195,7 +194,6 @@ pub const SNAP_TO_GRID: &[&str] = art!("icons/snap-to-grid");...@@ -195,7 +194,6 @@ pub const SNAP_TO_GRID: &[&str] = art!("icons/snap-to-grid");
195pub const STOP: &[&str] = art!("icons/stop");194pub const STOP: &[&str] = art!("icons/stop");
196pub const SUBPAGE: &[&str] = art!("icons/subpage");195pub const SUBPAGE: &[&str] = art!("icons/subpage");
197pub const SYNC_NOW: &[&str] = art!("icons/sync-now");196pub const SYNC_NOW: &[&str] = art!("icons/sync-now");
198pub const THEME: &[&str] = art!("icons/theme");
199pub const TO_DO: &[&str] = art!("icons/to-do");197pub const TO_DO: &[&str] = art!("icons/to-do");
200pub const UPDATE: &[&str] = art!("icons/update");198pub const UPDATE: &[&str] = art!("icons/update");
201pub const ZOOM_ACTUAL: &[&str] = art!("icons/zoom-actual");199pub const ZOOM_ACTUAL: &[&str] = art!("icons/zoom-actual");
crates/snowbound/src/background.rs+3
...@@ -89,18 +89,21 @@ pub fn menu(...@@ -89,18 +89,21 @@ pub fn menu(
89 let groups = [89 let groups = [
90 ui::popup::Group {90 ui::popup::Group {
91 heading: "Page Color",91 heading: "Page Color",
92 ruled: false,
92 cells: 2 + PAGE_COLORS.len(),93 cells: 2 + PAGE_COLORS.len(),
93 columns: COLUMNS,94 columns: COLUMNS,
94 size: SWATCH,95 size: SWATCH,
95 },96 },
96 ui::popup::Group {97 ui::popup::Group {
97 heading: "Background",98 heading: "Background",
99 ruled: false,
98 cells: 2 + ART.len(),100 cells: 2 + ART.len(),
99 columns: 3,101 columns: 3,
100 size: [TILE[0] + 8.0, TILE[1] + 8.0],102 size: [TILE[0] + 8.0, TILE[1] + 8.0],
101 },103 },
102 ui::popup::Group {104 ui::popup::Group {
103 heading: "Rule Lines",105 heading: "Rule Lines",
106 ruled: false,
104 cells: 1 + RULE_LINES.len(),107 cells: 1 + RULE_LINES.len(),
105 columns: COLUMNS,108 columns: COLUMNS,
106 size: [RULES; 2],109 size: [RULES; 2],
crates/snowbound/src/commands.rs+1-1
...@@ -596,7 +596,7 @@ pub const COMMANDS: &[Command] = &[...@@ -596,7 +596,7 @@ pub const COMMANDS: &[Command] = &[
596 row!(Id::Style(8), "Quote", NONE, NONE),596 row!(Id::Style(8), "Quote", NONE, NONE),
597 row!(Id::Style(9), "Code", NONE, NONE),597 row!(Id::Style(9), "Code", NONE, NONE),
598 row!(Id::Style(10), "Normal", NONE, NONE),598 row!(Id::Style(10), "Normal", NONE, NONE),
599 row!(Id::Themes, "Customize Theme", NONE, NONE),599 row!(Id::Themes, "Customize Font Styles", NONE, NONE),
600 row!(Id::CustomizeTags, "Customize Tags", NONE, NONE),600 row!(Id::CustomizeTags, "Customize Tags", NONE, NONE),
601 row!(601 row!(
602 Id::RemoveTags,602 Id::RemoveTags,
crates/snowbound/src/main.rs+3-1
...@@ -5184,6 +5184,7 @@ fn list_gallery(...@@ -5184,6 +5184,7 @@ fn list_gallery(
5184 let groups = [(headings[0], recent.len()), (headings[1], count + 1)].map(|(heading, cells)| {5184 let groups = [(headings[0], recent.len()), (headings[1], count + 1)].map(|(heading, cells)| {
5185 ui::popup::Group {5185 ui::popup::Group {
5186 heading,5186 heading,
5187 ruled: false,
5187 cells,5188 cells,
5188 columns: 5,5189 columns: 5,
5189 size,5190 size,
...@@ -5228,6 +5229,7 @@ fn pen_gallery(...@@ -5228,6 +5229,7 @@ fn pen_gallery(
5228 const CELL: [f32; 2] = [40.0, 24.0];5229 const CELL: [f32; 2] = [40.0, 24.0];
5229 let groups = [ui::popup::Group {5230 let groups = [ui::popup::Group {
5230 heading: "Pens",5231 heading: "Pens",
5232 ruled: false,
5231 cells: pens.len(),5233 cells: pens.len(),
5232 columns: 5,5234 columns: 5,
5233 size: CELL,5235 size: CELL,
...@@ -5666,7 +5668,7 @@ fn artwork(id: commands::Id) -> Option<&'static [&'static str]> {...@@ -5666,7 +5668,7 @@ fn artwork(id: commands::Id) -> Option<&'static [&'static str]> {
5666 Cmd::Style(8) => art::QUOTE,5668 Cmd::Style(8) => art::QUOTE,
5667 Cmd::Style(9) => art::CODE,5669 Cmd::Style(9) => art::CODE,
5668 Cmd::Style(_) => art::NORMAL,5670 Cmd::Style(_) => art::NORMAL,
5669 Cmd::Themes => art::CUSTOMIZE_THEME,5671 Cmd::Themes => art::STYLES,
5670 Cmd::OpenShared => art::OPEN_SHARED,5672 Cmd::OpenShared => art::OPEN_SHARED,
5671 Cmd::LiveShare => art::LIVE_SHARE,5673 Cmd::LiveShare => art::LIVE_SHARE,
5672 Cmd::MarkdownShortcuts => art::MARKDOWN,5674 Cmd::MarkdownShortcuts => art::MARKDOWN,
crates/snowbound/src/menus.rs+11-5
...@@ -151,7 +151,7 @@ impl Action {...@@ -151,7 +151,7 @@ impl Action {
151 Action::NewGroup => art::NEW_SECTION_GROUP,151 Action::NewGroup => art::NEW_SECTION_GROUP,
152 Action::Reveal => art::FOLDER,152 Action::Reveal => art::FOLDER,
153 Action::Close => art::CLOSE_NOTEBOOK,153 Action::Close => art::CLOSE_NOTEBOOK,
154 Action::Theme => art::THEME,154 Action::Theme => art::STYLES,
155 Action::Colors => art::SECTION_COLOR,155 Action::Colors => art::SECTION_COLOR,
156 Action::Sync => art::SYNC_NOW,156 Action::Sync => art::SYNC_NOW,
157 Action::NewNotebook => art::NEW_NOTEBOOK,157 Action::NewNotebook => art::NEW_NOTEBOOK,
...@@ -334,6 +334,11 @@ impl State {...@@ -334,6 +334,11 @@ impl State {
334 (action, item)334 (action, item)
335 };335 };
336 let nowhere = || self.destinations(target).is_empty();336 let nowhere = || self.destinations(target).is_empty();
337 // Last on a page, section or notebook, under a rule.
338 let styles = || {
339 let title = commands::command(commands::Id::Themes).title;
340 item(Action::Theme, title, false, true)
341 };
337 match target {342 match target {
338 // OneNote's Move or Copy takes a page out of the bin; nothing else changes it.343 // OneNote's Move or Copy takes a page out of the bin; nothing else changes it.
339 Target::Page { path, .. } if crate::recycle::binned(path) => vec![344 Target::Page { path, .. } if crate::recycle::binned(path) => vec![
...@@ -360,11 +365,11 @@ impl State {...@@ -360,11 +365,11 @@ impl State {
360 item(Action::NewPage, "New Page", false, true),365 item(Action::NewPage, "New Page", false, true),
361 // OneNote offers this beside New Page, which is a plain + here.366 // OneNote offers this beside New Page, which is a plain + here.
362 item(Action::NewSubpage, "New Subpage", false, false),367 item(Action::NewSubpage, "New Subpage", false, false),
363 item(Action::Theme, "Theme", false, true),
364 ];368 ];
365 // Levels and versions are known once the section is open.369 // Levels and versions are known once the section is open.
366 let Some(session) = self.session.as_ref().filter(|_| self.open(library, path))370 let Some(session) = self.session.as_ref().filter(|_| self.open(library, path))
367 else {371 else {
372 actions.push(styles());
368 return actions;373 return actions;
369 };374 };
370 let pages = &session.pages;375 let pages = &session.pages;
...@@ -393,6 +398,7 @@ impl State {...@@ -393,6 +398,7 @@ impl State {
393 session.page_versions(*space).is_empty(),398 session.page_versions(*space).is_empty(),
394 true,399 true,
395 ),400 ),
401 styles(),
396 ]);402 ]);
397 actions403 actions
398 }404 }
...@@ -423,7 +429,7 @@ impl State {...@@ -423,7 +429,7 @@ impl State {
423 section_file(library, path).is_none(),429 section_file(library, path).is_none(),
424 false,430 false,
425 ),431 ),
426 item(Action::Theme, "Theme", false, false),432 styles(),
427 ]);433 ]);
428 }434 }
429 actions435 actions
...@@ -493,12 +499,11 @@ impl State {...@@ -493,12 +499,11 @@ impl State {
493 library.folder().is_none(),499 library.folder().is_none(),
494 true,500 true,
495 ),501 ),
496 item(Action::Theme, "Theme", false, false),
497 item(502 item(
498 Action::RecycleBin,503 Action::RecycleBin,
499 "Notebook Recycle Bin",504 "Notebook Recycle Bin",
500 library.catalog().is_none(),505 library.catalog().is_none(),
501 true,506 false,
502 ),507 ),
503 item(508 item(
504 Action::Properties,509 Action::Properties,
...@@ -506,6 +511,7 @@ impl State {...@@ -506,6 +511,7 @@ impl State {
506 library.catalog().is_none(),511 library.catalog().is_none(),
507 false,512 false,
508 ),513 ),
514 styles(),
509 ]515 ]
510 }516 }
511 Target::Closed(_) | Target::Server(_) => {517 Target::Closed(_) | Target::Server(_) => {
crates/snowbound/src/symbol.rs+2
...@@ -82,12 +82,14 @@ pub fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, symbols: &[char]) -> Option<...@@ -82,12 +82,14 @@ pub fn gallery(ui: &mut Ui, id: Id, anchor: Anchor, symbols: &[char]) -> Option<
82 let groups = [82 let groups = [
83 ui::popup::Group {83 ui::popup::Group {
84 heading: "",84 heading: "",
85 ruled: false,
85 cells: symbols.len(),86 cells: symbols.len(),
86 columns: 5,87 columns: 5,
87 size: [SIZE; 2],88 size: [SIZE; 2],
88 },89 },
89 ui::popup::Group {90 ui::popup::Group {
90 heading: "",91 heading: "",
92 ruled: true,
91 cells: 1,93 cells: 1,
92 columns: 1,94 columns: 1,
93 size: [5.0 * SIZE, 28.0],95 size: [5.0 * SIZE, 28.0],
crates/snowbound/src/themes.rs+10-7
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1//! Style themes in the app (`resources/styles.md`): the page wears its own theme, else its1//! Style themes in the app (`resources/styles.md`): the page wears its own theme, else its
2//! section's, else its notebook's, as its paragraph style objects; the Styles gallery2//! section's, else its notebook's, as its paragraph style objects; the Styles gallery
3//! applies a style in the page's theme, and its Customize… opens the Themes dialog, which3//! applies a style in the page's theme, and its Customize Font Styles opens the dialog of
4//! edits a notebook's own themes with a live preview and gives one to the page, the section4//! that name, which edits a notebook's own themes with a live preview and gives one to the
5//! or the notebook.5//! page, the section or the notebook.
66
7use crate::{Library, State, filetime};7use crate::{Library, State, filetime};
8use accesskit::Role;8use accesskit::Role;
...@@ -255,12 +255,13 @@ impl State {...@@ -255,12 +255,13 @@ impl State {
255 ..Spec::default()255 ..Spec::default()
256 },256 },
257 );257 );
258 let named = crate::commands::command(crate::commands::Id::Themes).title;
258 if let Some(node) = ui.access(id()) {259 if let Some(node) = ui.access(id()) {
259 node.set_label("Themes");260 node.set_label(named);
260 }261 }
261 let title = match &dialog.targets[..] {262 let title = match &dialog.targets[..] {
262 [(scope, _)] => format!("Theme for {}", scope.name()),263 [(scope, _)] => format!("{named} for {}", scope.name()),
263 _ => "Themes".to_owned(),264 _ => named.to_owned(),
264 };265 };
265 ui.leaf(266 ui.leaf(
266 "title",267 "title",
...@@ -770,12 +771,14 @@ pub(crate) fn gallery(...@@ -770,12 +771,14 @@ pub(crate) fn gallery(
770 .enumerate()771 .enumerate()
771 .map(|(at, (name, _))| ui::popup::Group {772 .map(|(at, (name, _))| ui::popup::Group {
772 heading: if at == 0 { &sheet.name } else { "" },773 heading: if at == 0 { &sheet.name } else { "" },
774 ruled: false,
773 cells: 1,775 cells: 1,
774 columns: 1,776 columns: 1,
775 size: row(shown_size(&sheet.styles[*name], GALLERY_LARGEST) * 1.25),777 size: row(shown_size(&sheet.styles[*name], GALLERY_LARGEST) * 1.25),
776 })778 })
777 .chain([ui::popup::Group {779 .chain([ui::popup::Group {
778 heading: "",780 heading: "",
781 ruled: true,
779 cells: 1,782 cells: 1,
780 columns: 1,783 columns: 1,
781 size: row(16.0),784 size: row(16.0),
...@@ -807,7 +810,7 @@ pub(crate) fn gallery(...@@ -807,7 +810,7 @@ pub(crate) fn gallery(
807 "customize",810 "customize",
808 Spec {811 Spec {
809 size: [fill(), fill()],812 size: [fill(), fill()],
810 text: Some("Customize"),813 text: Some(crate::commands::command(Cmd::Themes).title),
811 ..Spec::default()814 ..Spec::default()
812 },815 },
813 );816 );
crates/ui/src/popup.rs+24
...@@ -1038,6 +1038,8 @@ pub fn table_picker(ui: &mut Ui, id: Id, anchor: Anchor, size: [usize; 2]) -> Op...@@ -1038,6 +1038,8 @@ pub fn table_picker(ui: &mut Ui, id: Id, anchor: Anchor, size: [usize; 2]) -> Op
1038#[derive(Clone, Copy, Debug)]1038#[derive(Clone, Copy, Debug)]
1039pub struct Group<'a> {1039pub struct Group<'a> {
1040 pub heading: &'a str,1040 pub heading: &'a str,
1041 /// Starts under a rule, as a gallery's command after its choices.
1042 pub ruled: bool,
1041 pub cells: usize,1043 pub cells: usize,
1042 pub columns: usize,1044 pub columns: usize,
1043 pub size: [f32; 2],1045 pub size: [f32; 2],
...@@ -1096,7 +1098,29 @@ pub fn gallery(...@@ -1096,7 +1098,29 @@ pub fn gallery(
1096 width + 2.0 * ui.theme.menu().pad,1098 width + 2.0 * ui.theme.menu().pad,
1097 );1099 );
1098 let mut index = 0;1100 let mut index = 0;
1101 let style = theme.menu();
1099 for (number, group) in groups.iter().enumerate() {1102 for (number, group) in groups.iter().enumerate() {
1103 if group.ruled {
1104 ui.open(
1105 ("rule", number),
1106 Spec {
1107 size: [fill(), px(style.rule_band)],
1108 ..Spec::default()
1109 },
1110 );
1111 ui.leaf(
1112 "line",
1113 Spec {
1114 flags: Flags::FLOAT,
1115 size: [fill(), px(1.0)],
1116 position: [0.0, (style.rule_band - 1.0) / 2.0],
1117 inset: [style.rule_inset, 0.0, style.rule_inset, 0.0],
1118 fill: Some(style.rule),
1119 ..Spec::default()
1120 },
1121 );
1122 ui.close();
1123 }
1100 if !group.heading.is_empty() {1124 if !group.heading.is_empty() {
1101 ui.leaf(1125 ui.leaf(
1102 ("heading", number),1126 ("heading", number),
crates/ui/src/tests.rs+1
...@@ -2911,6 +2911,7 @@ fn galleries_choose_across_their_groups_by_keys_and_clicks() {...@@ -2911,6 +2911,7 @@ fn galleries_choose_across_their_groups_by_keys_and_clicks() {
2911 let gallery = Id::ROOT.child("gallery");2911 let gallery = Id::ROOT.child("gallery");
2912 let groups = [("Recent", 2), ("Library", 5)].map(|(heading, cells)| popup::Group {2912 let groups = [("Recent", 2), ("Library", 5)].map(|(heading, cells)| popup::Group {
2913 heading,2913 heading,
2914 ruled: false,
2914 cells,2915 cells,
2915 columns: 3,2916 columns: 3,
2916 size: [30.0, 30.0],2917 size: [30.0, 30.0],