Keyboard Shortcuts: keep the OK button in view [skip ci]

The editor's content was a little taller than its dialog, so the outer scroll
area clipped the OK button and its scrollbar covered the right edge. The dialog
now has no outer scroll area: the command list takes whatever height is left,
so the button row always fits, in small windows too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pravdomil
2026-10-12 21:34:56 +02:00
co-authored by Claude Opus 5.5
parent 470ea7a3af
commit 07fe9cb1dc
+60 -32
View File
@@ -237,7 +237,9 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
}
}
super::dialogs::modal(ctx, "Keyboard Shortcuts", vec2(1000.0, 720.0), t, |ui| {
// No outer scroll area: the command list takes the height left over, so OK stays in view.
super::dialogs::modal_fit(ctx, "Keyboard Shortcuts", vec2(1000.0, 760.0), t, |ui, height| {
let top = ui.cursor().top();
// ---- preset bar
ui.horizontal(|ui| {
ui.label(tr("Preset:"));
@@ -437,42 +439,49 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
q.split_whitespace().all(|w| hay.contains(w))
})
.collect();
ui.allocate_ui_with_layout(vec2(ui.available_width(), 270.0), egui::Layout::left_to_right(egui::Align::Min), |ui| {
ui.set_height(270.0);
let footer = 6.0 + ui.spacing().interact_size.y + ui.spacing().item_spacing.y * 2.0;
let list_h = (height - (ui.cursor().top() - top) - footer).max(140.0);
ui.allocate_ui_with_layout(vec2(ui.available_width(), list_h), egui::Layout::left_to_right(egui::Align::Min), |ui| {
ui.set_height(list_h);
// Command list.
ui.vertical(|ui| {
ui.set_width(620.0);
let row_h = 20.0;
egui::ScrollArea::vertical().id_salt("sc-rows").max_height(230.0).auto_shrink([false, false]).show_rows(ui, row_h, rows.len(), |ui, range| {
for i in range {
let b = rows[i];
let keys = table.keys.get(&b.key).cloned().unwrap_or_default();
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
let sel = st.selected.as_deref() == Some(b.key.as_str());
if sel {
ui.painter().rect_filled(r, 0.0, t.row_selected);
} else if i % 2 == 1 {
ui.painter().rect_filled(r, 0.0, t.row_alt);
egui::ScrollArea::vertical().id_salt("sc-rows").max_height(list_h - 40.0).auto_shrink([false, false]).show_rows(
ui,
row_h,
rows.len(),
|ui, range| {
for i in range {
let b = rows[i];
let keys = table.keys.get(&b.key).cloned().unwrap_or_default();
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
let sel = st.selected.as_deref() == Some(b.key.as_str());
if sel {
ui.painter().rect_filled(r, 0.0, t.row_selected);
} else if i % 2 == 1 {
ui.painter().rect_filled(r, 0.0, t.row_alt);
}
let pp = ui.painter();
pp.text(pos2(r.min.x + 6.0, r.center().y), Align2::LEFT_CENTER, &b.label, Tokens::ui(11.5), t.text);
pp.text(pos2(r.min.x + 230.0, r.center().y), Align2::LEFT_CENTER, b.path.join(" > "), Tokens::ui(10.5), t.text_faint);
let ks: Vec<String> = keys.iter().map(|k| crate::menus::shortcut_text(k)).collect();
let col = if b.scope == APP_SCOPE { APP_COLOR } else { PANEL_COLOR };
pp.text(
pos2(r.max.x - 8.0, r.center().y),
Align2::RIGHT_CENTER,
ks.join(", "),
Tokens::ui(11.5),
if keys.is_empty() { t.text_faint } else { col },
);
app.auto.add(&format!("shortcuts.row.{i}"), r, &b.label);
if resp.clicked() {
st.selected = Some(b.key.clone());
st.pending.clear();
}
}
let pp = ui.painter();
pp.text(pos2(r.min.x + 6.0, r.center().y), Align2::LEFT_CENTER, &b.label, Tokens::ui(11.5), t.text);
pp.text(pos2(r.min.x + 230.0, r.center().y), Align2::LEFT_CENTER, b.path.join(" > "), Tokens::ui(10.5), t.text_faint);
let ks: Vec<String> = keys.iter().map(|k| crate::menus::shortcut_text(k)).collect();
let col = if b.scope == APP_SCOPE { APP_COLOR } else { PANEL_COLOR };
pp.text(
pos2(r.max.x - 8.0, r.center().y),
Align2::RIGHT_CENTER,
ks.join(", "),
Tokens::ui(11.5),
if keys.is_empty() { t.text_faint } else { col },
);
app.auto.add(&format!("shortcuts.row.{i}"), r, &b.label);
if resp.clicked() {
st.selected = Some(b.key.clone());
st.pending.clear();
}
}
});
},
);
ui.label(RichText::new(format!("{} of {} commands", rows.len(), table.bindables.len())).color(t.text_faint));
});
ui.separator();
@@ -603,6 +612,25 @@ mod tests {
out.textures_delta.clear();
}
#[test]
fn the_ok_button_stays_inside_the_dialog_in_a_small_window() {
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
let ctx = egui::Context::default();
crate::theme::install(&ctx, &app.tokens, crate::i18n::language(&app));
open(&mut app);
for size in [vec2(1680.0, 1020.0), vec2(1280.0, 720.0)] {
for _ in 0..3 {
app.auto.begin_frame();
let input = egui::RawInput { screen_rect: Some(Rect::from_min_size(pos2(0.0, 0.0), size)), ..Default::default() };
let mut out = ctx.run_ui(input, |ui| crate::panels::dialogs::show(&mut app, ui.ctx()));
out.textures_delta.clear();
}
let [_, y, _, h] = app.auto.find("shortcuts.close").expect("OK button").rect;
// The window less the modal's margin and its frame padding.
assert!(y + h <= size.y - 24.0 - 18.0 + 0.5, "OK ends at {} in a {size:?} window", y + h);
}
}
#[test]
fn recording_a_shortcut_does_not_also_type_the_key_into_the_field() {
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());