refactor(editor): route settings image profile buttons through shared feedback

This commit is contained in:
Kayshen-X 2026-06-14 03:57:53 +08:00
parent 4159dde2e3
commit 2dcdcc93e6
8 changed files with 170 additions and 34 deletions

View file

@ -9,4 +9,8 @@ pub enum AgentSettingsButton {
McpClientConfigCopy,
ImageSearchTest,
ImageGenAdd,
ImageProfileHeader(usize),
ImageProfileRemove(usize),
ImageProfileProvider(usize),
ImageProfileTest(usize),
}

View file

@ -639,10 +639,15 @@ fn paint_profile_row(
} else {
cx.backend.stroke_round_rect(row, 6.0, theme.border, 1.0);
}
if settings.hover_image_gen_profile_header == Some(index) {
cx.backend
.fill_round_rect(profile_header_rect(row), 6.0, theme.button_hover);
}
crate::widgets::button::paint_ghost_button_feedback(
cx.backend,
theme,
profile_header_rect(row),
settings.hover_image_gen_profile_header == Some(index),
ui.button_pressed(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileHeader(index),
)),
);
let dot = profile_active_rect(row);
if active {
cx.backend.fill_oval(dot, theme.primary);
@ -708,10 +713,15 @@ fn paint_profile_row(
);
let remove_hover = profile_remove_hover_rect(row);
if settings.hover_image_gen_profile_remove == Some(index) {
cx.backend
.fill_round_rect(remove_hover, 6.0, theme.button_hover);
}
crate::widgets::button::paint_ghost_button_feedback(
cx.backend,
theme,
remove_hover,
settings.hover_image_gen_profile_remove == Some(index),
ui.button_pressed(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileRemove(index),
)),
);
draw_icon(
cx.backend,
Icon::Trash,
@ -746,6 +756,9 @@ fn paint_profile_row(
profile,
profile_test_btn_rect(row),
settings.hover_image_gen_profile_test == Some(index),
ui.button_pressed(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileTest(index),
)),
);
let provider_rect = profile_provider_rect(row);
paint_provider_field(
@ -755,6 +768,9 @@ fn paint_profile_row(
provider_rect,
row.origin.x + 12.0,
settings.hover_image_gen_profile_provider == Some(index),
ui.button_pressed(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileProvider(index),
)),
);
if settings.image_gen_provider_menu_open == Some(index) {
let hovered = settings

View file

@ -4,7 +4,7 @@ use crate::theme::Theme;
use crate::widgets::agent_settings_caret::{paint_settings_input_view, settings_input_text};
use crate::widgets::icons::{draw_icon, Icon};
use crate::widgets::PaintCx;
use crate::{Color, Point2D, Rect, TextLayout};
use crate::{Point2D, Rect, TextLayout};
use op_editor_core::agent_settings::{
AgentSettings, ImageGenField, ImageGenProfile, ImageGenProvider, ImageSearchField,
ImageTestStatus, SettingsFocus,
@ -222,15 +222,13 @@ pub(super) fn paint_profile_test_button(
profile: &ImageGenProfile,
btn: Rect,
hovered: bool,
pressed: bool,
) {
let enabled =
!profile.api_key.trim().is_empty() && profile.test_status != ImageTestStatus::Testing;
paint_profile_test_status(cx, theme, profile, btn);
cx.backend.fill_round_rect(btn, 6.0, theme.muted);
if hovered {
cx.backend
.fill_round_rect(btn, 6.0, profile_button_hover_color(theme));
}
crate::widgets::button::paint_ghost_button_feedback(cx.backend, theme, btn, hovered, pressed);
cx.backend.stroke_round_rect(btn, 6.0, theme.border, 1.0);
let label = crate::widgets::agent_settings_i18n::t(ui, "settings.images.test");
let label_w = cx.backend.measure_text(label, 11.0);
@ -255,15 +253,6 @@ pub(super) fn paint_profile_test_button(
);
}
fn profile_button_hover_color(theme: &Theme) -> Color {
Color {
r: theme.foreground.r,
g: theme.foreground.g,
b: theme.foreground.b,
a: 0.12,
}
}
fn paint_profile_test_status(
cx: &mut PaintCx<'_>,
theme: &Theme,
@ -312,6 +301,7 @@ pub(super) fn paint_provider_field(
input: Rect,
label_x: f32,
hovered: bool,
pressed: bool,
) {
let label_lay = TextLayout::single_run(
"Provider",
@ -323,9 +313,7 @@ pub(super) fn paint_provider_field(
cx.backend
.draw_text(&label_lay, Point2D::new(label_x, input.origin.y + 16.0));
cx.backend.fill_round_rect(input, 6.0, theme.card);
if hovered {
cx.backend.fill_round_rect(input, 6.0, theme.button_hover);
}
crate::widgets::button::paint_ghost_button_feedback(cx.backend, theme, input, hovered, pressed);
cx.backend.stroke_round_rect(input, 6.0, theme.border, 1.0);
let value = ellipsize(cx, profile.provider.label(), input.size.x - 28.0, 11.0);
let value_lay = TextLayout::single_run(

View file

@ -4,7 +4,7 @@ use crate::{Color, Point2D, Rect, RenderBackend, TextLayout};
use op_editor_core::agent_settings::{
AgentSettingsTab, ImageGenField, ImageGenProvider, ImageTestStatus, SettingsFocus,
};
use op_editor_core::EditorState;
use op_editor_core::{AgentSettingsButton, ButtonPressTarget, EditorState};
#[derive(Default)]
struct CaptureBackend {
@ -406,14 +406,80 @@ fn images_tab_profile_controls_hover_paints_visible_washes() {
}
assert!(
backend.round_fills.iter().any(|(fill, color)| {
rect_eq(*fill, test_button)
&& !color_eq(*color, panel.theme.muted)
&& color.a > panel.theme.button_hover.a + 0.01
rect_eq(*fill, test_button) && color_eq(*color, panel.theme.button_hover)
}),
"hovered profile test button should paint a stronger visible hover wash"
"hovered profile test button should paint the shared hover token"
);
}
#[test]
fn pressed_image_gen_profile_controls_use_shared_button_feedback() {
for button in [
AgentSettingsButton::ImageProfileHeader(0),
AgentSettingsButton::ImageProfileRemove(0),
AgentSettingsButton::ImageProfileProvider(0),
AgentSettingsButton::ImageProfileTest(0),
] {
let mut state = EditorState::default();
state.editor_ui.agent_settings.tab = AgentSettingsTab::Images;
state.editor_ui.agent_settings.add_image_gen_profile();
state.editor_ui.agent_settings.focus = Some(SettingsFocus::ImageGenProfile {
index: 0,
field: ImageGenField::Name,
});
state.editor_ui.pressed_button = Some(ButtonPressTarget::AgentSettings(button));
let panel = AgentSettingsPanel::for_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 row = Rect {
origin: Point2D::new(content_x + 8.0, content_y + 36.0 + 24.0 + 28.0 + 36.0 + 8.0),
size: Point2D::new(content_w - 16.0, 32.0 + 8.0 + 5.0 * 36.0),
};
let target = match button {
AgentSettingsButton::ImageProfileHeader(_) => Rect {
origin: row.origin,
size: Point2D::new(row.size.x, 32.0),
},
AgentSettingsButton::ImageProfileRemove(_) => Rect {
origin: Point2D::new(row.origin.x + row.size.x - 30.0, row.origin.y + 2.0),
size: Point2D::new(28.0, 28.0),
},
AgentSettingsButton::ImageProfileProvider(_) => Rect {
origin: Point2D::new(row.origin.x + 110.0, row.origin.y + 40.0 + 36.0),
size: Point2D::new(row.size.x - 110.0 - 12.0, 24.0),
},
AgentSettingsButton::ImageProfileTest(_) => Rect {
origin: Point2D::new(
row.origin.x + row.size.x - 12.0 - 56.0,
row.origin.y + 40.0 + 72.0,
),
size: Point2D::new(56.0, 24.0),
},
_ => unreachable!("case list only includes image profile buttons"),
};
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(|(fill, color)| rect_eq(*fill, target) && color_eq(*color, expected)),
"pressed {button:?} should paint the shared pressed feedback token"
);
}
}
#[test]
fn disabled_image_gen_profile_test_hover_uses_visible_wash() {
let mut state = EditorState::default();
@ -449,11 +515,9 @@ fn disabled_image_gen_profile_test_hover_uses_visible_wash() {
assert!(
backend.round_fills.iter().any(|(fill, color)| {
rect_eq(*fill, test_button)
&& !color_eq(*color, panel.theme.muted)
&& color.a > panel.theme.button_hover.a + 0.01
rect_eq(*fill, test_button) && color_eq(*color, panel.theme.button_hover)
}),
"disabled profile test button hover should paint a visible wash"
"disabled profile test button hover should paint the shared hover token"
);
}

View file

@ -106,6 +106,10 @@ pub fn agent_settings_button(
W::CopyMcpClientConfig => Some(O::McpClientConfigCopy),
W::TestImageSearch => Some(O::ImageSearchTest),
W::AddGenConfig => Some(O::ImageGenAdd),
W::ToggleGenConfigEditor(index) => Some(O::ImageProfileHeader(index)),
W::RemoveGenConfig(index) => Some(O::ImageProfileRemove(index)),
W::ToggleGenProviderMenu(index) => Some(O::ImageProfileProvider(index)),
W::TestGenConfig(index) => Some(O::ImageProfileTest(index)),
_ => None,
}
}

View file

@ -2,6 +2,7 @@ use super::WidgetHostNative;
use op_editor_core::agent_settings::{
AgentSettingsTab, ImageGenField, ImageGenProvider, ImageTestStatus, SettingsFocus,
};
use op_editor_core::{AgentSettingsButton, ButtonPressTarget};
use op_editor_ui::widgets::agent_settings_panel::AgentSettingsPanel;
#[test]
@ -43,6 +44,15 @@ fn image_generation_profile_test_tracks_testing_status_like_ts() {
.agent_settings
.image_gen_profiles[0];
assert_eq!(profile.test_status, ImageTestStatus::Testing);
assert_eq!(
host.editor_state().editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileTest(0)
))
);
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state().editor_ui.pressed_button, None);
}
#[test]

View file

@ -617,6 +617,15 @@ fn image_generation_profile_buttons_add_activate_and_remove() {
1200.0,
800.0
));
assert_eq!(
host.editor_state().editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileRemove(1)
))
);
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state().editor_ui.pressed_button, None);
let settings = &host.editor_state().editor_ui.agent_settings;
assert_eq!(settings.image_gen_profiles.len(), 1);
assert_eq!(
@ -738,6 +747,14 @@ fn image_generation_provider_click_opens_menu_without_changing_profile() {
.image_gen_provider_menu_open,
Some(0)
);
assert_eq!(
host.editor_state().editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileProvider(0)
))
);
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state().editor_ui.pressed_button, None);
assert!(host.dispatch_agent_settings_press(
content_x + 110.0 + 20.0,
@ -784,6 +801,12 @@ fn image_generation_profile_header_click_toggles_editor_closed() {
let row_y = gen_top + 36.0 + 8.0;
assert!(host.dispatch_agent_settings_press(content_x + 72.0, row_y + 16.0, 1200.0, 800.0));
assert_eq!(
host.editor_state().editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileHeader(0)
))
);
assert_eq!(host.editor_state().editor_ui.agent_settings.focus, None);
assert!(host
@ -792,6 +815,8 @@ fn image_generation_profile_header_click_toggles_editor_closed() {
.settings_input
.text()
.is_empty());
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state().editor_ui.pressed_button, None);
}
#[test]

View file

@ -256,6 +256,15 @@ fn image_generation_profile_test_tracks_testing_status_like_ts() {
.agent_settings
.image_gen_profiles[0];
assert_eq!(profile.test_status, ImageTestStatus::Testing);
assert_eq!(
host.editor_state.editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileTest(0)
))
);
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state.editor_ui.pressed_button, None);
}
#[test]
@ -304,6 +313,14 @@ fn image_generation_provider_select_commits_and_closes_menu() {
field: ImageGenField::Name,
})
);
assert_eq!(
host.editor_state.editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileProvider(0)
))
);
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state.editor_ui.pressed_button, None);
assert!(host.dispatch_agent_settings_press(
content_x + 110.0 + 20.0,
@ -351,9 +368,17 @@ fn image_generation_profile_header_click_toggles_editor_closed() {
let row_y = gen_top + 36.0 + 8.0;
assert!(host.dispatch_agent_settings_press(content_x + 72.0, row_y + 16.0, 1200.0, 800.0));
assert_eq!(
host.editor_state.editor_ui.pressed_button,
Some(ButtonPressTarget::AgentSettings(
AgentSettingsButton::ImageProfileHeader(0)
))
);
assert_eq!(host.editor_state.editor_ui.agent_settings.focus, None);
assert!(host.editor_state.editor_ui.settings_input.text().is_empty());
assert!(host.apply_release_with_viewport(1200.0, 800.0));
assert_eq!(host.editor_state.editor_ui.pressed_button, None);
}
#[test]