From 1d76539d28ec7dbf0ad7e2af4604b61478d3f822 Mon Sep 17 00:00:00 2001 From: Mayank Verma Date: Sat, 20 Dec 2025 06:03:32 +0530 Subject: [PATCH] gpui: Fix hover styles not being applied during layout (#43324) Closes #43214 Release Notes: - Fixed GPUI hover styles not being applied during layout Here's the before/after: https://github.com/user-attachments/assets/5b1828bb-234a-493b-a33d-368ca01a773b --- crates/gpui/src/elements/div.rs | 95 ++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index cf55edefaf70c080e171a8e21b350fd3c6d82f75..547d967620d68c309563af1d2e56d2ab3f194d4f 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1730,6 +1730,11 @@ impl Interactivity { let clicked_state = clicked_state.borrow(); self.active = Some(clicked_state.element); } + if self.hover_style.is_some() || self.group_hover_style.is_some() { + element_state + .hover_state + .get_or_insert_with(Default::default); + } if let Some(active_tooltip) = element_state.active_tooltip.as_ref() { if self.tooltip_builder.is_some() { self.tooltip_id = set_tooltip_on_window(active_tooltip, window); @@ -2150,14 +2155,46 @@ impl Interactivity { { let hitbox = hitbox.clone(); let was_hovered = hitbox.is_hovered(window); + let hover_state = self.hover_style.as_ref().and_then(|_| { + element_state + .as_ref() + .and_then(|state| state.hover_state.as_ref()) + .cloned() + }); let current_view = window.current_view(); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| { let hovered = hitbox.is_hovered(window); if phase == DispatchPhase::Capture && hovered != was_hovered { + if let Some(hover_state) = &hover_state { + hover_state.borrow_mut().element = hovered; + } cx.notify(current_view); } }); } + + if let Some(group_hover) = self.group_hover_style.as_ref() { + if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) { + let hover_state = element_state + .as_ref() + .and_then(|element| element.hover_state.as_ref()) + .cloned(); + + let was_group_hovered = group_hitbox_id.is_hovered(window); + let current_view = window.current_view(); + + window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| { + let group_hovered = group_hitbox_id.is_hovered(window); + if phase == DispatchPhase::Capture && group_hovered != was_group_hovered { + if let Some(hover_state) = &hover_state { + hover_state.borrow_mut().group = group_hovered; + } + cx.notify(current_view); + } + }); + } + } + let drag_cursor_style = self.base_style.as_ref().mouse_cursor; let mut drag_listener = mem::take(&mut self.drag_listener); @@ -2346,8 +2383,8 @@ impl Interactivity { && hitbox.is_hovered(window); let mut was_hovered = was_hovered.borrow_mut(); - if is_hovered != *was_hovered { - *was_hovered = is_hovered; + if is_hovered != was_hovered.element { + was_hovered.element = is_hovered; drop(was_hovered); hover_listener(&is_hovered, window, cx); @@ -2580,22 +2617,46 @@ impl Interactivity { } } - if let Some(hitbox) = hitbox { - if !cx.has_active_drag() { - if let Some(group_hover) = self.group_hover_style.as_ref() - && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) - && group_hitbox_id.is_hovered(window) - { + if !cx.has_active_drag() { + if let Some(group_hover) = self.group_hover_style.as_ref() { + let is_group_hovered = + if let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) { + group_hitbox_id.is_hovered(window) + } else if let Some(element_state) = element_state.as_ref() { + element_state + .hover_state + .as_ref() + .map(|state| state.borrow().group) + .unwrap_or(false) + } else { + false + }; + + if is_group_hovered { style.refine(&group_hover.style); } + } - if let Some(hover_style) = self.hover_style.as_ref() - && hitbox.is_hovered(window) - { + if let Some(hover_style) = self.hover_style.as_ref() { + let is_hovered = if let Some(hitbox) = hitbox { + hitbox.is_hovered(window) + } else if let Some(element_state) = element_state.as_ref() { + element_state + .hover_state + .as_ref() + .map(|state| state.borrow().element) + .unwrap_or(false) + } else { + false + }; + + if is_hovered { style.refine(hover_style); } } + } + if let Some(hitbox) = hitbox { if let Some(drag) = cx.active_drag.take() { let mut can_drop = true; if let Some(can_drop_predicate) = &self.can_drop_predicate { @@ -2654,7 +2715,7 @@ impl Interactivity { pub struct InteractiveElementState { pub(crate) focus_handle: Option, pub(crate) clicked_state: Option>>, - pub(crate) hover_state: Option>>, + pub(crate) hover_state: Option>>, pub(crate) pending_mouse_down: Option>>>, pub(crate) scroll_offset: Option>>>, pub(crate) active_tooltip: Option>>>, @@ -2676,6 +2737,16 @@ impl ElementClickedState { } } +/// Whether or not the element or a group that contains it is hovered. +#[derive(Copy, Clone, Default, Eq, PartialEq)] +pub struct ElementHoverState { + /// True if this element's group is hovered, false otherwise + pub group: bool, + + /// True if this element is hovered, false otherwise + pub element: bool, +} + pub(crate) enum ActiveTooltip { /// Currently delaying before showing the tooltip. WaitingForShow { _task: Task<()> },