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.
|
/// field on your parameters struct.
|
||||||
///
|
///
|
||||||
/// See [`EguiState::from_size()`].
|
/// See [`EguiState::from_size()`].
|
||||||
//
|
|
||||||
// TODO: DPI scaling, this needs to be implemented on the framework level
|
|
||||||
pub fn create_egui_editor<T, U>(
|
pub fn create_egui_editor<T, U>(
|
||||||
egui_state: Arc<EguiState>,
|
egui_state: Arc<EguiState>,
|
||||||
user_state: T,
|
user_state: T,
|
||||||
|
@ -42,6 +40,8 @@ where
|
||||||
egui_state,
|
egui_state,
|
||||||
user_state: Arc::new(RwLock::new(user_state)),
|
user_state: Arc::new(RwLock::new(user_state)),
|
||||||
update: Arc::new(update),
|
update: Arc::new(update),
|
||||||
|
|
||||||
|
scaling_factor: AtomicCell::new(None),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ pub struct EguiState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub fn from_size(width: u32, height: u32) -> Arc<EguiState> {
|
||||||
Arc::new(EguiState {
|
Arc::new(EguiState {
|
||||||
size: AtomicCell::new((width, height)),
|
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) {
|
pub fn size(&self) -> (u32, u32) {
|
||||||
self.size.load()
|
self.size.load()
|
||||||
}
|
}
|
||||||
|
@ -79,6 +80,10 @@ struct EguiEditor<T> {
|
||||||
/// The plugin's state. This is kept in between editor openenings.
|
/// The plugin's state. This is kept in between editor openenings.
|
||||||
user_state: Arc<RwLock<T>>,
|
user_state: Arc<RwLock<T>>,
|
||||||
update: Arc<dyn Fn(&Context, &ParamSetter, &mut T) + 'static + Send + Sync>,
|
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>
|
impl<T> Editor for EguiEditor<T>
|
||||||
|
@ -93,16 +98,19 @@ where
|
||||||
let update = self.update.clone();
|
let update = self.update.clone();
|
||||||
let state = self.user_state.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(
|
let window = EguiWindow::open_parented(
|
||||||
&parent,
|
&parent,
|
||||||
WindowOpenOptions {
|
WindowOpenOptions {
|
||||||
title: String::from("egui window"),
|
title: String::from("egui window"),
|
||||||
size: Size::new(width as f64, height as f64),
|
// Baseview should be doing the DPI scaling for us
|
||||||
// TODO: Implement the plugin-specific DPI scaling APIs with a method on the
|
size: Size::new(unscaled_width as f64, unscaled_height as f64),
|
||||||
// `GuiContext` when baseview gets window resizing. For some reason passing
|
// NOTE: For some reason passing 1.0 here causes the UI to be scaled on macOS but
|
||||||
// 1.0 here causes the UI to be scaled on macOS but not the mouse events.
|
// not the mouse events.
|
||||||
scale: WindowScalePolicy::SystemScaleFactor,
|
scale: scaling_factor
|
||||||
|
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
|
||||||
|
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
|
||||||
gl_config: Some(GlConfig {
|
gl_config: Some(GlConfig {
|
||||||
version: (3, 2),
|
version: (3, 2),
|
||||||
red_bits: 8,
|
red_bits: 8,
|
||||||
|
@ -144,6 +152,11 @@ where
|
||||||
fn size(&self) -> (u32, u32) {
|
fn size(&self) -> (u32, u32) {
|
||||||
self.egui_state.size()
|
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`].
|
/// 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
|
/// gets closed. Implement the [`Drop`] trait on the returned handle if you need to explicitly
|
||||||
/// handle the editor's closing behavior.
|
/// 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
|
/// The wrapper guarantees that a previous handle has been dropped before this function is
|
||||||
/// called again.
|
/// called again.
|
||||||
//
|
//
|
||||||
|
@ -216,14 +219,24 @@ pub trait Editor: Send + Sync {
|
||||||
context: Arc<dyn GuiContext>,
|
context: Arc<dyn GuiContext>,
|
||||||
) -> Box<dyn Any + Send + Sync>;
|
) -> 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);
|
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
|
// 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
|
// 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
|
// 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.
|
// itself. This would also need an associated `PREFERRED_FRAME_RATE` constant.
|
||||||
// TODO: Add the things needed for DPI scaling
|
|
||||||
// TODO: Resizing
|
// TODO: Resizing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// explicitly pattern match on that unit
|
// explicitly pattern match on that unit
|
||||||
#![allow(clippy::unused_unit)]
|
#![allow(clippy::unused_unit)]
|
||||||
|
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||||
use clap_sys::events::{
|
use clap_sys::events::{
|
||||||
clap_event_header, clap_event_note, clap_event_param_mod, clap_event_param_value,
|
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
|
/// A handle for the currently active editor instance. The plugin should implement `Drop` on
|
||||||
/// this handle for its closing behavior.
|
/// this handle for its closing behavior.
|
||||||
editor_handle: RwLock<Option<Box<dyn Any + Send + Sync>>>,
|
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,
|
is_processing: AtomicBool,
|
||||||
/// The current IO configuration, modified through the `clap_plugin_audio_ports_config`
|
/// The current IO configuration, modified through the `clap_plugin_audio_ports_config`
|
||||||
|
@ -327,6 +333,7 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
plugin,
|
plugin,
|
||||||
editor,
|
editor,
|
||||||
editor_handle: RwLock::new(None),
|
editor_handle: RwLock::new(None),
|
||||||
|
editor_scaling_factor: AtomicF32::new(1.0),
|
||||||
|
|
||||||
is_processing: AtomicBool::new(false),
|
is_processing: AtomicBool::new(false),
|
||||||
current_bus_config: AtomicCell::new(BusConfig {
|
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 {
|
unsafe extern "C" fn ext_gui_set_scale(plugin: *const clap_plugin, scale: f64) -> bool {
|
||||||
// TOOD: Implement DPI scaling
|
check_null_ptr!(false, plugin);
|
||||||
false
|
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(
|
unsafe extern "C" fn ext_gui_get_size(
|
||||||
|
@ -1218,15 +1245,14 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
check_null_ptr!(false, plugin, width, height);
|
check_null_ptr!(false, plugin, width, height);
|
||||||
let wrapper = &*(plugin as *const Self);
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
match &wrapper.editor {
|
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size();
|
||||||
Some(editor) => {
|
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
|
||||||
(*width, *height) = editor.size();
|
(*width, *height) = (
|
||||||
true
|
(unscaled_width as f32 * scaling_factor).round() as u32,
|
||||||
}
|
(unscaled_height as f32 * scaling_factor).round() as u32,
|
||||||
None => {
|
);
|
||||||
unreachable!("We don't return the editor extension on plugins without an editor");
|
|
||||||
}
|
true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn ext_gui_can_resize(_plugin: *const clap_plugin) -> bool {
|
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);
|
check_null_ptr!(false, plugin);
|
||||||
let wrapper = &*(plugin as *const Self);
|
let wrapper = &*(plugin as *const Self);
|
||||||
|
|
||||||
match &wrapper.editor {
|
let (unscaled_width, unscaled_height) = wrapper.editor.as_ref().unwrap().size();
|
||||||
Some(editor) => {
|
let scaling_factor = wrapper.editor_scaling_factor.load(Ordering::Relaxed);
|
||||||
let (editor_width, editor_height) = editor.size();
|
let (editor_width, editor_height) = (
|
||||||
width == editor_width && height == editor_height
|
(unscaled_width as f32 * scaling_factor).round() as u32,
|
||||||
}
|
(unscaled_height as f32 * scaling_factor).round() as u32,
|
||||||
None => {
|
);
|
||||||
unreachable!("We don't return the editor extension on plugins without an editor");
|
|
||||||
}
|
width == editor_width && height == editor_height
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn ext_gui_show(_plugin: *const clap_plugin) {
|
unsafe extern "C" fn ext_gui_show(_plugin: *const clap_plugin) {
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
use atomic_float::AtomicF32;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use raw_window_handle::RawWindowHandle;
|
use raw_window_handle::RawWindowHandle;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::ffi::{c_void, CStr};
|
use std::ffi::{c_void, CStr};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vst3_com::utils::SharedVstPtr;
|
use vst3_com::utils::SharedVstPtr;
|
||||||
use vst3_sys::base::{kInvalidArgument, kResultFalse, kResultOk, tresult, TBool};
|
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 vst3_sys::VST3;
|
||||||
|
|
||||||
use super::inner::WrapperInner;
|
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
|
/// 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.
|
/// editor. This is managed separately so the lifetime bounds match up.
|
||||||
#[VST3(implements(IPlugView))]
|
#[VST3(implements(IPlugView, IPlugViewContentScaleSupport))]
|
||||||
pub(crate) struct WrapperView<P: Vst3Plugin> {
|
pub(crate) struct WrapperView<P: Vst3Plugin> {
|
||||||
inner: Arc<WrapperInner<P>>,
|
inner: Arc<WrapperInner<P>>,
|
||||||
editor: Arc<dyn Editor>,
|
editor: Arc<dyn Editor>,
|
||||||
editor_handle: RwLock<Option<Box<dyn Any>>>,
|
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>>>,
|
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> {
|
impl<P: Vst3Plugin> WrapperView<P> {
|
||||||
pub fn new(inner: Arc<WrapperInner<P>>, editor: Arc<dyn Editor>) -> Box<Self> {
|
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();
|
*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;
|
let size = &mut *size;
|
||||||
size.left = 0;
|
size.left = 0;
|
||||||
size.right = width as i32;
|
size.right = (unscaled_width as f32 * scaling_factor).round() as i32;
|
||||||
size.top = 0;
|
size.top = 0;
|
||||||
size.bottom = height as i32;
|
size.bottom = (unscaled_height as f32 * scaling_factor).round() as i32;
|
||||||
|
|
||||||
kResultOk
|
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
|
// 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 {
|
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