Implement dragging for the parameter slider
This commit is contained in:
parent
df4b56d818
commit
ee9e3701f3
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1396,7 +1396,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
|||
[[package]]
|
||||
name = "iced_baseview"
|
||||
version = "0.0.3"
|
||||
source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#b32cc3f2a4dafee4e67b3e482b3794bb16f98752"
|
||||
source = "git+https://github.com/robbert-vdh/iced_baseview.git?branch=feature/update-baseview#5d09ba42d28923d144c8b7339a9f3f5e1252b37b"
|
||||
dependencies = [
|
||||
"baseview",
|
||||
"copypasta",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
use nih_plug::param::internals::ParamPtr;
|
||||
|
||||
mod param_slider;
|
||||
pub mod param_slider;
|
||||
pub mod util;
|
||||
|
||||
pub use param_slider::ParamSlider;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! A slider that integrates with NIH-plug's [`Param`] types.
|
||||
|
||||
use crate::{
|
||||
alignment, backend, event, layout, mouse, renderer, text, Clipboard, Color, Element, Event,
|
||||
Layout, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
alignment, backend, event, layout, mouse, renderer, text, touch, Clipboard, Color, Element,
|
||||
Event, Layout, Length, Point, Rectangle, Shell, Size, Widget,
|
||||
};
|
||||
use nih_plug::prelude::{GuiContext, Param, ParamSetter};
|
||||
|
||||
|
@ -12,7 +12,13 @@ use super::ParamMessage;
|
|||
/// A slider that integrates with NIH-plug's [`Param`] types.
|
||||
///
|
||||
/// TODO: There are currently no styling options at all
|
||||
/// TODO: Handle Shift+drag for granular drag
|
||||
/// TODO: Handle Ctrl+click for reset
|
||||
/// TODO: Handle Double click for reset
|
||||
/// TODO: Handle Alt+click for text entry
|
||||
pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
|
||||
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.
|
||||
|
@ -24,12 +30,20 @@ pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
|
|||
font: Renderer::Font,
|
||||
}
|
||||
|
||||
/// State for a [`ParamSlider`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct State {
|
||||
drag_active: bool,
|
||||
}
|
||||
|
||||
impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> {
|
||||
/// Creates a new [`ParamSlider`] for the given parameter.
|
||||
pub fn new(param: &'a P, context: &'a dyn GuiContext) -> Self {
|
||||
pub fn new(state: &'a mut State, param: &'a P, context: &'a dyn GuiContext) -> Self {
|
||||
let setter = ParamSetter::new(context);
|
||||
|
||||
Self {
|
||||
state,
|
||||
|
||||
param,
|
||||
setter,
|
||||
|
||||
|
@ -85,14 +99,54 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
_shell: &mut Shell<'_, ParamMessage>,
|
||||
shell: &mut Shell<'_, ParamMessage>,
|
||||
) -> event::Status {
|
||||
// TODO: Handle interaction
|
||||
let bounds = layout.bounds();
|
||||
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
if bounds.contains(cursor_position) {
|
||||
shell.publish(ParamMessage::BeginSetParameter(self.param.as_ptr()));
|
||||
self.state.drag_active = true;
|
||||
|
||||
// Immediately trigger a parameter update if the value would be different
|
||||
self.set_normalized_value(
|
||||
shell,
|
||||
util::remap_rect_x_coordinate(&bounds, cursor_position.x),
|
||||
);
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
|
||||
if bounds.contains(cursor_position) {
|
||||
shell.publish(ParamMessage::EndSetParameter(self.param.as_ptr()));
|
||||
self.state.drag_active = false;
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. })
|
||||
| Event::Touch(touch::Event::FingerMoved { .. }) => {
|
||||
if self.state.drag_active && bounds.contains(cursor_position) {
|
||||
self.set_normalized_value(
|
||||
shell,
|
||||
util::remap_rect_x_coordinate(&bounds, cursor_position.x),
|
||||
);
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
|
@ -145,11 +199,11 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
|
|||
|
||||
// We'll visualize the difference between the current value and the default value
|
||||
let current_value = self.param.normalized_value();
|
||||
let fill_start_x = util::remap_rect_x(
|
||||
let fill_start_x = util::remap_rect_x_t(
|
||||
&bounds,
|
||||
self.setter.default_normalized_param_value(self.param),
|
||||
);
|
||||
let fill_end_x = util::remap_rect_x(&bounds, current_value);
|
||||
let fill_end_x = util::remap_rect_x_t(&bounds, current_value);
|
||||
|
||||
let fill_color = Color::from_rgb8(196, 196, 196);
|
||||
let fill_rect = Rectangle {
|
||||
|
@ -204,6 +258,26 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> {
|
||||
/// 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, shell: &mut Shell<'_, ParamMessage>, 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 = self.param.preview_plain(normalized_value);
|
||||
let current_plain_value = self.param.plain_value();
|
||||
if plain_value != current_plain_value {
|
||||
shell.publish(ParamMessage::SetParameterNormalized(
|
||||
self.param.as_ptr(),
|
||||
normalized_value,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Param> ParamSlider<'a, P, backend::Renderer> {
|
||||
/// Convert this [`ParamSlider`] into an [`Element`] with the correct message. You should have a
|
||||
/// variant on your own message type that wraps around [`ParamMessage`] so you can forward those
|
||||
|
|
|
@ -4,12 +4,24 @@ use crate::Rectangle;
|
|||
|
||||
/// Remap a `[0, 1]` value to an x-coordinate within this rectangle. The value will be clamped to
|
||||
/// `[0, 1]` if it isn't already in that range.
|
||||
pub fn remap_rect_x(rect: &Rectangle, t: f32) -> f32 {
|
||||
pub fn remap_rect_x_t(rect: &Rectangle, t: f32) -> f32 {
|
||||
rect.x + (rect.width * t.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Remap a `[0, 1]` value to a y-coordinate within this rectangle. The value will be clamped to
|
||||
/// `[0, 1]` if it isn't already in that range.
|
||||
pub fn remap_rect_y(rect: &Rectangle, t: f32) -> f32 {
|
||||
pub fn remap_rect_y_t(rect: &Rectangle, t: f32) -> f32 {
|
||||
rect.y + (rect.height * t.clamp(0.0, 1.0))
|
||||
}
|
||||
|
||||
/// Remap an x-coordinate to a `[0, 1]` value within this rectangle. The value will be clamped to
|
||||
/// `[0, 1]` if it isn't already in that range.
|
||||
pub fn remap_rect_x_coordinate(rect: &Rectangle, x_coord: f32) -> f32 {
|
||||
((x_coord - rect.x) / rect.width).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
/// Remap a y-coordinate to a `[0, 1]` value within this rectangle. The value will be clamped to
|
||||
/// `[0, 1]` if it isn't already in that range.
|
||||
pub fn remap_rect_y_coordinate(rect: &Rectangle, y_coord: f32) -> f32 {
|
||||
((y_coord - rect.y) / rect.height).clamp(0.0, 1.0)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use nih_plug::prelude::{Editor, GuiContext};
|
||||
use nih_plug_iced::widgets::{ParamMessage, ParamSlider};
|
||||
use nih_plug_iced::widgets as nih_widgets;
|
||||
use nih_plug_iced::*;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
@ -22,13 +22,14 @@ struct GainEditor {
|
|||
params: Pin<Arc<GainParams>>,
|
||||
context: Arc<dyn GuiContext>,
|
||||
|
||||
gain_slider_state: nih_widgets::param_slider::State,
|
||||
meter_dummy_state: widget::button::State,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Message {
|
||||
/// Update a parameter's value.
|
||||
ParamUpdate(ParamMessage),
|
||||
ParamUpdate(nih_widgets::ParamMessage),
|
||||
}
|
||||
|
||||
impl IcedEditor for GainEditor {
|
||||
|
@ -43,6 +44,8 @@ impl IcedEditor for GainEditor {
|
|||
let editor = GainEditor {
|
||||
params,
|
||||
context,
|
||||
|
||||
gain_slider_state: Default::default(),
|
||||
meter_dummy_state: widget::button::State::new(),
|
||||
};
|
||||
|
||||
|
@ -84,7 +87,7 @@ impl IcedEditor for GainEditor {
|
|||
.vertical_alignment(alignment::Vertical::Center),
|
||||
)
|
||||
.push(
|
||||
ParamSlider::new(&self.params.gain, self.context.as_ref()).map(Message::ParamUpdate)
|
||||
nih_widgets::ParamSlider::new(&mut self.gain_slider_state, &self.params.gain, self.context.as_ref()).map(Message::ParamUpdate)
|
||||
// Button::new(&mut self.gain_dummy_state, Text::new("Gain"))
|
||||
// .height(30.into())
|
||||
// .width(180.into()),
|
||||
|
|
Loading…
Reference in a new issue