Skip to content

Commit

Permalink
Allow changing handle shape of a slider (#3429)
Browse files Browse the repository at this point in the history
* Closes #1974

<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to add commits to your PR.
* Remember to run `cargo fmt` and `cargo cranky`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review you PR, but my time is limited!
-->

---------

Co-authored-by: Emil Ernerfeldt <[email protected]>
  • Loading branch information
YgorSouza and emilk authored Nov 21, 2023
1 parent f20b7b4 commit 49eecc4
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 16 deletions.
41 changes: 41 additions & 0 deletions crates/egui/src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,11 @@ pub struct Visuals {
/// Enabling this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::trailing_fill`].
pub slider_trailing_fill: bool,

/// Shape of the handle for sliders and similar widgets.
///
/// Changing this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::handle_shape`].
pub handle_shape: HandleShape,

/// Should the cursor change when the user hovers over an interactive/clickable item?
///
/// This is consistent with a lot of browser-based applications (vscode, github
Expand Down Expand Up @@ -880,6 +885,20 @@ 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))]
Expand Down Expand Up @@ -1125,6 +1144,7 @@ impl Visuals {
striped: false,

slider_trailing_fill: false,
handle_shape: HandleShape::Circle,

interact_cursor: None,

Expand Down Expand Up @@ -1687,6 +1707,7 @@ impl Visuals {
striped,

slider_trailing_fill,
handle_shape,
interact_cursor,

image_loading_spinners,
Expand Down Expand Up @@ -1755,6 +1776,8 @@ impl Visuals {

ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");

handle_shape.ui(ui);

ComboBox::from_label("Interact Cursor")
.selected_text(format!("{interact_cursor:?}"))
.show_ui(ui, |ui| {
Expand Down Expand Up @@ -1877,3 +1900,21 @@ fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) {
}
});
}

impl HandleShape {
pub fn ui(&mut self, ui: &mut Ui) {
ui.label("Widget handle shape");
ui.horizontal(|ui| {
ui.radio_value(self, HandleShape::Circle, "Circle");
if ui
.radio(matches!(self, HandleShape::Rect { .. }), "Rectangle")
.clicked()
{
*self = HandleShape::Rect { aspect_ratio: 0.5 };
}
if let HandleShape::Rect { aspect_ratio } = self {
ui.add(Slider::new(aspect_ratio, 0.1..=3.0).text("Aspect ratio"));
}
});
}
}
67 changes: 57 additions & 10 deletions crates/egui/src/widgets/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::ops::RangeInclusive;

use crate::*;
use crate::{style::HandleShape, *};

// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -87,6 +87,7 @@ pub struct Slider<'a> {
custom_formatter: Option<NumFormatter<'a>>,
custom_parser: Option<NumParser<'a>>,
trailing_fill: Option<bool>,
handle_shape: Option<HandleShape>,
}

impl<'a> Slider<'a> {
Expand Down Expand Up @@ -133,6 +134,7 @@ impl<'a> Slider<'a> {
custom_formatter: None,
custom_parser: None,
trailing_fill: None,
handle_shape: None,
}
}

Expand Down Expand Up @@ -302,6 +304,16 @@ impl<'a> Slider<'a> {
self
}

/// Change the shape of the slider handle
///
/// This setting can be enabled globally for all sliders with [`Visuals::handle_shape`].
/// Changing it here will override the above setting ONLY for this individual slider.
#[inline]
pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
self.handle_shape = Some(handle_shape);
self
}

/// Set custom formatter defining how numbers are converted into text.
///
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
Expand Down Expand Up @@ -570,7 +582,10 @@ impl<'a> Slider<'a> {
/// Just the slider, no text
fn slider_ui(&mut self, ui: &Ui, response: &Response) {
let rect = &response.rect;
let position_range = self.position_range(rect);
let handle_shape = self
.handle_shape
.unwrap_or_else(|| ui.style().visuals.handle_shape);
let position_range = self.position_range(rect, &handle_shape);

if let Some(pointer_position_2d) = response.interact_pointer_pos() {
let position = self.pointer_position(pointer_position_2d);
Expand Down Expand Up @@ -696,12 +711,37 @@ 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);

let handle_shape = self
.handle_shape
.unwrap_or_else(|| ui.style().visuals.handle_shape);
match 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 * aspect_ratio, radius),
SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
};
let v = v + Vec2::splat(visuals.expansion);
let rect = Rect::from_center_size(center, 2.0 * 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,
});
}
}
}
}

Expand All @@ -719,8 +759,12 @@ impl<'a> Slider<'a> {
}
}

fn position_range(&self, rect: &Rect) -> Rangef {
fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
let handle_radius = self.handle_radius(rect);
let handle_radius = match handle_shape {
style::HandleShape::Circle => handle_radius,
style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
};
match self.orientation {
SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
// The vertical case has to be flipped because the largest slider value maps to the
Expand Down Expand Up @@ -842,7 +886,10 @@ 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 handle_shape = self
.handle_shape
.unwrap_or_else(|| ui.style().visuals.handle_shape);
let position_range = self.position_range(&response.rect, &handle_shape);
let value_response = self.value_ui(ui, position_range);
if value_response.gained_focus()
|| value_response.has_focus()
Expand Down
23 changes: 17 additions & 6 deletions crates/egui_demo_lib/src/demo/sliders.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use egui::*;
use egui::{style::HandleShape, *};
use std::f64::INFINITY;

/// Showcase sliders
Expand All @@ -17,6 +17,7 @@ pub struct Sliders {
pub vertical: bool,
pub value: f64,
pub trailing_fill: bool,
pub handle_shape: HandleShape,
}

impl Default for Sliders {
Expand All @@ -33,6 +34,7 @@ impl Default for Sliders {
vertical: false,
value: 10.0,
trailing_fill: false,
handle_shape: HandleShape::Circle,
}
}
}
Expand Down Expand Up @@ -67,6 +69,7 @@ impl super::View for Sliders {
vertical,
value,
trailing_fill,
handle_shape,
} = self;

ui.label("You can click a slider value to edit it with the keyboard.");
Expand Down Expand Up @@ -99,7 +102,8 @@ impl super::View for Sliders {
.orientation(orientation)
.text("i32 demo slider")
.step_by(istep)
.trailing_fill(*trailing_fill),
.trailing_fill(*trailing_fill)
.handle_shape(*handle_shape),
);
*value = value_i32 as f64;
} else {
Expand All @@ -111,7 +115,8 @@ impl super::View for Sliders {
.orientation(orientation)
.text("f64 demo slider")
.step_by(istep)
.trailing_fill(*trailing_fill),
.trailing_fill(*trailing_fill)
.handle_shape(*handle_shape),
);

ui.label(
Expand All @@ -132,20 +137,26 @@ impl super::View for Sliders {
.logarithmic(true)
.smart_aim(*smart_aim)
.text("left")
.trailing_fill(*trailing_fill),
.trailing_fill(*trailing_fill)
.handle_shape(*handle_shape),
);
ui.add(
Slider::new(max, type_min..=type_max)
.logarithmic(true)
.smart_aim(*smart_aim)
.text("right")
.trailing_fill(*trailing_fill),
.trailing_fill(*trailing_fill)
.handle_shape(*handle_shape),
);

ui.separator();

ui.checkbox(trailing_fill, "Toggle trailing color");
ui.label("When enabled, trailing color will be painted up until the circle.");
ui.label("When enabled, trailing color will be painted up until the handle.");

ui.separator();

handle_shape.ui(ui);

ui.separator();

Expand Down

0 comments on commit 49eecc4

Please sign in to comment.