Preferences: one fixed size, buttons flush right, a real gear icon (#290)
- The dialog draws every page into a fixed, clipped 744x400 area, so no page (or translation) changes the window width; descriptions wrap and tall pages scroll inside it. - OK / Cancel / Apply sit flush right in the bottom bar (right-to-left layout within the fixed width); Restore Defaults stays left. - The "settings" icon is a gear: a solid ring with six square-ish teeth and a see-through centre hole, instead of the sun-like rays. - Scenario s46 visits every page and checks the window keeps its width and that Apply ends at the bottom bar's right edge (new `same_width` and `right_edge` scenario checks). Fixes #290 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
87bb2f37f8
commit
df09fdbc32
4 files changed
+130
-35
No files matched your search
+49
-30
@@ -396,52 +396,64 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let mut open = true;
|
||||
let mut button: Option<&str> = None;
|
||||
let before = app.preferences.clone();
|
||||
crate::frame::window(ctx, app.tr("ui.preferences"), crate::frame::Width::Wide)
|
||||
let shown = crate::frame::window(ctx, app.tr("ui.preferences"), crate::frame::Width::Wide)
|
||||
.id(egui::Id::new("sc_prefs"))
|
||||
.open(&mut open)
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| {
|
||||
// A fixed page area: the vertical separator fills the height it is given, so an
|
||||
// auto-sized one would grow every frame.
|
||||
ui.allocate_ui_with_layout(egui::vec2(700.0, 400.0), egui::Layout::left_to_right(egui::Align::Min), |ui| {
|
||||
ui.set_min_size(egui::vec2(700.0, 400.0));
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(140.0);
|
||||
for (i, s) in PAGES.iter().enumerate() {
|
||||
if ui.selectable_label(app.prefs_window.section == i, app.tr_label(s)).clicked() {
|
||||
app.prefs_window.section = i;
|
||||
app.keymap.read_only = false;
|
||||
}
|
||||
// One fixed size for every page (#290): the page is drawn into a clipped child of
|
||||
// that size and only that size is allocated, so no page (or translation) can widen
|
||||
// the window; descriptions wrap and tall pages scroll inside it.
|
||||
ui.set_max_width(PREFS_W);
|
||||
let (body, _) = ui.allocate_exact_size(egui::vec2(PREFS_W, PREFS_H), egui::Sense::hover());
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(body).layout(egui::Layout::left_to_right(egui::Align::Min)));
|
||||
child.shrink_clip_rect(body);
|
||||
let ui_body = &mut child;
|
||||
ui_body.vertical(|ui| {
|
||||
ui.set_width(PREFS_NAV_W);
|
||||
for (i, s) in PAGES.iter().enumerate() {
|
||||
if ui.selectable_label(app.prefs_window.section == i, app.tr_label(s)).clicked() {
|
||||
app.prefs_window.section = i;
|
||||
app.keymap.read_only = false;
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
// A fixed width, so the window keeps its size from page to page.
|
||||
ui.set_width(540.0);
|
||||
ui.label(RichText::new(app.tr_label(PAGES.get(app.prefs_window.section).copied().unwrap_or_default())).strong().size(14.0));
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
});
|
||||
ui_body.separator();
|
||||
ui_body.vertical(|ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.label(RichText::new(app.tr_label(PAGES.get(app.prefs_window.section).copied().unwrap_or_default())).strong().size(14.0));
|
||||
ui.add_space(6.0);
|
||||
if app.prefs_window.section == PAGE_SHORTCUTS {
|
||||
// The shortcuts table scrolls by itself.
|
||||
page(app, ui, &t);
|
||||
});
|
||||
} else {
|
||||
egui::ScrollArea::vertical().id_salt("sc_prefs_page").auto_shrink([false, false]).show(ui, |ui| page(app, ui, &t));
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
// Restore Defaults at the left; OK, Cancel and Apply flush right, as in every dialog.
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(PREFS_W);
|
||||
if ui.button(app.tr("ui.restore_defaults")).on_hover_text(app.tr("ui.reset_the_settings_on_this_page")).clicked() {
|
||||
button = Some("defaults");
|
||||
}
|
||||
crate::frame::right_aligned(ui, 700.0, 170.0, |ui| {
|
||||
if ui.button(app.tr("ui.heading_ok")).clicked() {
|
||||
button = Some("ok");
|
||||
}
|
||||
if ui.button(app.tr("ui.cancel")).clicked() {
|
||||
button = Some("cancel");
|
||||
}
|
||||
if ui.button(app.tr("ui.apply")).clicked() {
|
||||
button = Some("apply");
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
// Right to left: Apply is the rightmost.
|
||||
for (key, id) in [("ui.apply", "apply"), ("ui.cancel", "cancel"), ("ui.heading_ok", "ok")] {
|
||||
let r = ui.button(app.tr(key));
|
||||
crate::scenario::publish_rect(&format!("prefs:{id}"), r.rect);
|
||||
if r.clicked() {
|
||||
button = Some(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
crate::scenario::publish_rect("prefs:footer", ui.max_rect());
|
||||
});
|
||||
});
|
||||
if let Some(w) = &shown {
|
||||
crate::scenario::publish_rect("prefs:window", w.response.rect);
|
||||
}
|
||||
if app.preferences.msaa != before.msaa {
|
||||
save_graphics(app);
|
||||
}
|
||||
@@ -485,12 +497,19 @@ fn up_axis_changed(app: &mut SolveApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Preferences window's content size: the same on every page.
|
||||
const PREFS_W: f32 = 744.0;
|
||||
const PREFS_H: f32 = 400.0;
|
||||
/// The page list at its left.
|
||||
const PREFS_NAV_W: f32 = 128.0;
|
||||
|
||||
/// One page of the dialog.
|
||||
fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
|
||||
let _language = crate::i18n::scope(&app.preferences.language);
|
||||
let note = |ui: &mut egui::Ui, s: &str| {
|
||||
ui.label("");
|
||||
ui.label(RichText::new(s).size(11.0).color(t.text_dim));
|
||||
// Descriptions wrap within the page instead of widening it.
|
||||
ui.add(egui::Label::new(RichText::new(s).size(11.0).color(t.text_dim)).wrap());
|
||||
ui.end_row();
|
||||
};
|
||||
if app.prefs_window.section == PAGE_SHORTCUTS {
|
||||
|
||||
Reference in new issue
Block a user