diff --git a/crates/op-editor-core/src/editor_ui_state.rs b/crates/op-editor-core/src/editor_ui_state.rs index 8c96e7399..5f7b7b3bd 100644 --- a/crates/op-editor-core/src/editor_ui_state.rs +++ b/crates/op-editor-core/src/editor_ui_state.rs @@ -1031,6 +1031,9 @@ pub struct EditorUiState { /// Whether the Variables right-rail panel is explicitly open. pub variables_panel_open: bool, pub variables_preset_menu_open: bool, + /// Which row inside the open theme-preset dropdown the cursor is over — + /// drives the per-row hover wash. + pub variables_preset_menu_hover: Option, pub variables_add_menu_open: bool, /// Whether the preset dropdown's save-as-name input is showing /// (TS `showPresetNameInput`). This legacy preset-name draft still @@ -1293,6 +1296,7 @@ impl Default for EditorUiState { axis_dropdown_open: None, variables_panel_open: false, variables_preset_menu_open: false, + variables_preset_menu_hover: None, variables_add_menu_open: false, variables_preset_name_focus: false, pending_theme_preset_io: None, diff --git a/crates/op-editor-ui/src/widgets/variables_preset_menu.rs b/crates/op-editor-ui/src/widgets/variables_preset_menu.rs index 36a1b9451..a1c08e1eb 100644 --- a/crates/op-editor-ui/src/widgets/variables_preset_menu.rs +++ b/crates/op-editor-ui/src/widgets/variables_preset_menu.rs @@ -20,6 +20,7 @@ use crate::widgets::icons::{draw_icon, Icon}; use crate::widgets::PaintCx; use crate::{Color, Point2D, Rect, TextLayout}; use op_editor_core::editor_ui_state::Locale; +use op_editor_core::variables_panel_state::PresetMenuButton; use op_editor_core::EditorState; /// TS `w-56` = 14rem = 224 px. @@ -73,6 +74,7 @@ pub struct ThemePresetMenu { name_input_active: bool, name_draft: String, caret_pos: usize, + hover: Option, } impl ThemePresetMenu { @@ -89,6 +91,22 @@ impl ThemePresetMenu { String::new() }, caret_pos: state.ui.property_caret_pos, + hover: state.editor_ui.variables_preset_menu_hover, + } + } + + /// The full-width rect of the currently-hovered row (for the hover wash), or + /// `None` when nothing is hovered / the name input is active. + fn hover_row_rect(&self, menu: Rect) -> Option { + match self.hover? { + PresetMenuButton::SaveCurrent => Some(self.save_row_rect(menu)), + PresetMenuButton::Load(i) | PresetMenuButton::Delete(i) => { + (i < self.preset_names.len()).then(|| self.preset_row_rect(menu, i)) + } + PresetMenuButton::Import => Some(self.import_row_rect(menu)), + PresetMenuButton::Export => Some(self.export_row_rect(menu)), + // The name-input row owns its own focus chrome — no row wash. + PresetMenuButton::NameInput => None, } } @@ -212,6 +230,16 @@ impl ThemePresetMenu { cx.backend .stroke_round_rect(menu, RADIUS, theme.border, 1.0); + // Hovered-row wash (inset a few px so it reads as a padded highlight, + // not edge-to-edge) — drawn under the row content, jian-consistent. + if let Some(hr) = self.hover_row_rect(menu) { + let wash = Rect { + origin: Point2D::new(hr.origin.x + 4.0, hr.origin.y + 1.0), + size: Point2D::new(hr.size.x - 8.0, hr.size.y - 2.0), + }; + cx.backend.fill_round_rect(wash, 6.0, theme.button_hover); + } + // Save row — button or inline name input. let save = self.save_row_rect(menu); if self.name_input_active { diff --git a/crates/op-host-native/src/widget_host/input.rs b/crates/op-host-native/src/widget_host/input.rs index 583289a19..374a730b3 100644 --- a/crates/op-host-native/src/widget_host/input.rs +++ b/crates/op-host-native/src/widget_host/input.rs @@ -652,6 +652,19 @@ impl WidgetHostNative { } return true; } + // Preset dropdown is a top-most overlay over the variables panel — track + // its per-row hover and swallow moves over it first. + if self.editor_state.editor_ui.variables_preset_menu_open + && self.update_variables_preset_menu_hover( + x, + self.last_viewport_w, + self.last_viewport_h, + y, + ) + { + self.clear_lower_overlay_hover(); + return true; + } if self.editor_state.editor_ui.variables_panel_open { let point = Point2D::new(x, y); if let Some(panel_rect) = diff --git a/crates/op-host-native/src/widget_host/variables_preset_press.rs b/crates/op-host-native/src/widget_host/variables_preset_press.rs index 09da2b6a5..daa52b6a0 100644 --- a/crates/op-host-native/src/widget_host/variables_preset_press.rs +++ b/crates/op-host-native/src/widget_host/variables_preset_press.rs @@ -12,9 +12,24 @@ use super::WidgetHostNative; use op_editor_core::editor_ui_state::ThemePresetIo; +use op_editor_core::variables_panel_state::PresetMenuButton; use op_editor_ui::widgets::{PresetMenuHit, ThemePresetMenu}; use op_editor_ui::{Point2D, Rect}; +/// Map a press hit inside the preset dropdown to the persistent hover-row enum +/// (the `Blank` swallow target has no row, so it clears the hover). +fn preset_hit_to_button(hit: PresetMenuHit) -> Option { + match hit { + PresetMenuHit::SaveCurrent => Some(PresetMenuButton::SaveCurrent), + PresetMenuHit::NameInput => Some(PresetMenuButton::NameInput), + PresetMenuHit::Load(i) => Some(PresetMenuButton::Load(i)), + PresetMenuHit::Delete(i) => Some(PresetMenuButton::Delete(i)), + PresetMenuHit::Import => Some(PresetMenuButton::Import), + PresetMenuHit::Export => Some(PresetMenuButton::Export), + PresetMenuHit::Blank => None, + } +} + impl WidgetHostNative { /// Anchor rect of the variables panel's preset button. /// @@ -81,6 +96,31 @@ impl WidgetHostNative { Some((menu, rect)) } + /// Update the per-row hover for the open preset dropdown on a cursor move. + /// Returns `true` when the cursor is over the menu (the move is swallowed, + /// like any top-most overlay). + pub(in crate::widget_host) fn update_variables_preset_menu_hover( + &mut self, + x: f32, + viewport_w: f32, + viewport_h: f32, + y: f32, + ) -> bool { + let Some((menu, menu_rect)) = self.variables_preset_menu_with_rect(viewport_w, viewport_h) + else { + return false; + }; + let point = Point2D::new(x, y); + let new_hover = menu + .hit_test(menu_rect, point) + .and_then(preset_hit_to_button); + if new_hover != self.editor_state.editor_ui.variables_preset_menu_hover { + self.editor_state.editor_ui.variables_preset_menu_hover = new_hover; + self.mark_dirty(); + } + menu_rect.contains(point) + } + /// Press routing for the open preset dropdown. Runs BEFORE /// `dispatch_variables_panel_press` so the functional rows win /// over the panel's stub `TogglePresetMenu` mapping. Returns