diff --git a/Cargo.lock b/Cargo.lock index b67e796a..cfd1df36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -327,6 +327,24 @@ dependencies = [ "xcb-util", ] +[[package]] +name = "baseview" +version = "0.1.0" +source = "git+https://github.com/robbert-vdh/baseview.git?branch=feature/resize#b5d0751c82c3fd0301366f1f31c07f3cd53aa52f" +dependencies = [ + "cocoa", + "core-foundation", + "keyboard-types", + "nix", + "objc", + "raw-window-handle", + "uuid", + "winapi 0.3.9", + "x11", + "xcb 0.9.0", + "xcb-util", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -884,7 +902,7 @@ name = "egui-baseview" version = "0.0.0" source = "git+https://github.com/robbert-vdh/egui-baseview.git?branch=fix/update-dependencies#afde9c661809ee08d69864caa899246592d93f8c" dependencies = [ - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/mouse-event-modifiers)", "copypasta", "egui", "gl", @@ -1558,7 +1576,7 @@ name = "iced_baseview" version = "0.0.3" source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#116e9043779946c9b2a1b4d2a06bf46752930919" dependencies = [ - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/mouse-event-modifiers)", "copypasta", "iced_core", "iced_futures", @@ -2165,7 +2183,7 @@ dependencies = [ name = "nih_plug_egui" version = "0.0.0" dependencies = [ - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/mouse-event-modifiers)", "crossbeam", "egui", "egui-baseview", @@ -2179,7 +2197,7 @@ name = "nih_plug_iced" version = "0.0.0" dependencies = [ "atomic_refcell", - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/mouse-event-modifiers)", "crossbeam", "iced_baseview", "nih_plug", @@ -2190,7 +2208,7 @@ dependencies = [ name = "nih_plug_vizia" version = "0.0.0" dependencies = [ - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/resize)", "crossbeam", "femtovg", "nih_plug", @@ -3607,7 +3625,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vizia" version = "0.1.0" -source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#c671d86063e5f7754b90fca3c92350efa6d9fc0c" +source = "git+https://github.com/robbert-vdh/vizia.git?branch=patched#6c9844125ee68537842a025657cbd51734a2589d" dependencies = [ "vizia_baseview", "vizia_core", @@ -3616,9 +3634,9 @@ dependencies = [ [[package]] name = "vizia_baseview" version = "0.1.0" -source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#c671d86063e5f7754b90fca3c92350efa6d9fc0c" +source = "git+https://github.com/robbert-vdh/vizia.git?branch=patched#6c9844125ee68537842a025657cbd51734a2589d" dependencies = [ - "baseview", + "baseview 0.1.0 (git+https://github.com/robbert-vdh/baseview.git?branch=feature/resize)", "femtovg", "keyboard-types", "raw-window-handle", @@ -3628,7 +3646,7 @@ dependencies = [ [[package]] name = "vizia_core" version = "0.1.0" -source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#c671d86063e5f7754b90fca3c92350efa6d9fc0c" +source = "git+https://github.com/robbert-vdh/vizia.git?branch=patched#6c9844125ee68537842a025657cbd51734a2589d" dependencies = [ "bitflags", "copypasta", @@ -3651,7 +3669,7 @@ dependencies = [ [[package]] name = "vizia_derive" version = "0.1.0" -source = "git+https://github.com/robbert-vdh/vizia.git?branch=feature/baseview-modifiers#c671d86063e5f7754b90fca3c92350efa6d9fc0c" +source = "git+https://github.com/robbert-vdh/vizia.git?branch=patched#6c9844125ee68537842a025657cbd51734a2589d" dependencies = [ "proc-macro2", "quote", diff --git a/nih_plug_vizia/Cargo.toml b/nih_plug_vizia/Cargo.toml index 612f2889..53d4672a 100644 --- a/nih_plug_vizia/Cargo.toml +++ b/nih_plug_vizia/Cargo.toml @@ -11,10 +11,10 @@ description = "An adapter to use VIZIA GUIs with NIH-plug" nih_plug = { path = ".." } nih_plug_assets = { git = "https://github.com/robbert-vdh/nih_plug_assets.git" } -baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/mouse-event-modifiers" } +baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/resize" } crossbeam = "0.8" # Vizia doesn't re-export this, we will femtovg = { version = "0.3.0", default-features = false, features = ["image-loading"] } # This fork contains changed for better keyboard modifier handling and DPI -# scaling -vizia = { git = "https://github.com/robbert-vdh/vizia.git", branch = "feature/baseview-modifiers", default_features = false, features = ["baseview", "clipboard"] } +# scaling, window scaling, and a lot more fixes and improvements +vizia = { git = "https://github.com/robbert-vdh/vizia.git", branch = "patched", default_features = false, features = ["baseview", "clipboard"] } diff --git a/nih_plug_vizia/src/lib.rs b/nih_plug_vizia/src/lib.rs index 71fcbd4b..04c0defc 100644 --- a/nih_plug_vizia/src/lib.rs +++ b/nih_plug_vizia/src/lib.rs @@ -55,28 +55,67 @@ where })) } -// TODO: Once we add resizing, we may want to be able to remember the GUI size. In that case we need -// to make this serializable (only restoring the size of course) so it can be persisted. +/// State for a `nih_plug_vizia` editor. The scale factor can be manipulated at runtime by emitting +/// [`WindowEvent::SetScale`][vizia::WindowEvent::SetScale] events. +/// +/// # TODO +/// +/// Make this serializable so it can be persisted. pub struct ViziaState { + /// The window's size in logical pixels before applying `scale_factor`. size: AtomicCell<(u32, u32)>, + /// A scale factor that should be applied to `size` separate from from any system HiDPI scaling. + /// This can be used to allow GUIs to be scaled uniformly. + scale_factor: AtomicCell, open: AtomicBool, } impl ViziaState { - /// Initialize the GUI's state. This value can be passed to [`create_vizia_editor()`]. The window - /// size is in logical pixels, so before it is multiplied by the DPI scaling factor. + /// Initialize the GUI's state. This value can be passed to [`create_vizia_editor()`]. The + /// window size is in logical pixels, so before it is multiplied by the DPI scaling factor. pub fn from_size(width: u32, height: u32) -> Arc { Arc::new(ViziaState { size: AtomicCell::new((width, height)), + scale_factor: AtomicCell::new(1.0), open: AtomicBool::new(false), }) } - /// Return a `(width, height)` pair for the current size of the GUI in logical pixels. - pub fn size(&self) -> (u32, u32) { + /// The same as [`from_size()`][Self::from_size()], but with a separate initial scale factor. + /// This scale factor gets applied on top of any HiDPI scaling, and it can be modified at + /// runtime by emitting [`WindowEvent::SetScale`][vizia::WindowEvent::SetScale] events. + pub fn from_size_with_scale(width: u32, height: u32, scale_factor: f64) -> Arc { + Arc::new(ViziaState { + size: AtomicCell::new((width, height)), + scale_factor: AtomicCell::new(scale_factor), + open: AtomicBool::new(false), + }) + } + + /// Return a `(width, height)` pair for the current size of the GUI in logical pixels, after + /// applying the user scale factor. + pub fn scaled_logical_size(&self) -> (u32, u32) { + let (logical_width, logical_height) = self.size.load(); + let scale_factor = self.scale_factor.load(); + + ( + (logical_width as f64 * scale_factor).round() as u32, + (logical_height as f64 * scale_factor).round() as u32, + ) + } + + /// Return a `(width, height)` pair for the current size of the GUI in logical pixels before + /// applying the user scale factor. + pub fn inner_logical_size(&self) -> (u32, u32) { self.size.load() } + /// Get the non-DPI related uniform scaling factor the GUI's size will be multiplied with. This + /// can be changed by emitting [`WindowEvent::SetScale`][vizia::WindowEvent::SetScale] events. + pub fn user_scale_factor(&self) -> f64 { + self.scale_factor.load() + } + /// Whether the GUI is currently visible. // Called `is_open()` instead of `open()` to avoid the ambiguity. pub fn is_open(&self) -> bool { @@ -105,12 +144,15 @@ impl Editor for ViziaEditor { context: Arc, ) -> Box { let app = self.app.clone(); + let vizia_state = self.vizia_state.clone(); let apply_theming = self.apply_theming; - let (unscaled_width, unscaled_height) = self.vizia_state.size(); - let scaling_factor = self.scaling_factor.load(); - let window_description = - WindowDescription::new().with_inner_size(unscaled_width, unscaled_height); + let (unscaled_width, unscaled_height) = vizia_state.inner_logical_size(); + let system_scaling_factor = self.scaling_factor.load(); + let user_scale_factor = vizia_state.user_scale_factor(); + let window_description = WindowDescription::new() + .with_inner_size(unscaled_width, unscaled_height) + .with_scale_factor(user_scale_factor); let window = Application::new(window_description, move |cx| { // Set some default styles to match the iced integration @@ -136,10 +178,18 @@ impl Editor for ViziaEditor { } .build(cx); + // And we'll link `WindowEvent::ResizeWindow` and `WindowEvent::SetScale` events to our + // `ViziaState`. We'll notify the host when any of these change. + widgets::WindowModel { + context: context.clone(), + vizia_state: vizia_state.clone(), + } + .build(cx); + app(cx) }) .with_scale_policy( - scaling_factor + system_scaling_factor .map(|factor| WindowScalePolicy::ScaleFactor(factor as f64)) .unwrap_or(WindowScalePolicy::SystemScaleFactor), ) @@ -153,16 +203,21 @@ impl Editor for ViziaEditor { } fn size(&self) -> (u32, u32) { - self.vizia_state.size() + // This includes the user scale factor if set, but not any HiDPI scaling + self.vizia_state.scaled_logical_size() } fn set_scale_factor(&self, factor: f32) -> bool { + // We're making things a bit more complicated by having both a system scale factor, which is + // used for HiDPI and also known to the host, and a user scale factor that the user can use + // to arbitrarily resize the GUI self.scaling_factor.store(Some(factor)); true } fn param_values_changed(&self) { - // TODO: Update the GUI when this happens + // TODO: Update the GUI when this happens, right now this happens automatically as a result + // of of the reactivity } } diff --git a/nih_plug_vizia/src/widgets.rs b/nih_plug_vizia/src/widgets.rs index 8c60ab8a..213b3ef6 100644 --- a/nih_plug_vizia/src/widgets.rs +++ b/nih_plug_vizia/src/widgets.rs @@ -8,7 +8,9 @@ use nih_plug::prelude::{GuiContext, Param, ParamPtr}; use std::sync::Arc; -use vizia::{Context, Model}; +use vizia::{Context, Model, WindowEvent}; + +use super::ViziaState; mod generic_ui; mod param_slider; @@ -63,6 +65,13 @@ pub(crate) struct ParamModel { pub context: Arc, } +/// Handles interactions through `WindowEvent` for VIZIA GUIs by updating the `ViziaState`. +/// Registered in [`ViziaEditor::spawn()`][super::ViziaEditor::spawn()]. +pub(crate) struct WindowModel { + pub context: Arc, + pub vizia_state: Arc, +} + impl Model for ParamModel { fn event(&mut self, _cx: &mut vizia::Context, event: &mut vizia::Event) { if let Some(param_event) = event.message.downcast() { @@ -83,6 +92,56 @@ impl Model for ParamModel { } } +impl Model for WindowModel { + fn event(&mut self, cx: &mut vizia::Context, event: &mut vizia::Event) { + if let Some(window_event) = event.message.downcast() { + match *window_event { + WindowEvent::ResizeWindow(logical_width, logical_height) => { + let logical_size = + (logical_width.round() as u32, logical_height.round() as u32); + let old_size @ (old_logical_width, old_logical_height) = + self.vizia_state.size.load(); + + // Don't do anything if the current size already matches the new size, this + // could otherwise also cause a feedback loop on resize failure + if logical_size == old_size { + return; + } + + // Our embedded baseview window will have already been resized. If the host does + // not accept our new size, then we'll try to undo that + self.vizia_state.size.store(logical_size); + if !self.context.request_resize() { + self.vizia_state.size.store(old_size); + cx.emit(WindowEvent::ResizeWindow( + old_logical_width as f32, + old_logical_height as f32, + )); + } + } + WindowEvent::SetScale(user_scale_factor) => { + let old_user_scale_factor = self.vizia_state.scale_factor.load(); + + // Don't do anything if the current scale already matches the new scale + if user_scale_factor == old_user_scale_factor { + return; + } + + // This works the same as the `ResizeWindow` handler. The actual window size + // reported to the host gets calculated from a combination of the window's + // logical size (before user scaling) and the user scale factor. + self.vizia_state.scale_factor.store(user_scale_factor); + if !self.context.request_resize() { + self.vizia_state.scale_factor.store(old_user_scale_factor); + cx.emit(WindowEvent::SetScale(old_user_scale_factor)); + } + } + + _ => (), + } + } + } +} impl From> for RawParamEvent { fn from(event: ParamEvent<'_, P>) -> Self { match event {