diff --git a/nih_plug_egui/src/widgets.rs b/nih_plug_egui/src/widgets.rs new file mode 100644 index 00000000..fcb3b751 --- /dev/null +++ b/nih_plug_egui/src/widgets.rs @@ -0,0 +1,13 @@ +//! Custom egui widgets for displaying parameter values. +//! +//! # Note +//! +//! None of these widgets are finalized, and their sizes or looks can change at any point. Feel free +//! to copy the widgets and modify them to your personal taste. + +mod param_slider; + +pub use param_slider::ParamSlider; + +// TODO: At some opint add some generic UI widget that shows an entire Params struct (in order) +// along with the parameter's names as sliders diff --git a/nih_plug_egui/src/widgets/param_slider.rs b/nih_plug_egui/src/widgets/param_slider.rs new file mode 100644 index 00000000..e120a98a --- /dev/null +++ b/nih_plug_egui/src/widgets/param_slider.rs @@ -0,0 +1,110 @@ +use egui::{vec2, Color32, Response, Sense, Stroke, TextStyle, Ui, Widget}; + +use nih_plug::{FloatParam, Param, ParamSetter}; + +/// A slider widget similar to [egui::widgets::Slider] that knows about NIH-plug parameters ranges +/// and can get values for it. +/// +/// TODO: To keep things simple this currently only supports FloatParam, but it should be generic +/// over any kind of parameter +/// TODO: Vertical orientation +/// TODO: (before I forget) mouse scrolling, ctrl+click and double click to reset +pub struct ParamSlider<'a> { + param: &'a FloatParam, + setter: &'a ParamSetter<'a>, +} + +impl<'a> ParamSlider<'a> { + /// Create a new slider for a parameter. Use the other methods to modify the slider before + /// passing it to [Ui::add()]. + pub fn for_param(param: &'a FloatParam, setter: &'a ParamSetter<'a>) -> Self { + Self { param, setter } + } + + fn normalized_value(&self) -> f32 { + self.param.normalized_value() + } + + fn set_normalized_value(&self, normalized: f32) { + // TODO: The gesture should start on mouse down and end up mouse up + self.setter.begin_set_parameter(self.param); + self.setter.set_parameter_normalized(self.param, normalized); + self.setter.end_set_parameter(self.param); + } +} + +impl Widget for ParamSlider<'_> { + fn ui(self, ui: &mut Ui) -> Response { + // Allocate space, but add some padding on the top and bottom to make it look a bit slimmer. + let height = ui + .fonts() + .row_height(TextStyle::Body) + .max(ui.spacing().interact_size.y); + let slider_height = ui.painter().round_to_pixel(height * 0.65); + let response = ui + .vertical(|ui| { + ui.allocate_space(vec2( + ui.spacing().slider_width, + (height - slider_height) / 2.0, + )); + let response = ui.allocate_response( + vec2(ui.spacing().slider_width, slider_height), + Sense::click_and_drag(), + ); + ui.allocate_space(vec2( + ui.spacing().slider_width, + (height - slider_height) / 2.0, + )); + response + }) + .inner; + + // Handle user input + // TODO: We only show the position now + if let Some(click_pos) = response.interact_pointer_pos() { + let aim_radius = ui.input().aim_radius(); + let proportion = egui::emath::smart_aim::best_in_range_f64( + egui::emath::remap_clamp( + click_pos.x - aim_radius, + response.rect.x_range(), + 0.0..=1.0, + ) as f64, + egui::emath::remap_clamp( + click_pos.x + aim_radius, + response.rect.x_range(), + 0.0..=1.0, + ) as f64, + ); + + self.set_normalized_value(proportion as f32); + } + + // And finally draw the thing + if ui.is_rect_visible(response.rect) { + // We'll do a flat widget with background -> filled foreground -> slight border + ui.painter() + .rect_filled(response.rect, 0.0, ui.visuals().widgets.inactive.bg_fill); + + let filled_proportion = self.normalized_value(); + let mut filled_rect = response.rect; + filled_rect.set_width(response.rect.width() * filled_proportion); + let filled_bg = if response.dragged() { + // TODO: Use something that works with a light theme + Color32::DARK_GRAY + } else { + ui.visuals().selection.bg_fill + }; + ui.painter().rect_filled(filled_rect, 0.0, filled_bg); + + ui.painter().rect_stroke( + response.rect, + 0.0, + Stroke::new(1.0, ui.visuals().widgets.active.bg_fill), + ); + + // TODO: Render the text + } + + response + } +}