A panel label too wide for its panel (beside its caret) wraps between words onto a second line; a single word wider than the panel stays whole. Each line is centred, and the label's hover area grows to hold both lines. A panel's buttons are centred in the panel, so a one-button panel such as Finish Sketch lines its icon up with its label. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
This commit is contained in:
2 files changed
+69
-12
No files matched your search
@@ -55,3 +55,47 @@ fn translated_dialog_buttons_do_not_wrap() {
|
||||
assert_eq!(out.shapes.iter().find_map(|shape| cancel_rows(&shape.shape)), Some(1));
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_panel_label_wraps_centred_and_its_button_is_centred() {
|
||||
fn label_rows(shape: &egui::Shape) -> Option<Vec<egui::Rect>> {
|
||||
match shape {
|
||||
egui::Shape::Text(text) if text.galley.job.text == "SKIZZE BEENDEN" => {
|
||||
Some(text.galley.rows.iter().map(|row| row.rect().translate(text.pos.to_vec2())).collect())
|
||||
}
|
||||
egui::Shape::Vec(shapes) => shapes.iter().find_map(label_rows),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn hover_rect(shape: &egui::Shape, hover: egui::Color32, at: egui::Pos2) -> Option<egui::Rect> {
|
||||
match shape {
|
||||
egui::Shape::Rect(r) if r.fill == hover && r.rect.contains(at) => Some(r.rect),
|
||||
egui::Shape::Vec(shapes) => shapes.iter().find_map(|s| hover_rect(s, hover, at)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
let mut h = Harness::new();
|
||||
h.app.preferences.language = "de".into();
|
||||
h.app.home.open = false;
|
||||
h.app.run("sketch.create", json!({"plane":"xy"})).unwrap();
|
||||
h.frames(2);
|
||||
let label = scenario::handles("panel:SKETCH:FINISH SKETCH")[0].1;
|
||||
let button = scenario::handles("button:sketch.finish")[0].1;
|
||||
assert!((label.x - button.x).abs() < 1.0, "the only button sits centred over its label: {label:?} {button:?}");
|
||||
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 1000.0));
|
||||
let mut out =
|
||||
h.ctx.run_ui(egui::RawInput { screen_rect: Some(screen), events: vec![egui::Event::PointerMoved(label)], ..Default::default() }, |ui| {
|
||||
h.app.ui(ui)
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
let mut out = h.ctx.run_ui(egui::RawInput { screen_rect: Some(screen), ..Default::default() }, |ui| h.app.ui(ui));
|
||||
let rows = out.shapes.iter().find_map(|shape| label_rows(&shape.shape)).expect("the Finish Sketch panel label is drawn");
|
||||
assert_eq!(rows.len(), 2, "the label wider than its one-button panel wraps: {rows:?}");
|
||||
assert!(rows[0].width() != rows[1].width(), "the lines differ in width: {rows:?}");
|
||||
assert!((rows[0].center().x - rows[1].center().x).abs() < 1.0, "each line is centred: {rows:?}");
|
||||
let hover = crate::theme::Tokens::get().hover;
|
||||
let lr = out.shapes.iter().find_map(|shape| hover_rect(&shape.shape, hover, label)).expect("the hovered label is highlighted");
|
||||
assert!(rows.iter().all(|row| lr.contains_rect(*row)), "the highlight holds both lines: {lr:?} {rows:?}");
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
@@ -555,7 +555,11 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// Dropped on the panel's free space (all of a collapsed panel): the button goes to
|
||||
// its end.
|
||||
let drawn = if collapsed { 0 } else { shown.len() };
|
||||
let free = Rect::from_min_size(pos2(px + 4.0 + drawn as f32 * 40.0, top + 2.0), vec2((width - 8.0 - drawn as f32 * 40.0).max(8.0), 38.0));
|
||||
// Buttons narrower than the panel's minimum width (a one-button panel) are centred.
|
||||
let inset = if drawn == 0 { 0.0 } else { ((width - 8.0 - (drawn as f32 * 40.0 - 2.0)) / 2.0).max(0.0).floor() };
|
||||
let bx = px + 4.0 + inset;
|
||||
let free_x = bx + drawn as f32 * 40.0;
|
||||
let free = Rect::from_min_size(pos2(free_x, top + 2.0), vec2((px + width - 4.0 - free_x).max(8.0), 38.0));
|
||||
let fresp = ui.interact(free, ui.id().with(("panel_free", panel)), if collapsed { Sense::click() } else { Sense::hover() });
|
||||
if fresp.dnd_hover_payload::<ToolbarDrag>().is_some() {
|
||||
painter.line_segment([pos2(free.left(), free.top() + 2.0), pos2(free.left(), free.bottom() - 2.0)], Stroke::new(2.0, t.accent));
|
||||
@@ -584,7 +588,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
for (i, c) in shown.iter().enumerate().take(drawn) {
|
||||
let br = Rect::from_min_size(pos2(px + 4.0 + i as f32 * 40.0, top + 2.0), vec2(38.0, 38.0));
|
||||
let br = Rect::from_min_size(pos2(bx + i as f32 * 40.0, top + 2.0), vec2(38.0, 38.0));
|
||||
let info = c.info(&app.session);
|
||||
let resp = ui.interact(br, ui.id().with(("cmd", c.id)), Sense::click_and_drag());
|
||||
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), br.center());
|
||||
@@ -644,13 +648,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
painter.text(br.center(), Align2::CENTER_CENTER, "—", FontId::proportional(14.0), t.border);
|
||||
}
|
||||
// Panel label with drop-down.
|
||||
let lr = Rect::from_min_size(pos2(px, top + 42.0), vec2(width, 18.0));
|
||||
let label = app.tr_label(panel).to_string();
|
||||
let lresp = ui.interact(lr, ui.id().with(("panel", panel)), Sense::click());
|
||||
crate::scenario::publish_handle(&format!("panel:{tab}:{panel}"), lr.center());
|
||||
if lresp.hovered() && enabled_panel {
|
||||
painter.rect_filled(lr, 3.0, t.hover);
|
||||
}
|
||||
let lc = if *panel == "FINISH SKETCH" {
|
||||
t.sketch_accent
|
||||
} else if enabled_panel {
|
||||
@@ -658,12 +656,27 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
} else {
|
||||
t.text_dim
|
||||
};
|
||||
let g = painter.layout_no_wrap(label, FontId::proportional(10.5), lc);
|
||||
let gx = lr.center().x - (g.size().x + 9.0) / 2.0;
|
||||
let gy = lr.center().y - g.size().y / 2.0;
|
||||
// A label too wide for its panel (beside the caret) wraps between words onto a
|
||||
// second line; a single word wider than the panel stays whole. Each line is
|
||||
// centred, and the hover area grows to hold them.
|
||||
let font = FontId::proportional(10.5);
|
||||
let widest_word = label.split_whitespace().map(|w| painter.layout_no_wrap(w.to_string(), font.clone(), lc).size().x).fold(0.0, f32::max);
|
||||
let mut job = egui::text::LayoutJob::simple(label, font, lc, (width - 14.0).max(widest_word + 1.0));
|
||||
job.wrap.max_rows = 2;
|
||||
job.halign = egui::Align::Center;
|
||||
let g = painter.layout_job(job);
|
||||
let gw = g.size().x;
|
||||
let lr = Rect::from_center_size(pos2(px + width / 2.0, top + 51.0), vec2(width, (g.size().y + 2.0).max(18.0)));
|
||||
let lresp = ui.interact(lr, ui.id().with(("panel", panel)), Sense::click());
|
||||
crate::scenario::publish_handle(&format!("panel:{tab}:{panel}"), lr.center());
|
||||
if lresp.hovered() && enabled_panel {
|
||||
painter.rect_filled(lr, 3.0, t.hover);
|
||||
}
|
||||
// Centred rows sit around x = 0, so the galley is placed at the text's centre.
|
||||
let gx = lr.center().x - 4.5;
|
||||
let gy = lr.center().y - g.size().y / 2.0;
|
||||
painter.galley(pos2(gx, gy), g, lc);
|
||||
caret(&painter, pos2(gx + gw + 6.0, lr.center().y), lc);
|
||||
caret(&painter, pos2(gx + gw / 2.0 + 6.0, lr.center().y), lc);
|
||||
if lresp.clicked() {
|
||||
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new("sc_panel_open"), popup_id));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user