From fd422a3fc17b866c2078affce96fc23a2ff458cf Mon Sep 17 00:00:00 2001 From: DuBearte <130190089+DuBearte@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:01:09 -0400 Subject: [PATCH] Dialogs: the Select chip keeps its size on hover (#1387) New frame::add_outlined adds a button that draws its own outline with the theme's outline width set to the same width in every state, so its padding no longer drops a pixel on hover. Used by the dialogs' Select chip, Sketch Text's Path chip and the bug report's Open GitHub draft button. New dialog_chip_hover test. Fixes the second point of #1335. --- crates/ui-egui/src/dialogs.rs | 2 +- crates/ui-egui/src/frame.rs | 16 ++++++++++++++++ crates/ui-egui/src/issue_report.rs | 15 ++++++++------- crates/ui-egui/src/sketch_text.rs | 2 +- crates/ui-egui/tests/dialog_chip_hover.rs | 19 +++++++++++++++++++ 5 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 crates/ui-egui/tests/dialog_chip_hover.rs diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index dbf7df3b..dd8f83fb 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1638,7 +1638,7 @@ fn input_rows(d: &mut Dialog, ui: &mut egui::Ui, s: &Session) { .stroke(stroke) .min_size(vec2(0.0, 22.0)) .sense(egui::Sense::CLICK); - let chip = ui.add(b); + let chip = crate::frame::add_outlined(ui, b, stroke.width); publish_row(&chip); let tooltip = if custom_axis { if let Kind::Revolve { axis: Some(axis), .. } = &d.kind { diff --git a/crates/ui-egui/src/frame.rs b/crates/ui-egui/src/frame.rs index 3e323a2c..6e7dcff2 100644 --- a/crates/ui-egui/src/frame.rs +++ b/crates/ui-egui/src/frame.rs @@ -108,6 +108,22 @@ pub fn window<'a>(ctx: &egui::Context, title: impl Into, width: Widt .max_width(max) } +/// Add a button drawn with its own outline (`Button::stroke`) of `width`, keeping the same size +/// in every state (#1335). egui sizes a button's padding as the spacing minus the *theme's* +/// outline width for the state (none while idle, 1 px hovered), which keeps a themed button's +/// size steady, but a button with its own outline then lost a pixel of padding each side on +/// hover: it shrank, and the panel around it with it. +pub fn add_outlined(ui: &mut Ui, button: egui::Button<'_>, width: f32) -> egui::Response { + ui.scope(|ui| { + let w = &mut ui.visuals_mut().widgets; + for state in [&mut w.inactive, &mut w.hovered, &mut w.active, &mut w.open] { + state.bg_stroke.width = width; + } + ui.add(button) + }) + .inner +} + /// Floating model controls share the report's read-only drawing boundary with windows. pub fn area(id: egui::Id) -> egui::Area { egui::Area::new(id).enabled(!crate::issue_report::cad_input_blocked()) diff --git a/crates/ui-egui/src/issue_report.rs b/crates/ui-egui/src/issue_report.rs index 30b6040c..2a6d70bb 100644 --- a/crates/ui-egui/src/issue_report.rs +++ b/crates/ui-egui/src/issue_report.rs @@ -598,13 +598,14 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) { ui.add(egui::Label::new(crate::i18n::tr_label("3. Click Submit new issue on GitHub to send your report.")).wrap()); ui.horizontal_wrapped(|ui| { let t = crate::theme::Tokens::get(); - if ui - .add( - egui::Button::new(RichText::new(crate::i18n::tr_label("Open GitHub draft")).strong().color(t.text)) - .fill(t.accent_soft) - .stroke(egui::Stroke::new(1.0, t.accent)), - ) - .clicked() + if crate::frame::add_outlined( + ui, + egui::Button::new(RichText::new(crate::i18n::tr_label("Open GitHub draft")).strong().color(t.text)) + .fill(t.accent_soft) + .stroke(egui::Stroke::new(1.0, t.accent)), + 1.0, + ) + .clicked() { if copy_needed { ctx.copy_text(draft.body.clone()); diff --git a/crates/ui-egui/src/sketch_text.rs b/crates/ui-egui/src/sketch_text.rs index 346b3b37..9c391f5f 100644 --- a/crates/ui-egui/src/sketch_text.rs +++ b/crates/ui-egui/src/sketch_text.rs @@ -497,7 +497,7 @@ fn path_row(ui: &mut egui::Ui, k: &mut Tx, inputs: &mut [SelInput], active: &mut }; let stroke = if k.picking { egui::Stroke::new(1.5, t.accent) } else { egui::Stroke::new(1.0, t.dialog_border.gamma_multiply(1.6)) }; let chip = egui::Button::new(text).fill(if on { t.chip } else { egui::Color32::TRANSPARENT }).stroke(stroke).min_size(egui::vec2(0.0, 22.0)); - let chip = ui.add(chip).on_hover_text(crate::i18n::tr("sketch_text.path_hint")); + let chip = crate::frame::add_outlined(ui, chip, stroke.width).on_hover_text(crate::i18n::tr("sketch_text.path_hint")); crate::scenario::publish_handle("dialog:Path", chip.rect.center()); if chip.clicked() { k.picking = true; diff --git a/crates/ui-egui/tests/dialog_chip_hover.rs b/crates/ui-egui/tests/dialog_chip_hover.rs new file mode 100644 index 00000000..39ed962e --- /dev/null +++ b/crates/ui-egui/tests/dialog_chip_hover.rs @@ -0,0 +1,19 @@ +//! Hovering a dialog's Select chip doesn't change its size or the panel's (#1335). +use serde_json::json; +use solidcraft_ui_egui::scenario::{Harness, handle_at}; + +#[test] +fn hovering_the_select_chip_keeps_its_size() { + let mut h = Harness::new(); + h.app.home.open = false; + h.app.start("sketch.create"); + h.frames(3); + let away = handle_at("dialog:Plane").unwrap(); + h.step(&json!({"move":[away.x, away.y]})).unwrap(); + h.frames(3); + let hovered = handle_at("dialog:Plane").unwrap(); + assert!(away.distance(hovered) < 0.01, "the chip moved on hover: {away:?} -> {hovered:?}"); + h.step(&json!({"move":[5.0, 600.0]})).unwrap(); + h.frames(3); + assert!(away.distance(handle_at("dialog:Plane").unwrap()) < 0.01); +}