diff --git a/crates/op-editor-core/src/agent_settings_button_state.rs b/crates/op-editor-core/src/agent_settings_button_state.rs index 3efdf62dc..5a7dcbcac 100644 --- a/crates/op-editor-core/src/agent_settings_button_state.rs +++ b/crates/op-editor-core/src/agent_settings_button_state.rs @@ -7,4 +7,6 @@ pub enum AgentSettingsButton { Close, McpServer, McpClientConfigCopy, + ImageSearchTest, + ImageGenAdd, } diff --git a/crates/op-editor-ui/src/widgets/agent_settings_images.rs b/crates/op-editor-ui/src/widgets/agent_settings_images.rs index 0eb0c0c69..ae5c50cc2 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_images.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_images.rs @@ -14,6 +14,7 @@ use op_editor_core::agent_settings::{ ImageTestStatus, SettingsFocus, }; use op_editor_core::editor_ui_state::EditorUiState; +use op_editor_core::{AgentSettingsButton, ButtonPressTarget}; const TITLE_H: f32 = 36.0; const ADVANCED_ROW_H: f32 = 24.0; @@ -474,10 +475,15 @@ pub(super) fn paint_images_tab( let test_btn = test_btn_rect(content, settings); paint_search_test_status(cx, theme, settings, test_btn); cx.backend.fill_round_rect(test_btn, 6.0, theme.muted); - if settings.hover_image_search_test_button { - cx.backend - .fill_round_rect(test_btn, 6.0, image_button_hover_color(theme)); - } + crate::widgets::button::paint_ghost_button_feedback( + cx.backend, + theme, + test_btn, + settings.hover_image_search_test_button, + ui.button_pressed(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageSearchTest, + )), + ); cx.backend .stroke_round_rect(test_btn, 6.0, theme.border, 1.0); let test_label = t_settings(ui, "settings.images.test"); @@ -516,10 +522,15 @@ pub(super) fn paint_images_tab( .draw_text(&gen_title, Point2D::new(content.origin.x, gen_top + 20.0)); let add_btn = add_btn_rect(content, settings); cx.backend.fill_round_rect(add_btn, 6.0, theme.muted); - if settings.hover_image_gen_add_button { - cx.backend - .fill_round_rect(add_btn, 6.0, image_button_hover_color(theme)); - } + crate::widgets::button::paint_ghost_button_feedback( + cx.backend, + theme, + add_btn, + settings.hover_image_gen_add_button, + ui.button_pressed(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageGenAdd, + )), + ); cx.backend .stroke_round_rect(add_btn, 6.0, theme.border, 1.0); let add_label = t_settings(ui, "settings.images.add"); @@ -564,15 +575,6 @@ pub(super) fn paint_images_tab( } } -fn image_button_hover_color(theme: &Theme) -> Color { - Color { - r: theme.foreground.r, - g: theme.foreground.g, - b: theme.foreground.b, - a: 0.12, - } -} - fn paint_search_test_status( cx: &mut PaintCx<'_>, theme: &Theme, diff --git a/crates/op-editor-ui/src/widgets/agent_settings_switch_style_tests.rs b/crates/op-editor-ui/src/widgets/agent_settings_switch_style_tests.rs index 4c2d25a4e..20aaefe7b 100644 --- a/crates/op-editor-ui/src/widgets/agent_settings_switch_style_tests.rs +++ b/crates/op-editor-ui/src/widgets/agent_settings_switch_style_tests.rs @@ -56,16 +56,6 @@ fn rect_eq(a: Rect, b: Rect) -> bool { && (a.size.y - b.size.y).abs() < 0.01 } -fn has_visible_hover_fill( - backend: &CaptureBackend, - rect: Rect, - theme: crate::theme::Theme, -) -> bool { - backend.round_fills.iter().any(|(r, color)| { - rect_eq(*r, rect) && !color_eq(*color, theme.muted) && color.a > theme.button_hover.a + 0.01 - }) -} - fn settings_content_metrics(rect: Rect) -> (f32, f32, f32) { ( rect.origin.x + 200.0 + 24.0, @@ -258,15 +248,61 @@ fn hovered_image_settings_buttons_paint_hover_wash() { panel.paint(&mut cx, rect); assert!( - has_visible_hover_fill(&backend, search_test, panel.theme), - "hovering the image search test button should paint a visible wash over its base fill" + backend.round_fills.iter().any( + |(r, color)| rect_eq(*r, search_test) && color_eq(*color, panel.theme.button_hover) + ), + "hovering the image search test button should paint the shared hover token" ); assert!( - has_visible_hover_fill(&backend, add, panel.theme), - "hovering the image generation add button should paint a visible wash over its base fill" + backend + .round_fills + .iter() + .any(|(r, color)| rect_eq(*r, add) && color_eq(*color, panel.theme.button_hover)), + "hovering the image generation add button should paint the shared hover token" ); } +#[test] +fn pressed_image_settings_buttons_use_shared_button_feedback() { + for (button, expected_rect) in [ + ( + AgentSettingsButton::ImageSearchTest, + image_search_test_button_rect as fn(Rect) -> Rect, + ), + ( + AgentSettingsButton::ImageGenAdd, + image_gen_add_button_rect as fn(Rect) -> Rect, + ), + ] { + let mut state = EditorState::default(); + state.editor_ui.theme_mode = ThemeMode::Light; + state.editor_ui.agent_settings.tab = AgentSettingsTab::Images; + state.editor_ui.agent_settings.images_advanced_open = true; + state.editor_ui.pressed_button = Some(ButtonPressTarget::AgentSettings(button)); + let panel = AgentSettingsPanel::for_editor(&state); + let rect = panel.rect(1200.0, 800.0); + let target = expected_rect(rect); + let expected = panel + .theme + .button_hover + .with_alpha(panel.theme.button_hover.a * 1.8); + let mut backend = CaptureBackend::default(); + let mut cx = PaintCx { + backend: &mut backend, + }; + + panel.paint(&mut cx, rect); + + assert!( + backend + .round_fills + .iter() + .any(|(r, color)| rect_eq(*r, target) && color_eq(*color, expected)), + "pressed {button:?} should paint the shared pressed feedback token" + ); + } +} + #[test] fn enabled_mcp_cli_cell_uses_subtle_border_not_primary_outline() { let mut state = EditorState::default(); 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 99af5766a..ad8bad5d7 100644 --- a/crates/op-editor-ui/src/widgets/editor_state_ext.rs +++ b/crates/op-editor-ui/src/widgets/editor_state_ext.rs @@ -104,6 +104,8 @@ pub fn agent_settings_button( W::Close => Some(O::Close), W::ToggleMcpServer => Some(O::McpServer), W::CopyMcpClientConfig => Some(O::McpClientConfigCopy), + W::TestImageSearch => Some(O::ImageSearchTest), + W::AddGenConfig => Some(O::ImageGenAdd), _ => None, } } 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 d760cae58..4bbf05510 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 @@ -658,6 +658,32 @@ fn image_generation_profile_focus_accepts_text_and_commits() { .is_empty()); } +#[test] +fn image_generation_add_press_sets_and_release_clears_agent_settings_button() { + let mut host = WidgetHostNative::new(); + host.editor_state_mut().editor_ui.agent_settings.tab = AgentSettingsTab::Images; + + let panel = AgentSettingsPanel::for_editor(host.editor_state()); + let rect = panel.rect(1200.0, 800.0); + let content_x = rect.origin.x + 200.0 + 24.0; + let content_y = rect.origin.y + 24.0; + let content_w = rect.size.x - 200.0 - 48.0; + let gen_top = content_y + 36.0 + 24.0 + 28.0; + let add_x = content_x + content_w - 36.0; + let add_y = gen_top + 18.0; + + assert!(host.dispatch_agent_settings_press(add_x, add_y, 1200.0, 800.0)); + assert_eq!( + host.editor_state().editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageGenAdd + )) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state().editor_ui.pressed_button, None); +} + #[test] fn image_generation_provider_click_opens_menu_without_changing_profile() { let mut host = WidgetHostNative::new(); @@ -860,6 +886,15 @@ fn image_search_test_tracks_invalid_and_testing_status_like_ts() { .agent_settings .images_search_ready ); + assert_eq!( + host.editor_state().editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageSearchTest + )) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state().editor_ui.pressed_button, None); } #[test] 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 3646d5b35..7015464fa 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 @@ -155,6 +155,68 @@ fn save_builtin_agent_draft_persists_provider() { assert!(settings.focus.is_none()); } +#[test] +fn image_generation_add_press_sets_and_release_clears_agent_settings_button() { + let mut host = WidgetHost::new(); + host.editor_state.editor_ui.agent_settings.tab = AgentSettingsTab::Images; + + let panel = AgentSettingsPanel::for_editor(&host.editor_state); + let rect = panel.rect(1200.0, 800.0); + let content_x = rect.origin.x + 200.0 + 24.0; + let content_y = rect.origin.y + 24.0; + let content_w = rect.size.x - 200.0 - 48.0; + let gen_top = content_y + 36.0 + 24.0 + 28.0; + let add_x = content_x + content_w - 36.0; + let add_y = gen_top + 18.0; + + assert!(host.dispatch_agent_settings_press(add_x, add_y, 1200.0, 800.0)); + assert_eq!( + host.editor_state.editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageGenAdd + )) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state.editor_ui.pressed_button, None); +} + +#[test] +fn image_search_test_press_sets_and_release_clears_agent_settings_button() { + let mut host = WidgetHost::new(); + host.editor_state.editor_ui.agent_settings.tab = AgentSettingsTab::Images; + host.editor_state + .editor_ui + .agent_settings + .images_advanced_open = true; + host.editor_state + .editor_ui + .agent_settings + .openverse_client_id = "client".into(); + host.editor_state + .editor_ui + .agent_settings + .openverse_client_secret = "secret".into(); + + let panel = AgentSettingsPanel::for_editor(&host.editor_state); + let rect = panel.rect(1200.0, 800.0); + let content_y = rect.origin.y + 24.0; + let content_w = rect.size.x - 200.0 - 48.0; + let x = rect.origin.x + 200.0 + 24.0 + content_w - 28.0; + let y = content_y + 36.0 + 24.0 + 22.0 + 36.0 + 10.0 + 36.0 + 14.0 + 18.0; + + assert!(host.dispatch_agent_settings_press(x, y, 1200.0, 800.0)); + assert_eq!( + host.editor_state.editor_ui.pressed_button, + Some(ButtonPressTarget::AgentSettings( + AgentSettingsButton::ImageSearchTest + )) + ); + + assert!(host.apply_release_with_viewport(1200.0, 800.0)); + assert_eq!(host.editor_state.editor_ui.pressed_button, None); +} + #[test] fn image_generation_profile_test_tracks_testing_status_like_ts() { let mut host = WidgetHost::new();