Path: blob/main/crates/bevy_feathers/src/controls/slider.rs
9416 views
use core::f32::consts::PI;12use bevy_app::{Plugin, PreUpdate};3use bevy_color::Color;4use bevy_ecs::{5bundle::Bundle,6children,7component::Component,8entity::Entity,9hierarchy::Children,10lifecycle::RemovedComponents,11query::{Added, Changed, Has, Or, Spawned, With},12reflect::ReflectComponent,13schedule::IntoScheduleConfigs,14system::{Commands, Query, Res},15};16use bevy_input_focus::tab_navigation::TabIndex;17use bevy_picking::PickingSystems;18use bevy_reflect::{prelude::ReflectDefault, Reflect};19use bevy_text::FontSize;20use bevy_ui::{21widget::Text, AlignItems, BackgroundGradient, ColorStop, Display, FlexDirection, Gradient,22InteractionDisabled, InterpolationColorSpace, JustifyContent, LinearGradient, Node,23PositionType, UiRect, Val,24};25use bevy_ui_widgets::{Slider, SliderPrecision, SliderRange, SliderValue, TrackClick};2627use crate::{28constants::{fonts, size},29cursor::EntityCursor,30font_styles::InheritableFont,31handle_or_path::HandleOrPath,32rounded_corners::RoundedCorners,33theme::{ThemeFontColor, ThemedText, UiTheme},34tokens,35};3637/// Slider template properties, passed to [`slider`] function.38pub struct SliderProps {39/// Slider current value40pub value: f32,41/// Slider minimum value42pub min: f32,43/// Slider maximum value44pub max: f32,45}4647impl Default for SliderProps {48fn default() -> Self {49Self {50value: 0.0,51min: 0.0,52max: 1.0,53}54}55}5657#[derive(Component, Default, Clone)]58#[require(Slider)]59#[derive(Reflect)]60#[reflect(Component, Clone, Default)]61struct SliderStyle;6263/// Marker for the text64#[derive(Component, Default, Clone, Reflect)]65#[reflect(Component, Clone, Default)]66struct SliderValueText;6768/// Spawn a new slider widget.69///70/// # Arguments71///72/// * `props` - construction properties for the slider.73/// * `overrides` - a bundle of components that are merged in with the normal slider components.74///75/// # Emitted events76///77/// * [`bevy_ui_widgets::ValueChange<f32>`] when the slider value is changed.78///79/// These events can be disabled by adding an [`bevy_ui::InteractionDisabled`] component to the entity80pub fn slider<B: Bundle>(props: SliderProps, overrides: B) -> impl Bundle {81(82Node {83height: size::ROW_HEIGHT,84justify_content: JustifyContent::Center,85align_items: AlignItems::Center,86padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),87flex_grow: 1.0,88border_radius: RoundedCorners::All.to_border_radius(6.0),89..Default::default()90},91Slider {92track_click: TrackClick::Drag,93},94SliderStyle,95SliderValue(props.value),96SliderRange::new(props.min, props.max),97EntityCursor::System(bevy_window::SystemCursorIcon::EwResize),98TabIndex(0),99// Use a gradient to draw the moving bar100BackgroundGradient(vec![Gradient::Linear(LinearGradient {101angle: PI * 0.5,102stops: vec![103ColorStop::new(Color::NONE, Val::Percent(0.)),104ColorStop::new(Color::NONE, Val::Percent(50.)),105ColorStop::new(Color::NONE, Val::Percent(50.)),106ColorStop::new(Color::NONE, Val::Percent(100.)),107],108color_space: InterpolationColorSpace::Srgba,109})]),110overrides,111children![(112// Text container113Node {114display: Display::Flex,115position_type: PositionType::Absolute,116flex_direction: FlexDirection::Row,117align_items: AlignItems::Center,118justify_content: JustifyContent::Center,119..Default::default()120},121ThemeFontColor(tokens::SLIDER_TEXT),122InheritableFont {123font: HandleOrPath::Path(fonts::MONO.to_owned()),124font_size: FontSize::Px(12.0),125},126children![(Text::new("10.0"), ThemedText, SliderValueText,)],127)],128)129}130131fn update_slider_styles(132mut q_sliders: Query<133(Entity, Has<InteractionDisabled>, &mut BackgroundGradient),134(With<SliderStyle>, Or<(Spawned, Added<InteractionDisabled>)>),135>,136theme: Res<UiTheme>,137mut commands: Commands,138) {139for (slider_ent, disabled, mut gradient) in q_sliders.iter_mut() {140set_slider_styles(141slider_ent,142&theme,143disabled,144gradient.as_mut(),145&mut commands,146);147}148}149150fn update_slider_styles_remove(151mut q_sliders: Query<(Entity, Has<InteractionDisabled>, &mut BackgroundGradient)>,152mut removed_disabled: RemovedComponents<InteractionDisabled>,153theme: Res<UiTheme>,154mut commands: Commands,155) {156removed_disabled.read().for_each(|ent| {157if let Ok((slider_ent, disabled, mut gradient)) = q_sliders.get_mut(ent) {158set_slider_styles(159slider_ent,160&theme,161disabled,162gradient.as_mut(),163&mut commands,164);165}166});167}168169fn set_slider_styles(170slider_ent: Entity,171theme: &Res<'_, UiTheme>,172disabled: bool,173gradient: &mut BackgroundGradient,174commands: &mut Commands,175) {176let bar_color = theme.color(&match disabled {177true => tokens::SLIDER_BAR_DISABLED,178false => tokens::SLIDER_BAR,179});180181let bg_color = theme.color(&tokens::SLIDER_BG);182183let cursor_shape = match disabled {184true => bevy_window::SystemCursorIcon::NotAllowed,185false => bevy_window::SystemCursorIcon::EwResize,186};187188if let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] {189linear_gradient.stops[0].color = bar_color;190linear_gradient.stops[1].color = bar_color;191linear_gradient.stops[2].color = bg_color;192linear_gradient.stops[3].color = bg_color;193}194195// Change cursor shape196commands197.entity(slider_ent)198.insert(EntityCursor::System(cursor_shape));199}200201fn update_slider_pos(202mut q_sliders: Query<203(204Entity,205&SliderValue,206&SliderRange,207&SliderPrecision,208&mut BackgroundGradient,209),210(211With<SliderStyle>,212Or<(213Changed<SliderValue>,214Changed<SliderRange>,215Changed<Children>,216)>,217),218>,219q_children: Query<&Children>,220mut q_slider_text: Query<&mut Text, With<SliderValueText>>,221) {222for (slider_ent, value, range, precision, mut gradient) in q_sliders.iter_mut() {223if let [Gradient::Linear(linear_gradient)] = &mut gradient.0[..] {224let percent_value = (range.thumb_position(value.0) * 100.0).clamp(0.0, 100.0);225linear_gradient.stops[1].point = Val::Percent(percent_value);226linear_gradient.stops[2].point = Val::Percent(percent_value);227}228229// Find slider text child entity and update its text with the formatted value230q_children.iter_descendants(slider_ent).for_each(|child| {231if let Ok(mut text) = q_slider_text.get_mut(child) {232let label = format!("{}", value.0);233let decimals_len = label234.split_once('.')235.map(|(_, decimals)| decimals.len() as i32)236.unwrap_or(precision.0);237238// Don't format with precision if the value has more decimals than the precision239text.0 = if precision.0 >= 0 && decimals_len <= precision.0 {240format!("{:.precision$}", value.0, precision = precision.0 as usize)241} else {242label243};244}245});246}247}248249/// Plugin which registers the systems for updating the slider styles.250pub struct SliderPlugin;251252impl Plugin for SliderPlugin {253fn build(&self, app: &mut bevy_app::App) {254app.add_systems(255PreUpdate,256(257update_slider_styles,258update_slider_styles_remove,259update_slider_pos,260)261.in_set(PickingSystems::Last),262);263}264}265266267