1
0
Fork 0

Add DPI scaling support

That hopefully works.
This commit is contained in:
Robbert van der Helm 2022-03-05 13:37:35 +01:00
parent e491ff6319
commit 9267a8371c
4 changed files with 139 additions and 42 deletions

View file

@ -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`].

View file

@ -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
}

View file

@ -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) {

View file

@ -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
}
}
}