Skip to content

Core slider #19584

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jun 15, 2025
Merged

Core slider #19584

merged 21 commits into from
Jun 15, 2025

Conversation

viridia
Copy link
Contributor

@viridia viridia commented Jun 11, 2025

Objective

This is part of the "core widgets" effort: #19236.

Solution

This PR adds the "core slider" widget to the collection.

Testing

Tested using examples core_widgets and core_widgets_observers.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets M-Needs-Release-Note Work that should be called out in the blog due to impact X-Contentious There are nontrivial implications that should be thought through S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 11, 2025
@alice-i-cecile alice-i-cecile added this to the 0.17 milestone Jun 11, 2025
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestions, but this is looking good. Like always, please push back against me where you disagree.

@ickshonpe ickshonpe self-requested a review June 13, 2025 11:26
if let Ok((slider, value, range, step, disabled)) = q_slider.get(trigger.target().unwrap()) {
let event = &trigger.event().input;
if !disabled && event.state == ButtonState::Pressed {
let new_value = match event.key_code {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also have a shift + left/right big step here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How big? A fixed multiplier of StepSize (say 5x), or do we want a more complex StepSize?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the multiplier would need to be configurable, depending on the range it can easily be way too big or too small otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would like to punt on this for now:

  • I don't want to get too elaborate.
  • This is outside the bounds of use cases I'm familiar with - I want to stay grounded and not add features based on speculation.
  • I just checked the behavior of range sliders in HTML and they don't do this.

That being said, if someone comes along and has a practical need for this, I'm happy to reconsider.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm fine with leaving this for now, it's not essential.

width: Val::Px(12.0),
height: Val::Px(12.0),
position_type: PositionType::Absolute,
left: Val::Percent(0.0), // This will be updated by the slider's value
..default()
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use UI transform so the thumb is positioned at its center, then the parent doesn't need to know the thumb's size.

Suggested change
},
},
UiTransform::from_translation(Val2::percent(-50., -50.)),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In response to this, I've updated the comment on CoreSlider with a long explanation of thumb positioning and travel. The goal here is to ensure that during dragging, the visible offset between pointer position and the thumb remains constant. This means that both the absolute positioning of the thumb, and the calculation of the slider value during dragging, need to use the same divisor - otherwise the thumb will move either slower or faster than the mouse. This is even more critical in "snap" clicking mode.

Positioning from the center, like you suggest, will work, but does not remove the responsibility of the stylist to account for the reduced travel to accommodate the thumb width. Note that it is perfectly acceptable to have a zero-width thumb with additional absolutely positioned decorative elements, if you want the thumb to overhang the ends. But for something like an editor, where sliders are often going to be lined up in a column with other widgets (like your text editor) we will want to avoid overhang so that the ends of the sliders line up nicely with the other widgets and produce a straight margin.

Comment on lines 484 to 490
display: Display::Flex,
position_type: PositionType::Absolute,
left: Val::Px(0.0),
// Track is short by 12px to accommodate the thumb. This should match thumb_size
right: Val::Px(12.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
display: Display::Flex,
position_type: PositionType::Absolute,
left: Val::Px(0.0),
// Track is short by 12px to accommodate the thumb. This should match thumb_size
right: Val::Px(12.0),
top: Val::Px(0.0),
bottom: Val::Px(0.0),
position_type: PositionType::Absolute,
left: Val::Px(3.0),
right: Val::Px(3.0),

Copy link
Contributor

@ickshonpe ickshonpe Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be possible to simplify things further and move all these constraints to the background rail so we don't need to specify left and right here, but I've not been doing much CSS lately 😓.

Copy link
Contributor

@ickshonpe ickshonpe Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I just didn't realise that the track wasn't a child of the visible rail. With the rail parenting the track it all composes nicely:

            // Slider background rail
            Spawn((
                Node {
                    flex_direction: FlexDirection::Column,
                    padding: UiRect::all(Val::Px(3.)),
                    ..default()
                },
                BackgroundColor(SLIDER_TRACK), // Border color for the checkbox
                BorderRadius::MAX,
                children![(
                    // Invisible track to allow absolute placement of thumb entity. This is narrower than
                    // the actual slider, which allows us to position the thumb entity using simple
                    // percentages, without having to measure the actual width of the slider thumb.
                    Node::default(),
                    children![(
                        // Thumb
                        DemoSliderThumb,
                        CoreSliderThumb,
                        Node {
                            position_type: PositionType::Absolute,
                            width: Val::Px(12.0),
                            aspect_ratio: Some(1.),
                            left: Val::Percent(0.0), // This will be updated by the slider's value
                            ..default()
                        },
                        UiTransform::from_translation(Val2::percent(-50., -50.)),
                        BorderRadius::MAX,
                        BackgroundColor(SLIDER_THUMB),
                    )],
                )],
            )),

/// shorctuts. Defaults to 1.0.
#[derive(Component, Debug, PartialEq, Clone)]
#[component(immutable)]
pub struct SliderStep(pub f32);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name might be confusing if we want to add support for discrete ranges later on (or would that be a separate widget?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question of discrete ranges is something I have given a lot of thought to.

First, I'm not confident that I could build a slider that is generic in the value type (integer vs. float) due to the fact that we have so many different components and observers that all need to work together.

One solution is to quantize after the fact: that is, the callback can truncate or modify the value as it wishes, and display the value as an integer. This also permits other kinds of trickery, like log-scale sliders.

That being said I have ideas for other kinds of widgets, some of which would edit discrete values - such as a "game difficulty" chooser which lets you pick between a set of text labels.

Comment on lines +449 to +457
display: Display::Flex,
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Stretch,
justify_items: JustifyItems::Center,
column_gap: Val::Px(4.0),
height: Val::Px(12.0),
width: Val::Percent(30.0),
..default()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
display: Display::Flex,
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Stretch,
justify_items: JustifyItems::Center,
column_gap: Val::Px(4.0),
height: Val::Px(12.0),
width: Val::Percent(30.0),
..default()
flex_direction: FlexDirection::Column,
height: Val::Px(12.0),
width: Val::Percent(30.0),
..default()

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With a UiScale != 1 slider dragging breaks. Hover isn't affected and the sliders seem to work correctly with different window scale factors though so I think it isn't anything wrong here. The culprit is probably a bug in the picking backend somewhere.

@viridia
Copy link
Contributor Author

viridia commented Jun 13, 2025

With a UiScale != 1 slider dragging breaks. Hover isn't affected and the sliders seem to work correctly with different window scale factors though so I think it isn't anything wrong here. The culprit is probably a bug in the picking backend somewhere.

That's strange. I'm developing on a MacBook Pro, which normally has a scaling factor of 2 (assuming we're talking about the same thing).

@ickshonpe
Copy link
Contributor

That's strange. I'm developing on a MacBook Pro, which normally has a scaling factor of 2 (assuming we're talking about the same thing).

UiScale is a different thing, it's a resource you can use to set a global UI scaling for all UI elements. The window scale factor and UiScale are multiplied together to get the final scaling. So on your MacBook Pro with UiScale set to 3 you'd get a final scale factor of 6.

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these changes should fix the UiScale bugs for now.

viridia and others added 2 commits June 13, 2025 10:53
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
@viridia
Copy link
Contributor Author

viridia commented Jun 13, 2025

I had to do an unpleasant rebase and force push because of all the changes to Trigger.

@alice-i-cecile alice-i-cecile requested a review from ickshonpe June 13, 2025 17:59
viridia added 2 commits June 13, 2025 17:33
Don't allow track click while disabled.
Don't set focus to thumb.
Copy link
Contributor

@MalekiRe MalekiRe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Played around with this a lot, the code is great, LGTM

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 15, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 15, 2025
Merged via the queue into bevyengine:main with commit 30aa36e Jun 15, 2025
38 checks passed
hukasu pushed a commit to hukasu/bevy that referenced this pull request Jun 15, 2025
# Objective

This is part of the "core widgets" effort: bevyengine#19236. 

## Solution

This PR adds the "core slider" widget to the collection.

## Testing

Tested using examples `core_widgets` and `core_widgets_observers`.

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
@viridia viridia deleted the core_slider branch June 20, 2025 18:17
@viridia viridia restored the core_slider branch June 20, 2025 18:18
@viridia viridia deleted the core_slider branch July 7, 2025 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants