// Ardura 2023 - Changing the toggle_switch.rs to be a Button now that I've proven that works as a param for sample loading // This lets me have buttons in nih-plug without using native egui (especially with turning off params for users) use nih_plug::prelude::{Param, ParamSetter}; use nih_plug_egui::egui::{ self, style::WidgetVisuals, Align2, Color32, FontId, Rect, Response, Stroke, Ui, Vec2, Widget, }; struct SliderRegion<'a, P: Param> { param: &'a P, param_setter: &'a ParamSetter<'a>, font: FontId, background_color: Color32, text_color: Color32, } impl<'a, P: Param> SliderRegion<'a, P> { fn new( param: &'a P, param_setter: &'a ParamSetter, font: FontId, background_color: Color32, text_color: Color32, ) -> Self { SliderRegion { param, param_setter, font, background_color, text_color, } } // Handle the input for a given response. Returns an f32 containing the normalized value of // the parameter. fn handle_response(&mut self, ui: &Ui, response: &Response, rect: Rect) -> f32 { let mut value = self.param.modulated_normalized_value(); let how_on; let visuals: WidgetVisuals; // Check if our button is clicked if response.clicked() { if value == 0.0 { self.param_setter.set_parameter_normalized(self.param, 1.0); how_on = ui.ctx().animate_bool(response.id, true); visuals = ui.style().interact_selectable(&response, true); self.text_color = Color32::BLACK; value = 1.0; } else { self.param_setter.set_parameter_normalized(self.param, 0.0); how_on = ui.ctx().animate_bool(response.id, false); visuals = ui.style().interact_selectable(&response, false); self.text_color = visuals.text_color(); value = 0.0; } } else { let temp: bool = if value > 0.0 { true } else { false }; how_on = ui.ctx().animate_bool(response.id, temp); visuals = ui.style().interact_selectable(&response, temp); if temp { self.text_color = Color32::BLACK; } else { self.text_color = visuals.text_color(); } } // DRAWING let rect = rect.expand(visuals.expansion); ui.painter().rect( rect, 0.5, if self.background_color == Color32::TEMPORARY_COLOR { visuals.bg_fill.linear_multiply(0.8) } else { self.background_color }, visuals.bg_stroke, ); // Paint the circle, animating it from left to right with `how_on`: ui.painter().rect_stroke( rect, 0.5, Stroke::new( 1.0, visuals .bg_stroke .color .linear_multiply((how_on + 0.2).clamp(0.2, 1.2)), ), ); let center = egui::pos2(rect.center().x, rect.center().y); ui.painter().text( center, Align2::CENTER_CENTER, self.param.name(), self.font.clone(), if self.text_color == Color32::TEMPORARY_COLOR { visuals.text_color() } else { self.text_color }, ); value } } pub struct BoolButton<'a, P: Param> { slider_region: SliderRegion<'a, P>, // Scaling is in ui.spacing().interact_size.y Units scaling_x: f32, scaling_y: f32, deselect_timer: usize, inactive_iterator: usize, } #[allow(dead_code)] /// Create a BoolButton Object sized by ui.spacing().interact_size.y Units impl<'a, P: Param> BoolButton<'a, P> { pub fn for_param( param: &'a P, param_setter: &'a ParamSetter, x_scaling: f32, y_scaling: f32, font: FontId, ) -> Self { BoolButton { // Pass things to slider to get around slider_region: SliderRegion::new( param, param_setter, font, Color32::TEMPORARY_COLOR, Color32::TEMPORARY_COLOR, ), scaling_x: x_scaling, scaling_y: y_scaling, deselect_timer: 200, inactive_iterator: 0, } } // The time for the button to become available again pub fn with_deselect_timer(mut self, amount_in_samples: usize) -> Self { self.deselect_timer = amount_in_samples; self } // To be called from gui thread to move from inactive to active pub fn increment_deselect(mut self) { self.inactive_iterator += 1; } pub fn with_background_color(mut self, new_color: Color32) -> Self { self.slider_region.background_color = new_color; self } pub fn with_text_color(mut self, new_color: Color32) -> Self { self.slider_region.background_color = new_color; self } } impl<'a, P: Param> Widget for BoolButton<'a, P> { fn ui(mut self, ui: &mut Ui) -> Response { // Figure out the size to reserve on screen for widget let (rect, response) = ui.allocate_exact_size( ui.spacing().interact_size.y * Vec2::new(self.scaling_x, self.scaling_y), egui::Sense::click(), ); self.slider_region.handle_response(&ui, &response, rect); if self.inactive_iterator < self.deselect_timer { self.increment_deselect(); } response } }