From 9327c1b3260087538ff6991d4dd90ff4f882901d Mon Sep 17 00:00:00 2001 From: Ygor Souza Date: Mon, 2 Oct 2023 21:54:21 +0200 Subject: [PATCH] Add rectangular handle for slider Closes #1974 --- crates/egui/src/style.rs | 47 +++++++++++++++++++++++++++ crates/egui/src/widgets/slider.rs | 53 ++++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 3c44b292a6c9..bd91b5124bfd 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -615,6 +615,19 @@ pub struct Selection { pub stroke: Stroke, } +/// Shape of the handle for sliders and similar widgets. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum HandleShape { + /// Circular handle + Circle, + /// Rectangular handle + Rect { + /// Aspect ratio of the rectangle. Set to < 1.0 to make it narrower. + aspect_ratio: f32, + }, +} + /// The visuals of widgets for different states of interaction. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -683,6 +696,9 @@ pub struct WidgetVisuals { /// Make the frame this much larger. pub expansion: f32, + + /// Shape of the handle for sliders and similar widgets. + pub handle_shape: HandleShape, } impl WidgetVisuals { @@ -934,6 +950,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, inactive: WidgetVisuals { weak_bg_fill: Color32::from_gray(60), // button background @@ -942,6 +959,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, hovered: WidgetVisuals { weak_bg_fill: Color32::from_gray(70), @@ -950,6 +968,7 @@ impl Widgets { fg_stroke: Stroke::new(1.5, Color32::from_gray(240)), rounding: Rounding::same(3.0), expansion: 1.0, + handle_shape: HandleShape::Circle, }, active: WidgetVisuals { weak_bg_fill: Color32::from_gray(55), @@ -958,6 +977,7 @@ impl Widgets { fg_stroke: Stroke::new(2.0, Color32::WHITE), rounding: Rounding::same(2.0), expansion: 1.0, + handle_shape: HandleShape::Circle, }, open: WidgetVisuals { weak_bg_fill: Color32::from_gray(27), @@ -966,6 +986,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::from_gray(210)), rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, } } @@ -979,6 +1000,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // normal text color rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, inactive: WidgetVisuals { weak_bg_fill: Color32::from_gray(230), // button background @@ -987,6 +1009,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, hovered: WidgetVisuals { weak_bg_fill: Color32::from_gray(220), @@ -995,6 +1018,7 @@ impl Widgets { fg_stroke: Stroke::new(1.5, Color32::BLACK), rounding: Rounding::same(3.0), expansion: 1.0, + handle_shape: HandleShape::Circle, }, active: WidgetVisuals { weak_bg_fill: Color32::from_gray(165), @@ -1003,6 +1027,7 @@ impl Widgets { fg_stroke: Stroke::new(2.0, Color32::BLACK), rounding: Rounding::same(2.0), expansion: 1.0, + handle_shape: HandleShape::Circle, }, open: WidgetVisuals { weak_bg_fill: Color32::from_gray(220), @@ -1011,6 +1036,7 @@ impl Widgets { fg_stroke: Stroke::new(1.0, Color32::BLACK), rounding: Rounding::same(2.0), expansion: 0.0, + handle_shape: HandleShape::Circle, }, } } @@ -1357,6 +1383,7 @@ impl WidgetVisuals { rounding, fg_stroke, expansion, + handle_shape, } = self; ui_color(ui, weak_bg_fill, "optional background fill") .on_hover_text("For buttons, combo-boxes, etc"); @@ -1369,6 +1396,7 @@ impl WidgetVisuals { stroke_ui(ui, fg_stroke, "foreground stroke (text)"); ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion")) .on_hover_text("make shapes this much larger"); + handle_shape_ui(ui, handle_shape); } } @@ -1630,3 +1658,22 @@ fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) { } }); } + +fn handle_shape_ui(ui: &mut Ui, handle_shape: &mut HandleShape) { + ui.label("Widget handle shape"); + ui.horizontal(|ui| { + ui.radio_value(handle_shape, HandleShape::Circle, "Circle"); + if ui + .radio( + matches!(handle_shape, HandleShape::Rect { aspect_ratio: _ }), + "Rectangle", + ) + .clicked() + { + *handle_shape = HandleShape::Rect { aspect_ratio: 0.5 }; + } + if let HandleShape::Rect { aspect_ratio } = handle_shape { + ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio")); + } + }); +} diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 1d4c5f8d9984..fd6b9a90d196 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -550,7 +550,7 @@ impl<'a> Slider<'a> { /// Just the slider, no text fn slider_ui(&mut self, ui: &mut Ui, response: &Response) { let rect = &response.rect; - let position_range = self.position_range(rect); + let position_range = self.position_range(rect, &ui.style().interact(response).handle_shape); if let Some(pointer_position_2d) = response.interact_pointer_pos() { let position = self.pointer_position(pointer_position_2d); @@ -676,12 +676,36 @@ impl<'a> Slider<'a> { ); } - ui.painter().add(epaint::CircleShape { - center, - radius: self.handle_radius(rect) + visuals.expansion, - fill: visuals.bg_fill, - stroke: visuals.fg_stroke, - }); + let radius = self.handle_radius(rect, &visuals.handle_shape); + match visuals.handle_shape { + style::HandleShape::Circle => { + ui.painter().add(epaint::CircleShape { + center, + radius: radius + visuals.expansion, + fill: visuals.bg_fill, + stroke: visuals.fg_stroke, + }); + } + style::HandleShape::Rect { aspect_ratio } => { + let v = match self.orientation { + SliderOrientation::Horizontal => Vec2::new(radius, radius / aspect_ratio), + SliderOrientation::Vertical => Vec2::new(radius / aspect_ratio, radius), + }; + let v = v + Vec2::splat(visuals.expansion); + let rect = Rect { + min: center - v, + max: center + v, + }; + ui.painter().add(epaint::RectShape { + fill: visuals.bg_fill, + stroke: visuals.fg_stroke, + rect, + rounding: visuals.rounding, + fill_texture_id: Default::default(), + uv: Rect::ZERO, + }); + } + } } } @@ -699,8 +723,8 @@ impl<'a> Slider<'a> { } } - fn position_range(&self, rect: &Rect) -> Rangef { - let handle_radius = self.handle_radius(rect); + fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef { + let handle_radius = self.handle_radius(rect, handle_shape); match self.orientation { SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius), SliderOrientation::Vertical => rect.y_range().shrink(handle_radius), @@ -720,12 +744,16 @@ impl<'a> Slider<'a> { } } - fn handle_radius(&self, rect: &Rect) -> f32 { + fn handle_radius(&self, rect: &Rect, handle_shape: &style::HandleShape) -> f32 { let limit = match self.orientation { SliderOrientation::Horizontal => rect.height(), SliderOrientation::Vertical => rect.width(), }; - limit / 2.5 + let aspect_ratio = match handle_shape { + style::HandleShape::Circle => 1.0, + style::HandleShape::Rect { aspect_ratio } => *aspect_ratio, + }; + limit / (2.5 / aspect_ratio) } fn rail_radius_limit(&self, rect: &Rect) -> f32 { @@ -820,7 +848,8 @@ impl<'a> Slider<'a> { let slider_response = response.clone(); let value_response = if self.show_value { - let position_range = self.position_range(&response.rect); + let position_range = + self.position_range(&response.rect, &ui.style().interact(&response).handle_shape); let value_response = self.value_ui(ui, position_range); if value_response.gained_focus() || value_response.has_focus()