diff --git a/crates/op-editor-core/src/agent_settings_button_state.rs b/crates/op-editor-core/src/agent_settings_button_state.rs new file mode 100644 index 000000000..5b444eb2c --- /dev/null +++ b/crates/op-editor-core/src/agent_settings_button_state.rs @@ -0,0 +1,8 @@ +//! Canonical pressed-state targets for the agent-settings modal. + +/// `EditorUiState.pressed_button` target for plain settings-modal +/// buttons that use shared ghost button feedback. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentSettingsButton { + Close, +} diff --git a/crates/op-editor-core/src/button_press_state.rs b/crates/op-editor-core/src/button_press_state.rs index f32896985..8acc3f11c 100644 --- a/crates/op-editor-core/src/button_press_state.rs +++ b/crates/op-editor-core/src/button_press_state.rs @@ -15,4 +15,5 @@ pub enum ButtonPressTarget { Git(crate::git_button_state::GitButton), ExportDialog(crate::export_dialog_state::ExportDialogButton), FigmaImport(crate::figma_import_state::FigmaImportButton), + AgentSettings(crate::agent_settings_button_state::AgentSettingsButton), } diff --git a/crates/op-editor-core/src/lib.rs b/crates/op-editor-core/src/lib.rs index 7d055ab92..acf18056f 100644 --- a/crates/op-editor-core/src/lib.rs +++ b/crates/op-editor-core/src/lib.rs @@ -10,6 +10,7 @@ pub mod agent_indicators; pub mod agent_settings; pub mod agent_settings_builtin_presets; +pub mod agent_settings_button_state; pub mod agent_settings_connection; pub mod align; pub mod align_guides; @@ -140,6 +141,7 @@ pub use agent_settings_builtin_presets::{ builtin_agent_preset, infer_builtin_agent_preset, normalize_builtin_agent_preset, BuiltinAgentPreset, BuiltinAgentPresetKey, BUILTIN_AGENT_PRESETS, }; +pub use agent_settings_button_state::AgentSettingsButton; pub use align::AlignAction; pub use button_press_state::ButtonPressTarget; pub use chat::{ diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs index f1ded7e8c..3dd8c1bad 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel.rs @@ -23,6 +23,7 @@ use op_editor_core::agent_settings::{ use op_editor_core::editor_ui_state::EditorUiState; use op_editor_core::BuiltinAgentPresetKey; use op_editor_core::EditorState; +use op_editor_core::{AgentSettingsButton, ButtonPressTarget}; pub const PANEL_WIDTH: f32 = 720.0; pub const PANEL_HEIGHT: f32 = 720.0; @@ -464,7 +465,7 @@ fn paint_panel( } } cx.backend.restore(); - paint_close(cx, theme, settings, panel); + paint_close(cx, theme, settings, _ui, panel); } fn paint_sidebar( @@ -536,11 +537,22 @@ fn paint_sidebar( } } -fn paint_close(cx: &mut PaintCx<'_>, theme: &Theme, settings: &AgentSettings, panel: Rect) { +fn paint_close( + cx: &mut PaintCx<'_>, + theme: &Theme, + settings: &AgentSettings, + ui: &EditorUiState, + panel: Rect, +) { let close = close_rect(panel); - if settings.hover_agent_settings_close { - cx.backend.fill_round_rect(close, 6.0, theme.button_hover); - } + let pressed = ui.button_pressed(ButtonPressTarget::AgentSettings(AgentSettingsButton::Close)); + crate::widgets::button::paint_ghost_button_feedback( + cx.backend, + theme, + close, + settings.hover_agent_settings_close, + pressed, + ); draw_icon( cx.backend, Icon::Close, diff --git a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs index 433ea07e1..d3ca78179 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_panel_tests.rs @@ -6,7 +6,7 @@ use op_editor_core::agent_settings::{ AcpAgentField, AgentSettingsTab, BuiltinAgentField, ImageSearchField, ImageTestStatus, SettingsFocus, }; -use op_editor_core::EditorState; +use op_editor_core::{AgentSettingsButton, ButtonPressTarget, EditorState}; #[derive(Default)] struct CaptureBackend { @@ -139,6 +139,37 @@ fn close_button_hover_paints_visible_wash() { ); } +#[test] +fn pressed_close_button_uses_shared_button_feedback() { + let mut state = EditorState::default(); + state.editor_ui.pressed_button = + Some(ButtonPressTarget::AgentSettings(AgentSettingsButton::Close)); + let panel = AgentSettingsPanel::for_editor(&state); + let rect = panel.rect(1200.0, 800.0); + let mut backend = CaptureBackend::default(); + let mut cx = PaintCx { + backend: &mut backend, + }; + + panel.paint(&mut cx, rect); + + let close = Rect { + origin: Point2D::new(rect.origin.x + rect.size.x - 32.0, rect.origin.y + 16.0), + size: Point2D::new(16.0, 16.0), + }; + let expected = panel + .theme + .button_hover + .with_alpha(panel.theme.button_hover.a * 1.8); + assert!( + backend + .round_fills + .iter() + .any(|(fill, color)| *fill == close && color_eq(*color, expected)), + "pressed close button should paint the shared pressed feedback token" + ); +} + #[test] fn agents_nav_icon_uses_ts_pen_glyph_not_pencil() { const PEN_PATH: &str = diff --git a/crates/op-editor-ui/src/widgets/editor_state_ext.rs b/crates/op-editor-ui/src/widgets/editor_state_ext.rs index 85f80430a..1fda0b373 100644 --- a/crates/op-editor-ui/src/widgets/editor_state_ext.rs +++ b/crates/op-editor-ui/src/widgets/editor_state_ext.rs @@ -93,6 +93,19 @@ pub fn figma_import_button( } } +/// Map a widget-layer `AgentSettingsHit` onto the canonical +/// `op_editor_core::AgentSettingsButton` for shared pressed feedback. +pub fn agent_settings_button( + hit: crate::widgets::agent_settings_panel::AgentSettingsHit, +) -> Option { + use crate::widgets::agent_settings_panel::AgentSettingsHit as W; + use op_editor_core::AgentSettingsButton as O; + match hit { + W::Close => Some(O::Close), + _ => None, + } +} + /// Map a widget-layer `ExportDialogHit` onto the canonical /// `op_editor_core::ExportDialogButton` for the modal export dialog's /// hover wash. The `Format` arm reuses [`export_format`] to canonicalise diff --git a/crates/op-host-native/src/widget_host/agent_settings_tests.rs b/crates/op-host-native/src/widget_host/agent_settings_tests.rs index cf69f3fa1..c78defd99 100644 --- a/crates/op-host-native/src/widget_host/agent_settings_tests.rs +++ b/crates/op-host-native/src/widget_host/agent_settings_tests.rs @@ -3,7 +3,7 @@ use op_editor_core::agent_settings::{ AcpAgentField, AgentSettingsTab, BuiltinAgentField, ImageGenField, ImageGenProvider, ImageSearchField, ImageTestStatus, SettingsFocus, }; -use op_editor_core::BuiltinAgentPresetKey; +use op_editor_core::{AgentSettingsButton, BuiltinAgentPresetKey, ButtonPressTarget}; use op_editor_ui::widgets::agent_settings_panel::AgentSettingsPanel; fn agent_settings_content_metrics(host: &WidgetHostNative) -> (f32, f32, f32) { @@ -24,6 +24,24 @@ fn acp_card_y(content_y: f32) -> f32 { acp_header_y(content_y) + 28.0 + 28.0 } +#[test] +fn close_press_sets_and_release_clears_agent_settings_button() { + let mut host = WidgetHostNative::new(); + let panel = AgentSettingsPanel::for_editor(host.editor_state()); + let rect = panel.rect(1200.0, 800.0); + let close_x = rect.origin.x + rect.size.x - 24.0; + let close_y = rect.origin.y + 24.0; + + assert!(host.dispatch_agent_settings_press(close_x, close_y, 1200.0, 800.0)); + assert_eq!( + host.editor_state().editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings(AgentSettingsButton::Close)) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state().editor_ui.pressed_button, None); +} + #[test] fn builtin_agent_api_key_focus_accepts_text_and_rebuilds_models() { let mut host = WidgetHostNative::new(); diff --git a/crates/op-host-native/src/widget_host/press_helpers.rs b/crates/op-host-native/src/widget_host/press_helpers.rs index 7b42e1aa0..5d34ee164 100644 --- a/crates/op-host-native/src/widget_host/press_helpers.rs +++ b/crates/op-host-native/src/widget_host/press_helpers.rs @@ -59,7 +59,11 @@ impl WidgetHostNative { let panel = AgentSettingsPanel::for_editor(&self.editor_state); let panel_rect = panel.rect(vw, vh); let point = Point2D::new(x, y); - match panel.hit_test(panel_rect, point) { + let hit = panel.hit_test(panel_rect, point); + self.editor_state.editor_ui.pressed_button = + op_editor_ui::widgets::editor_state_ext::agent_settings_button(hit) + .map(op_editor_core::ButtonPressTarget::AgentSettings); + match hit { AgentSettingsHit::Close | AgentSettingsHit::Outside => { self.commit_settings_focus_if_any(); self.editor_state.editor_ui.agent_settings_open = false; diff --git a/crates/op-host-web/src/widget_host/agent_settings_press.rs b/crates/op-host-web/src/widget_host/agent_settings_press.rs index 96d1d5da6..f130103a0 100644 --- a/crates/op-host-web/src/widget_host/agent_settings_press.rs +++ b/crates/op-host-web/src/widget_host/agent_settings_press.rs @@ -25,7 +25,11 @@ impl WidgetHost { }; let panel = AgentSettingsPanel::for_editor(&self.editor_state); let panel_rect = panel.rect(vw, vh); - match panel.hit_test(panel_rect, Point2D::new(x, y)) { + let hit = panel.hit_test(panel_rect, Point2D::new(x, y)); + self.editor_state.editor_ui.pressed_button = + op_editor_ui::widgets::editor_state_ext::agent_settings_button(hit) + .map(op_editor_core::ButtonPressTarget::AgentSettings); + match hit { AgentSettingsHit::Close | AgentSettingsHit::Outside => { self.commit_settings_focus(); self.editor_state.editor_ui.agent_settings_open = false; diff --git a/crates/op-host-web/src/widget_host/agent_settings_press_tests.rs b/crates/op-host-web/src/widget_host/agent_settings_press_tests.rs index 0dbd6b2a3..87d5e7637 100644 --- a/crates/op-host-web/src/widget_host/agent_settings_press_tests.rs +++ b/crates/op-host-web/src/widget_host/agent_settings_press_tests.rs @@ -3,8 +3,27 @@ use op_editor_core::agent_settings::{ AgentSettingsTab, BuiltinAgentField, ImageGenField, ImageGenProvider, ImageTestStatus, SettingsFocus, }; +use op_editor_core::{AgentSettingsButton, ButtonPressTarget}; use op_editor_ui::widgets::agent_settings_panel::AgentSettingsPanel; +#[test] +fn close_press_sets_and_release_clears_agent_settings_button() { + let mut host = WidgetHost::new(); + let panel = AgentSettingsPanel::for_editor(&host.editor_state); + let rect = panel.rect(1200.0, 800.0); + let close_x = rect.origin.x + rect.size.x - 24.0; + let close_y = rect.origin.y + 24.0; + + assert!(host.dispatch_agent_settings_press(close_x, close_y, 1200.0, 800.0)); + assert_eq!( + host.editor_state.editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings(AgentSettingsButton::Close)) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state.editor_ui.pressed_button, None); +} + #[test] fn toggling_builtin_kind_commits_focused_api_key_draft() { let mut host = WidgetHost::new();