bevy_ui_builders/systems/
hover.rs

1//! Universal hover effects system for all UI elements
2//!
3//! Provides centralized hover components and systems that can be used by any UI element
4//! to create consistent hover effects across the entire interface.
5
6use bevy::prelude::*;
7use bevy_plugin_builder::define_plugin;
8
9// ============================================================================
10// COMPONENTS
11// ============================================================================
12
13/// Component for hover scale effects - scales the element on hover
14#[derive(Component, Clone)]
15pub struct HoverScale(pub f32);
16
17impl Default for HoverScale {
18    fn default() -> Self {
19        Self(1.05) // 5% scale increase by default
20    }
21}
22
23/// Component for hover brightness effects - brightens colors on hover
24#[derive(Component, Clone)]
25pub struct HoverBrightness(pub f32);
26
27impl Default for HoverBrightness {
28    fn default() -> Self {
29        Self(1.2) // 20% brighter by default
30    }
31}
32
33/// Component for hover opacity effects
34#[derive(Component, Clone)]
35pub struct HoverOpacity {
36    pub normal: f32,
37    pub hovered: f32,
38}
39
40impl Default for HoverOpacity {
41    fn default() -> Self {
42        Self {
43            normal: 1.0,
44            hovered: 0.9,
45        }
46    }
47}
48
49/// Component for hover outline effects
50#[derive(Component, Clone)]
51pub struct HoverOutline {
52    pub color: Color,
53    pub width: Val,
54    pub active: bool,
55}
56
57impl Default for HoverOutline {
58    fn default() -> Self {
59        Self {
60            color: Color::srgba(1.0, 1.0, 1.0, 0.3),
61            width: Val::Px(2.0),
62            active: false,
63        }
64    }
65}
66
67/// Component for hover color changes
68#[derive(Component, Clone)]
69pub struct HoverColors {
70    pub normal_bg: Color,
71    pub hover_bg: Color,
72    pub normal_border: Color,
73    pub hover_border: Color,
74}
75
76/// Component storing original colors for restoration
77#[derive(Component)]
78pub struct OriginalColors {
79    pub background: Color,
80    pub border: Color,
81}
82
83/// Component for smooth hover animations
84#[derive(Component)]
85pub struct HoverAnimationState {
86    /// Current scale value (animated)
87    pub current_scale: f32,
88    /// Target scale value
89    pub target_scale: f32,
90    /// Current opacity value (animated)
91    pub current_opacity: f32,
92    /// Target opacity value
93    pub target_opacity: f32,
94    /// Current color blend factor (0.0 = normal, 1.0 = hover)
95    pub current_blend: f32,
96    /// Target color blend factor
97    pub target_blend: f32,
98    /// Animation speed (higher = faster)
99    pub animation_speed: f32,
100}
101
102impl Default for HoverAnimationState {
103    fn default() -> Self {
104        Self {
105            current_scale: 1.0,
106            target_scale: 1.0,
107            current_opacity: 1.0,
108            target_opacity: 1.0,
109            current_blend: 0.0,
110            target_blend: 0.0,
111            animation_speed: 10.0,
112        }
113    }
114}
115
116// Note: HoverCursor functionality removed due to Bevy 0.16 cursor API changes
117// Cursor icons now require window entity component manipulation
118
119/// Resource for global hover configuration
120#[derive(Resource)]
121pub struct HoverConfig {
122    pub default_scale: f32,
123    pub default_brightness: f32,
124    pub animation_speed: f32,
125    pub enable_sounds: bool,
126}
127
128impl Default for HoverConfig {
129    fn default() -> Self {
130        Self {
131            default_scale: 1.05,
132            default_brightness: 1.2,
133            animation_speed: 10.0,
134            enable_sounds: false,
135        }
136    }
137}
138
139// ============================================================================
140// SYSTEMS
141// ============================================================================
142
143/// Universal system to handle hover scale effects
144pub fn universal_hover_scale(
145    mut query: Query<(
146        &Interaction,
147        &HoverScale,
148        &mut HoverAnimationState,
149    ), Changed<Interaction>>,
150) {
151    for (interaction, hover_scale, mut animation) in &mut query {
152        match interaction {
153            Interaction::Hovered => {
154                animation.target_scale = hover_scale.0;
155            }
156            Interaction::Pressed => {
157                animation.target_scale = hover_scale.0 * 0.98; // Slightly smaller when pressed
158            }
159            Interaction::None => {
160                animation.target_scale = 1.0;
161            }
162        }
163    }
164}
165
166/// Universal system to handle hover color effects
167pub fn universal_hover_colors(
168    mut query: Query<(
169        &Interaction,
170        &HoverColors,
171        &mut HoverAnimationState,
172    ), Changed<Interaction>>,
173) {
174    for (interaction, _colors, mut animation) in &mut query {
175        match interaction {
176            Interaction::Hovered => {
177                animation.target_blend = 0.5; // Halfway to hover colors
178            }
179            Interaction::Pressed => {
180                animation.target_blend = 1.0; // Full hover colors
181            }
182            Interaction::None => {
183                animation.target_blend = 0.0; // Normal colors
184            }
185        }
186    }
187}
188
189/// Universal system to handle hover opacity effects
190pub fn universal_hover_opacity(
191    mut query: Query<(
192        &Interaction,
193        &HoverOpacity,
194        &mut HoverAnimationState,
195    ), Changed<Interaction>>,
196) {
197    for (interaction, hover_opacity, mut animation) in &mut query {
198        match interaction {
199            Interaction::Hovered | Interaction::Pressed => {
200                animation.target_opacity = hover_opacity.hovered;
201            }
202            Interaction::None => {
203                animation.target_opacity = hover_opacity.normal;
204            }
205        }
206    }
207}
208
209/// Universal system to handle hover outline effects
210pub fn universal_hover_outline(
211    mut query: Query<(
212        Entity,
213        &Interaction,
214        &HoverOutline,
215        &mut BorderColor,
216        Option<&OriginalColors>,
217    ), Changed<Interaction>>,
218    mut commands: Commands,
219) {
220    for (entity, interaction, outline, mut border_color, original) in &mut query {
221
222        match interaction {
223            Interaction::Hovered | Interaction::Pressed => {
224                if !outline.active {
225                    // Store original border if not already stored
226                    if original.is_none() {
227                        commands.entity(entity).insert(OriginalColors {
228                            background: Color::NONE,
229                            border: border_color.0,
230                        });
231                    }
232                    border_color.0 = outline.color;
233                    // Note: Border width would need to be set on Node component at spawn time
234                }
235            }
236            Interaction::None => {
237                if let Some(orig) = original {
238                    border_color.0 = orig.border;
239                }
240            }
241        }
242    }
243}
244
245/// System to smoothly animate hover transitions
246pub fn animate_hover_transitions(
247    mut query: Query<(
248        &mut HoverAnimationState,
249        Option<&mut Transform>,
250        Option<(&HoverColors, &mut BackgroundColor, &mut BorderColor)>,
251    )>,
252    time: Res<Time>,
253) {
254    let delta = time.delta_secs();
255
256    for (mut animation, transform, colors) in &mut query {
257        // Animate scale
258        if let Some(mut transform) = transform {
259            let scale_diff = animation.target_scale - animation.current_scale;
260            if scale_diff.abs() > 0.001 {
261                animation.current_scale += scale_diff * animation.animation_speed * delta;
262                transform.scale = Vec3::splat(animation.current_scale);
263            }
264        }
265
266        // Animate colors
267        if let Some((hover_colors, mut bg_color, mut border_color)) = colors {
268            let blend_diff = animation.target_blend - animation.current_blend;
269            if blend_diff.abs() > 0.001 {
270                animation.current_blend += blend_diff * animation.animation_speed * delta;
271
272                // Blend between normal and hover colors
273                bg_color.0 = lerp_color(
274                    hover_colors.normal_bg,
275                    hover_colors.hover_bg,
276                    animation.current_blend,
277                );
278                border_color.0 = lerp_color(
279                    hover_colors.normal_border,
280                    hover_colors.hover_border,
281                    animation.current_blend,
282                );
283            }
284        }
285
286        // Animate opacity
287        let opacity_diff = animation.target_opacity - animation.current_opacity;
288        if opacity_diff.abs() > 0.001 {
289            animation.current_opacity += opacity_diff * animation.animation_speed * delta;
290            // Note: Actual opacity application would need to be done per-element type
291        }
292    }
293}
294
295// Note: Cursor icon hover system removed due to Bevy 0.16 API changes
296// Implementing cursor changes requires window entity component manipulation
297
298/// Initialize hover animation state when hover components are added
299pub fn init_hover_animation(
300    mut commands: Commands,
301    query: Query<
302        Entity,
303        Or<(
304            Added<HoverScale>,
305            Added<HoverColors>,
306            Added<HoverOpacity>,
307        )>,
308    >,
309) {
310    for entity in &query {
311        commands.entity(entity).try_insert(HoverAnimationState::default());
312    }
313}
314
315// ============================================================================
316// HELPER FUNCTIONS
317// ============================================================================
318
319/// Helper function to lerp between two colors
320fn lerp_color(from: Color, to: Color, t: f32) -> Color {
321    let from_linear = from.to_linear();
322    let to_linear = to.to_linear();
323
324    Color::LinearRgba(LinearRgba {
325        red: from_linear.red + (to_linear.red - from_linear.red) * t,
326        green: from_linear.green + (to_linear.green - from_linear.green) * t,
327        blue: from_linear.blue + (to_linear.blue - from_linear.blue) * t,
328        alpha: from_linear.alpha + (to_linear.alpha - from_linear.alpha) * t,
329    })
330}
331
332/// Apply brightness multiplier to a color
333pub fn apply_brightness(color: Color, brightness: f32) -> Color {
334    let rgba = color.to_linear();
335    Color::LinearRgba(LinearRgba {
336        red: (rgba.red * brightness).min(1.0),
337        green: (rgba.green * brightness).min(1.0),
338        blue: (rgba.blue * brightness).min(1.0),
339        alpha: rgba.alpha,
340    })
341}
342
343// ============================================================================
344// PLUGIN
345// ============================================================================
346
347/// Plugin that adds universal hover effect systems for all UI elements
348define_plugin!(HoverPlugin {
349    custom_init: |app: &mut App| {
350        app.insert_resource(HoverConfig::default());
351    },
352    update: [
353        // Initialize animation states
354        init_hover_animation,
355
356        // Handle hover state changes
357        universal_hover_scale,
358        universal_hover_colors,
359        universal_hover_opacity,
360        universal_hover_outline,
361
362        // Animate transitions
363        animate_hover_transitions,
364    ]
365});