1
0
Fork 0

Use computed sizes for ViziaState

This commit is contained in:
Robbert van der Helm 2023-03-07 21:22:15 +01:00
parent 17a95e703f
commit f6041789cd
8 changed files with 78 additions and 41 deletions

View file

@ -14,11 +14,23 @@ state is to list breaking changes.
This document is now also used to keep track of non-breaking changes.
### Breaking changes
- The way window sizes work in `ViziaState` has been reworked to be more
predictable and reliable. Instead of creating a `ViziaState` with a predefined
size and then tracking the window's current size in that object, `ViziaState`
now takes a callback that returns the window's current logical size. This can
be used to compute the window's current size based on the plugin's state. The
result is that window sizes always match the plugin's current state and
recalling an old incorrect size is no longer possible.
### Added
- Debug builds now include debug assertions that detect incorrect use of the
`GuiContext`'s parameter setting methods.
### Changed
## [2023-02-28]
### Breaking changes

View file

@ -67,9 +67,14 @@ impl Editor for ViziaEditor {
// And we'll link `WindowEvent::ResizeWindow` and `WindowEvent::SetScale` events to our
// `ViziaState`. We'll notify the host when any of these change.
let current_inner_window_size = cx.window_size();
widgets::WindowModel {
context: context.clone(),
vizia_state: vizia_state.clone(),
last_inner_window_size: AtomicCell::new((
current_inner_window_size.width,
current_inner_window_size.height,
)),
}
.build(cx);

View file

@ -7,6 +7,7 @@ use crossbeam::atomic::AtomicCell;
use nih_plug::params::persist::PersistentField;
use nih_plug::prelude::{Editor, GuiContext};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use vizia::prelude::*;
@ -81,13 +82,14 @@ pub enum ViziaTheming {
Custom,
}
/// State for an `nih_plug_vizia` editor. The scale factor can be manipulated at runtime by changing
/// `cx.user_scale_factor`.
#[derive(Debug, Serialize, Deserialize)]
/// State for an `nih_plug_vizia` editor. The scale factor can be manipulated at runtime using
/// `cx.set_user_scale_factor()`.
#[derive(Serialize, Deserialize)]
pub struct ViziaState {
/// The window's size in logical pixels before applying `scale_factor`.
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
size: AtomicCell<(u32, u32)>,
/// A function that returns the window's current size in logical pixels, before any sort of
/// scaling is applied. This size can be computed based on the plugin's current state.
#[serde(skip, default = "empty_size_fn")]
size_fn: Box<dyn Fn() -> (u32, u32) + Send + Sync>,
/// 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.
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
@ -95,17 +97,27 @@ pub struct ViziaState {
/// Whether the editor's window is currently open.
#[serde(skip)]
open: AtomicBool,
}
/// Whether the size should be saved. If the window's size is always scaled uniformly, then this
/// is not needed and can only result in problems.
should_save_size: bool,
/// A default implementation for `size_fn` needed to be able to derive the `Deserialize` trait.
fn empty_size_fn() -> Box<dyn Fn() -> (u32, u32) + Send + Sync> {
Box::new(|| (0, 0))
}
impl Debug for ViziaState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (width, height) = (self.size_fn)();
f.debug_struct("ViziaState")
.field("size_fn", &format!("<fn> ({}, {})", width, height))
.field("scale_factor", &self.scale_factor)
.field("open", &self.open)
.finish()
}
}
impl<'a> PersistentField<'a, ViziaState> for Arc<ViziaState> {
fn set(&self, new_value: ViziaState) {
if self.should_save_size {
self.size.store(new_value.size.load());
}
self.scale_factor.store(new_value.scale_factor.load());
}
@ -119,42 +131,35 @@ impl<'a> PersistentField<'a, ViziaState> for Arc<ViziaState> {
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.
///
/// Setting `should_save_size` to `false` may be useful when the size is supposed to be fixed
/// and only the scaling factor changes. This allows the object to be persisted in a `Params`
/// object without accidentally restoring old sizes after the window's logical size has changed
/// in a plugin update.
pub fn from_size(width: u32, height: u32, should_save_size: bool) -> Arc<ViziaState> {
/// callback always returns the window's current size is in logical pixels, so before it is
/// multiplied by the DPI scaling factor. This size can be computed based on the plugin's
/// current state.
pub fn new(size_fn: impl Fn() -> (u32, u32) + Send + Sync + 'static) -> Arc<ViziaState> {
Arc::new(ViziaState {
size: AtomicCell::new((width, height)),
size_fn: Box::new(size_fn),
scale_factor: AtomicCell::new(1.0),
open: AtomicBool::new(false),
should_save_size,
})
}
/// 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 changing `cx.user_scale_factor`.
pub fn from_size_with_scale(
width: u32,
height: u32,
scale_factor: f64,
should_save_size: bool,
/// The same as [`new()`][Self::new()], 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
/// changing `cx.set_user_scale_factor()`.
pub fn new_with_default_scale_factor(
size_fn: impl Fn() -> (u32, u32) + Send + Sync + 'static,
default_scale_factor: f64,
) -> Arc<ViziaState> {
Arc::new(ViziaState {
size: AtomicCell::new((width, height)),
scale_factor: AtomicCell::new(scale_factor),
size_fn: Box::new(size_fn),
scale_factor: AtomicCell::new(default_scale_factor),
open: AtomicBool::new(false),
should_save_size,
})
}
/// Returns 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 (logical_width, logical_height) = self.inner_logical_size();
let scale_factor = self.scale_factor.load();
(
@ -166,7 +171,7 @@ impl ViziaState {
/// Returns 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()
(self.size_fn)()
}
/// Get the non-DPI related uniform scaling factor the GUI's size will be multiplied with. This

View file

@ -5,6 +5,8 @@
//! None of these widgets are finalized, and their sizes or looks can change at any point. Feel free
//! to copy the widgets and modify them to your personal taste.
use crossbeam::atomic::AtomicCell;
use nih_plug::nih_debug_assert_eq;
use nih_plug::prelude::{GuiContext, Param, ParamPtr};
use std::sync::Arc;
use vizia::prelude::*;
@ -78,6 +80,10 @@ pub(crate) struct ParamModel {
pub(crate) struct WindowModel {
pub context: Arc<dyn GuiContext>,
pub vizia_state: Arc<ViziaState>,
/// The last known unscaled logical window size. Used to prevent sending duplicate resize
/// requests.
pub last_inner_window_size: AtomicCell<(u32, u32)>,
}
impl Model for ParamModel {
@ -104,8 +110,16 @@ impl Model for WindowModel {
event.map(|window_event, _| {
if let WindowEvent::GeometryChanged { .. } = window_event {
let logical_size = (cx.window_size().width, cx.window_size().height);
// `self.vizia_state.inner_logical_size()` should match `logical_size`. Since it's
// computed we need to store the last logical size on this object.
nih_debug_assert_eq!(
logical_size,
self.vizia_state.inner_logical_size(),
"The window size set on the vizia context does not match the size returned by \
'ViziaState::size_fn'"
);
let old_logical_size @ (old_logical_width, old_logical_height) =
self.vizia_state.size.load();
self.last_inner_window_size.load();
let scale_factor = cx.user_scale_factor();
let old_user_scale_factor = self.vizia_state.scale_factor.load();
@ -117,13 +131,14 @@ impl Model for WindowModel {
// 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);
self.last_inner_window_size.store(logical_size);
self.vizia_state.scale_factor.store(scale_factor);
if !self.context.request_resize() {
self.vizia_state.size.store(old_logical_size);
self.last_inner_window_size.store(old_logical_size);
self.vizia_state.scale_factor.store(old_user_scale_factor);
// This will cause the window's size to be reverted on the next event loop
// NOTE: Is resizing back the correct behavior now that the size is computed?
cx.set_window_size(WindowSize {
width: old_logical_width,
height: old_logical_height,

View file

@ -31,7 +31,7 @@ impl Model for Data {}
// Makes sense to also define this here, makes it a bit easier to keep track of
pub(crate) fn default_state() -> Arc<ViziaState> {
ViziaState::from_size(400, 390, false)
ViziaState::new(|| (400, 390))
}
pub(crate) fn create(

View file

@ -60,7 +60,7 @@ impl Model for Data {}
// Makes sense to also define this here, makes it a bit easier to keep track of
pub(crate) fn default_state() -> Arc<ViziaState> {
ViziaState::from_size(EDITOR_WIDTH, EDITOR_HEIGHT, false)
ViziaState::new(|| (EDITOR_WIDTH, EDITOR_HEIGHT))
}
pub(crate) fn create(editor_data: Data, editor_state: Arc<ViziaState>) -> Option<Box<dyn Editor>> {

View file

@ -19,7 +19,7 @@ impl Model for Data {}
// Makes sense to also define this here, makes it a bit easier to keep track of
pub(crate) fn default_state() -> Arc<ViziaState> {
ViziaState::from_size(200, 150, false)
ViziaState::new(|| (200, 150))
}
pub(crate) fn create(

View file

@ -36,7 +36,7 @@ impl Model for Data {}
// Makes sense to also define this here, makes it a bit easier to keep track of
pub(crate) fn default_state() -> Arc<ViziaState> {
ViziaState::from_size(680, 535, false)
ViziaState::new(|| (680, 535))
}
pub(crate) fn create(