diff --git a/crates/ui-egui/src/extra_windows.rs b/crates/ui-egui/src/extra_windows.rs index 71e63fa..e269573 100644 --- a/crates/ui-egui/src/extra_windows.rs +++ b/crates/ui-egui/src/extra_windows.rs @@ -52,15 +52,16 @@ pub fn run(app: &mut SoundApp, id: &str, p: &Value) -> Option { } pub fn show(app: &mut SoundApp, ctx: &egui::Context) { - apply_customization(app, ctx); auto_update(app, ctx); renderer(app, ctx); ui_customization(app, ctx); + apply_customization(app, ctx); } fn apply_customization(app: &mut SoundApp, ctx: &egui::Context) { let want = (app.extra.ui_scale.clamp(0.75, 1.5), app.extra.reduce_motion); - if app.extra.applied == Some(want) { + // Rescaling mid-drag would move the Interface scale slider out from under the pointer. + if app.extra.applied == Some(want) || ctx.input(|i| i.pointer.any_down()) { return; } // Only touch the zoom once the user has changed it, so platform scaling stays untouched. diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 90a4e08..fb978c3 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -55,6 +55,8 @@ fn search_chord(m: Modifiers, mac: bool) -> bool { } pub fn handle(app: &mut SoundApp, ctx: &egui::Context) { + // Cmd+= and Cmd+0 are Mix/Edit and Quantize to Grid, not egui's built-in interface zoom. + ctx.options_mut(|o| o.zoom_with_keyboard = false); if ctx.egui_wants_keyboard_input() || app.dialogs.open.is_some() { return; } diff --git a/crates/ui-egui/tests/appearance.rs b/crates/ui-egui/tests/appearance.rs new file mode 100644 index 0000000..87145a8 --- /dev/null +++ b/crates/ui-egui/tests/appearance.rs @@ -0,0 +1,47 @@ +use egui::accesskit::Role; +use egui::{Event, Modifiers, PointerButton, Pos2, Vec2, pos2, vec2}; +use egui_kittest::Harness; +use egui_kittest::kittest::Queryable; +use soundcraft_ui_egui::{Services, SoundApp}; + +const WINDOW_PX: Vec2 = vec2(1600.0, 1000.0); + +fn frame(h: &mut Harness<'_, SoundApp>, px: Pos2, pressed: Option) { + let zoom = h.ctx.zoom_factor(); + h.set_size(WINDOW_PX / zoom); + let pos = (px.to_vec2() / zoom).to_pos2(); + h.input_mut().events.push(Event::PointerMoved(pos)); + if let Some(pressed) = pressed { + h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }); + } + h.step(); +} + +#[test] +fn interface_scale_slider_follows_the_pointer() { + let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default()); + app.extra.show_ui_customization = true; + let mut h = Harness::builder().with_size(WINDOW_PX).with_pixels_per_point(1.0).build_ui_state( + |ui, app: &mut SoundApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + app, + ); + h.run_steps(6); + let rail = h.get_by_role(Role::Slider).rect(); + let start = pos2(rail.left() + rail.width() / 3.0, rail.center().y); + let end = pos2(rail.right() + 40.0, rail.center().y); + frame(&mut h, start, Some(true)); + let mut scales = vec![]; + for i in 1..=20 { + frame(&mut h, start.lerp(end, i as f32 / 20.0), None); + scales.push(h.state().extra.ui_scale); + } + assert!(scales.windows(2).all(|w| w[0] <= w[1]), "{scales:?}"); + frame(&mut h, end, Some(false)); + frame(&mut h, end, None); + assert_eq!(h.state().extra.ui_scale, 1.5); + assert_eq!(h.ctx.zoom_factor(), 1.5); +} diff --git a/crates/ui-egui/tests/shortcuts.rs b/crates/ui-egui/tests/shortcuts.rs index fc7fdc4..5d04d47 100644 --- a/crates/ui-egui/tests/shortcuts.rs +++ b/crates/ui-egui/tests/shortcuts.rs @@ -1,7 +1,7 @@ use egui::os::OperatingSystem; use egui::{Key, Modifiers}; use serde_json::json; -use soundcraft_ui_egui::{Services, SoundApp, shortcuts}; +use soundcraft_ui_egui::{MainWindow, Services, SoundApp, shortcuts}; fn press(ctx: &egui::Context, app: &mut SoundApp, key: Key, modifiers: Modifiers) { let events = [true, false].map(|pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers }).to_vec(); @@ -24,3 +24,23 @@ fn ctrl_shift_arrows_nudge_the_selected_clip_gain() { assert_eq!(app.engine.session().find_clip(kick).unwrap().1.gain_db, 1.0, "{os:?}"); } } + +#[test] +fn cmd_equals_toggles_mix_and_edit_without_zooming() { + let mac_cmd = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE }; + let pc_ctrl = Modifiers { ctrl: true, command: true, ..Modifiers::NONE }; + for (os, cmd) in [(OperatingSystem::Mac, mac_cmd), (OperatingSystem::Windows, pc_ctrl), (OperatingSystem::Nix, pc_ctrl)] { + let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default()); + let ctx = egui::Context::default(); + ctx.set_os(os); + ctx.set_zoom_factor(1.25); + press(&ctx, &mut app, Key::Equals, cmd); + assert_eq!(app.ui.window, MainWindow::Mix, "{os:?}"); + press(&ctx, &mut app, Key::Equals, cmd); + press(&ctx, &mut app, Key::Num0, cmd); + press(&ctx, &mut app, Key::Minus, cmd); + ctx.run_ui(egui::RawInput::default(), |_| {}).textures_delta.clear(); + assert_eq!(app.ui.window, MainWindow::Edit, "{os:?}"); + assert_eq!(ctx.zoom_factor(), 1.25, "{os:?}"); + } +}