Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3480,6 +3480,18 @@ description = "Illustrates how to access `winit::window::Window`'s `hittest` fun
category = "UI (User Interface)"
wasm = false

[[example]]
name = "vertical_slider"
path = "examples/ui/vertical_slider.rs"
doc-scrape-examples = true
required-features = ["experimental_bevy_ui_widgets"]

[package.metadata.example.vertical_slider]
name = "Vertical Slider"
description = "Simple example showing vertical and horizontal slider widgets with snap behavior and value labels"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "font_atlas_debug"
path = "examples/ui/font_atlas_debug.rs"
Expand Down
77 changes: 60 additions & 17 deletions crates/bevy_ui_widgets/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,22 +264,49 @@ pub(crate) fn slider_on_pointer_down(
return;
}

// Detect orientation: vertical if height > width
Copy link
Member

Choose a reason for hiding this comment

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

This is a clever idea and I want to keep it, but we should call this out in the docs somewhere.

let is_vertical = node.size().y > node.size().x;

// Find thumb size by searching descendants for the first entity with SliderThumb
let thumb_size = q_children
.iter_descendants(press.entity)
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
.find_map(|child_id| {
q_thumb.get(child_id).ok().map(|thumb| {
if is_vertical {
thumb.size().y
} else {
thumb.size().x
}
})
})
.unwrap_or(0.0);

// Detect track click.
let local_pos = transform.try_inverse().unwrap().transform_point2(
press.pointer_location.position * node_target.scale_factor() / ui_scale.0,
);
let track_width = node.size().x - thumb_size;
let track_size = if is_vertical {
node.size().y - thumb_size
} else {
node.size().x - thumb_size
};

// Avoid division by zero
let click_val = if track_width > 0. {
local_pos.x * range.span() / track_width + range.center()
let click_val = if track_size > 0. {
if is_vertical {
// For vertical sliders: bottom-to-top (0 at bottom, max at top)
// local_pos.y ranges from -height/2 (top) to +height/2 (bottom)
let y_from_bottom = (node.size().y / 2.0) - local_pos.y;
let adjusted_y = y_from_bottom - thumb_size / 2.0;
adjusted_y * range.span() / track_size + range.start()
} else {
// For horizontal sliders: convert from center-origin to left-origin
let x_from_left = local_pos.x + node.size().x / 2.0;
let adjusted_x = x_from_left - thumb_size / 2.0;
adjusted_x * range.span() / track_size + range.start()
}
} else {
0.
range.center()
};

// Compute new value from click position
Expand Down Expand Up @@ -330,7 +357,6 @@ pub(crate) fn slider_on_drag(
mut event: On<Pointer<Drag>>,
mut q_slider: Query<
(
&SliderValue,
&ComputedNode,
&SliderRange,
Option<&SliderPrecision>,
Expand All @@ -345,23 +371,42 @@ pub(crate) fn slider_on_drag(
mut commands: Commands,
ui_scale: Res<UiScale>,
) {
if let Ok((value, node, range, precision, transform, drag, disabled)) =
q_slider.get_mut(event.entity)
if let Ok((node, range, precision, transform, drag, disabled)) = q_slider.get_mut(event.entity)
{
event.propagate(false);
if drag.dragging && !disabled {
// Detect orientation: vertical if height > width
let is_vertical = node.size().y > node.size().x;

let mut distance = event.distance / ui_scale.0;
distance.y *= -1.;
let distance = transform.transform_vector2(distance);

// Find thumb size by searching descendants for the first entity with SliderThumb
let thumb_size = q_children
.iter_descendants(event.entity)
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
.find_map(|child_id| {
q_thumb.get(child_id).ok().map(|thumb| {
if is_vertical {
thumb.size().y
} else {
thumb.size().x
}
})
})
.unwrap_or(0.0);
let slider_width = ((node.size().x - thumb_size) * node.inverse_scale_factor).max(1.0);

let slider_size = if is_vertical {
((node.size().y - thumb_size) * node.inverse_scale_factor).max(1.0)
} else {
((node.size().x - thumb_size) * node.inverse_scale_factor).max(1.0)
};

let drag_distance = if is_vertical { distance.y } else { distance.x };

let span = range.span();
let new_value = if span > 0. {
drag.offset + (distance.x * span) / slider_width
drag.offset + (drag_distance * span) / slider_size
} else {
range.start() + span * 0.5
};
Expand All @@ -371,12 +416,10 @@ pub(crate) fn slider_on_drag(
.unwrap_or(new_value),
);

if rounded_value != value.0 {
commands.trigger(ValueChange {
source: event.entity,
value: rounded_value,
});
}
commands.trigger(ValueChange {
source: event.entity,
value: rounded_value,
});
}
}
}
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,7 @@ Example | Description
[UI Texture Slice Flipping and Tiling](../examples/ui/ui_texture_slice_flip_and_tile.rs) | Illustrates how to flip and tile images with 9 Slicing in UI
[UI Transform](../examples/ui/ui_transform.rs) | An example demonstrating how to translate, rotate and scale UI elements.
[UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements
[Vertical Slider](../examples/ui/vertical_slider.rs) | Simple example showing vertical and horizontal slider widgets with snap behavior and value labels
[Viewport Debug](../examples/ui/viewport_debug.rs) | An example for debugging viewport coordinates
[Viewport Node](../examples/ui/viewport_node.rs) | Demonstrates how to create a viewport node with picking support
[Virtual Keyboard](../examples/ui/virtual_keyboard.rs) | Example demonstrating a virtual keyboard widget
Expand Down
Loading