From bf955e4dc23eebd80e28979fccbf539c9e1eacf0 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Sun, 5 Jul 2026 17:55:12 +0800 Subject: [PATCH] fix(panels): hover highlight for the Effects add-menu rows The Effects "+" add-menu (Drop Shadow / Layer Blur) painted flat rows with no hover feedback, unlike the sibling property-panel dropdowns. Add effect_add_menu_hover to editor_ui (cleared on toggle/close), a PropertyPanel::effect_add_menu_row_at hit helper, and a native update_effect_add_menu_hover cursor-move pass (mirroring the export picker) that highlights the hovered row with the standard muted wash. --- crates/op-editor-core/src/editor_ui_state.rs | 7 ++++ .../src/widgets/property_panel.rs | 15 +++++++ .../src/widgets/property_panel_effects.rs | 16 +++++++- .../src/widget_host/geometry.rs | 39 +++++++++++++++++++ .../op-host-native/src/widget_host/input.rs | 6 +++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/crates/op-editor-core/src/editor_ui_state.rs b/crates/op-editor-core/src/editor_ui_state.rs index dc092765d..16fe9a96c 100644 --- a/crates/op-editor-core/src/editor_ui_state.rs +++ b/crates/op-editor-core/src/editor_ui_state.rs @@ -1040,6 +1040,10 @@ pub struct EditorUiState { /// Whether the Effects section's "+" add-menu (Drop Shadow / Layer /// Blur choice) is open. `false` = closed. pub effect_add_picker_open: bool, + /// Row index hovered in the Effects add-menu (`None` = none), so the + /// popover highlights the row under the cursor like the other + /// property-panel dropdowns. + pub effect_add_menu_hover: Option, /// Fill/stroke colour-variable dropdown currently open in the /// PropertyPanel; `None` means closed. pub property_color_variable_picker_open: Option, @@ -1351,6 +1355,7 @@ impl Default for EditorUiState { fill_type_picker: jian_widgets::components::select::SelectState::default(), fill_type_picker_index: 0, effect_add_picker_open: false, + effect_add_menu_hover: None, property_color_variable_picker_open: None, image_fill_popover_open: false, font_picker: jian_widgets::components::select::SelectState::default(), @@ -1478,6 +1483,7 @@ impl EditorUiState { /// Toggle the Effects "+" add-menu (Drop Shadow / Layer Blur). pub fn toggle_effect_add_picker(&mut self) { self.effect_add_picker_open = !self.effect_add_picker_open; + self.effect_add_menu_hover = None; } /// Close the Effects add-menu. Returns true when it was open (so @@ -1485,6 +1491,7 @@ impl EditorUiState { pub fn close_effect_add_picker(&mut self) -> bool { let was = self.effect_add_picker_open; self.effect_add_picker_open = false; + self.effect_add_menu_hover = None; was } diff --git a/crates/op-editor-ui/src/widgets/property_panel.rs b/crates/op-editor-ui/src/widgets/property_panel.rs index 763b773e6..5c71596b4 100644 --- a/crates/op-editor-ui/src/widgets/property_panel.rs +++ b/crates/op-editor-ui/src/widgets/property_panel.rs @@ -217,6 +217,9 @@ pub struct PropertyPanel { /// Row index the cursor is over in the open Export select /// popup — `None` when no popup is open or no row is hovered. pub export_picker_hover: Option, + /// Row index the cursor is over in the open Effects "+" add-menu + /// (`None` when closed or no row hovered) — drives the row highlight. + pub effect_add_menu_hover: Option, /// Vertical scroll offset (px, ≥ 0) — paint + hit-test shift the /// section content up by this so a tall inspector stays usable. pub scroll: f32, @@ -492,6 +495,7 @@ impl PropertyPanel { export_scale_picker_open: ui.export_scale_picker_open, export_format_picker_open: ui.export_format_picker_open, export_picker_hover: ui.export_picker_hover, + effect_add_menu_hover: ui.effect_add_menu_hover, scroll: ui.property_panel_scroll.offset.max(0.0), locale: ui.locale, // Inert in the multi-select aggregate view. @@ -554,6 +558,16 @@ impl PropertyPanel { } } + /// Row index under `point` in the open Effects add-menu — drives the + /// hover highlight (mirrors [`Self::export_picker_row_at`]). + pub fn effect_add_menu_row_at(&self, panel_rect: Rect, point: Point2D) -> Option { + let add_rect = self.effect_add_button_rect(self.scrolled_rect(panel_rect))?; + let menu = crate::widgets::property_panel_effects::effect_add_menu_rect(add_rect); + crate::widgets::property_panel_effects::effect_add_menu_row_rects(menu) + .into_iter() + .position(|(_, row)| row.contains(point)) + } + /// The Effects section "+" button rect — `scrolled` is the already /// scroll-adjusted panel rect (`scrolled_rect`). The anchor the /// add-menu popover drops from. @@ -1288,6 +1302,7 @@ impl Widget for PropertyPanel { cx, &self.theme, add_rect, + self.effect_add_menu_hover, ); } } diff --git a/crates/op-editor-ui/src/widgets/property_panel_effects.rs b/crates/op-editor-ui/src/widgets/property_panel_effects.rs index c1c958c94..d3038edd0 100644 --- a/crates/op-editor-ui/src/widgets/property_panel_effects.rs +++ b/crates/op-editor-ui/src/widgets/property_panel_effects.rs @@ -94,7 +94,14 @@ pub(crate) fn effect_add_menu_row_rects(menu: Rect) -> Vec<(PropertyPanelAction, /// Paint the Effects "+" add-menu popover (Drop Shadow / Layer Blur) /// anchored to `add_rect`. Caller gates this on the picker being open. -pub(crate) fn paint_effect_add_menu(cx: &mut PaintCx<'_>, theme: &Theme, add_rect: Rect) { +/// `hover` is the row index under the cursor (`None` = none), highlighted +/// with the same `muted` row wash the other property-panel dropdowns use. +pub(crate) fn paint_effect_add_menu( + cx: &mut PaintCx<'_>, + theme: &Theme, + add_rect: Rect, + hover: Option, +) { let menu = effect_add_menu_rect(add_rect); cx.backend .fill_round_rect(menu, INPUT_RADIUS, theme.popover); @@ -102,6 +109,13 @@ pub(crate) fn paint_effect_add_menu(cx: &mut PaintCx<'_>, theme: &Theme, add_rec .stroke_round_rect(menu, INPUT_RADIUS, theme.border, 1.0); for (i, (_, label)) in EFFECT_ADD_MENU_ROWS.iter().enumerate() { let ry = menu.origin.y + 4.0 + i as f32 * EFFECT_ADD_MENU_ROW_H; + if hover == Some(i) { + let row = Rect { + origin: Point2D::new(menu.origin.x + 4.0, ry), + size: Point2D::new(menu.size.x - 8.0, EFFECT_ADD_MENU_ROW_H), + }; + cx.backend.fill_round_rect(row, 6.0, theme.muted); + } let text = TextLayout::single_run( label, "system-ui", diff --git a/crates/op-host-native/src/widget_host/geometry.rs b/crates/op-host-native/src/widget_host/geometry.rs index ab6a70703..0620a032f 100644 --- a/crates/op-host-native/src/widget_host/geometry.rs +++ b/crates/op-host-native/src/widget_host/geometry.rs @@ -246,6 +246,45 @@ impl WidgetHostNative { } } + /// Update the Effects "+" add-menu hovered row from the cursor + /// position (mirrors [`Self::update_export_picker_hover`]). Returns + /// `true` when the hover changed. + pub(in crate::widget_host) fn update_effect_add_menu_hover( + &mut self, + x: f32, + y: f32, + viewport_w: f32, + viewport_h: f32, + ) -> bool { + use op_editor_ui::widgets::{PropertyPanel, TOP_BAR_HEIGHT}; + use op_editor_ui::{Point2D, Rect}; + if !self.editor_state.editor_ui.effect_add_picker_open { + return false; + } + self.refresh_layout_scene(); + let Some(panel) = PropertyPanel::for_selection(&self.editor_state) else { + return false; + }; + let property_rect = Rect { + origin: Point2D::new( + viewport_w - self.editor_state.editor_ui.property_panel_width, + TOP_BAR_HEIGHT, + ), + size: Point2D::new( + self.editor_state.editor_ui.property_panel_width, + (viewport_h - TOP_BAR_HEIGHT).max(0.0), + ), + }; + let new_hover = panel.effect_add_menu_row_at(property_rect, Point2D::new(x, y)); + if new_hover != self.editor_state.editor_ui.effect_add_menu_hover { + self.editor_state.editor_ui.effect_add_menu_hover = new_hover; + self.mark_dirty(); + true + } else { + false + } + } + /// Update the layer-panel hover id from the current cursor /// position. Returns `true` if the hover state changed. pub fn update_layer_hover(&mut self, x: f32, y: f32, viewport_w: f32, viewport_h: f32) -> bool { diff --git a/crates/op-host-native/src/widget_host/input.rs b/crates/op-host-native/src/widget_host/input.rs index 1e88c0ad3..7e4276b42 100644 --- a/crates/op-host-native/src/widget_host/input.rs +++ b/crates/op-host-native/src/widget_host/input.rs @@ -792,6 +792,12 @@ impl WidgetHostNative { { return true; } + // Effects "+" add-menu row hover (no-op when closed). + if !over_topmost + && self.update_effect_add_menu_hover(x, y, self.last_viewport_w, self.last_viewport_h) + { + return true; + } // Padding-mode gear popover row hover (no-op when closed). if self.update_padding_mode_popover_hover(x, y) { return true;