Add option to ParamSlider to always fill from left
This commit is contained in:
parent
80db6121f3
commit
64eaf37370
2 changed files with 114 additions and 63 deletions
|
@ -14,7 +14,7 @@ use vizia::{Context, Model};
|
||||||
mod param_slider;
|
mod param_slider;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
pub use param_slider::ParamSlider;
|
pub use param_slider::{ParamSlider, ParamSliderExt, ParamSliderStyle};
|
||||||
|
|
||||||
/// Register the default theme for the widgets exported by this module. This is automatically called
|
/// Register the default theme for the widgets exported by this module. This is automatically called
|
||||||
/// for you when using [`create_vizia_editor()`][super::create_vizia_editor()].
|
/// for you when using [`create_vizia_editor()`][super::create_vizia_editor()].
|
||||||
|
|
|
@ -34,6 +34,18 @@ pub struct ParamSlider {
|
||||||
granular_drag_start_x_value: Option<(f32, f32)>,
|
granular_drag_start_x_value: Option<(f32, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
enum ParamSliderEvent {
|
enum ParamSliderEvent {
|
||||||
/// Text input has been cancelled without submitting a new value.
|
/// Text input has been cancelled without submitting a new value.
|
||||||
CancelTextInput,
|
CancelTextInput,
|
||||||
|
@ -45,21 +57,28 @@ enum ParamSliderEvent {
|
||||||
#[derive(Lens)]
|
#[derive(Lens)]
|
||||||
// TODO: Lens requires everything to be marked as `pub`
|
// TODO: Lens requires everything to be marked as `pub`
|
||||||
pub struct ParamSliderInternal {
|
pub struct ParamSliderInternal {
|
||||||
|
/// What style to use for the slider.
|
||||||
|
style: ParamSliderStyle,
|
||||||
/// Will be set to `true` when the field gets Alt+Click'ed which will replae the label with a
|
/// Will be set to `true` when the field gets Alt+Click'ed which will replae the label with a
|
||||||
/// text box.
|
/// text box.
|
||||||
text_input_active: bool,
|
text_input_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParamSliderInternalEvent {
|
enum ParamSliderInternalEvent {
|
||||||
|
SetStyle(ParamSliderStyle),
|
||||||
SetTextInputActive(bool),
|
SetTextInputActive(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model for ParamSliderInternal {
|
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(ParamSliderInternalEvent::SetTextInputActive(value)) = event.message.downcast()
|
if let Some(param_slider_internal_event) = event.message.downcast() {
|
||||||
{
|
match param_slider_internal_event {
|
||||||
cx.current.set_active(cx, *value);
|
ParamSliderInternalEvent::SetStyle(style) => self.style = *style,
|
||||||
self.text_input_active = *value;
|
ParamSliderInternalEvent::SetTextInputActive(value) => {
|
||||||
|
cx.current.set_active(cx, *value);
|
||||||
|
self.text_input_active = *value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,12 +89,14 @@ impl ParamSlider {
|
||||||
/// the `Data` struct is used in `gain_gui_vizia`), the `ParamSetter` for retrieving the
|
/// 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's default value, and a projection function that maps the `Params` object to the
|
||||||
/// parameter you want to display a widget for.
|
/// parameter you want to display a widget for.
|
||||||
|
///
|
||||||
|
/// See [`ParamSliderExt`] for additonal options.
|
||||||
pub fn new<'a, L, Params, P, F>(
|
pub fn new<'a, L, Params, P, F>(
|
||||||
cx: &'a mut Context,
|
cx: &'a mut Context,
|
||||||
params: L,
|
params: L,
|
||||||
setter: &ParamSetter,
|
setter: &ParamSetter,
|
||||||
params_to_param: F,
|
params_to_param: F,
|
||||||
) -> Handle<'a, Self>
|
) -> Handle<'a, ParamSlider>
|
||||||
where
|
where
|
||||||
L: Lens<Target = Params> + Copy,
|
L: Lens<Target = Params> + Copy,
|
||||||
F: 'static + Fn(&Params) -> &P + Copy,
|
F: 'static + Fn(&Params) -> &P + Copy,
|
||||||
|
@ -98,7 +119,6 @@ impl ParamSlider {
|
||||||
let step_count = *params
|
let step_count = *params
|
||||||
.map(move |params| params_to_param(params).step_count())
|
.map(move |params| params_to_param(params).step_count())
|
||||||
.get(cx);
|
.get(cx);
|
||||||
let draw_fill_from_default = step_count.is_none() && (0.45..=0.55).contains(&default_value);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
param_ptr,
|
param_ptr,
|
||||||
|
@ -109,70 +129,82 @@ impl ParamSlider {
|
||||||
}
|
}
|
||||||
.build2(cx, move |cx| {
|
.build2(cx, move |cx| {
|
||||||
ParamSliderInternal {
|
ParamSliderInternal {
|
||||||
|
style: ParamSliderStyle::Centered,
|
||||||
text_input_active: false,
|
text_input_active: false,
|
||||||
}
|
}
|
||||||
.build(cx);
|
.build(cx);
|
||||||
|
|
||||||
Binding::new(
|
Binding::new(cx, ParamSliderInternal::style, move |cx, style| {
|
||||||
cx,
|
let style = *style.get(cx);
|
||||||
ParamSliderInternal::text_input_active,
|
let draw_fill_from_default = matches!(style, ParamSliderStyle::Centered)
|
||||||
move |cx, text_input_active| {
|
&& step_count.is_none()
|
||||||
let param_display_value_lens =
|
&& (0.45..=0.55).contains(&default_value);
|
||||||
params.map(move |params| params_to_param(params).to_string());
|
|
||||||
let normalized_param_value_lens =
|
|
||||||
params.map(move |params| params_to_param(params).normalized_value());
|
|
||||||
|
|
||||||
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
Binding::new(
|
||||||
// label with the slider.
|
cx,
|
||||||
if *text_input_active.get(cx) {
|
ParamSliderInternal::text_input_active,
|
||||||
Textbox::new(cx, param_display_value_lens)
|
move |cx, text_input_active| {
|
||||||
.class("value-entry")
|
let param_display_value_lens =
|
||||||
.on_submit(|cx, string| cx.emit(ParamSliderEvent::TextInput(string)))
|
params.map(move |params| params_to_param(params).to_string());
|
||||||
.on_focus_out(|cx| cx.emit(ParamSliderEvent::CancelTextInput))
|
let normalized_param_value_lens =
|
||||||
.child_space(Stretch(1.0))
|
params.map(move |params| params_to_param(params).normalized_value());
|
||||||
.height(Stretch(1.0))
|
|
||||||
.width(Stretch(1.0));
|
|
||||||
} else {
|
|
||||||
ZStack::new(cx, move |cx| {
|
|
||||||
// The filled bar portion
|
|
||||||
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, mut delta) = if draw_fill_from_default {
|
|
||||||
(
|
|
||||||
default_value.min(current_value),
|
|
||||||
(default_value - current_value).abs(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(0.0, current_value)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't draw the filled portion at all if it could have been a
|
// Only draw the text input widget when it gets focussed. Otherwise, overlay the
|
||||||
// rounding error since those slivers just look weird
|
// label with the slider.
|
||||||
if delta < 1e-3 {
|
if *text_input_active.get(cx) {
|
||||||
delta = 0.0;
|
Textbox::new(cx, param_display_value_lens)
|
||||||
}
|
.class("value-entry")
|
||||||
|
.on_submit(|cx, string| {
|
||||||
handle
|
cx.emit(ParamSliderEvent::TextInput(string))
|
||||||
.left(Percentage(start_t * 100.0))
|
|
||||||
.width(Percentage(delta * 100.0));
|
|
||||||
})
|
})
|
||||||
// Hovering is handled on the param slider as a whole, this should
|
.on_focus_out(|cx| cx.emit(ParamSliderEvent::CancelTextInput))
|
||||||
// not affect that
|
.child_space(Stretch(1.0))
|
||||||
.hoverable(false);
|
|
||||||
|
|
||||||
Label::new(cx, param_display_value_lens)
|
|
||||||
.class("value")
|
|
||||||
.height(Stretch(1.0))
|
.height(Stretch(1.0))
|
||||||
.width(Stretch(1.0))
|
.width(Stretch(1.0));
|
||||||
.hoverable(false);
|
} else {
|
||||||
})
|
ZStack::new(cx, move |cx| {
|
||||||
.hoverable(false);
|
// 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, mut delta) = if draw_fill_from_default {
|
||||||
|
(
|
||||||
|
default_value.min(current_value),
|
||||||
|
(default_value - current_value).abs(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(0.0, 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 = 0.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);
|
||||||
|
|
||||||
|
Label::new(cx, param_display_value_lens)
|
||||||
|
.class("value")
|
||||||
|
.height(Stretch(1.0))
|
||||||
|
.width(Stretch(1.0))
|
||||||
|
.hoverable(false);
|
||||||
|
})
|
||||||
|
.hoverable(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,3 +358,22 @@ impl View for ParamSlider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue