2022-03-19 11:17:13 +11:00
|
|
|
//! A slider that integrates with NIH-plug's [`Param`] types.
|
|
|
|
|
|
|
|
use nih_plug::param::internals::ParamPtr;
|
|
|
|
use nih_plug::prelude::{Param, ParamSetter};
|
|
|
|
use vizia::*;
|
|
|
|
|
|
|
|
use super::util::{self, ModifiersExt};
|
|
|
|
use super::RawParamEvent;
|
|
|
|
|
|
|
|
/// When shift+dragging a parameter, one pixel dragged corresponds to this much change in the
|
|
|
|
/// noramlized parameter.
|
|
|
|
const GRANULAR_DRAG_MULTIPLIER: f32 = 0.1;
|
|
|
|
|
|
|
|
/// A slider that integrates with NIH-plug's [`Param`] types.
|
|
|
|
///
|
|
|
|
/// 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
|
2022-03-20 01:28:58 +11:00
|
|
|
/// TODO: Text entry doesn't work correctly yet because vizia's still missing some functionality
|
2022-03-19 11:17:13 +11:00
|
|
|
pub struct ParamSlider {
|
|
|
|
// We're not allowed to store a reference to the parameter internally, at least not in the
|
|
|
|
// struct that implements [`View`]
|
|
|
|
param_ptr: ParamPtr,
|
|
|
|
|
|
|
|
/// Will be set to `true` if we're dragging the parameter. Resetting the parameter or entering a
|
|
|
|
/// text value should not initiate a drag.
|
|
|
|
drag_active: bool,
|
|
|
|
/// Whether the next click is a double click. Vizia will send a double click event followed by a
|
|
|
|
/// regular mouse down event when double clicking.
|
|
|
|
is_double_click: bool,
|
2022-03-19 12:18:30 +11:00
|
|
|
/// We keep track of the start coordinate and normalized value when holding down Shift while
|
|
|
|
/// dragging for higher precision dragging. This is a `None` value when granular dragging is not
|
|
|
|
/// active.
|
|
|
|
granular_drag_start_x_value: Option<(f32, f32)>,
|
2022-03-19 11:17:13 +11:00
|
|
|
}
|
|
|
|
|
2022-03-20 04:49:49 +11:00
|
|
|
/// How the [`ParamSlider`] should display its values. Set this using
|
|
|
|
/// [`ParamSliderExt::slider_style()`].
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Data)]
|
|
|
|
pub enum ParamSliderStyle {
|
|
|
|
/// Visualize the offset from the default value for continuous parameters with a default value
|
|
|
|
/// at around half of its range, fill the bar from the left for discrete parameters and
|
|
|
|
/// continous parameters without centered default values.
|
|
|
|
Centered,
|
|
|
|
/// Always fill the bar starting from the left.
|
|
|
|
FromLeft,
|
2022-03-20 05:29:19 +11:00
|
|
|
/// Show the current step instead of filling a portion fothe bar, useful for discrete
|
|
|
|
/// parameters.
|
|
|
|
CurrentStep,
|
2022-03-20 05:42:50 +11:00
|
|
|
/// The same as `CurrentStep`, but overlay the labels over the steps instead of showing the
|
|
|
|
/// active value. Only useful for discrete parameters with two, maybe three possible values.
|
|
|
|
CurrentStepLabeled,
|
2022-03-20 04:49:49 +11:00
|
|
|
}
|
|
|
|
|
2022-03-20 01:28:58 +11:00
|
|
|
enum ParamSliderEvent {
|
|
|
|
/// Text input has been cancelled without submitting a new value.
|
|
|
|
CancelTextInput,
|
|
|
|
/// A new value has been sent by the text input dialog after pressint Enter.
|
|
|
|
TextInput(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Internal param slider state the view needs to react to.
|
|
|
|
#[derive(Lens)]
|
|
|
|
// TODO: Lens requires everything to be marked as `pub`
|
|
|
|
pub struct ParamSliderInternal {
|
2022-03-20 04:49:49 +11:00
|
|
|
/// What style to use for the slider.
|
|
|
|
style: ParamSliderStyle,
|
2022-03-20 01:28:58 +11:00
|
|
|
/// Will be set to `true` when the field gets Alt+Click'ed which will replae the label with a
|
|
|
|
/// text box.
|
|
|
|
text_input_active: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ParamSliderInternalEvent {
|
2022-03-20 04:49:49 +11:00
|
|
|
SetStyle(ParamSliderStyle),
|
2022-03-20 01:28:58 +11:00
|
|
|
SetTextInputActive(bool),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Model for ParamSliderInternal {
|
2022-03-20 03:47:50 +11:00
|
|
|
fn event(&mut self, cx: &mut Context, event: &mut Event) {
|
2022-03-20 04:49:49 +11:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-03-20 01:28:58 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-19 11:17:13 +11:00
|
|
|
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.
|
2022-03-20 04:49:49 +11:00
|
|
|
///
|
|
|
|
/// See [`ParamSliderExt`] for additonal options.
|
2022-03-19 11:17:13 +11:00
|
|
|
pub fn new<'a, L, Params, P, F>(
|
|
|
|
cx: &'a mut Context,
|
|
|
|
params: L,
|
|
|
|
setter: &ParamSetter,
|
|
|
|
params_to_param: F,
|
2022-03-20 04:49:49 +11:00
|
|
|
) -> Handle<'a, ParamSlider>
|
2022-03-19 11:17:13 +11:00
|
|
|
where
|
2022-03-20 01:28:58 +11:00
|
|
|
L: Lens<Target = Params> + Copy,
|
2022-03-19 11:17:13 +11:00
|
|
|
F: 'static + Fn(&Params) -> &P + Copy,
|
|
|
|
Params: 'static,
|
|
|
|
P: Param,
|
|
|
|
{
|
|
|
|
// 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
|
|
|
|
// this appraoch 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
|
|
|
|
.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 step_count = *params
|
|
|
|
.map(move |params| params_to_param(params).step_count())
|
|
|
|
.get(cx);
|
|
|
|
|
|
|
|
Self {
|
|
|
|
param_ptr,
|
|
|
|
|
|
|
|
drag_active: false,
|
|
|
|
is_double_click: false,
|
2022-03-19 12:18:30 +11:00
|
|
|
granular_drag_start_x_value: None,
|
2022-03-19 11:17:13 +11:00
|
|
|
}
|
2022-03-20 01:28:58 +11:00
|
|
|
.build2(cx, move |cx| {
|
|
|
|
ParamSliderInternal {
|
2022-03-20 04:49:49 +11:00
|
|
|
style: ParamSliderStyle::Centered,
|
2022-03-20 01:28:58 +11:00
|
|
|
text_input_active: false,
|
|
|
|
}
|
|
|
|
.build(cx);
|
|
|
|
|
2022-03-20 04:49:49 +11:00
|
|
|
Binding::new(cx, ParamSliderInternal::style, move |cx, style| {
|
|
|
|
let style = *style.get(cx);
|
|
|
|
let draw_fill_from_default = matches!(style, ParamSliderStyle::Centered)
|
|
|
|
&& step_count.is_none()
|
|
|
|
&& (0.45..=0.55).contains(&default_value);
|
|
|
|
|
2022-03-20 11:12:10 +11:00
|
|
|
let param_display_value_lens =
|
|
|
|
params.map(move |params| params_to_param(params).to_string());
|
|
|
|
let param_preview_display_value_lens = |normalized_value| {
|
|
|
|
params.map(move |params| {
|
|
|
|
params_to_param(params).normalized_value_to_string(normalized_value, true)
|
|
|
|
})
|
|
|
|
};
|
|
|
|
let normalized_param_value_lens =
|
|
|
|
params.map(move |params| params_to_param(params).normalized_value());
|
2022-03-20 01:28:58 +11:00
|
|
|
|
2022-03-20 11:12:10 +11:00
|
|
|
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
|
|
|
// label with the slider. We need to always include the text input widget in the
|
|
|
|
// tree because using a `Binding` here would cause the text box to get recreated
|
|
|
|
// whenever `ParamSliderInternal::text_input_active` changes.
|
|
|
|
Textbox::new(cx, param_display_value_lens.clone())
|
|
|
|
.class("value-entry")
|
|
|
|
.on_submit(|cx, string| cx.emit(ParamSliderEvent::TextInput(string)))
|
|
|
|
.on_focus_out(|cx| cx.emit(ParamSliderEvent::CancelTextInput))
|
|
|
|
.display(ParamSliderInternal::text_input_active)
|
|
|
|
.child_space(Stretch(1.0))
|
|
|
|
.height(Stretch(1.0))
|
|
|
|
.width(Stretch(1.0));
|
2022-03-20 04:49:49 +11:00
|
|
|
|
2022-03-20 11:12:10 +11:00
|
|
|
// This only gets shown when the text box isn't active
|
|
|
|
ZStack::new(cx, move |cx| {
|
|
|
|
// The filled bar portion. This can be visualized in a couple different
|
|
|
|
// ways depending on the current style property. See
|
|
|
|
// [`ParamSliderStyle`].
|
|
|
|
Element::new(cx)
|
|
|
|
.class("fill")
|
|
|
|
.height(Stretch(1.0))
|
|
|
|
.bind(normalized_param_value_lens, move |handle, value| {
|
|
|
|
let current_value = *value.get(handle.cx);
|
|
|
|
let (start_t, delta) = match style {
|
|
|
|
ParamSliderStyle::Centered if draw_fill_from_default => {
|
|
|
|
let delta = (default_value - current_value).abs();
|
|
|
|
(
|
|
|
|
default_value.min(current_value),
|
|
|
|
// Don't draw the filled portion at all if it could have been a
|
|
|
|
// rounding error since those slivers just look weird
|
|
|
|
if delta >= 1e-3 { delta } else { 0.0 },
|
|
|
|
)
|
|
|
|
}
|
|
|
|
ParamSliderStyle::Centered | ParamSliderStyle::FromLeft => {
|
|
|
|
(0.0, current_value)
|
|
|
|
}
|
|
|
|
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) / 2.0,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
handle
|
|
|
|
.left(Percentage(start_t * 100.0))
|
|
|
|
.width(Percentage(delta * 100.0));
|
|
|
|
})
|
|
|
|
// Hovering is handled on the param slider as a whole, this should
|
|
|
|
// not affect that
|
|
|
|
.hoverable(false);
|
2022-03-20 04:49:49 +11:00
|
|
|
|
2022-03-20 11:12:10 +11:00
|
|
|
// 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;
|
|
|
|
Label::new(
|
|
|
|
cx,
|
|
|
|
param_preview_display_value_lens(normalized_value),
|
|
|
|
)
|
|
|
|
.class("value")
|
|
|
|
.class("value--multiple")
|
|
|
|
.height(Stretch(1.0))
|
|
|
|
.width(Stretch(1.0))
|
|
|
|
.hoverable(false);
|
|
|
|
}
|
2022-03-20 04:49:49 +11:00
|
|
|
})
|
2022-03-20 11:12:10 +11:00
|
|
|
.height(Stretch(1.0))
|
|
|
|
.width(Stretch(1.0))
|
2022-03-20 04:49:49 +11:00
|
|
|
.hoverable(false);
|
|
|
|
}
|
2022-03-20 11:12:10 +11:00
|
|
|
_ => {
|
|
|
|
Label::new(cx, param_display_value_lens)
|
|
|
|
.class("value")
|
|
|
|
.class("value--single")
|
|
|
|
.height(Stretch(1.0))
|
|
|
|
.width(Stretch(1.0))
|
|
|
|
.hoverable(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.display(ParamSliderInternal::text_input_active.map(|active| !active))
|
|
|
|
.hoverable(false);
|
2022-03-20 04:49:49 +11:00
|
|
|
});
|
2022-03-19 11:17:13 +11:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set the normalized value for a parameter if that would change the parameter's plain value
|
|
|
|
/// (to avoid unnecessary duplicate parameter changes). The begin- and end set parameter
|
|
|
|
/// messages need to be sent before calling this function.
|
|
|
|
fn set_normalized_value(&self, cx: &mut Context, 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.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,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl View for ParamSlider {
|
|
|
|
fn element(&self) -> Option<String> {
|
|
|
|
Some(String::from("param-slider"))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn event(&mut self, cx: &mut Context, event: &mut Event) {
|
2022-03-20 01:28:58 +11:00
|
|
|
if let Some(param_slider_event) = event.message.downcast() {
|
|
|
|
match param_slider_event {
|
|
|
|
ParamSliderEvent::CancelTextInput => {
|
|
|
|
cx.emit(ParamSliderInternalEvent::SetTextInputActive(false))
|
|
|
|
}
|
|
|
|
ParamSliderEvent::TextInput(string) => {
|
|
|
|
if let Some(normalized_value) =
|
|
|
|
unsafe { self.param_ptr.string_to_normalized_value(string) }
|
|
|
|
{
|
|
|
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
|
|
|
self.set_normalized_value(cx, normalized_value);
|
|
|
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
cx.emit(ParamSliderInternalEvent::SetTextInputActive(false))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-19 11:17:13 +11:00
|
|
|
if let Some(window_event) = event.message.downcast() {
|
|
|
|
match window_event {
|
|
|
|
WindowEvent::MouseDown(MouseButton::Left) => {
|
2022-03-20 01:28:58 +11:00
|
|
|
if cx.modifiers.alt() {
|
2022-03-20 11:23:44 +11:00
|
|
|
// ALt+Click brings up a text entry dialog
|
2022-03-20 01:28:58 +11:00
|
|
|
cx.emit(ParamSliderInternalEvent::SetTextInputActive(true));
|
2022-03-20 11:23:44 +11:00
|
|
|
cx.current.set_active(cx, true);
|
|
|
|
|
2022-03-20 01:28:58 +11:00
|
|
|
// TODO: Once vizia implements it: (and probably do this from
|
|
|
|
// `SetTextInputActive`)
|
|
|
|
// - Focus the text box
|
|
|
|
// - Select all text
|
|
|
|
// - Move the caret to the end
|
|
|
|
} else if cx.modifiers.command() || self.is_double_click {
|
|
|
|
// Ctrl+Click and double click should reset the parameter instead of initiating
|
|
|
|
// a drag operation
|
2022-03-19 11:17:13 +11:00
|
|
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
|
|
|
cx.emit(RawParamEvent::ResetParameter(self.param_ptr));
|
|
|
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
|
|
} else {
|
|
|
|
self.drag_active = true;
|
|
|
|
cx.capture();
|
2022-03-19 11:48:57 +11:00
|
|
|
// NOTE: Otherwise we don't get key up events
|
|
|
|
cx.focused = cx.current;
|
2022-03-19 11:17:13 +11:00
|
|
|
cx.current.set_active(cx, true);
|
|
|
|
|
|
|
|
// When holding down shift while clicking on a parameter we want to
|
|
|
|
// granuarly edit the parameter without jumping to a new value
|
|
|
|
cx.emit(RawParamEvent::BeginSetParameter(self.param_ptr));
|
|
|
|
if cx.modifiers.shift() {
|
2022-03-19 12:18:30 +11:00
|
|
|
self.granular_drag_start_x_value = Some((cx.mouse.cursorx, unsafe {
|
|
|
|
self.param_ptr.normalized_value()
|
|
|
|
}));
|
2022-03-19 11:17:13 +11:00
|
|
|
} else {
|
2022-03-19 12:18:30 +11:00
|
|
|
self.granular_drag_start_x_value = None;
|
2022-03-19 11:17:13 +11:00
|
|
|
self.set_normalized_value(
|
|
|
|
cx,
|
|
|
|
util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-03-20 01:28:58 +11:00
|
|
|
|
|
|
|
// We'll set this here because weird things like Alt+double click should not
|
|
|
|
// cause the next click to become a reset
|
|
|
|
self.is_double_click = false;
|
2022-03-19 11:17:13 +11:00
|
|
|
}
|
|
|
|
WindowEvent::MouseDoubleClick(MouseButton::Left) => {
|
|
|
|
// Vizia will send a regular mouse down after this, so we'll handle the reset
|
|
|
|
// there
|
|
|
|
self.is_double_click = true;
|
|
|
|
}
|
|
|
|
WindowEvent::MouseUp(MouseButton::Left) => {
|
|
|
|
if self.drag_active {
|
|
|
|
self.drag_active = false;
|
|
|
|
cx.release();
|
|
|
|
cx.current.set_active(cx, false);
|
|
|
|
|
|
|
|
cx.emit(RawParamEvent::EndSetParameter(self.param_ptr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WindowEvent::MouseMove(x, _y) => {
|
|
|
|
if self.drag_active {
|
|
|
|
// If shift is being held then the drag should be more granular instead of
|
|
|
|
// absolute
|
|
|
|
if cx.modifiers.shift() {
|
2022-03-19 12:18:30 +11:00
|
|
|
let (drag_start_x, drag_start_value) =
|
|
|
|
*self.granular_drag_start_x_value.get_or_insert_with(|| {
|
|
|
|
(cx.mouse.cursorx, unsafe {
|
|
|
|
self.param_ptr.normalized_value()
|
|
|
|
})
|
|
|
|
});
|
2022-03-19 11:17:13 +11:00
|
|
|
|
|
|
|
self.set_normalized_value(
|
|
|
|
cx,
|
|
|
|
util::remap_current_entity_x_coordinate(
|
|
|
|
cx,
|
2022-03-19 12:18:30 +11:00
|
|
|
// This can be optimized a bit
|
|
|
|
util::remap_current_entity_x_t(cx, drag_start_value)
|
|
|
|
+ (*x - drag_start_x) * GRANULAR_DRAG_MULTIPLIER,
|
2022-03-19 11:17:13 +11:00
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
2022-03-19 12:18:30 +11:00
|
|
|
self.granular_drag_start_x_value = None;
|
2022-03-19 11:17:13 +11:00
|
|
|
|
|
|
|
self.set_normalized_value(
|
|
|
|
cx,
|
|
|
|
util::remap_current_entity_x_coordinate(cx, *x),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-19 11:48:57 +11:00
|
|
|
WindowEvent::KeyUp(_, Some(Key::Shift)) => {
|
|
|
|
// If this happens while dragging, snap back to reality uh I mean the current screen
|
|
|
|
// position
|
2022-03-19 12:18:30 +11:00
|
|
|
if self.drag_active && self.granular_drag_start_x_value.is_some() {
|
|
|
|
self.granular_drag_start_x_value = None;
|
2022-03-19 11:48:57 +11:00
|
|
|
self.set_normalized_value(
|
|
|
|
cx,
|
|
|
|
util::remap_current_entity_x_coordinate(cx, cx.mouse.cursorx),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2022-03-19 11:17:13 +11:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-20 04:49:49 +11:00
|
|
|
|
|
|
|
/// Extension methods for [`ParamSlider`] handles.
|
|
|
|
pub trait ParamSliderExt {
|
|
|
|
/// Change how the [`ParamSlider`] visualizes the current value.
|
|
|
|
fn set_style(self, style: ParamSliderStyle) -> Self;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ParamSliderExt for Handle<'_, ParamSlider> {
|
|
|
|
fn set_style(self, style: ParamSliderStyle) -> Self {
|
|
|
|
self.cx.event_queue.push_back(
|
|
|
|
Event::new(ParamSliderInternalEvent::SetStyle(style))
|
|
|
|
.target(self.entity)
|
|
|
|
.origin(self.entity)
|
|
|
|
.propagate(Propagation::Subtree),
|
|
|
|
);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|