From 7d4351e4faf898668a69386f7c457d05f8ede1e5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Nov 2022 15:36:23 +0100 Subject: [PATCH] Add a parameter button widget Useful for toggling boolean parameters. --- nih_plug_vizia/assets/widgets.css | 21 ++++++ nih_plug_vizia/src/widgets.rs | 2 + nih_plug_vizia/src/widgets/param_button.rs | 87 ++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 nih_plug_vizia/src/widgets/param_button.rs diff --git a/nih_plug_vizia/assets/widgets.css b/nih_plug_vizia/assets/widgets.css index 30d8c8f5..dd9d39ef 100644 --- a/nih_plug_vizia/assets/widgets.css +++ b/nih_plug_vizia/assets/widgets.css @@ -26,6 +26,27 @@ generic-ui .label { right: 0; } +param-button { + height: 30px; + width: auto; + border-color: #0a0a0a; + border-width: 1px; + background-color: transparent; + transition: background-color 0.1 0; +} +param-button:hover { + background-color: #8080801a; + transition: background-color 0.1 0; +} +param-button:checked { + background-color: #e5e5e5; + transition: background-color 0.1 0; +} + +param-button label { + /* TODO */ +} + param-slider { height: 30px; width: 180px; diff --git a/nih_plug_vizia/src/widgets.rs b/nih_plug_vizia/src/widgets.rs index 6b0a534d..6e1aecae 100644 --- a/nih_plug_vizia/src/widgets.rs +++ b/nih_plug_vizia/src/widgets.rs @@ -12,12 +12,14 @@ use vizia::prelude::*; use super::ViziaState; mod generic_ui; +mod param_button; mod param_slider; mod peak_meter; mod resize_handle; pub mod util; pub use generic_ui::GenericUi; +pub use param_button::ParamButton; pub use param_slider::{ParamSlider, ParamSliderExt, ParamSliderStyle}; pub use peak_meter::PeakMeter; pub use resize_handle::ResizeHandle; diff --git a/nih_plug_vizia/src/widgets/param_button.rs b/nih_plug_vizia/src/widgets/param_button.rs new file mode 100644 index 00000000..73b8e46a --- /dev/null +++ b/nih_plug_vizia/src/widgets/param_button.rs @@ -0,0 +1,87 @@ +//! A toggleable button that integrates with NIH-plug's [`Param`] types. + +use nih_plug::prelude::{Param, ParamPtr}; +use vizia::prelude::*; + +use super::RawParamEvent; + +/// A toggleable button that integrates with NIH-plug's [`Param`] types. Only makes sense with +/// [`BoolParam`][nih_plug::prelude::BoolParam]s. Clicking on the button will toggle between the +/// parameter's minimum and maximum value. The `:checked` pseudoclass indicates whether or not the +/// button is currently pressed. +#[derive(Lens)] +pub struct ParamButton { + // We're not allowed to store a reference to the parameter internally, at least not in the + // struct that implements [`View`] + param_ptr: ParamPtr, +} + +impl ParamButton { + /// Creates a new [`ParamButton`] for the given parameter. To accommodate 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`) 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. + pub fn new(cx: &mut Context, params: L, params_to_param: F) -> Handle + where + L: Lens + Clone, + F: 'static + Fn(&Params) -> &P + Copy, + Params: 'static, + P: Param, + { + let param_ptr = params + .clone() + .map(move |params| params_to_param(params).as_ptr()) + .get(cx); + let param_name = params + .clone() + .map(move |params| params_to_param(params).name().to_owned()) + .get(cx); + + // We'll add the `:checked` pseudoclass when the button is pressed + // NOTE: We use the normalized value _with modulation_ for this. There's no convenient way + // to show both modulated and unmodulated values here. + let param_value_lens = params.map(move |params| params_to_param(params).normalized_value()); + + Self { param_ptr } + .build(cx, move |cx| { + Label::new(cx, ¶m_name); + }) + .bind(param_value_lens, |cx, value| { + cx.checked(value.map(|v| v >= &0.5)); + }) + } + + /// Set the parameter's normalized value to either 0.0 or 1.0 depending on its current value. + fn toggle_value(&self, cx: &mut EventContext) { + let current_value = unsafe { self.param_ptr.unmodulated_normalized_value() }; + let new_value = if current_value >= 0.5 { 0.0 } else { 1.0 }; + + cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr)); + cx.emit(RawParamEvent::SetParameterNormalized( + self.param_ptr, + new_value, + )); + cx.emit(RawParamEvent::EndSetParameter(self.param_ptr)); + } +} + +impl View for ParamButton { + fn element(&self) -> Option<&'static str> { + Some("param-button") + } + + fn event(&mut self, cx: &mut EventContext, event: &mut Event) { + event.map(|window_event, meta| match window_event { + WindowEvent::MouseDown(MouseButton::Left) + // We don't need special double and triple click handling + | WindowEvent::MouseDoubleClick(MouseButton::Left) + | WindowEvent::MouseTripleClick(MouseButton::Left) => { + self.toggle_value(cx); + meta.consume(); + } + _ => {} + }); + } +}