Move Vizia view param wrangling to helper struct
This cleans up the main widget's code a lot. We can move some more behavior to this helper to reduce duplication.
This commit is contained in:
parent
f7230b9f43
commit
14cb1cb679
|
@ -12,6 +12,7 @@ use vizia::prelude::*;
|
||||||
use super::ViziaState;
|
use super::ViziaState;
|
||||||
|
|
||||||
mod generic_ui;
|
mod generic_ui;
|
||||||
|
pub mod param_base;
|
||||||
mod param_button;
|
mod param_button;
|
||||||
mod param_slider;
|
mod param_slider;
|
||||||
mod peak_meter;
|
mod peak_meter;
|
||||||
|
|
229
nih_plug_vizia/src/widgets/param_base.rs
Normal file
229
nih_plug_vizia/src/widgets/param_base.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
//! A base widget for creating other widgets that integrate with NIH-plug's [`Param`] types.
|
||||||
|
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use vizia::prelude::*;
|
||||||
|
|
||||||
|
use super::RawParamEvent;
|
||||||
|
|
||||||
|
/// A helper for creating parameter widgets. The general idea is that a parameter widget struct can
|
||||||
|
/// adds a `ParamWidgetBase` field on its struct, and then calls [`ParamWidgetBase::view()`] in its
|
||||||
|
/// view build function. The stored `ParamWidgetbBase` object can then be used in the widget's event
|
||||||
|
/// handlers to interact with the parameter.
|
||||||
|
#[derive(Lens)]
|
||||||
|
pub struct ParamWidgetBase {
|
||||||
|
/// We're not allowed to store a reference to the parameter internally, at least not in the
|
||||||
|
/// struct that implements [`View`].
|
||||||
|
param_ptr: ParamPtr,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data and lenses that can be used to draw the parameter widget. The [`param`][Self::param] field
|
||||||
|
/// should only be used for looking up static data. Prefer the [`make_lens()`][Self::make_lens()]
|
||||||
|
/// function for binding parameter data to element properties.
|
||||||
|
pub struct ParamWidgetData<L, Params, P, FMap>
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
{
|
||||||
|
// HACK: This needs to be a static reference because of the way bindings in Vizia works. This
|
||||||
|
// feels very wrong, but I don't think there is an alternative. The field is not `pub`
|
||||||
|
// for this reason.
|
||||||
|
param: &'static P,
|
||||||
|
params: L,
|
||||||
|
params_to_param: FMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, Params, P, FMap> Clone for ParamWidgetData<L, Params, P, FMap>
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
param: self.param,
|
||||||
|
params: self.params.clone(),
|
||||||
|
params_to_param: self.params_to_param,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, Params, P, FMap> ParamWidgetData<L, Params, P, FMap>
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
{
|
||||||
|
/// The parameter in question. This can be used for querying static information about the
|
||||||
|
/// parameter. Don't use this to get the parameter's current value, use the lenses instead.
|
||||||
|
pub fn param(&self) -> &P {
|
||||||
|
self.param
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a lens from a parameter's field. This can be used to bind one of the parameter's
|
||||||
|
/// value getters to a property.
|
||||||
|
pub fn make_lens<R, F>(&self, f: F) -> impl Lens<Target = R>
|
||||||
|
where
|
||||||
|
F: Fn(&P) -> R + Clone + 'static,
|
||||||
|
R: Clone + 'static,
|
||||||
|
{
|
||||||
|
let params_to_param = self.params_to_param;
|
||||||
|
|
||||||
|
self.params.clone().map(move |params| {
|
||||||
|
let param = params_to_param(params);
|
||||||
|
f(param)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a [`ParamWidgetData`] function that forwards the function call to the underlying
|
||||||
|
/// `ParamPtr`.
|
||||||
|
macro_rules! param_ptr_forward(
|
||||||
|
(pub fn $method:ident(&self $(, $arg_name:ident: $arg_ty:ty)*) -> $ret:ty) => {
|
||||||
|
/// Calls the corresponding method on the underlying [`ParamPtr`] object.
|
||||||
|
pub fn $method(&self $(, $arg_name: $arg_ty)*) -> $ret {
|
||||||
|
unsafe { self.param_ptr.$method($($arg_name),*) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
);
|
||||||
|
|
||||||
|
impl ParamWidgetBase {
|
||||||
|
/// Creates a [`ParamWidgetBase`] for the given parameter. This can be stored on a widget object
|
||||||
|
/// and used as part of the widget's event handling. 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<L, Params, P, FMap>(cx: &mut Context, params: L, params_to_param: FMap) -> Self
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
{
|
||||||
|
// We need to do a bit of a nasty and erase the lifetime bound by going through a raw
|
||||||
|
// ParamPtr. Vizia requires all lens data to be 'static and Clone.
|
||||||
|
let param_ptr = params
|
||||||
|
.map(move |params| params_to_param(params).as_ptr())
|
||||||
|
.get(cx);
|
||||||
|
|
||||||
|
Self { param_ptr }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a view using the a parameter's data. This is not tied to a particular
|
||||||
|
/// [`ParamWidgetBase`] instance, but it allows you to easily create lenses for the parameter's
|
||||||
|
/// values and access static parameter data.
|
||||||
|
///
|
||||||
|
/// This can be used directly as an argument to [`View::build()`].
|
||||||
|
pub fn view<L, Params, P, FMap, F, R>(
|
||||||
|
params: L,
|
||||||
|
params_to_param: FMap,
|
||||||
|
content: F,
|
||||||
|
) -> impl FnOnce(&mut Context) -> R
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
F: FnOnce(&mut Context, ParamWidgetData<L, Params, P, FMap>) -> R,
|
||||||
|
{
|
||||||
|
move |cx| {
|
||||||
|
// We'll provide the raw `&P` to the callbacks to make creating parameter widgets more
|
||||||
|
// convenient.
|
||||||
|
// SAFETY: This &P won't outlive this function, and in the context of NIH-plug &P will
|
||||||
|
// outlive the editor
|
||||||
|
let param: &P = unsafe {
|
||||||
|
&*params
|
||||||
|
.clone()
|
||||||
|
.map(move |params| params_to_param(params) as *const P)
|
||||||
|
.get(cx)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The widget can use this to access data parameter data and to create lenses for working
|
||||||
|
// with the parameter's values
|
||||||
|
let param_data = ParamWidgetData {
|
||||||
|
param,
|
||||||
|
params,
|
||||||
|
params_to_param,
|
||||||
|
};
|
||||||
|
|
||||||
|
content(cx, param_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience function for using [`ParamWidgetData::make_lens()`]. Whenever possible,
|
||||||
|
/// [`view()`][Self::view()] should be used instead.
|
||||||
|
pub fn make_lens<L, Params, P, FMap, F, R>(
|
||||||
|
params: L,
|
||||||
|
params_to_param: FMap,
|
||||||
|
f: F,
|
||||||
|
) -> impl Lens<Target = R>
|
||||||
|
where
|
||||||
|
L: Lens<Target = Params> + Clone,
|
||||||
|
Params: 'static,
|
||||||
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
|
F: Fn(&P) -> R + Clone + 'static,
|
||||||
|
R: Clone + 'static,
|
||||||
|
{
|
||||||
|
params.map(move |params| {
|
||||||
|
let param = params_to_param(params);
|
||||||
|
f(param)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start an automation gesture. This **must** be called before `set_normalized_value()`
|
||||||
|
/// is called. Usually this is done on mouse down.
|
||||||
|
pub fn begin_set_parameter(&self, cx: &mut EventContext) {
|
||||||
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the normalized value for a parameter if that would change the parameter's plain value
|
||||||
|
/// (to avoid unnecessary duplicate parameter changes). `begin_set_parameter()` **must** be
|
||||||
|
/// called before this is called to start an automation gesture, and `end_set_parameter()` must
|
||||||
|
/// be called at the end of the gesture.
|
||||||
|
pub fn set_normalized_value(&self, cx: &mut EventContext, normalized_value: f32) {
|
||||||
|
// This snaps to the nearest plain value if the parameter is stepped in some way.
|
||||||
|
// TODO: As an optimization, we could add a `const CONTINUOUS: bool` to the parameter to
|
||||||
|
// avoid this normalized->plain->normalized conversion for parameters that don't need
|
||||||
|
// it
|
||||||
|
let plain_value = unsafe { self.param_ptr.preview_plain(normalized_value) };
|
||||||
|
let current_plain_value = unsafe { self.param_ptr.unmodulated_plain_value() };
|
||||||
|
if plain_value != current_plain_value {
|
||||||
|
// For the aforementioned snapping
|
||||||
|
let normalized_plain_value = unsafe { self.param_ptr.preview_normalized(plain_value) };
|
||||||
|
cx.emit(RawParamEvent::SetParameterNormalized(
|
||||||
|
self.param_ptr,
|
||||||
|
normalized_plain_value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End an automation gesture. This must be called at the end of a gesture, after zero or more
|
||||||
|
/// `set_normalized_value()` calls. Usually this is done on mouse down.
|
||||||
|
pub fn end_set_parameter(&self, cx: &mut EventContext) {
|
||||||
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
param_ptr_forward!(pub fn name(&self) -> &str);
|
||||||
|
param_ptr_forward!(pub fn unit(&self) -> &'static str);
|
||||||
|
param_ptr_forward!(pub fn poly_modulation_id(&self) -> Option<u32>);
|
||||||
|
param_ptr_forward!(pub fn plain_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn unmodulated_plain_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn normalized_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn unmodulated_normalized_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn default_plain_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn default_normalized_value(&self) -> f32);
|
||||||
|
param_ptr_forward!(pub fn step_count(&self) -> Option<usize>);
|
||||||
|
param_ptr_forward!(pub fn previous_normalized_step(&self, from: f32) -> f32);
|
||||||
|
param_ptr_forward!(pub fn next_normalized_step(&self, from: f32) -> f32);
|
||||||
|
param_ptr_forward!(pub fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String);
|
||||||
|
param_ptr_forward!(pub fn string_to_normalized_value(&self, string: &str) -> Option<f32>);
|
||||||
|
param_ptr_forward!(pub fn preview_normalized(&self, plain: f32) -> f32);
|
||||||
|
param_ptr_forward!(pub fn preview_plain(&self, normalized: f32) -> f32);
|
||||||
|
param_ptr_forward!(pub fn flags(&self) -> ParamFlags);
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
//! A toggleable button that integrates with NIH-plug's [`Param`] types.
|
//! A toggleable button that integrates with NIH-plug's [`Param`] types.
|
||||||
|
|
||||||
use nih_plug::prelude::{Param, ParamPtr};
|
use nih_plug::prelude::Param;
|
||||||
use vizia::prelude::*;
|
use vizia::prelude::*;
|
||||||
|
|
||||||
use super::RawParamEvent;
|
use super::param_base::ParamWidgetBase;
|
||||||
|
|
||||||
/// A toggleable button that integrates with NIH-plug's [`Param`] types. Only makes sense with
|
/// 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
|
/// [`BoolParam`][nih_plug::prelude::BoolParam]s. Clicking on the button will toggle between the
|
||||||
|
@ -11,9 +11,7 @@ use super::RawParamEvent;
|
||||||
/// button is currently pressed.
|
/// button is currently pressed.
|
||||||
#[derive(Lens)]
|
#[derive(Lens)]
|
||||||
pub struct ParamButton {
|
pub struct ParamButton {
|
||||||
// We're not allowed to store a reference to the parameter internally, at least not in the
|
param_base: ParamWidgetBase,
|
||||||
// struct that implements [`View`]
|
|
||||||
param_ptr: ParamPtr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParamButton {
|
impl ParamButton {
|
||||||
|
@ -23,45 +21,45 @@ impl ParamButton {
|
||||||
/// `Params` object to the parameter you want to display a widget for. Parameter changes are
|
/// `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
|
/// handled by emitting [`ParamEvent`][super::ParamEvent]s which are automatically handled by
|
||||||
/// the VIZIA wrapper.
|
/// the VIZIA wrapper.
|
||||||
pub fn new<L, Params, P, F>(cx: &mut Context, params: L, params_to_param: F) -> Handle<Self>
|
pub fn new<L, Params, P, FMap>(
|
||||||
|
cx: &mut Context,
|
||||||
|
params: L,
|
||||||
|
params_to_param: FMap,
|
||||||
|
) -> Handle<Self>
|
||||||
where
|
where
|
||||||
L: Lens<Target = Params> + Clone,
|
L: Lens<Target = Params> + Clone,
|
||||||
F: 'static + Fn(&Params) -> &P + Copy,
|
|
||||||
Params: 'static,
|
Params: 'static,
|
||||||
P: Param,
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
{
|
{
|
||||||
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
|
// 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
|
// NOTE: We use the normalized value _with modulation_ for this. There's no convenient way
|
||||||
// to show both modulated and unmodulated values here.
|
// to show both modulated and unmodulated values here.
|
||||||
let param_value_lens = params.map(move |params| params_to_param(params).normalized_value());
|
let param_value_lens =
|
||||||
|
ParamWidgetBase::make_lens(params.clone(), params_to_param, |param| {
|
||||||
|
param.normalized_value()
|
||||||
|
});
|
||||||
|
|
||||||
Self { param_ptr }
|
Self {
|
||||||
.build(cx, move |cx| {
|
param_base: ParamWidgetBase::new(cx, params.clone(), params_to_param),
|
||||||
Label::new(cx, ¶m_name);
|
}
|
||||||
})
|
.build(
|
||||||
.checked(param_value_lens.map(|v| v >= &0.5))
|
cx,
|
||||||
|
ParamWidgetBase::view(params, params_to_param, move |cx, param_data| {
|
||||||
|
Label::new(cx, param_data.param().name());
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.checked(param_value_lens.map(|v| v >= &0.5))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the parameter's normalized value to either 0.0 or 1.0 depending on its current value.
|
/// 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) {
|
fn toggle_value(&self, cx: &mut EventContext) {
|
||||||
let current_value = unsafe { self.param_ptr.unmodulated_normalized_value() };
|
let current_value = self.param_base.unmodulated_normalized_value();
|
||||||
let new_value = if current_value >= 0.5 { 0.0 } else { 1.0 };
|
let new_value = if current_value >= 0.5 { 0.0 } else { 1.0 };
|
||||||
|
|
||||||
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
self.param_base.begin_set_parameter(cx);
|
||||||
cx.emit(RawParamEvent::SetParameterNormalized(
|
self.param_base.set_normalized_value(cx, new_value);
|
||||||
self.param_ptr,
|
self.param_base.end_set_parameter(cx);
|
||||||
new_value,
|
|
||||||
));
|
|
||||||
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
//! A slider that integrates with NIH-plug's [`Param`] types.
|
//! A slider that integrates with NIH-plug's [`Param`] types.
|
||||||
|
|
||||||
use nih_plug::prelude::{Param, ParamPtr};
|
use nih_plug::prelude::Param;
|
||||||
use vizia::prelude::*;
|
use vizia::prelude::*;
|
||||||
|
|
||||||
|
use super::param_base::ParamWidgetBase;
|
||||||
use super::util::{self, ModifiersExt};
|
use super::util::{self, ModifiersExt};
|
||||||
use super::RawParamEvent;
|
|
||||||
|
|
||||||
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
||||||
/// normalized parameter.
|
/// normalized parameter.
|
||||||
|
@ -14,13 +14,9 @@ const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
||||||
/// [`set_style()`][ParamSliderExt::set_style()] method to change how the value gets displayed.
|
/// [`set_style()`][ParamSliderExt::set_style()] method to change how the value gets displayed.
|
||||||
///
|
///
|
||||||
/// TODO: Handle scrolling for steps (and shift+scroll for smaller steps?)
|
/// TODO: Handle scrolling for steps (and shift+scroll for smaller steps?)
|
||||||
/// TODO: We may want to add a couple dedicated event handlers if it seems like those would be
|
|
||||||
/// useful, having a completely self contained widget is perfectly fine for now though
|
|
||||||
#[derive(Lens)]
|
#[derive(Lens)]
|
||||||
pub struct ParamSlider {
|
pub struct ParamSlider {
|
||||||
// We're not allowed to store a reference to the parameter internally, at least not in the
|
param_base: ParamWidgetBase,
|
||||||
// struct that implements [`View`]
|
|
||||||
param_ptr: ParamPtr,
|
|
||||||
|
|
||||||
/// Will be set to `true` if we're dragging the parameter. Resetting the parameter or entering a
|
/// Will be set to `true` if we're dragging the parameter. Resetting the parameter or entering a
|
||||||
/// text value should not initiate a drag.
|
/// text value should not initiate a drag.
|
||||||
|
@ -74,33 +70,22 @@ impl ParamSlider {
|
||||||
/// the VIZIA wrapper.
|
/// the VIZIA wrapper.
|
||||||
///
|
///
|
||||||
/// See [`ParamSliderExt`] for additional options.
|
/// See [`ParamSliderExt`] for additional options.
|
||||||
pub fn new<L, Params, P, F>(cx: &mut Context, params: L, params_to_param: F) -> Handle<Self>
|
pub fn new<L, Params, P, FMap>(
|
||||||
|
cx: &mut Context,
|
||||||
|
params: L,
|
||||||
|
params_to_param: FMap,
|
||||||
|
) -> Handle<Self>
|
||||||
where
|
where
|
||||||
L: Lens<Target = Params> + Clone,
|
L: Lens<Target = Params> + Clone,
|
||||||
F: 'static + Fn(&Params) -> &P + Copy,
|
|
||||||
Params: 'static,
|
Params: 'static,
|
||||||
P: Param,
|
P: Param + 'static,
|
||||||
|
FMap: Fn(&Params) -> &P + Copy + 'static,
|
||||||
{
|
{
|
||||||
// We'll visualize the difference between the current value and the default value if the
|
// We'll visualize the difference between the current value and the default value if the
|
||||||
// default value lies somewhere in the middle and the parameter is continuous. Otherwise
|
// default value lies somewhere in the middle and the parameter is continuous. Otherwise
|
||||||
// this approach looks a bit jarring.
|
// this approach looks a bit jarring.
|
||||||
// We need to do a bit of a nasty and erase the lifetime bound by going through the raw
|
|
||||||
// GuiContext and a ParamPtr.
|
|
||||||
let param_ptr = params
|
|
||||||
.clone()
|
|
||||||
.map(move |params| params_to_param(params).as_ptr())
|
|
||||||
.get(cx);
|
|
||||||
let default_value = params
|
|
||||||
.clone()
|
|
||||||
.map(move |params| params_to_param(params).default_normalized_value())
|
|
||||||
.get(cx);
|
|
||||||
let step_count = params
|
|
||||||
.clone()
|
|
||||||
.map(move |params| params_to_param(params).step_count())
|
|
||||||
.get(cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
param_ptr,
|
param_base: ParamWidgetBase::new(cx, params.clone(), params_to_param),
|
||||||
|
|
||||||
drag_active: false,
|
drag_active: false,
|
||||||
granular_drag_start_x_value: None,
|
granular_drag_start_x_value: None,
|
||||||
|
@ -108,263 +93,251 @@ impl ParamSlider {
|
||||||
style: ParamSliderStyle::Centered,
|
style: ParamSliderStyle::Centered,
|
||||||
text_input_active: false,
|
text_input_active: false,
|
||||||
}
|
}
|
||||||
.build(cx, move |cx| {
|
.build(
|
||||||
Binding::new(cx, ParamSlider::style, move |cx, style| {
|
cx,
|
||||||
let style = style.get(cx);
|
ParamWidgetBase::view(params, params_to_param, move |cx, param_data| {
|
||||||
let draw_fill_from_default = matches!(style, ParamSliderStyle::Centered)
|
Binding::new(cx, ParamSlider::style, move |cx, style| {
|
||||||
&& step_count.is_none()
|
let style = style.get(cx);
|
||||||
&& (0.45..=0.55).contains(&default_value);
|
|
||||||
|
|
||||||
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
let default_value = param_data.param().default_normalized_value();
|
||||||
// label with the slider. Creating the textbox based on
|
let step_count = param_data.param().step_count();
|
||||||
// `ParamSliderInternal::text_input_active` lets us focus the textbox when it gets
|
|
||||||
// created.
|
|
||||||
let params = params.clone();
|
|
||||||
Binding::new(
|
|
||||||
cx,
|
|
||||||
ParamSlider::text_input_active,
|
|
||||||
move |cx, text_input_active| {
|
|
||||||
// Can't use `.to_string()` here as that would include the modulation.
|
|
||||||
let param_display_value_lens = params.clone().map(move |params| {
|
|
||||||
let param = params_to_param(params);
|
|
||||||
param.normalized_value_to_string(
|
|
||||||
param.unmodulated_normalized_value(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let param_preview_display_value_lens = {
|
|
||||||
let params = params.clone();
|
|
||||||
move |normalized_value| {
|
|
||||||
params.clone().map(move |params| {
|
|
||||||
params_to_param(params)
|
|
||||||
.normalized_value_to_string(normalized_value, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let unmodulated_normalized_param_value_lens =
|
|
||||||
params.clone().map(move |params| {
|
|
||||||
params_to_param(params).unmodulated_normalized_value()
|
|
||||||
});
|
|
||||||
|
|
||||||
// The resulting tuple `(start_t, delta)` corresponds to the start and the
|
// Can't use `.to_string()` here as that would include the modulation.
|
||||||
// signed width of the bar. `start_t` is in `[0, 1]`, and `delta` is in
|
let unmodulated_normalized_value_lens =
|
||||||
// `[-1, 1]`.
|
param_data.make_lens(|param| param.unmodulated_normalized_value());
|
||||||
let fill_start_delta_lens =
|
let display_value_lens = param_data.make_lens(|param| {
|
||||||
unmodulated_normalized_param_value_lens.map(move |current_value| {
|
param.normalized_value_to_string(param.unmodulated_normalized_value(), true)
|
||||||
match style {
|
});
|
||||||
ParamSliderStyle::Centered if draw_fill_from_default => {
|
|
||||||
let delta = (default_value - current_value).abs();
|
// This is used to draw labels for `CurrentStepLabeled`
|
||||||
(
|
let make_preview_value_lens = {
|
||||||
default_value.min(*current_value),
|
let param_data = param_data.clone();
|
||||||
// Don't draw the filled portion at all if it
|
move |normalized_value| {
|
||||||
// could have been a rounding error since those
|
param_data.make_lens(move |param| {
|
||||||
// slivers just look weird
|
param.normalized_value_to_string(normalized_value, true)
|
||||||
if delta >= 1e-3 { delta } else { 0.0 },
|
})
|
||||||
)
|
}
|
||||||
}
|
};
|
||||||
ParamSliderStyle::Centered | ParamSliderStyle::FromLeft => {
|
|
||||||
(0.0, *current_value)
|
// The resulting tuple `(start_t, delta)` corresponds to the start and the
|
||||||
}
|
// signed width of the bar. `start_t` is in `[0, 1]`, and `delta` is in
|
||||||
ParamSliderStyle::CurrentStep { even: true }
|
// `[-1, 1]`.
|
||||||
| ParamSliderStyle::CurrentStepLabeled { even: true }
|
let draw_fill_from_default = matches!(style, ParamSliderStyle::Centered)
|
||||||
if step_count.is_some() =>
|
&& step_count.is_none()
|
||||||
{
|
&& (0.45..=0.55).contains(&default_value);
|
||||||
// Assume the normalized value is distributed evenly
|
let fill_start_delta_lens = unmodulated_normalized_value_lens.map({
|
||||||
// across the range.
|
let param_data = param_data.clone();
|
||||||
let step_count = step_count.unwrap() as f32;
|
|
||||||
let discrete_values = step_count + 1.0;
|
move |current_value| {
|
||||||
let previous_step =
|
|
||||||
(current_value * step_count) / discrete_values;
|
|
||||||
(previous_step, discrete_values.recip())
|
|
||||||
}
|
|
||||||
ParamSliderStyle::CurrentStep { .. }
|
|
||||||
| ParamSliderStyle::CurrentStepLabeled { .. } => {
|
|
||||||
let previous_step = unsafe {
|
|
||||||
param_ptr.previous_normalized_step(*current_value)
|
|
||||||
};
|
|
||||||
let next_step = unsafe {
|
|
||||||
param_ptr.next_normalized_step(*current_value)
|
|
||||||
};
|
|
||||||
(
|
|
||||||
(previous_step + current_value) / 2.0,
|
|
||||||
((next_step - current_value)
|
|
||||||
+ (current_value - previous_step))
|
|
||||||
/ 2.0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// If the parameter is being modulated by the host (this only works for CLAP
|
|
||||||
// plugins with hosts that support this), then this is the difference
|
|
||||||
// between the 'true' value and the current value after modulation has been
|
|
||||||
// applied.
|
|
||||||
let modulation_start_delta_lens = params.clone().map(move |params| {
|
|
||||||
match style {
|
match style {
|
||||||
// Don't show modulation for stepped parameters since it wouldn't
|
ParamSliderStyle::Centered if draw_fill_from_default => {
|
||||||
// make a lot of sense visually
|
let delta = (default_value - current_value).abs();
|
||||||
ParamSliderStyle::CurrentStep { .. }
|
|
||||||
| ParamSliderStyle::CurrentStepLabeled { .. } => (0.0, 0.0),
|
// Don't draw the filled portion at all if it could have been a
|
||||||
ParamSliderStyle::Centered | ParamSliderStyle::FromLeft => {
|
// rounding error since those slivers just look weird
|
||||||
let param = params_to_param(params);
|
|
||||||
let modulation_start = param.unmodulated_normalized_value();
|
|
||||||
(
|
(
|
||||||
modulation_start,
|
default_value.min(*current_value),
|
||||||
param.normalized_value() - modulation_start,
|
if delta >= 1e-3 { delta } else { 0.0 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ParamSliderStyle::Centered | ParamSliderStyle::FromLeft => {
|
||||||
|
(0.0, *current_value)
|
||||||
|
}
|
||||||
|
ParamSliderStyle::CurrentStep { even: true }
|
||||||
|
| ParamSliderStyle::CurrentStepLabeled { even: true }
|
||||||
|
if step_count.is_some() =>
|
||||||
|
{
|
||||||
|
// Assume the normalized value is distributed evenly
|
||||||
|
// across the range.
|
||||||
|
let step_count = step_count.unwrap() as f32;
|
||||||
|
let discrete_values = step_count + 1.0;
|
||||||
|
let previous_step =
|
||||||
|
(current_value * step_count) / discrete_values;
|
||||||
|
|
||||||
|
(previous_step, discrete_values.recip())
|
||||||
|
}
|
||||||
|
ParamSliderStyle::CurrentStep { .. }
|
||||||
|
| ParamSliderStyle::CurrentStepLabeled { .. } => {
|
||||||
|
let previous_step =
|
||||||
|
param_data.param().previous_normalized_step(*current_value);
|
||||||
|
let next_step =
|
||||||
|
param_data.param().next_normalized_step(*current_value);
|
||||||
|
|
||||||
|
(
|
||||||
|
(previous_step + current_value) / 2.0,
|
||||||
|
((next_step - current_value)
|
||||||
|
+ (current_value - previous_step))
|
||||||
|
/ 2.0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if text_input_active.get(cx) {
|
// If the parameter is being modulated by the host (this only works for CLAP
|
||||||
Textbox::new(cx, param_display_value_lens)
|
// plugins with hosts that support this), then this is the difference
|
||||||
.class("value-entry")
|
// between the 'true' value and the current value after modulation has been
|
||||||
.on_submit(|cx, string, success| {
|
// applied.
|
||||||
if success {
|
let modulation_start_delta_lens = param_data.make_lens(move |param| {
|
||||||
cx.emit(ParamSliderEvent::TextInput(string))
|
match style {
|
||||||
} else {
|
// Don't show modulation for stepped parameters since it wouldn't
|
||||||
cx.emit(ParamSliderEvent::CancelTextInput);
|
// make a lot of sense visually
|
||||||
}
|
ParamSliderStyle::CurrentStep { .. }
|
||||||
})
|
| ParamSliderStyle::CurrentStepLabeled { .. } => (0.0, 0.0),
|
||||||
.on_build(|cx| {
|
ParamSliderStyle::Centered | ParamSliderStyle::FromLeft => {
|
||||||
cx.emit(TextEvent::StartEdit);
|
let modulation_start = param.unmodulated_normalized_value();
|
||||||
cx.emit(TextEvent::SelectAll);
|
|
||||||
})
|
(
|
||||||
// `.child_space(Stretch(1.0))` no longer works
|
modulation_start,
|
||||||
.class("align_center")
|
param.normalized_value() - modulation_start,
|
||||||
.child_top(Stretch(1.0))
|
)
|
||||||
.child_bottom(Stretch(1.0))
|
}
|
||||||
.height(Stretch(1.0))
|
}
|
||||||
.width(Stretch(1.0));
|
});
|
||||||
} else {
|
|
||||||
ZStack::new(cx, move |cx| {
|
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
||||||
// The filled bar portion. This can be visualized in a couple
|
// label with the slider. Creating the textbox based on
|
||||||
// different ways depending on the current style property. See
|
// `ParamSliderInternal::text_input_active` lets us focus the textbox when it gets
|
||||||
// [`ParamSliderStyle`].
|
// created.
|
||||||
Element::new(cx)
|
Binding::new(
|
||||||
.class("fill")
|
cx,
|
||||||
|
ParamSlider::text_input_active,
|
||||||
|
move |cx, text_input_active| {
|
||||||
|
if text_input_active.get(cx) {
|
||||||
|
Textbox::new(cx, display_value_lens.clone())
|
||||||
|
.class("value-entry")
|
||||||
|
.on_submit(|cx, string, success| {
|
||||||
|
if success {
|
||||||
|
cx.emit(ParamSliderEvent::TextInput(string))
|
||||||
|
} else {
|
||||||
|
cx.emit(ParamSliderEvent::CancelTextInput);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_build(|cx| {
|
||||||
|
cx.emit(TextEvent::StartEdit);
|
||||||
|
cx.emit(TextEvent::SelectAll);
|
||||||
|
})
|
||||||
|
// `.child_space(Stretch(1.0))` no longer works
|
||||||
|
.class("align_center")
|
||||||
|
.child_top(Stretch(1.0))
|
||||||
|
.child_bottom(Stretch(1.0))
|
||||||
.height(Stretch(1.0))
|
.height(Stretch(1.0))
|
||||||
.left(
|
.width(Stretch(1.0));
|
||||||
fill_start_delta_lens
|
} else {
|
||||||
.clone()
|
let display_value_lens = display_value_lens.clone();
|
||||||
.map(|(start_t, _)| Percentage(start_t * 100.0)),
|
let fill_start_delta_lens = fill_start_delta_lens.clone();
|
||||||
)
|
let modulation_start_delta_lens =
|
||||||
.width(
|
modulation_start_delta_lens.clone();
|
||||||
fill_start_delta_lens
|
let make_preview_value_lens = make_preview_value_lens.clone();
|
||||||
.clone()
|
|
||||||
.map(|(_, delta)| Percentage(delta * 100.0)),
|
|
||||||
)
|
|
||||||
// Hovering is handled on the param slider as a whole, this
|
|
||||||
// should not affect that
|
|
||||||
.hoverable(false);
|
|
||||||
|
|
||||||
// If the parameter is being modulated, then we'll display another
|
ZStack::new(cx, move |cx| {
|
||||||
// filled bar showing the current modulation delta
|
// The filled bar portion. This can be visualized in a couple
|
||||||
// VIZIA's bindings make this a bit, uh, difficult to read
|
// different ways depending on the current style property. See
|
||||||
Element::new(cx)
|
// [`ParamSliderStyle`].
|
||||||
.class("fill")
|
Element::new(cx)
|
||||||
.class("fill--modulation")
|
.class("fill")
|
||||||
.height(Stretch(1.0))
|
.height(Stretch(1.0))
|
||||||
.visibility(
|
.left(
|
||||||
modulation_start_delta_lens
|
fill_start_delta_lens
|
||||||
.clone()
|
.clone()
|
||||||
.map(|(_, delta)| *delta != 0.0),
|
.map(|(start_t, _)| Percentage(start_t * 100.0)),
|
||||||
)
|
)
|
||||||
// Widths cannot be negative, so we need to compensate the start
|
.width(
|
||||||
// position if the width does happen to be negative
|
fill_start_delta_lens
|
||||||
.width(
|
.map(|(_, delta)| Percentage(delta * 100.0)),
|
||||||
modulation_start_delta_lens
|
)
|
||||||
.clone()
|
// Hovering is handled on the param slider as a whole, this
|
||||||
.map(|(_, delta)| Percentage(delta.abs() * 100.0)),
|
// should not affect that
|
||||||
)
|
.hoverable(false);
|
||||||
.left(modulation_start_delta_lens.clone().map(
|
|
||||||
|(start_t, delta)| {
|
|
||||||
if *delta < 0.0 {
|
|
||||||
Percentage((start_t + delta) * 100.0)
|
|
||||||
} else {
|
|
||||||
Percentage(start_t * 100.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.hoverable(false);
|
|
||||||
|
|
||||||
// Either display the current value, or display all values over the
|
// If the parameter is being modulated, then we'll display another
|
||||||
// parameter's steps
|
// filled bar showing the current modulation delta
|
||||||
// TODO: Do the same thing as in the iced widget where we draw the
|
// VIZIA's bindings make this a bit, uh, difficult to read
|
||||||
// text overlapping the fill area slightly differently. We can
|
Element::new(cx)
|
||||||
// set the cip region directly in vizia.
|
.class("fill")
|
||||||
match (style, step_count) {
|
.class("fill--modulation")
|
||||||
(
|
.height(Stretch(1.0))
|
||||||
ParamSliderStyle::CurrentStepLabeled { .. },
|
.visibility(
|
||||||
Some(step_count),
|
modulation_start_delta_lens
|
||||||
) => {
|
.clone()
|
||||||
HStack::new(cx, |cx| {
|
.map(|(_, delta)| *delta != 0.0),
|
||||||
// There are step_count + 1 possible values for a
|
)
|
||||||
// discrete parameter
|
// Widths cannot be negative, so we need to compensate the start
|
||||||
for value in 0..step_count + 1 {
|
// position if the width does happen to be negative
|
||||||
let normalized_value =
|
.width(
|
||||||
value as f32 / step_count as f32;
|
modulation_start_delta_lens
|
||||||
Label::new(
|
.clone()
|
||||||
cx,
|
.map(|(_, delta)| Percentage(delta.abs() * 100.0)),
|
||||||
param_preview_display_value_lens(
|
)
|
||||||
normalized_value,
|
.left(modulation_start_delta_lens.map(
|
||||||
),
|
|(start_t, delta)| {
|
||||||
)
|
if *delta < 0.0 {
|
||||||
|
Percentage((start_t + delta) * 100.0)
|
||||||
|
} else {
|
||||||
|
Percentage(start_t * 100.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.hoverable(false);
|
||||||
|
|
||||||
|
// Either display the current value, or display all values over the
|
||||||
|
// parameter's steps
|
||||||
|
// TODO: Do the same thing as in the iced widget where we draw the
|
||||||
|
// text overlapping the fill area slightly differently. We can
|
||||||
|
// set the cip region directly in vizia.
|
||||||
|
match (style, step_count) {
|
||||||
|
(
|
||||||
|
ParamSliderStyle::CurrentStepLabeled { .. },
|
||||||
|
Some(step_count),
|
||||||
|
) => {
|
||||||
|
HStack::new(cx, |cx| {
|
||||||
|
// There are step_count + 1 possible values for a
|
||||||
|
// discrete parameter
|
||||||
|
for value in 0..step_count + 1 {
|
||||||
|
let normalized_value =
|
||||||
|
value as f32 / step_count as f32;
|
||||||
|
let preview_lens =
|
||||||
|
make_preview_value_lens(normalized_value);
|
||||||
|
|
||||||
|
Label::new(cx, preview_lens)
|
||||||
|
.class("value")
|
||||||
|
.class("value--multiple")
|
||||||
|
.child_space(Stretch(1.0))
|
||||||
|
.height(Stretch(1.0))
|
||||||
|
.width(Stretch(1.0))
|
||||||
|
.hoverable(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.height(Stretch(1.0))
|
||||||
|
.width(Stretch(1.0))
|
||||||
|
.hoverable(false);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Label::new(cx, display_value_lens)
|
||||||
.class("value")
|
.class("value")
|
||||||
.class("value--multiple")
|
.class("value--single")
|
||||||
.child_space(Stretch(1.0))
|
.child_space(Stretch(1.0))
|
||||||
.height(Stretch(1.0))
|
.height(Stretch(1.0))
|
||||||
.width(Stretch(1.0))
|
.width(Stretch(1.0))
|
||||||
.hoverable(false);
|
.hoverable(false);
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
.height(Stretch(1.0))
|
})
|
||||||
.width(Stretch(1.0))
|
.hoverable(false);
|
||||||
.hoverable(false);
|
}
|
||||||
}
|
},
|
||||||
_ => {
|
);
|
||||||
Label::new(cx, param_display_value_lens)
|
});
|
||||||
.class("value")
|
}),
|
||||||
.class("value--single")
|
)
|
||||||
.child_space(Stretch(1.0))
|
|
||||||
.height(Stretch(1.0))
|
|
||||||
.width(Stretch(1.0))
|
|
||||||
.hoverable(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.hoverable(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the normalized value for a parameter if that would change the parameter's plain value
|
/// `self.param_base.set_normalized_value()`, but resulting from a mouse drag. When using the
|
||||||
/// (to avoid unnecessary duplicate parameter changes). The begin- and end set parameter
|
/// 'even' stepped slider styles from [`ParamSliderStyle`] this will remap the normalized range
|
||||||
/// messages need to be sent before calling this function.
|
/// to match up with the fill value display. This still needs to be wrapped in a parameter
|
||||||
fn set_normalized_value(&self, cx: &mut EventContext, normalized_value: f32) {
|
/// automation gesture.
|
||||||
// This snaps to the nearest plain value if the parameter is stepped in some way.
|
|
||||||
// TODO: As an optimization, we could add a `const CONTINUOUS: bool` to the parameter to
|
|
||||||
// avoid this normalized->plain->normalized conversion for parameters that don't need
|
|
||||||
// it
|
|
||||||
let plain_value = unsafe { self.param_ptr.preview_plain(normalized_value) };
|
|
||||||
let current_plain_value = unsafe { self.param_ptr.unmodulated_plain_value() };
|
|
||||||
if plain_value != current_plain_value {
|
|
||||||
// For the aforementioned snapping
|
|
||||||
let normalized_plain_value = unsafe { self.param_ptr.preview_normalized(plain_value) };
|
|
||||||
cx.emit(RawParamEvent::SetParameterNormalized(
|
|
||||||
self.param_ptr,
|
|
||||||
normalized_plain_value,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `set_normalized_value()`, but resulting from a mouse drag. When using the 'even' stepped
|
|
||||||
/// slider styles from [`ParamSliderStyle`] this will remap the normalized range to match up
|
|
||||||
/// with the fill value display.
|
|
||||||
fn set_normalized_value_drag(&self, cx: &mut EventContext, normalized_value: f32) {
|
fn set_normalized_value_drag(&self, cx: &mut EventContext, normalized_value: f32) {
|
||||||
let normalized_value = match (self.style, unsafe { self.param_ptr.step_count() }) {
|
let normalized_value = match (self.style, self.param_base.step_count()) {
|
||||||
(
|
(
|
||||||
ParamSliderStyle::CurrentStep { even: true }
|
ParamSliderStyle::CurrentStep { even: true }
|
||||||
| ParamSliderStyle::CurrentStepLabeled { even: true },
|
| ParamSliderStyle::CurrentStepLabeled { even: true },
|
||||||
|
@ -380,7 +353,7 @@ impl ParamSlider {
|
||||||
_ => normalized_value,
|
_ => normalized_value,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_normalized_value(cx, normalized_value);
|
self.param_base.set_normalized_value(cx, normalized_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,12 +371,10 @@ impl View for ParamSlider {
|
||||||
meta.consume();
|
meta.consume();
|
||||||
}
|
}
|
||||||
ParamSliderEvent::TextInput(string) => {
|
ParamSliderEvent::TextInput(string) => {
|
||||||
if let Some(normalized_value) =
|
if let Some(normalized_value) = self.param_base.string_to_normalized_value(string) {
|
||||||
unsafe { self.param_ptr.string_to_normalized_value(string) }
|
self.param_base.begin_set_parameter(cx);
|
||||||
{
|
self.param_base.set_normalized_value(cx, normalized_value);
|
||||||
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
self.param_base.end_set_parameter(cx);
|
||||||
self.set_normalized_value(cx, normalized_value);
|
|
||||||
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.text_input_active = false;
|
self.text_input_active = false;
|
||||||
|
@ -426,12 +397,9 @@ impl View for ParamSlider {
|
||||||
} else if cx.modifiers.command() {
|
} else if cx.modifiers.command() {
|
||||||
// Ctrl+Click and double click should reset the parameter instead of initiating
|
// Ctrl+Click and double click should reset the parameter instead of initiating
|
||||||
// a drag operation
|
// a drag operation
|
||||||
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
self.param_base.begin_set_parameter(cx);
|
||||||
cx.emit(RawParamEvent::SetParameterNormalized(
|
self.param_base.set_normalized_value(cx, self.param_base.default_normalized_value());
|
||||||
self.param_ptr,
|
self.param_base.end_set_parameter(cx);
|
||||||
unsafe { self.param_ptr.default_normalized_value() },
|
|
||||||
));
|
|
||||||
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
||||||
} else {
|
} else {
|
||||||
self.drag_active = true;
|
self.drag_active = true;
|
||||||
cx.capture();
|
cx.capture();
|
||||||
|
@ -441,11 +409,10 @@ impl View for ParamSlider {
|
||||||
|
|
||||||
// When holding down shift while clicking on a parameter we want to granuarly
|
// When holding down shift while clicking on a parameter we want to granuarly
|
||||||
// edit the parameter without jumping to a new value
|
// edit the parameter without jumping to a new value
|
||||||
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
self.param_base.begin_set_parameter(cx);
|
||||||
if cx.modifiers.shift() {
|
if cx.modifiers.shift() {
|
||||||
self.granular_drag_start_x_value = Some((cx.mouse.cursorx, unsafe {
|
self.granular_drag_start_x_value = Some((cx.mouse.cursorx, self.param_base.unmodulated_normalized_value()
|
||||||
self.param_ptr.unmodulated_normalized_value()
|
));
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
self.granular_drag_start_x_value = None;
|
self.granular_drag_start_x_value = None;
|
||||||
self.set_normalized_value_drag(
|
self.set_normalized_value_drag(
|
||||||
|
@ -460,12 +427,9 @@ impl View for ParamSlider {
|
||||||
WindowEvent::MouseDoubleClick(MouseButton::Left) => {
|
WindowEvent::MouseDoubleClick(MouseButton::Left) => {
|
||||||
// Ctrl+Click and double click should reset the parameter instead of initiating
|
// Ctrl+Click and double click should reset the parameter instead of initiating
|
||||||
// a drag operation
|
// a drag operation
|
||||||
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
self.param_base.begin_set_parameter(cx);
|
||||||
cx.emit(RawParamEvent::SetParameterNormalized(
|
self.param_base.set_normalized_value(cx, self.param_base.default_normalized_value());
|
||||||
self.param_ptr,
|
self.param_base.end_set_parameter(cx);
|
||||||
unsafe { self.param_ptr.default_normalized_value() },
|
|
||||||
));
|
|
||||||
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
||||||
|
|
||||||
meta.consume();
|
meta.consume();
|
||||||
}
|
}
|
||||||
|
@ -475,7 +439,7 @@ impl View for ParamSlider {
|
||||||
cx.release();
|
cx.release();
|
||||||
cx.set_active(false);
|
cx.set_active(false);
|
||||||
|
|
||||||
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
self.param_base.end_set_parameter(cx);
|
||||||
|
|
||||||
meta.consume();
|
meta.consume();
|
||||||
}
|
}
|
||||||
|
@ -487,9 +451,8 @@ impl View for ParamSlider {
|
||||||
if cx.modifiers.shift() {
|
if cx.modifiers.shift() {
|
||||||
let (drag_start_x, drag_start_value) =
|
let (drag_start_x, drag_start_value) =
|
||||||
*self.granular_drag_start_x_value.get_or_insert_with(|| {
|
*self.granular_drag_start_x_value.get_or_insert_with(|| {
|
||||||
(cx.mouse.cursorx, unsafe {
|
(cx.mouse.cursorx, self.param_base.unmodulated_normalized_value()
|
||||||
self.param_ptr.unmodulated_normalized_value()
|
)
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.set_normalized_value_drag(
|
self.set_normalized_value_drag(
|
||||||
|
@ -516,10 +479,7 @@ impl View for ParamSlider {
|
||||||
// position
|
// position
|
||||||
if self.drag_active && self.granular_drag_start_x_value.is_some() {
|
if self.drag_active && self.granular_drag_start_x_value.is_some() {
|
||||||
self.granular_drag_start_x_value = None;
|
self.granular_drag_start_x_value = None;
|
||||||
self.set_normalized_value(
|
self.param_base.set_normalized_value(cx, util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx));
|
||||||
cx,
|
|
||||||
util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
Loading…
Reference in a new issue