Add DPI scaling support
That hopefully works.
This commit is contained in:
parent
e491ff6319
commit
9267a8371c
|
@ -27,8 +27,6 @@ pub mod widgets;
|
|||
/// field on your parameters struct.
|
||||
///
|
||||
/// See [`EguiState::from_size()`].
|
||||
//
|
||||
// TODO: DPI scaling, this needs to be implemented on the framework level
|
||||
pub fn create_egui_editor<T, U>(
|
||||
egui_state: Arc<EguiState>,
|
||||
user_state: T,
|
||||
|
@ -42,6 +40,8 @@ where
|
|||
egui_state,
|
||||
user_state: Arc::new(RwLock::new(user_state)),
|
||||
update: Arc::new(update),
|
||||
|
||||
scaling_factor: AtomicCell::new(None),
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ pub struct EguiState {
|
|||
}
|
||||
|
||||
impl EguiState {
|
||||
/// Initialize the GUI's state. This is passed to [`create_egui_editor()`].
|
||||
/// Initialize the GUI's state. This value can be passed to [`create_egui_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<EguiState> {
|
||||
Arc::new(EguiState {
|
||||
size: AtomicCell::new((width, height)),
|
||||
|
@ -61,7 +62,7 @@ impl EguiState {
|
|||
})
|
||||
}
|
||||
|
||||
/// Return a `(width, height)` pair for the current size of the GUI.
|
||||
/// Return a `(width, height)` pair for the current size of the GUI in logical pixels.
|
||||
pub fn size(&self) -> (u32, u32) {
|
||||
self.size.load()
|
||||
}
|
||||
|
@ -79,6 +80,10 @@ struct EguiEditor<T> {
|
|||
/// The plugin's state. This is kept in between editor openenings.
|
||||
user_state: Arc<RwLock<T>>,
|
||||
update: Arc<dyn Fn(&Context, &ParamSetter, &mut T) + 'static + Send + Sync>,
|
||||
|
||||
/// The scaling factor reported by the host, if any. On macOS this will never be set and we
|
||||
/// should use the system scaling factor instead.
|
||||
scaling_factor: AtomicCell<Option<f32>>,
|
||||
}
|
||||
|
||||
impl<T> Editor for EguiEditor<T>
|
||||
|
@ -93,16 +98,19 @@ where
|
|||
let update = self.update.clone();
|
||||
let state = self.user_state.clone();
|
||||
|
||||
let (width, height) = self.egui_state.size();
|
||||
let (unscaled_width, unscaled_height) = self.egui_state.size();
|
||||
let scaling_factor = self.scaling_factor.load();
|
||||
let window = EguiWindow::open_parented(
|
||||
&parent,
|
||||
WindowOpenOptions {
|
||||
title: String::from("egui window"),
|
||||
size: Size::new(width as f64, height as f64),
|
||||
// TODO: Implement the plugin-specific DPI scaling APIs with a method on the
|
||||
// `GuiContext` when baseview gets window resizing. For some reason passing
|
||||
// 1.0 here causes the UI to be scaled on macOS but not the mouse events.
|
||||
scale: WindowScalePolicy::SystemScaleFactor,
|
||||
// Baseview should be doing the DPI scaling for us
|
||||
size: Size::new(unscaled_width as f64, unscaled_height as f64),
|
||||
// NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but
|
||||
// not the mouse events.
|
||||
scale: scaling_factor
|
||||
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||
gl_config: Some(GlConfig {
|
||||
version: (3, 2),
|
||||
red_bits: 8,
|
||||
|
@ -144,6 +152,11 @@ where
|
|||
fn size(&self) -> (u32, u32) {
|
||||
self.egui_state.size()
|
||||
}
|
||||
|
||||
fn set_scale_factor(&self, factor: f32) -> bool {
|
||||
self.scaling_factor.store(Some(factor));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// The window handle used for [`EguiEditor`].
|
||||
|
|
|
@ -201,6 +201,9 @@ pub trait Editor: Send + Sync {
|
|||
/// gets closed. Implement the [`Drop`] trait on the returned handle if you need to explicitly
|
||||
/// handle the editor's closing behavior.
|
||||
///
|
||||
/// If [`set_scale_factor()`][Self::set_scale_factor()] has been called, then any created
|
||||
/// windows should have their sizes multiplied by that factor.
|
||||
///
|
||||
/// The wrapper guarantees that a previous handle has been dropped before this function is
|
||||
/// called again.
|
||||
//
|
||||
|
@ -216,14 +219,24 @@ pub trait Editor: Send + Sync {
|
|||
context: Arc<dyn GuiContext>,
|
||||
) -> Box<dyn Any + Send + Sync>;
|
||||
|
||||
/// Return the (currnent) size of the editor in pixels as a `(width, height)` pair.
|
||||
/// Return the (currnent) size of the editor in pixels as a `(width, height)` pair. This size
|
||||
/// must be reported in _logical pixels_, i.e. the size before being multiplied by the DPI
|
||||
/// scaling factor to get the actual physical screen pixels.
|
||||
fn size(&self) -> (u32, u32);
|
||||
|
||||
/// Set the DPI scaling factor, if supported. The plugin APIs don't make any guarantees on when
|
||||
/// this is called, but for now just assume it will be the first function that gets called
|
||||
/// before creating the editor. If this is set, then any windows created by this editor should
|
||||
/// have their sizes multiplied by this scaling factor on Windows and Linux.
|
||||
///
|
||||
/// Right now this is never called on macOS since DPI scaling is built into the operating system
|
||||
/// there.
|
||||
fn set_scale_factor(&self, factor: f32) -> bool;
|
||||
|
||||
// TODO: Reconsider adding a tick function here for the Linux `IRunLoop`. To keep this platform
|
||||
// and API agnostic, add a way to ask the GuiContext if the wrapper already provides a
|
||||
// tick function. If it does not, then the Editor implementation must handle this by
|
||||
// itself. This would also need an associated `PREFERRED_FRAME_RATE` constant.
|
||||
// TODO: Add the things needed for DPI scaling
|
||||
// TODO: Resizing
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// explicitly pattern match on that unit
|
||||
#![allow(clippy::unused_unit)]
|
||||
|
||||
use atomic_float::AtomicF32;
|
||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||
use clap_sys::events::{
|
||||
clap_event_header, clap_event_note, clap_event_param_mod, clap_event_param_value,
|
||||
|
@ -101,6 +102,11 @@ pub struct Wrapper<P: ClapPlugin> {
|
|||
/// A handle for the currently active editor instance. The plugin should implement `Drop` on
|
||||
/// this handle for its closing behavior.
|
||||
editor_handle: RwLock<Option<Box<dyn Any + Send + Sync>>>,
|
||||
/// The DPI scaling factor as passed to the [IPlugViewContentScaleSupport::set_scale_factor()]
|
||||
/// function. Defaults to 1.0, and will be kept there on macOS. When reporting and handling size
|
||||
/// the sizes communicated to and from the DAW should be scaled by this factor since NIH-plug's
|
||||
/// APIs only deal in logical pixels.
|
||||
editor_scaling_factor: AtomicF32,
|
||||
|
||||
is_processing: AtomicBool,
|
||||
/// The current IO configuration, modified through the `clap_plugin_audio_ports_config`
|
||||
|
@ -327,6 +333,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
plugin,
|
||||
editor,
|
||||
editor_handle: RwLock::new(None),
|
||||
editor_scaling_factor: AtomicF32::new(1.0),
|
||||
|
||||
is_processing: AtomicBool::new(false),
|
||||
current_bus_config: AtomicCell::new(BusConfig {
|
||||
|
@ -1205,9 +1212,29 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ext_gui_set_scale(_plugin: *const clap_plugin, _scale: f64) -> bool {
|
||||
// TOOD: Implement DPI scaling
|
||||
false
|
||||
unsafe extern "C" fn ext_gui_set_scale(plugin: *const clap_plugin, scale: f64) -> bool {
|
||||
check_null_ptr!(false, plugin);
|
||||
let wrapper = &*(plugin as *const Self);
|
||||
|
||||
// On macOS scaling is done by the OS, and all window sizes are in logical pixels
|
||||
if cfg!(target_os = "macos") {
|
||||
nih_debug_assert_failure!("Ignoring host request to set explicit DPI scaling factor");
|
||||
return false;
|
||||
}
|
||||
|
||||
if wrapper
|
||||
.editor
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.set_scale_factor(scale as f32)
|
||||
{
|
||||
wrapper
|
||||
.editor_scaling_factor
|
||||
.store(scale as f32, std::sync::atomic::Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ext_gui_get_size(
|
||||
|
@ -1218,15 +1245,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
check_null_ptr!(false, plugin, width, height);
|
||||
let wrapper = &*(plugin as *const Self);
|
||||
|
||||
match &wrapper.editor {
|
||||
Some(editor) => {
|
||||
(*width, *height) = editor.size();
|
||||
true
|
||||
}
|
||||
None => {
|
||||
unreachable!("We don't return the editor extension on plugins without an editor");
|
||||
}
|
||||
}
|
||||
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size();
|
||||
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
|
||||
(*width, *height) = (
|
||||
(unscaled_width as f32 * scaling_factor).round() as u32,
|
||||
(unscaled_height as f32 * scaling_factor).round() as u32,
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ext_gui_can_resize(_plugin: *const clap_plugin) -> bool {
|
||||
|
@ -1251,15 +1277,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
check_null_ptr!(false, plugin);
|
||||
let wrapper = &*(plugin as *const Self);
|
||||
|
||||
match &wrapper.editor {
|
||||
Some(editor) => {
|
||||
let (editor_width, editor_height) = editor.size();
|
||||
width == editor_width && height == editor_height
|
||||
}
|
||||
None => {
|
||||
unreachable!("We don't return the editor extension on plugins without an editor");
|
||||
}
|
||||
}
|
||||
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size();
|
||||
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
|
||||
let (editor_width, editor_height) = (
|
||||
(unscaled_width as f32 * scaling_factor).round() as u32,
|
||||
(unscaled_height as f32 * scaling_factor).round() as u32,
|
||||
);
|
||||
|
||||
width == editor_width && height == editor_height
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ext_gui_show(_plugin: *const clap_plugin) {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use atomic_float::AtomicF32;
|
||||
use parking_lot::RwLock;
|
||||
use raw_window_handle::RawWindowHandle;
|
||||
use std::any::Any;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use vst3_com::utils::SharedVstPtr;
|
||||
use vst3_sys::base::{kInvalidArgument, kResultFalse, kResultOk, tresult, TBool};
|
||||
use vst3_sys::gui::{IPlugFrame, IPlugView};
|
||||
use vst3_sys::gui::{IPlugFrame, IPlugView, IPlugViewContentScaleSupport};
|
||||
use vst3_sys::VST3;
|
||||
|
||||
use super::inner::WrapperInner;
|
||||
|
@ -30,19 +32,30 @@ const VST3_PLATFORM_X11_WINDOW: &str = "X11EmbedWindowID";
|
|||
|
||||
/// The plugin's [`IPlugView`] instance created in [`IEditController::create_view()`] if `P` has an
|
||||
/// editor. This is managed separately so the lifetime bounds match up.
|
||||
#[VST3(implements(IPlugView))]
|
||||
#[VST3(implements(IPlugView, IPlugViewContentScaleSupport))]
|
||||
pub(crate) struct WrapperView<P: Vst3Plugin> {
|
||||
inner: Arc<WrapperInner<P>>,
|
||||
editor: Arc<dyn Editor>,
|
||||
editor_handle: RwLock<Option<Box<dyn Any>>>,
|
||||
|
||||
/// The `IPlugFrame` instance passed by the host during `IPlugView::set_frame`.
|
||||
/// The `IPlugFrame` instance passed by the host during [IPlugView::set_frame()].
|
||||
pub plug_frame: RwLock<Option<VstPtr<dyn IPlugFrame>>>,
|
||||
/// The DPI scaling factor as passed to the [IPlugViewContentScaleSupport::set_scale_factor()]
|
||||
/// function. Defaults to 1.0, and will be kept there on macOS. When reporting and handling size
|
||||
/// the sizes communicated to and from the DAW should be scaled by this factor since NIH-plug's
|
||||
/// APIs only deal in logical pixels.
|
||||
scaling_factor: AtomicF32,
|
||||
}
|
||||
|
||||
impl<P: Vst3Plugin> WrapperView<P> {
|
||||
pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<dyn Editor>) -> Box<Self> {
|
||||
Self::allocate(inner, editor, RwLock::new(None), RwLock::new(None))
|
||||
Self::allocate(
|
||||
inner,
|
||||
editor,
|
||||
RwLock::new(None),
|
||||
RwLock::new(None),
|
||||
AtomicF32::new(1.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,19 +184,35 @@ impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
|
|||
|
||||
*size = mem::zeroed();
|
||||
|
||||
let (width, height) = self.editor.size();
|
||||
let (unscaled_width, unscaled_height) = self.editor.size();
|
||||
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
|
||||
let size = &mut *size;
|
||||
size.left = 0;
|
||||
size.right = width as i32;
|
||||
size.right = (unscaled_width as f32 * scaling_factor).round() as i32;
|
||||
size.top = 0;
|
||||
size.bottom = height as i32;
|
||||
size.bottom = (unscaled_height as f32 * scaling_factor).round() as i32;
|
||||
|
||||
kResultOk
|
||||
}
|
||||
|
||||
unsafe fn on_size(&self, _new_size: *mut vst3_sys::gui::ViewRect) -> tresult {
|
||||
unsafe fn on_size(&self, new_size: *mut vst3_sys::gui::ViewRect) -> tresult {
|
||||
// TODO: Implement resizing
|
||||
kResultOk
|
||||
check_null_ptr!(new_size);
|
||||
|
||||
let (unscaled_width, unscaled_height) = self.editor.size();
|
||||
let scaling_factor = self.scaling_factor.load(Ordering::Relaxed);
|
||||
let (editor_width, editor_height) = (
|
||||
(unscaled_width as f32 * scaling_factor).round() as i32,
|
||||
(unscaled_height as f32 * scaling_factor).round() as i32,
|
||||
);
|
||||
|
||||
let width = (*new_size).right - (*new_size).left;
|
||||
let height = (*new_size).bottom - (*new_size).top;
|
||||
if width == editor_width && height == editor_height {
|
||||
kResultOk
|
||||
} else {
|
||||
kResultFalse
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn on_focus(&self, _state: TBool) -> tresult {
|
||||
|
@ -217,3 +246,20 @@ impl<P: Vst3Plugin> IPlugView for WrapperView<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Vst3Plugin> IPlugViewContentScaleSupport for WrapperView<P> {
|
||||
unsafe fn set_scale_factor(&self, factor: f32) -> tresult {
|
||||
// On macOS scaling is done by the OS, and all window sizes are in logical pixels
|
||||
if cfg!(target_os = "macos") {
|
||||
nih_debug_assert_failure!("Ignoring host request to set explicit DPI scaling factor");
|
||||
return kResultFalse;
|
||||
}
|
||||
|
||||
if self.editor.set_scale_factor(factor) {
|
||||
self.scaling_factor.store(factor, Ordering::Relaxed);
|
||||
kResultOk
|
||||
} else {
|
||||
kResultFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue