1
0
Fork 0

Refactor GUIs to use param's own default value

This removes the need to pass a lot of these `ParamSetter`s and
`GuiContext`s around. We also don't need explicit events to reset a
parameter anymore since you can get this information from the parameter
itself.
This commit is contained in:
Robbert van der Helm 2022-03-21 13:09:51 +01:00
parent a844051054
commit f084f14095
18 changed files with 54 additions and 168 deletions

View file

@ -124,9 +124,8 @@ impl<'a, P: Param> ParamSlider<'a, P> {
/// Begin and end drag still need to be called when using this..
fn reset_param(&self) {
let normalized_default = self.setter.default_normalized_param_value(self.param);
self.setter
.set_parameter_normalized(self.param, normalized_default);
.set_parameter(self.param, self.param.default_plain_value());
}
fn granular_drag(&self, ui: &Ui, drag_delta: Vec2) {

View file

@ -216,10 +216,6 @@ pub trait IcedEditor: 'static + Send + Sync + Sized {
ParamMessage::SetParameterNormalized(p, v) => unsafe {
context.raw_set_parameter_normalized(p, v)
},
ParamMessage::ResetParameter(p) => unsafe {
let default_value = context.raw_default_normalized_param_value(p);
context.raw_set_parameter_normalized(p, default_value);
},
ParamMessage::EndSetParameter(p) => unsafe { context.raw_end_set_parameter(p) },
}
}

View file

@ -27,9 +27,6 @@ pub enum ParamMessage {
/// Set a parameter to a new normalized value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
SetParameterNormalized(ParamPtr, f32),
/// Reset a parameter to its default value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
ResetParameter(ParamPtr),
/// End an automation gesture for a parameter.
EndSetParameter(ParamPtr),
}

View file

@ -8,7 +8,7 @@ use std::marker::PhantomData;
use std::pin::Pin;
use nih_plug::param::internals::ParamPtr;
use nih_plug::prelude::{GuiContext, Param, Params};
use nih_plug::prelude::{Param, Params};
use super::{ParamMessage, ParamSlider};
use crate::backend::Renderer;
@ -27,7 +27,6 @@ pub trait ParamWidget {
/// Create an [`Element`] for a widget for the specified parameter.
fn into_widget_element<'a, P: Param>(
param: &'a P,
context: &'a dyn GuiContext,
state: &'a mut Self::State,
) -> Element<'a, ParamMessage>;
@ -38,14 +37,13 @@ pub trait ParamWidget {
/// Undefined behavior of the `ParamPtr` does not point to a valid parameter.
unsafe fn into_widget_element_raw<'a>(
param: &ParamPtr,
context: &'a dyn GuiContext,
state: &'a mut Self::State,
) -> Element<'a, ParamMessage> {
match param {
ParamPtr::FloatParam(p) => Self::into_widget_element(&**p, context, state),
ParamPtr::IntParam(p) => Self::into_widget_element(&**p, context, state),
ParamPtr::BoolParam(p) => Self::into_widget_element(&**p, context, state),
ParamPtr::EnumParam(p) => Self::into_widget_element(&**p, context, state),
ParamPtr::FloatParam(p) => Self::into_widget_element(&**p, state),
ParamPtr::IntParam(p) => Self::into_widget_element(&**p, state),
ParamPtr::BoolParam(p) => Self::into_widget_element(&**p, state),
ParamPtr::EnumParam(p) => Self::into_widget_element(&**p, state),
}
}
}
@ -62,7 +60,6 @@ pub struct GenericUi<'a, W: ParamWidget> {
state: &'a mut State<W>,
params: Pin<&'a dyn Params>,
context: &'a dyn GuiContext,
width: Length,
height: Length,
@ -89,16 +86,11 @@ where
W: ParamWidget,
{
/// Creates a new [`GenericUi`] for all provided parameters.
pub fn new(
state: &'a mut State<W>,
params: Pin<&'a dyn Params>,
context: &'a dyn GuiContext,
) -> Self {
pub fn new(state: &'a mut State<W>, params: Pin<&'a dyn Params>) -> Self {
Self {
state,
params,
context,
width: Length::Fill,
height: Length::Fill,
@ -193,9 +185,7 @@ where
.horizontal_alignment(alignment::Horizontal::Right)
.vertical_alignment(alignment::Vertical::Center),
)
.push(unsafe {
W::into_widget_element_raw(&param_ptr, self.context, widget_state)
});
.push(unsafe { W::into_widget_element_raw(&param_ptr, widget_state) });
if self.pad_scrollbar {
// There's already spacing applied, so this element doesn't actually need to hae any
// size of its own
@ -279,10 +269,9 @@ impl ParamWidget for GenericSlider {
fn into_widget_element<'a, P: Param>(
param: &'a P,
context: &'a dyn GuiContext,
state: &'a mut Self::State,
) -> Element<'a, ParamMessage> {
ParamSlider::new(state, param, context).into()
ParamSlider::new(state, param).into()
}
}

View file

@ -1,7 +1,7 @@
//! A slider that integrates with NIH-plug's [`Param`] types.
use atomic_refcell::AtomicRefCell;
use nih_plug::prelude::{GuiContext, Param, ParamSetter};
use nih_plug::prelude::Param;
use std::borrow::Borrow;
use crate::backend::widget;
@ -31,9 +31,6 @@ pub struct ParamSlider<'a, P: Param> {
state: &'a mut State,
param: &'a P,
/// We'll visualize the parameter's current value by drawing the difference between the current
/// normalized value and the default normalized value.
setter: ParamSetter<'a>,
height: Length,
width: Length,
@ -102,14 +99,11 @@ impl widget::text_input::StyleSheet for TextInputStyle {
impl<'a, P: Param> ParamSlider<'a, P> {
/// Creates a new [`ParamSlider`] for the given parameter.
pub fn new(state: &'a mut State, param: &'a P, context: &'a dyn GuiContext) -> Self {
let setter = ParamSetter::new(context);
pub fn new(state: &'a mut State, param: &'a P) -> Self {
Self {
state,
param,
setter,
width: Length::Units(180),
height: Length::Units(30),
@ -328,10 +322,7 @@ impl<'a, P: Param> Widget<ParamMessage, Renderer> for ParamSlider<'a, P> {
self.state.drag_active = false;
shell.publish(ParamMessage::BeginSetParameter(self.param.as_ptr()));
self.set_normalized_value(
shell,
self.setter.default_normalized_param_value(self.param),
);
self.set_normalized_value(shell, self.param.default_normalized_value());
shell.publish(ParamMessage::EndSetParameter(self.param.as_ptr()));
} else if self.state.keyboard_modifiers.shift() {
shell.publish(ParamMessage::BeginSetParameter(self.param.as_ptr()));
@ -494,7 +485,7 @@ impl<'a, P: Param> Widget<ParamMessage, Renderer> for ParamSlider<'a, P> {
// default value lies somewhere in the middle and the parameter is continuous. Otherwise
// this appraoch looks a bit jarring.
let current_value = self.param.normalized_value();
let default_value = self.setter.default_normalized_param_value(self.param);
let default_value = self.param.default_normalized_value();
let fill_start_x = util::remap_rect_x_t(
&bounds_without_borders,
if self.param.step_count().is_none() && (0.45..=0.55).contains(&default_value) {

View file

@ -2,7 +2,7 @@
use baseview::{WindowHandle, WindowScalePolicy};
use crossbeam::atomic::AtomicCell;
use nih_plug::prelude::{Editor, GuiContext, ParamSetter, ParentWindowHandle};
use nih_plug::prelude::{Editor, GuiContext, ParentWindowHandle};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use vizia::{Application, Color, Context, Entity, Model, PropSet, WindowDescription};
@ -23,7 +23,7 @@ pub mod widgets;
/// See [VIZIA](https://github.com/vizia/vizia)'s repository for examples on how to use this.
pub fn create_vizia_editor<F>(vizia_state: Arc<ViziaState>, app: F) -> Option<Box<dyn Editor>>
where
F: Fn(&mut Context, &ParamSetter) + 'static + Send + Sync,
F: Fn(&mut Context) + 'static + Send + Sync,
{
Some(Box::new(ViziaEditor {
vizia_state,
@ -66,7 +66,7 @@ impl ViziaState {
struct ViziaEditor {
vizia_state: Arc<ViziaState>,
/// The user's app function.
app: Arc<dyn Fn(&mut Context, &ParamSetter) + 'static + Send + Sync>,
app: Arc<dyn Fn(&mut Context) + '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.
@ -87,8 +87,6 @@ impl Editor for ViziaEditor {
WindowDescription::new().with_inner_size(unscaled_width, unscaled_height);
let window = Application::new(window_description, move |cx| {
let setter = ParamSetter::new(context.as_ref());
// Set some default styles to match the iced integration
// TODO: Maybe add a way to override this behavior
// NOTE: vizia's font rendering looks way too dark and thick. Going one font weight
@ -112,7 +110,7 @@ impl Editor for ViziaEditor {
}
.build(cx);
app(cx, &setter)
app(cx)
})
.with_scale_policy(
scaling_factor

View file

@ -38,9 +38,6 @@ pub enum ParamEvent<'a, P: Param> {
/// Set a parameter to a new normalized value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
SetParameterNormalized(&'a P, f32),
/// Reset a parameter to its default value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
ResetParameter(&'a P),
/// End an automation gesture for a parameter.
EndSetParameter(&'a P),
}
@ -53,9 +50,6 @@ pub enum RawParamEvent {
/// Set a parameter to a new normalized value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
SetParameterNormalized(ParamPtr, f32),
/// Reset a parameter to its default value. This needs to be surrounded by a matching
/// `BeginSetParameter` and `EndSetParameter`.
ResetParameter(ParamPtr),
/// End an automation gesture for a parameter.
EndSetParameter(ParamPtr),
}
@ -78,10 +72,6 @@ impl Model for ParamModel {
RawParamEvent::SetParameterNormalized(p, v) => unsafe {
self.context.raw_set_parameter_normalized(p, v)
},
RawParamEvent::ResetParameter(p) => unsafe {
let default_value = self.context.raw_default_normalized_param_value(p);
self.context.raw_set_parameter_normalized(p, default_value);
},
RawParamEvent::EndSetParameter(p) => unsafe {
self.context.raw_end_set_parameter(p)
},
@ -100,7 +90,6 @@ impl<P: Param> From<ParamEvent<'_, P>> for RawParamEvent {
ParamEvent::SetParameterNormalized(p, v) => {
RawParamEvent::SetParameterNormalized(p.as_ptr(), v)
}
ParamEvent::ResetParameter(p) => RawParamEvent::ResetParameter(p.as_ptr()),
ParamEvent::EndSetParameter(p) => RawParamEvent::EndSetParameter(p.as_ptr()),
}
}

View file

@ -1,7 +1,7 @@
//! A slider that integrates with NIH-plug's [`Param`] types.
use nih_plug::param::internals::ParamPtr;
use nih_plug::prelude::{Param, ParamSetter};
use nih_plug::prelude::Param;
use vizia::*;
use super::util::{self, ModifiersExt};
@ -76,13 +76,15 @@ enum ParamSliderInternalEvent {
}
impl Model for ParamSliderInternal {
fn event(&mut self, cx: &mut Context, event: &mut Event) {
fn event(&mut self, _cx: &mut Context, event: &mut Event) {
if let Some(param_slider_internal_event) = event.message.downcast() {
match param_slider_internal_event {
ParamSliderInternalEvent::SetStyle(style) => self.style = *style,
ParamSliderInternalEvent::SetTextInputActive(value) => {
cx.current.set_active(cx, *value);
self.text_input_active = *value;
ParamSliderInternalEvent::SetTextInputActive(active) => {
self.text_input_active = *active;
if *active {
// TODO: Interact with the Textbox widget
}
}
}
}
@ -92,17 +94,17 @@ impl Model for ParamSliderInternal {
impl ParamSlider {
/// Creates a new [`ParamSlider`] for the given parameter. To accomdate VIZIA's mapping system,
/// you'll need to provide a lens containing your `Params` implementation object (check out how
/// the `Data` struct is used in `gain_gui_vizia`), the `ParamSetter` for retrieving the
/// parameter's default value, and a projection function that maps the `Params` object to the
/// parameter you want to display a widget for.
/// the `Data` struct is used in `gain_gui_vizia`) and a projection function that maps the
/// `Params` object to the parameter you want to display a widget for. Parameter changes are
/// handled by emitting [`ParamEvent`][super::ParamEvent]s which are automatically handled by
/// the VIZIA wrapper.
///
/// See [`ParamSliderExt`] for additonal options.
pub fn new<'a, L, Params, P, F>(
cx: &'a mut Context,
pub fn new<L, Params, P, F>(
cx: &mut Context,
params: L,
setter: &ParamSetter,
params_to_param: F,
) -> Handle<'a, ParamSlider>
) -> Handle<'_, ParamSlider>
where
L: Lens<Target = Params> + Copy,
F: 'static + Fn(&Params) -> &P + Copy,
@ -117,11 +119,9 @@ impl ParamSlider {
let param_ptr = *params
.map(move |params| params_to_param(params).as_ptr())
.get(cx);
let default_value = unsafe {
setter
.raw_context
.raw_default_normalized_param_value(param_ptr)
};
let default_value = *params
.map(move |params| params_to_param(params).default_normalized_value())
.get(cx);
let step_count = *params
.map(move |params| params_to_param(params).step_count())
.get(cx);
@ -320,7 +320,10 @@ impl View for ParamSlider {
// Ctrl+Click and double click should reset the parameter instead of initiating
// a drag operation
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
cx.emit(RawParamEvent::ResetParameter(self.param_ptr));
cx.emit(RawParamEvent::SetParameterNormalized(
self.param_ptr,
unsafe { self.param_ptr.default_normalized_value() },
));
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
} else {
self.drag_active = true;

View file

@ -85,11 +85,7 @@ impl IcedEditor for CrispEditor {
}
fn view(&mut self) -> Element<'_, Self::Message> {
GenericUi::new(
&mut self.generic_ui_state,
self.params.as_ref(),
self.context.as_ref(),
)
GenericUi::new(&mut self.generic_ui_state, self.params.as_ref())
.pad_scrollbar()
.map(Message::ParamUpdate)
}

View file

@ -84,12 +84,7 @@ impl IcedEditor for DiopserEditor {
}
fn view(&mut self) -> Element<'_, Self::Message> {
GenericUi::new(
&mut self.generic_ui_state,
self.params.as_ref(),
self.context.as_ref(),
)
.map(Message::ParamUpdate)
GenericUi::new(&mut self.generic_ui_state, self.params.as_ref()).map(Message::ParamUpdate)
}
fn background_color(&self) -> nih_plug_iced::Color {

View file

@ -95,11 +95,7 @@ impl IcedEditor for GainEditor {
.vertical_alignment(alignment::Vertical::Center),
)
.push(
nih_widgets::ParamSlider::new(
&mut self.gain_slider_state,
&self.params.gain,
self.context.as_ref(),
)
nih_widgets::ParamSlider::new(&mut self.gain_slider_state, &self.params.gain)
.map(Message::ParamUpdate),
)
.push(Space::with_height(10.into()))

View file

@ -32,7 +32,7 @@ pub(crate) fn create(
peak_meter: Arc<AtomicF32>,
editor_state: Arc<ViziaState>,
) -> Option<Box<dyn Editor>> {
create_vizia_editor(editor_state, move |cx, setter| {
create_vizia_editor(editor_state, move |cx| {
cx.add_theme(STYLE);
Data {
@ -53,13 +53,13 @@ pub(crate) fn create(
Label::new(cx, "Gain").bottom(Pixels(-1.0));
VStack::new(cx, |cx| {
ParamSlider::new(cx, Data::params, setter, |params| &params.gain);
ParamSlider::new(cx, Data::params, setter, |params| &params.gain)
ParamSlider::new(cx, Data::params, |params| &params.gain);
ParamSlider::new(cx, Data::params, |params| &params.gain)
.set_style(ParamSliderStyle::FromLeft);
ParamSlider::new(cx, Data::params, setter, |params| &params.foo);
ParamSlider::new(cx, Data::params, setter, |params| &params.foo)
ParamSlider::new(cx, Data::params, |params| &params.foo);
ParamSlider::new(cx, Data::params, |params| &params.foo)
.set_style(ParamSliderStyle::CurrentStep);
ParamSlider::new(cx, Data::params, setter, |params| &params.foo)
ParamSlider::new(cx, Data::params, |params| &params.foo)
.set_style(ParamSliderStyle::CurrentStepLabeled);
})
.row_between(Pixels(5.0));

View file

@ -71,16 +71,6 @@ pub trait GuiContext: Send + Sync + 'static {
/// The implementing function still needs to check if `param` actually exists. This function is
/// mostly marked as unsafe for API reasons.
unsafe fn raw_end_set_parameter(&self, param: ParamPtr);
/// Retrieve the default value for a parameter, in case you forgot. This does not perform a
/// callback Create a [`ParamSetter`] and use [`ParamSetter::default_param_value()`] instead for
/// a safe, user friendly API.
///
/// # Safety
///
/// The implementing function still needs to check if `param` actually exists. This function is
/// mostly marked as unsafe for API reasons.
unsafe fn raw_default_normalized_param_value(&self, param: ParamPtr) -> f32;
}
/// Information about the plugin's transport. Depending on the plugin API and the host not all
@ -385,19 +375,4 @@ impl<'a> ParamSetter<'a> {
pub fn end_set_parameter<P: Param>(&self, param: &P) {
unsafe { self.raw_context.raw_end_set_parameter(param.as_ptr()) };
}
/// Retrieve the default value for a parameter, in case you forgot. The value is already
/// normalized to `[0, 1]`. This is useful when implementing GUIs, and it does not perform a callback.
pub fn default_normalized_param_value<P: Param>(&self, param: &P) -> f32 {
unsafe {
self.raw_context
.raw_default_normalized_param_value(param.as_ptr())
}
}
/// The same as [`default_normalized_param_value()`][Self::default_normalized_param_value()],
/// but without the normalization.
pub fn default_param_value<P: Param>(&self, param: &P) -> P::Plain {
param.preview_plain(self.default_normalized_param_value(param))
}
}

View file

@ -75,16 +75,6 @@ impl<P: ClapPlugin> GuiContext for WrapperGuiContext<P> {
None => nih_debug_assert_failure!("Unknown parameter: {:?}", param),
}
}
unsafe fn raw_default_normalized_param_value(&self, param: ParamPtr) -> f32 {
match self.wrapper.param_ptr_to_hash.get(&param) {
Some(hash) => self.wrapper.param_defaults_normalized[hash],
None => {
nih_debug_assert_failure!("Unknown parameter: {:?}", param);
0.5
}
}
}
}
impl<P: ClapPlugin> ProcessContext for WrapperProcessContext<'_, P> {

View file

@ -178,10 +178,6 @@ pub struct Wrapper<P: ClapPlugin> {
/// by slashes, and they're only used to allow the DAW to display parameters in a tree
/// structure.
param_group_by_hash: HashMap<u32, String>,
/// The default normalized parameter value for every parameter in `param_ids`. We need to store
/// this in case the host requeries the parmaeter later. This is also indexed by the hash so we
/// can retrieve them later for the UI if needed.
pub param_defaults_normalized: HashMap<u32, f32>,
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
/// and when storing and restoring plugin state.
param_id_to_hash: HashMap<String, u32>,
@ -370,10 +366,6 @@ impl<P: ClapPlugin> Wrapper<P> {
.iter()
.map(|(_, hash, _, group)| (*hash, group.clone()))
.collect();
let param_defaults_normalized = param_id_hashes_ptrs_groups
.iter()
.map(|(_, hash, ptr, _)| (*hash, unsafe { ptr.normalized_value() }))
.collect();
let param_id_to_hash = param_id_hashes_ptrs_groups
.iter()
.map(|(id, hash, _, _)| (id.clone(), *hash))
@ -509,7 +501,6 @@ impl<P: ClapPlugin> Wrapper<P> {
param_hashes,
param_by_hash,
param_group_by_hash,
param_defaults_normalized,
param_id_to_hash,
param_ptr_to_hash,
output_parameter_events: ArrayQueue::new(OUTPUT_EVENT_QUEUE_CAPACITY),
@ -1746,8 +1737,8 @@ impl<P: ClapPlugin> Wrapper<P> {
} else {
let param_hash = &wrapper.param_hashes[param_index as usize];
let param_group = &wrapper.param_group_by_hash[param_hash];
let default_value = &wrapper.param_defaults_normalized[param_hash];
let param_ptr = &wrapper.param_by_hash[param_hash];
let default_value = param_ptr.default_normalized_value();
let step_count = param_ptr.step_count();
param_info.id = *param_hash;
@ -1767,7 +1758,7 @@ impl<P: ClapPlugin> Wrapper<P> {
// range option
// TODO: This should probably be encapsulated in some way so we don't forget about this in one place
param_info.max_value = step_count.unwrap_or(1) as f64;
param_info.default_value = *default_value as f64 * step_count.unwrap_or(1) as f64;
param_info.default_value = default_value as f64 * step_count.unwrap_or(1) as f64;
}
true

View file

@ -80,16 +80,6 @@ impl<P: Vst3Plugin> GuiContext for WrapperGuiContext<P> {
None => nih_debug_assert_failure!("Component handler not yet set"),
}
}
unsafe fn raw_default_normalized_param_value(&self, param: ParamPtr) -> f32 {
match self.inner.param_ptr_to_hash.get(&param) {
Some(hash) => self.inner.param_defaults_normalized[hash],
None => {
nih_debug_assert_failure!("Unknown parameter: {:?}", param);
0.5
}
}
}
}
impl<P: Vst3Plugin> ProcessContext for WrapperProcessContext<'_, P> {

View file

@ -86,10 +86,6 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// addresses will remain stable, as they are obtained from a pinned object.
pub param_by_hash: HashMap<u32, ParamPtr>,
pub param_units: ParamUnits,
/// The default normalized parameter value for every parameter in `param_ids`. We need to store
/// this in case the host requeries the parmaeter later. This is also indexed by the hash so we
/// can retrieve them later for the UI if needed.
pub param_defaults_normalized: HashMap<u32, f32>,
/// Mappings from string parameter indentifiers to parameter hashes. Useful for debug logging
/// and when storing and restorign plugin state.
pub param_id_to_hash: HashMap<String, u32>,
@ -182,10 +178,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
.map(|(_, hash, _, group_name)| (*hash, group_name.as_str())),
)
.expect("Inconsistent parameter groups");
let param_defaults_normalized = param_id_hashes_ptrs_groups
.iter()
.map(|(_, hash, ptr, _)| (*hash, unsafe { ptr.normalized_value() }))
.collect();
let param_id_to_hash = param_id_hashes_ptrs_groups
.iter()
.map(|(id, hash, _, _)| (id.clone(), *hash))
@ -231,7 +223,6 @@ impl<P: Vst3Plugin> WrapperInner<P> {
param_hashes,
param_by_hash,
param_units,
param_defaults_normalized,
param_id_to_hash,
param_ptr_to_hash,
};

View file

@ -340,15 +340,15 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
.param_units
.get_vst3_unit_id(*param_hash)
.expect("Inconsistent parameter data");
let default_value = &self.inner.param_defaults_normalized[param_hash];
let param_ptr = &self.inner.param_by_hash[param_hash];
let default_value = param_ptr.default_normalized_value();
info.id = *param_hash;
u16strlcpy(&mut info.title, param_ptr.name());
u16strlcpy(&mut info.short_title, param_ptr.name());
u16strlcpy(&mut info.units, param_ptr.unit());
info.step_count = param_ptr.step_count().unwrap_or(0) as i32;
info.default_normalized_value = *default_value as f64;
info.default_normalized_value = default_value as f64;
info.unit_id = *param_unit;
info.flags = vst3_sys::vst::ParameterFlags::kCanAutomate as i32;
}