Allow changing handle shape of a slider (#3429)

* 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 <emil.ernerfeldt@gmail.com>
This commit is contained in:
YgorSouza 2023-11-21 11:24:29 +01:00 committed by GitHub
parent f20b7b43bf
commit 49eecc4287
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 16 deletions

View File

@ -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
@ -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))]
@ -1125,6 +1144,7 @@ impl Visuals {
striped: false,
slider_trailing_fill: false,
handle_shape: HandleShape::Circle,
interact_cursor: None,
@ -1687,6 +1707,7 @@ impl Visuals {
striped,
slider_trailing_fill,
handle_shape,
interact_cursor,
image_loading_spinners,
@ -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| {
@ -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"));
}
});
}
}

View File

@ -2,7 +2,7 @@
use std::ops::RangeInclusive;
use crate::*;
use crate::{style::HandleShape, *};
// ----------------------------------------------------------------------------
@ -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> {
@ -133,6 +134,7 @@ impl<'a> Slider<'a> {
custom_formatter: None,
custom_parser: None,
trailing_fill: None,
handle_shape: None,
}
}
@ -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
@ -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);
@ -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,
});
}
}
}
}
@ -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
@ -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()

View File

@ -1,4 +1,4 @@
use egui::*;
use egui::{style::HandleShape, *};
use std::f64::INFINITY;
/// Showcase sliders
@ -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 {
@ -33,6 +34,7 @@ impl Default for Sliders {
vertical: false,
value: 10.0,
trailing_fill: false,
handle_shape: HandleShape::Circle,
}
}
}
@ -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.");
@ -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 {
@ -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(
@ -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();