1
0
Fork 0

Finish the drawing for the iced param slider

This commit is contained in:
Robbert van der Helm 2022-03-14 13:17:19 +01:00
parent 291abb8bcf
commit 4e9ee27c19
6 changed files with 96 additions and 54 deletions

View file

@ -1,4 +1,4 @@
//! Utilities for creating these widgets //! Utilities for creating these widgets.
use egui::Color32; use egui::Color32;

View file

@ -52,8 +52,8 @@ pub fn create_iced_editor<E: IcedEditor>(
/// A plugin editor using `iced`. This wraps around [`Application`] with the only change being that /// A plugin editor using `iced`. This wraps around [`Application`] with the only change being that
/// the usual `new()` function now additionally takes a `Arc<dyn GuiContext>` that the editor can /// the usual `new()` function now additionally takes a `Arc<dyn GuiContext>` that the editor can
/// store to interact with the parameters. The editor should have a `Pin<Arc<impl Params>>` as part /// store to interact with the parameters. The editor should have a `Pin<Arc<impl Params>>` as part
/// of their [`Flags`][Self::Flags] so it can read the current parameter values. See [`Application`] /// of their [`InitializationFlags`][Self::InitializationFlags] so it can read the current parameter
/// for more information. /// values. See [`Application`] for more information.
pub trait IcedEditor: 'static + Send + Sync + Sized { pub trait IcedEditor: 'static + Send + Sync + Sized {
/// See [`Application::Executor`]. You'll likely want to use [`crate::executor::Default`]. /// See [`Application::Executor`]. You'll likely want to use [`crate::executor::Default`].
type Executor: Executor; type Executor: Executor;

View file

@ -8,6 +8,7 @@
use nih_plug::param::internals::ParamPtr; use nih_plug::param::internals::ParamPtr;
mod param_slider; mod param_slider;
pub mod util;
pub use param_slider::ParamSlider; pub use param_slider::ParamSlider;

View file

@ -1,14 +1,12 @@
//! A slider that integrates with NIH-plug's [`Param`] types. //! A slider that integrates with NIH-plug's [`Param`] types.
use crate::backend; use crate::{
use crate::event::{self, Event}; alignment, backend, event, layout, mouse, renderer, text, Clipboard, Color, Element, Event,
use crate::layout; Layout, Length, Point, Rectangle, Shell, Size, Widget,
use crate::mouse; };
use crate::renderer; use nih_plug::prelude::{GuiContext, Param, ParamSetter};
use crate::text;
use crate::{Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size, Widget};
use nih_plug::prelude::Param;
use super::util;
use super::ParamMessage; use super::ParamMessage;
/// A slider that integrates with NIH-plug's [`Param`] types. /// A slider that integrates with NIH-plug's [`Param`] types.
@ -16,6 +14,9 @@ use super::ParamMessage;
/// TODO: There are currently no styling options at all /// TODO: There are currently no styling options at all
pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> { pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
param: &'a P, 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.
setter: ParamSetter<'a>,
height: Length, height: Length,
width: Length, width: Length,
@ -25,9 +26,13 @@ pub struct ParamSlider<'a, P: Param, Renderer: text::Renderer> {
impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> { impl<'a, P: Param, Renderer: text::Renderer> ParamSlider<'a, P, Renderer> {
/// Creates a new [`ParamSlider`] for the given parameter. /// Creates a new [`ParamSlider`] for the given parameter.
pub fn new(param: &'a P) -> Self { pub fn new(param: &'a P, context: &'a dyn GuiContext) -> Self {
let setter = ParamSetter::new(context);
Self { Self {
param, param,
setter,
width: Length::Units(180), width: Length::Units(180),
height: Length::Units(30), height: Length::Units(30),
text_size: None, text_size: None,
@ -111,17 +116,19 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
fn draw( fn draw(
&self, &self,
renderer: &mut Renderer, renderer: &mut Renderer,
_style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: Point, cursor_position: Point,
_viewport: &Rectangle, _viewport: &Rectangle,
) { ) {
const BORDER_WIDTH: f32 = 1.0;
let bounds = layout.bounds(); let bounds = layout.bounds();
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
// TODO: // The bar itself
let background_color = if is_mouse_over { let background_color = if is_mouse_over {
Color::new(0.5, 0.5, 0.5, 0.2) Color::new(0.5, 0.5, 0.5, 0.1)
} else { } else {
Color::TRANSPARENT Color::TRANSPARENT
}; };
@ -130,51 +137,70 @@ impl<'a, P: Param, Renderer: text::Renderer> Widget<ParamMessage, Renderer>
renderer::Quad { renderer::Quad {
bounds, bounds,
border_color: Color::BLACK, border_color: Color::BLACK,
border_width: 1.0, border_width: BORDER_WIDTH,
border_radius: 0.0, border_radius: 0.0,
}, },
background_color, background_color,
); );
// TODO: // 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(
&bounds,
self.setter.default_normalized_param_value(self.param),
);
let fill_end_x = util::remap_rect_x(&bounds, current_value);
// renderer.fill_text(Text { let fill_color = Color::from_rgb8(196, 196, 196);
// content: &Renderer::ARROW_DOWN_ICON.to_string(), let fill_rect = Rectangle {
// font: Renderer::ICON_FONT, x: fill_start_x.min(fill_end_x),
// size: bounds.height * style.icon_size, y: bounds.y + BORDER_WIDTH,
// bounds: Rectangle { width: (fill_end_x - fill_start_x).abs(),
// x: bounds.x + bounds.width - f32::from(self.padding.horizontal()), height: bounds.height - BORDER_WIDTH * 2.0,
// y: bounds.center_y(), };
// ..bounds renderer.fill_quad(
// }, renderer::Quad {
// color: style.text_color, bounds: fill_rect,
// horizontal_alignment: alignment::Horizontal::Right, border_color: Color::TRANSPARENT,
// vertical_alignment: alignment::Vertical::Center, border_width: 0.0,
// }); border_radius: 0.0,
},
fill_color,
);
// if let Some(label) = self // We'll overlay the label on the slider. To make it more readable (and because it looks
// .selected // cool), the parts that overlap with the fill rect will be rendered in white while the rest
// .as_ref() // will be rendered in black.
// .map(ToString::to_string) let display_value = self.param.to_string();
// .as_ref() let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()) as f32;
// .or_else(|| self.placeholder.as_ref()) let text_bounds = Rectangle {
// { x: bounds.center_x(),
// renderer.fill_text(Text { y: bounds.center_y(),
// content: label, ..bounds
// size: f32::from(self.text_size.unwrap_or(renderer.default_size())), };
// font: self.font.clone(), renderer.fill_text(text::Text {
// color: is_selected content: &display_value,
// .then(|| style.text_color) font: self.font.clone(),
// .unwrap_or(style.placeholder_color), size: text_size,
// bounds: Rectangle { bounds: text_bounds,
// x: bounds.x + f32::from(self.padding.left), color: style.text_color,
// y: bounds.center_y(), horizontal_alignment: alignment::Horizontal::Center,
// ..bounds vertical_alignment: alignment::Vertical::Center,
// }, });
// horizontal_alignment: alignment::Horizontal::Left,
// vertical_alignment: alignment::Vertical::Center, // This will clip to the filled area
// }) renderer.with_layer(fill_rect, |renderer| {
// } let filled_text_color = Color::from_rgb8(80, 80, 80);
renderer.fill_text(text::Text {
content: &display_value,
font: self.font.clone(),
size: text_size,
bounds: text_bounds,
color: filled_text_color,
horizontal_alignment: alignment::Horizontal::Center,
vertical_alignment: alignment::Vertical::Center,
});
});
} }
} }

View file

@ -0,0 +1,15 @@
//! Utilities for creating these widgets.
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 {
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 {
rect.y + (rect.height * t.clamp(0.0, 1.0))
}

View file

@ -84,7 +84,7 @@ impl IcedEditor for GainEditor {
.vertical_alignment(alignment::Vertical::Center), .vertical_alignment(alignment::Vertical::Center),
) )
.push( .push(
ParamSlider::new(&self.params.gain).map(Message::ParamUpdate) ParamSlider::new(&self.params.gain, self.context.as_ref()).map(Message::ParamUpdate)
// Button::new(&mut self.gain_dummy_state, Text::new("Gain")) // Button::new(&mut self.gain_dummy_state, Text::new("Gain"))
// .height(30.into()) // .height(30.into())
// .width(180.into()), // .width(180.into()),