diff --git a/crates/ui-egui/src/canvas/home.rs b/crates/ui-egui/src/canvas/home.rs index 672f2dd3d..cd2ac9c44 100644 --- a/crates/ui-egui/src/canvas/home.rs +++ b/crates/ui-egui/src/canvas/home.rs @@ -31,7 +31,11 @@ fn home_content(app: &mut VectorcraftApp, ui: &mut Ui) { ui.add_space(4.0); ui.label(egui::RichText::new(tl!("Vector illustration — fast, open, scriptable.")).size(14.0).color(t.text_dim)); ui.add_space(22.0); + // `horizontal` fills the width, so the row's own width sets its left edge. + let gap = ui.spacing().item_spacing.x + 8.0; + let row = widgets::button_width(ui, tl!("New file")) + gap + widgets::button_width(ui, tl!("Open")); ui.horizontal(|ui| { + ui.add_space(((ui.available_width() - row) / 2.0).max(0.0)); if widgets::primary_button(ui, tl!("New file")).clicked() { app.run("file.newDialog", json!({})).ok(); } @@ -60,7 +64,7 @@ fn home_content(app: &mut VectorcraftApp, ui: &mut Ui) { ui.vertical_centered(|ui| { ui.label(egui::RichText::new(tl!("Community")).font(theme::semibold(14.0)).color(t.text)); ui.add_space(10.0); - crate::community::links(app, ui); + crate::community::links_centered(app, ui); }); } diff --git a/crates/ui-egui/src/community.rs b/crates/ui-egui/src/community.rs index 089a421ce..034807b8b 100644 --- a/crates/ui-egui/src/community.rs +++ b/crates/ui-egui/src/community.rs @@ -53,27 +53,44 @@ pub fn discord_button(app: &mut VectorcraftApp, ui: &mut Ui, large: bool) -> egu resp } -/// One link row: icon, label, opens the command's URL. -fn link(app: &mut VectorcraftApp, ui: &mut Ui, icon: &str, label: &str, cmd: &str, url: &str) { +/// One link row: icon, label, opens the command's URL. `centered` drops the icon so the label +/// alone sits in the middle of the column. +fn link(app: &mut VectorcraftApp, ui: &mut Ui, icon: &str, label: &str, cmd: &str, url: &str, centered: bool) { let t = Tokens::get(ui.ctx()); - ui.horizontal(|ui| { - icons::icon(ui, icon, 16.0, t.icon); - let r = ui.add(egui::Button::new(egui::RichText::new(label).color(t.accent).size(13.0)).frame(false)).on_hover_text(url); - if r.clicked() { - app.open_link(cmd); - } - }); + let button = |ui: &mut Ui| ui.add(egui::Button::new(egui::RichText::new(label).color(t.accent).size(13.0)).frame(false)).on_hover_text(url); + let r = if centered { + button(ui) + } else { + ui.horizontal(|ui| { + icons::icon(ui, icon, 16.0, t.icon); + button(ui) + }) + .inner + }; + if r.clicked() { + app.open_link(cmd); + } } /// The Discord button and the website / app page / GitHub links. pub fn links(app: &mut VectorcraftApp, ui: &mut Ui) { + links_in(app, ui, false); +} + +/// [`links`] for a centred column (the Home screen's, as PhotoCraft's, #4684): the Discord button +/// and the links, each in the middle. +pub fn links_centered(app: &mut VectorcraftApp, ui: &mut Ui) { + links_in(app, ui, true); +} + +fn links_in(app: &mut VectorcraftApp, ui: &mut Ui, centered: bool) { discord_button(app, ui, true); ui.add_space(8.0); let l = app.session.execute("help.links", &serde_json::json!({})).unwrap_or_default(); let s = |k: &str| l[k].as_str().unwrap_or("").to_string(); - link(app, ui, "globe", tl!("ArtCraft website"), "help.website", &s("website")); - link(app, ui, "external-link", tl!("VectorCraft on getartcraft.com"), "help.appPage", &s("appPage")); - link(app, ui, "git-branch", tl!("Source code on GitHub"), "help.github", &s("github")); + link(app, ui, "globe", tl!("ArtCraft website"), "help.website", &s("website"), centered); + link(app, ui, "external-link", tl!("VectorCraft on getartcraft.com"), "help.appPage", &s("appPage"), centered); + link(app, ui, "git-branch", tl!("Source code on GitHub"), "help.github", &s("github"), centered); } #[cfg(test)] diff --git a/crates/ui-egui/src/tests/home.rs b/crates/ui-egui/src/tests/home.rs index 249aa7f76..cfe150038 100644 --- a/crates/ui-egui/src/tests/home.rs +++ b/crates/ui-egui/src/tests/home.rs @@ -440,11 +440,11 @@ fn a_queued_reopen_keeps_the_roots_it_was_asked_in() { } /// The x-centre of the first text shape whose text is `text` on a drawn Home screen, and the -/// window's centre (#4684). +/// window's centre (#4684). A centred galley's `pos` is its anchor, so its rect says where it lies. fn text_centre_x(app: &mut VectorcraftApp, text: &str) -> Option<(f32, f32)> { fn find(s: &egui::Shape, text: &str) -> Option { match s { - egui::Shape::Text(t) if t.galley.text() == text => Some(t.pos.x + t.galley.size().x / 2.0), + egui::Shape::Text(t) if t.galley.text() == text => Some(t.pos.x + t.galley.rect.center().x), egui::Shape::Vec(v) => v.iter().find_map(|s| find(s, text)), _ => None, } @@ -460,12 +460,32 @@ fn text_centre_x(app: &mut VectorcraftApp, text: &str) -> Option<(f32, f32)> { Some((x, screen.center().x)) } -/// The Home screen centres its welcome and its community block, as PhotoCraft's does (#4684). +/// The Home screen centres its welcome, its New / Open buttons and its community block, as +/// PhotoCraft's does (#4684). #[test] fn home_welcome_and_community_are_centred() { let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); - for text in ["Welcome to VectorCraft", "Community"] { + for text in ["Welcome to VectorCraft", "Community", "ArtCraft website", "VectorCraft on getartcraft.com", "Source code on GitHub"] { let (x, centre) = text_centre_x(&mut app, text).unwrap_or_else(|| panic!("{text} is drawn")); - assert!((x - centre).abs() < 2.0, "{text} is centred: {x} vs {centre}"); + assert!((x - centre).abs() < 6.0, "{text} is centred: {x} vs {centre}"); } + // The Discord button is centred; its label lies right of its icon, 0.375 of the button's 36 pt + // height right of the middle. + let (x, centre) = text_centre_x(&mut app, "Join our Discord").unwrap(); + assert!((x - centre - 0.375 * 36.0).abs() < 2.0, "the Discord button is centred: label at {x} about {centre}"); + // The two buttons (a label's width plus 32, at least 72, each) lie symmetrically about the + // centre: the first one's left edge and the second one's right edge are equally far from it. + let width = |label: &str| { + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut w = 0.0; + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| w = crate::widgets::button_width(ui, label)); + out.textures_delta.clear(); + w + }; + let (new_x, centre) = text_centre_x(&mut app, "New file").unwrap(); + let (open_x, _) = text_centre_x(&mut app, "Open").unwrap(); + let left = new_x - width("New file") / 2.0; + let right = open_x + width("Open") / 2.0; + assert!((centre - left - (right - centre)).abs() < 6.0, "New / Open row is centred: {left}..{right} about {centre}"); } diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index efde0d0b1..5a6daf1fa 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -28,7 +28,7 @@ pub use buttons::{ SPLIT_ARROW, flat_button, flat_button_fit, icon_button, icon_button_enabled, menu_item, menu_item_name, paint_icon_button, primary_button, primary_button_enabled, secondary_button, split_button, split_button_named, toggle_icon, }; -pub(crate) use buttons::{icon_button_info, primary_button_width, secondary_button_width}; +pub(crate) use buttons::{button_width, icon_button_info, primary_button_width, secondary_button_width}; #[cfg(test)] use chips::gradient_chip_colors; pub use chips::{ProxyClicks, chip_button, chip_button_named, fill_stroke_proxy, gradient_chip, paint_chip, proxy_chip, swatch_tile}; diff --git a/crates/ui-egui/src/widgets/buttons.rs b/crates/ui-egui/src/widgets/buttons.rs index 44ee3c477..a33f7cac8 100644 --- a/crates/ui-egui/src/widgets/buttons.rs +++ b/crates/ui-egui/src/widgets/buttons.rs @@ -101,6 +101,13 @@ fn flat_fill(ui: &Ui, rect: Rect, resp: &Response) { } } +/// The width [`primary_button`] and [`secondary_button`] give `text` (their label plus padding, at +/// least 72), for centring a row of them. +pub(crate) fn button_width(ui: &Ui, text: &str) -> f32 { + let galley = ui.painter().layout_no_wrap(tl!(text).to_string(), theme::semibold(12.5), Color32::PLACEHOLDER); + (galley.size().x + 32.0).max(72.0) +} + /// The accent call-to-action button (dialog OK/Create). pub fn primary_button(ui: &mut Ui, text: &str) -> Response { primary_button_enabled(ui, text, true)