refactor(editor): migrate model picker to shared select
This commit is contained in:
parent
8b9e0a07a7
commit
f6628e2b28
|
|
@ -918,18 +918,11 @@ pub struct EditorUiState {
|
|||
pub icon_picker_load_more_request: Option<crate::icon_picker_state::IconifyLoadMoreRequest>,
|
||||
|
||||
// --- AI chat model picker --------------------------------------
|
||||
/// AI chat model-picker dropdown open.
|
||||
pub chat_model_picker_open: bool,
|
||||
/// Vertical scroll offset of the model-picker dropdown, in px.
|
||||
/// Non-zero only when the connected catalog is taller than the
|
||||
/// picker's capped height; the host clamps it on wheel input.
|
||||
pub chat_model_picker_scroll: f32,
|
||||
/// AI chat model-picker dropdown interaction state.
|
||||
pub chat_model_picker: jian_widgets::components::select::SelectState,
|
||||
/// Text filter, caret, selection, and blink state for the chat
|
||||
/// model-picker search box.
|
||||
pub chat_model_picker_input: jian_core::text_input::TextInputState,
|
||||
/// Index into `chat.available_models` of the model row the cursor
|
||||
/// is over, or `None`. Drives the picker's hover-row tint.
|
||||
pub chat_model_picker_hover: Option<usize>,
|
||||
/// Hovered chat design JSON card `(message_index, block_index)`;
|
||||
/// drives the TS-style hover reveal of the card's copy affordance.
|
||||
pub chat_design_block_hover: Option<(usize, usize)>,
|
||||
|
|
@ -1242,10 +1235,8 @@ impl Default for EditorUiState {
|
|||
icon_picker_hover: None,
|
||||
icon_picker_remote: crate::icon_picker_state::IconPickerRemoteState::default(),
|
||||
icon_picker_load_more_request: None,
|
||||
chat_model_picker_open: false,
|
||||
chat_model_picker_scroll: 0.0,
|
||||
chat_model_picker: jian_widgets::components::select::SelectState::default(),
|
||||
chat_model_picker_input: jian_core::text_input::TextInputState::default(),
|
||||
chat_model_picker_hover: None,
|
||||
chat_design_block_hover: None,
|
||||
chat_example_hover: None,
|
||||
chat_header_hover: None,
|
||||
|
|
@ -1383,6 +1374,29 @@ impl EditorUiState {
|
|||
changed
|
||||
}
|
||||
|
||||
pub fn toggle_chat_model_picker(&mut self) -> bool {
|
||||
let opening = !self.chat_model_picker.open;
|
||||
self.close_chat_model_picker();
|
||||
if opening {
|
||||
self.chat_model_picker.open = true;
|
||||
}
|
||||
opening
|
||||
}
|
||||
|
||||
pub fn close_chat_model_picker(&mut self) -> bool {
|
||||
let changed = self.chat_model_picker.open
|
||||
|| self.chat_model_picker.hover.is_some()
|
||||
|| self.chat_model_picker.pressed.is_some()
|
||||
|| self.chat_model_picker.scroll.offset != 0.0
|
||||
|| !self.chat_model_picker_input.text().is_empty();
|
||||
self.chat_model_picker.open = false;
|
||||
self.chat_model_picker.hover = None;
|
||||
self.chat_model_picker.pressed = None;
|
||||
self.chat_model_picker.scroll.offset = 0.0;
|
||||
self.chat_model_picker_input.set_text("");
|
||||
changed
|
||||
}
|
||||
|
||||
/// Whether the preset dropdown's save-as-name input owns the
|
||||
/// keyboard. Gated on the menu being open so a stale focus flag
|
||||
/// (e.g. the menu closed by a panel-side `close_variable_menus`)
|
||||
|
|
@ -1662,4 +1676,31 @@ mod tests {
|
|||
assert_eq!(ui.font_picker.pressed, None);
|
||||
assert_eq!(ui.font_picker.scroll.offset, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chat_model_picker_helpers_reset_select_interaction_state_and_search() {
|
||||
let mut ui = EditorUiState::new();
|
||||
ui.chat_model_picker_input.set_text("gpt");
|
||||
ui.chat_model_picker.hover = Some(1);
|
||||
ui.chat_model_picker.pressed = Some(1);
|
||||
ui.chat_model_picker.scroll.offset = 28.0;
|
||||
|
||||
assert!(ui.toggle_chat_model_picker());
|
||||
assert!(ui.chat_model_picker.open);
|
||||
assert!(ui.chat_model_picker_input.text().is_empty());
|
||||
assert_eq!(ui.chat_model_picker.hover, None);
|
||||
assert_eq!(ui.chat_model_picker.pressed, None);
|
||||
assert_eq!(ui.chat_model_picker.scroll.offset, 0.0);
|
||||
|
||||
ui.chat_model_picker_input.set_text("claude");
|
||||
ui.chat_model_picker.hover = Some(0);
|
||||
ui.chat_model_picker.pressed = Some(0);
|
||||
ui.chat_model_picker.scroll.offset = 28.0;
|
||||
assert!(!ui.toggle_chat_model_picker());
|
||||
assert!(!ui.chat_model_picker.open);
|
||||
assert!(ui.chat_model_picker_input.text().is_empty());
|
||||
assert_eq!(ui.chat_model_picker.hover, None);
|
||||
assert_eq!(ui.chat_model_picker.pressed, None);
|
||||
assert_eq!(ui.chat_model_picker.scroll.offset, 0.0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -579,10 +579,7 @@ impl EditorState {
|
|||
}
|
||||
}
|
||||
}
|
||||
self.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_ui.chat_model_picker_input.set_text("");
|
||||
self.editor_ui.chat_model_picker_hover = None;
|
||||
self.editor_ui.close_chat_model_picker();
|
||||
}
|
||||
|
||||
/// Recompute the chat model-picker's `available_models` from the
|
||||
|
|
|
|||
|
|
@ -545,13 +545,13 @@ fn select_chat_model_picks_model_and_syncs_agent() {
|
|||
crate::ModelEntry::new(crate::AgentProvider::ClaudeCode, "claude", "Claude"),
|
||||
crate::ModelEntry::new(crate::AgentProvider::GeminiCli, "gemini", "Gemini"),
|
||||
];
|
||||
s.editor_ui.chat_model_picker_open = true;
|
||||
s.editor_ui.chat_model_picker.open = true;
|
||||
s.select_chat_model(1);
|
||||
assert_eq!(s.chat.selected_model, 1);
|
||||
// GeminiCli is index 4 in AgentProvider::ALL.
|
||||
assert_eq!(s.editor_ui.chat_selected_agent, 4);
|
||||
// Picker closes on selection.
|
||||
assert!(!s.editor_ui.chat_model_picker_open);
|
||||
assert!(!s.editor_ui.chat_model_picker.open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -562,11 +562,11 @@ fn select_chat_model_bad_index_still_closes_picker() {
|
|||
"c",
|
||||
"C",
|
||||
)];
|
||||
s.editor_ui.chat_model_picker_open = true;
|
||||
s.editor_ui.chat_model_picker.open = true;
|
||||
s.select_chat_model(9);
|
||||
// Out-of-range index ignored — selected_model unchanged.
|
||||
assert_eq!(s.chat.selected_model, 0);
|
||||
assert!(!s.editor_ui.chat_model_picker_open);
|
||||
assert!(!s.editor_ui.chat_model_picker.open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -679,13 +679,13 @@ fn select_chat_model_keeps_agent_sync_unchanged_for_acp_models() {
|
|||
crate::ModelEntry::new(crate::AgentProvider::CodexCli, "acp:acp-1", "Local ACP"),
|
||||
];
|
||||
s.editor_ui.chat_selected_agent = 0;
|
||||
s.editor_ui.chat_model_picker_open = true;
|
||||
s.editor_ui.chat_model_picker.open = true;
|
||||
|
||||
s.select_chat_model(1);
|
||||
|
||||
assert_eq!(s.chat.selected_model, 1);
|
||||
assert_eq!(s.editor_ui.chat_selected_agent, 0);
|
||||
assert!(!s.editor_ui.chat_model_picker_open);
|
||||
assert!(!s.editor_ui.chat_model_picker.open);
|
||||
}
|
||||
|
||||
// --- Layer collapse (Gap 3) -----------------------------------------
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl EditorState {
|
|||
if self.editor_ui.agent_settings_open && self.editor_ui.agent_settings.focus.is_some() {
|
||||
return Some(&self.editor_ui.settings_input);
|
||||
}
|
||||
if self.editor_ui.chat_model_picker_open {
|
||||
if self.editor_ui.chat_model_picker.open {
|
||||
return Some(&self.editor_ui.chat_model_picker_input);
|
||||
}
|
||||
if self.chat.focused {
|
||||
|
|
@ -88,7 +88,7 @@ impl EditorState {
|
|||
if self.editor_ui.agent_settings_open && self.editor_ui.agent_settings.focus.is_some() {
|
||||
return Some(&mut self.editor_ui.settings_input);
|
||||
}
|
||||
if self.editor_ui.chat_model_picker_open {
|
||||
if self.editor_ui.chat_model_picker.open {
|
||||
return Some(&mut self.editor_ui.chat_model_picker_input);
|
||||
}
|
||||
if self.chat.focused {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub enum AIChatHit {
|
|||
/// Click on the plus affordance in the header.
|
||||
NewChat,
|
||||
/// Click on the model chip (bottom-left of the input toolbar) —
|
||||
/// host toggles `ui.chat_model_picker_open` to open / close the
|
||||
/// host toggles `ui.chat_model_picker.open` to open / close the
|
||||
/// model dropdown.
|
||||
ToggleModelPicker,
|
||||
/// Click on a model row in the open picker dropdown — payload
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ use crate::widgets::property_panel_text_input::paint_text_input_view_value;
|
|||
use crate::widgets::PaintCx;
|
||||
use crate::{Color, Point2D, Rect, TextLayout};
|
||||
use jian_core::text_input::TextInputState;
|
||||
pub use jian_widgets::components::select::SelectHit;
|
||||
use jian_widgets::components::select::SelectState;
|
||||
use op_editor_core::chat::{AgentProvider, ModelEntry};
|
||||
|
||||
/// Height of a provider group-header row.
|
||||
|
|
@ -184,32 +186,60 @@ pub fn model_at(
|
|||
scroll: f32,
|
||||
search: &str,
|
||||
) -> Option<usize> {
|
||||
let mut state = SelectState::default();
|
||||
state.open = true;
|
||||
state.scroll.offset = scroll;
|
||||
match model_picker_hit(&state, rect, point, models, search) {
|
||||
SelectHit::Row(index) => Some(index),
|
||||
SelectHit::Inside | SelectHit::Outside => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared select-style hit protocol for the searchable model picker.
|
||||
/// Search/header/empty/padding chrome returns `Inside`; model rows
|
||||
/// return `Row(index)` where `index` addresses `available_models`.
|
||||
pub fn model_picker_hit(
|
||||
state: &SelectState,
|
||||
rect: Rect,
|
||||
point: Point2D,
|
||||
models: &[ModelEntry],
|
||||
search: &str,
|
||||
) -> SelectHit {
|
||||
if !state.open {
|
||||
return SelectHit::Outside;
|
||||
}
|
||||
if point.x < rect.origin.x
|
||||
|| point.x > rect.origin.x + rect.size.x
|
||||
|| point.y < rect.origin.y
|
||||
|| point.y > rect.origin.y + rect.size.y
|
||||
{
|
||||
return None;
|
||||
return SelectHit::Outside;
|
||||
}
|
||||
let list_rect = model_list_rect(rect);
|
||||
if point.y < list_rect.origin.y {
|
||||
return None;
|
||||
return SelectHit::Inside;
|
||||
}
|
||||
let mut hit = None;
|
||||
let mut hit = SelectHit::Inside;
|
||||
// Walk from a scroll-shifted origin — the same offset paint
|
||||
// applies via `translate` — then keep only hits whose row band
|
||||
// actually falls inside the (unscrolled) card rect.
|
||||
walk_rows(models, search, list_rect.origin.y - scroll, |row, y, h| {
|
||||
if let Row::Model { idx, .. } = row {
|
||||
walk_rows(
|
||||
models,
|
||||
search,
|
||||
list_rect.origin.y - state.scroll.offset,
|
||||
|row, y, h| {
|
||||
if point.y >= y
|
||||
&& point.y < y + h
|
||||
&& point.y >= list_rect.origin.y
|
||||
&& point.y <= rect.origin.y + rect.size.y
|
||||
{
|
||||
hit = Some(*idx);
|
||||
hit = match row {
|
||||
Row::Model { idx, .. } => SelectHit::Row(*idx),
|
||||
Row::Header { .. } => SelectHit::Inside,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
hit
|
||||
}
|
||||
|
||||
|
|
@ -243,13 +273,14 @@ pub fn paint_model_picker(
|
|||
rect: Rect,
|
||||
models: &[ModelEntry],
|
||||
selected: usize,
|
||||
scroll: f32,
|
||||
hover: Option<usize>,
|
||||
state: &SelectState,
|
||||
input: &TextInputState,
|
||||
now_ms: u64,
|
||||
locale: op_editor_core::Locale,
|
||||
) {
|
||||
let search = input.text();
|
||||
let scroll = state.scroll.offset;
|
||||
let hover = state.hover;
|
||||
// Card background + border — painted unscrolled so the frame
|
||||
// stays put while the rows scroll inside it.
|
||||
cx.backend.fill_round_rect(rect, 10.0, theme.card);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use super::ai_chat_model_picker::*;
|
||||
use crate::{Point2D, Rect};
|
||||
use jian_widgets::components::select::{SelectHit, SelectState};
|
||||
use op_editor_core::chat::{AgentProvider, ModelEntry};
|
||||
|
||||
fn entry(p: AgentProvider, v: &str) -> ModelEntry {
|
||||
|
|
@ -109,6 +110,34 @@ fn model_at_filters_by_search_and_returns_original_index() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_picker_hit_uses_shared_select_state_protocol() {
|
||||
let models = vec![
|
||||
entry(AgentProvider::ClaudeCode, "a"),
|
||||
entry(AgentProvider::CodexCli, "b"),
|
||||
];
|
||||
let mut state = SelectState::default();
|
||||
state.open = true;
|
||||
let rect = Rect {
|
||||
origin: Point2D::new(0.0, 0.0),
|
||||
size: Point2D::new(220.0, picker_view_height(&models, "")),
|
||||
};
|
||||
let first_row_y = MODEL_SEARCH_H + MODEL_PICKER_PAD_Y + MODEL_GROUP_H + MODEL_ROW_H / 2.0;
|
||||
|
||||
assert_eq!(
|
||||
model_picker_hit(&state, rect, Point2D::new(100.0, first_row_y), &models, ""),
|
||||
SelectHit::Row(0)
|
||||
);
|
||||
assert_eq!(
|
||||
model_picker_hit(&state, rect, Point2D::new(100.0, 12.0), &models, ""),
|
||||
SelectHit::Inside
|
||||
);
|
||||
assert_eq!(
|
||||
model_picker_hit(&state, rect, Point2D::new(-1.0, 12.0), &models, ""),
|
||||
SelectHit::Outside
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_group_header_prefers_retained_provider_display_name() {
|
||||
let mut entry = ModelEntry::builtin(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::widgets::icons::{draw_icon, Icon};
|
|||
use crate::widgets::{LayoutBox, LayoutCx, PaintCx, Widget, WidgetId};
|
||||
use crate::{Color, Point2D, Rect, TextLayout};
|
||||
use jian_core::text_input::TextInputState;
|
||||
use jian_widgets::components::select::SelectState;
|
||||
use op_editor_core::chat::ChatState;
|
||||
use op_editor_core::EditorState;
|
||||
|
||||
|
|
@ -86,12 +87,8 @@ pub struct AIChatPlaceholder<'a> {
|
|||
/// Number of currently selected canvas nodes, shown in the
|
||||
/// bottom toolbar like the TS panel.
|
||||
pub(crate) selected_count: usize,
|
||||
/// Whether the model-picker dropdown is open.
|
||||
pub model_picker_open: bool,
|
||||
/// Vertical scroll offset of the open model-picker dropdown.
|
||||
pub model_picker_scroll: f32,
|
||||
/// Index into `state.available_models` of the picker row under the cursor.
|
||||
pub model_picker_hover: Option<usize>,
|
||||
/// Model-picker dropdown interaction state.
|
||||
pub model_picker: &'a SelectState,
|
||||
/// Text state for the model-picker search query.
|
||||
pub model_picker_input: &'a TextInputState,
|
||||
pub design_hover: Option<(usize, usize)>,
|
||||
|
|
@ -128,9 +125,7 @@ impl<'a> AIChatPlaceholder<'a> {
|
|||
label_tip_select_elements: translate(ui, "ai.tipSelectElements").to_string(),
|
||||
label_no_models: translate(ui, "ai.noModelsConnected").to_string(),
|
||||
selected_count: state.selection_count(),
|
||||
model_picker_open: ui.chat_model_picker_open,
|
||||
model_picker_scroll: ui.chat_model_picker_scroll,
|
||||
model_picker_hover: ui.chat_model_picker_hover,
|
||||
model_picker: &ui.chat_model_picker,
|
||||
model_picker_input: &ui.chat_model_picker_input,
|
||||
design_hover: ui.chat_design_block_hover,
|
||||
example_hover: ui.chat_example_hover,
|
||||
|
|
@ -231,7 +226,7 @@ impl<'a> AIChatPlaceholder<'a> {
|
|||
}
|
||||
|
||||
pub fn model_picker_bounds(&self, rect: Rect) -> Option<Rect> {
|
||||
if !self.model_picker_open {
|
||||
if !self.model_picker.open {
|
||||
return None;
|
||||
}
|
||||
let input_rect = self.input_rect(rect);
|
||||
|
|
@ -692,7 +687,7 @@ impl<'a> Widget for AIChatPlaceholder<'a> {
|
|||
|
||||
// Model-picker dropdown paints last so it sits above the
|
||||
// message list / examples / input.
|
||||
if self.model_picker_open {
|
||||
if self.model_picker.open {
|
||||
let picker = self.model_picker_rect(rect, input_rect);
|
||||
crate::widgets::ai_chat_model_picker::paint_model_picker(
|
||||
cx,
|
||||
|
|
@ -700,8 +695,7 @@ impl<'a> Widget for AIChatPlaceholder<'a> {
|
|||
picker,
|
||||
&self.state.available_models,
|
||||
self.state.selected_model,
|
||||
self.model_picker_scroll,
|
||||
self.model_picker_hover,
|
||||
self.model_picker,
|
||||
self.model_picker_input,
|
||||
self.now_ms,
|
||||
self.locale,
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ fn multiline_input_expands_above_footer_toolbar() {
|
|||
fn hit_test_resolves_model_search_clear_button() {
|
||||
let mut s = EditorState::new();
|
||||
seed_available_model(&mut s);
|
||||
s.editor_ui.chat_model_picker_open = true;
|
||||
s.editor_ui.chat_model_picker.open = true;
|
||||
s.editor_ui.chat_model_picker_input.set_text("231");
|
||||
let panel = AIChatPlaceholder::from_editor(&s);
|
||||
let rect = Rect::xywh(0.0, 0.0, AI_CHAT_WIDTH, AI_CHAT_HEIGHT);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ impl<'a> AIChatPlaceholder<'a> {
|
|||
// open it behaves modally: a row click selects, any other
|
||||
// click dismisses it. Hit-tested before the input so a row
|
||||
// click isn't eaten by the message list beneath.
|
||||
if self.model_picker_open {
|
||||
if self.model_picker.open {
|
||||
let picker = self.model_picker_rect(rect, input_rect);
|
||||
if crate::widgets::ai_chat_model_picker::search_clear_hit(
|
||||
picker,
|
||||
|
|
@ -77,17 +77,20 @@ impl<'a> AIChatPlaceholder<'a> {
|
|||
) {
|
||||
return Some(AIChatHit::ClearModelSearch);
|
||||
}
|
||||
if let Some(idx) = crate::widgets::ai_chat_model_picker::model_at(
|
||||
match crate::widgets::ai_chat_model_picker::model_picker_hit(
|
||||
self.model_picker,
|
||||
picker,
|
||||
point,
|
||||
&self.state.available_models,
|
||||
self.model_picker_scroll,
|
||||
self.model_picker_input.text(),
|
||||
) {
|
||||
return Some(AIChatHit::SelectModel(idx));
|
||||
}
|
||||
if (picker).contains(point) {
|
||||
return Some(AIChatHit::FocusModelSearch);
|
||||
jian_widgets::components::select::SelectHit::Row(idx) => {
|
||||
return Some(AIChatHit::SelectModel(idx));
|
||||
}
|
||||
jian_widgets::components::select::SelectHit::Inside => {
|
||||
return Some(AIChatHit::FocusModelSearch);
|
||||
}
|
||||
jian_widgets::components::select::SelectHit::Outside => {}
|
||||
}
|
||||
return Some(AIChatHit::ToggleModelPicker);
|
||||
}
|
||||
|
|
@ -295,7 +298,7 @@ impl<'a> AIChatPlaceholder<'a> {
|
|||
rect: Rect,
|
||||
point: Point2D,
|
||||
) -> Option<op_editor_core::ChatFooterButton> {
|
||||
if self.state.collapsed || self.model_picker_open {
|
||||
if self.state.collapsed || self.model_picker.open {
|
||||
return None;
|
||||
}
|
||||
let input_rect = self.input_rect(rect);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use jian_core::text_input::TextInputState;
|
||||
use jian_widgets::components::select::SelectState;
|
||||
use op_editor_core::chat::{AgentProvider, ModelEntry};
|
||||
use op_editor_core::Locale;
|
||||
use op_editor_ui::theme::Theme;
|
||||
|
|
@ -52,12 +53,19 @@ fn caret_fills(fills: &[(Rect, Color)], theme: Theme) -> Vec<Rect> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn open_select_state() -> SelectState {
|
||||
let mut state = SelectState::default();
|
||||
state.open = true;
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_picker_search_paints_visible_caret_at_blink_on_phase() {
|
||||
let theme = Theme::dark();
|
||||
let models = vec![ModelEntry::new(AgentProvider::CodexCli, "gpt-5", "gpt-5")];
|
||||
let rect = Rect::xywh(10.0, 20.0, 240.0, picker_view_height(&models, ""));
|
||||
let input = TextInputState::default();
|
||||
let state = open_select_state();
|
||||
let mut backend = CaptureBackend::default();
|
||||
let mut cx = PaintCx {
|
||||
backend: &mut backend,
|
||||
|
|
@ -69,8 +77,7 @@ fn model_picker_search_paints_visible_caret_at_blink_on_phase() {
|
|||
rect,
|
||||
&models,
|
||||
0,
|
||||
0.0,
|
||||
None,
|
||||
&state,
|
||||
&input,
|
||||
100,
|
||||
Locale::EnUs,
|
||||
|
|
@ -85,6 +92,7 @@ fn model_picker_search_hides_caret_at_blink_off_phase() {
|
|||
let models = vec![ModelEntry::new(AgentProvider::CodexCli, "gpt-5", "gpt-5")];
|
||||
let rect = Rect::xywh(10.0, 20.0, 240.0, picker_view_height(&models, ""));
|
||||
let input = TextInputState::default();
|
||||
let state = open_select_state();
|
||||
let mut backend = CaptureBackend::default();
|
||||
let mut cx = PaintCx {
|
||||
backend: &mut backend,
|
||||
|
|
@ -96,8 +104,7 @@ fn model_picker_search_hides_caret_at_blink_off_phase() {
|
|||
rect,
|
||||
&models,
|
||||
0,
|
||||
0.0,
|
||||
None,
|
||||
&state,
|
||||
&input,
|
||||
500,
|
||||
Locale::EnUs,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ impl WidgetHostNative {
|
|||
|| eui.preset_name_input_active()
|
||||
|| self.variables_search_active()
|
||||
|| eui.agent_settings.focus.is_some()
|
||||
|| eui.chat_model_picker_open
|
||||
|| eui.chat_model_picker.open
|
||||
|| self.editor_state.chat.focused
|
||||
|| git.commit_focused
|
||||
|| git.remote_focused
|
||||
|
|
@ -61,10 +61,7 @@ impl WidgetHostNative {
|
|||
// Variables-panel search box defocuses; its typed filter
|
||||
// persists (TS keeps the input value on blur).
|
||||
eui.variables_search_focus = false;
|
||||
eui.chat_model_picker_open = false;
|
||||
eui.chat_model_picker_scroll = 0.0;
|
||||
eui.chat_model_picker_input.set_text("");
|
||||
eui.chat_model_picker_hover = None;
|
||||
eui.close_chat_model_picker();
|
||||
self.editor_state.chat.blur_input(self.now_ms);
|
||||
if was_focused {
|
||||
self.mark_dirty();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fn top_bar_gap_press_blurs_chat_and_model_picker() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
seed(&mut host, ONE_RECT);
|
||||
host.editor_state_mut().chat.focused = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker.open = true;
|
||||
host.editor_state_mut()
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
|
|
@ -45,7 +45,7 @@ fn top_bar_gap_press_blurs_chat_and_model_picker() {
|
|||
!state.chat.focused,
|
||||
"top-bar gap press must blur the chat input"
|
||||
);
|
||||
assert!(!state.editor_ui.chat_model_picker_open);
|
||||
assert!(!state.editor_ui.chat_model_picker.open);
|
||||
assert!(state.editor_ui.chat_model_picker_input.text().is_empty());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,34 +2,36 @@ use super::WidgetHostNative;
|
|||
|
||||
impl WidgetHostNative {
|
||||
pub(in crate::widget_host) fn apply_chat_model_picker_text(&mut self, c: char) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open || c.is_control() {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open || c.is_control() {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
let mut s = [0u8; 4];
|
||||
ui.chat_model_picker_input
|
||||
.insert_str(c.encode_utf8(&mut s), self.now_ms);
|
||||
ui.chat_model_picker_scroll = 0.0;
|
||||
ui.chat_model_picker_hover = None;
|
||||
ui.chat_model_picker.scroll.offset = 0.0;
|
||||
ui.chat_model_picker.hover = None;
|
||||
ui.chat_model_picker.pressed = None;
|
||||
self.mark_dirty();
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::widget_host) fn apply_chat_model_picker_backspace(&mut self) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
ui.chat_model_picker_input.backspace(self.now_ms);
|
||||
ui.chat_model_picker_input.touch(self.now_ms);
|
||||
ui.chat_model_picker_scroll = 0.0;
|
||||
ui.chat_model_picker_hover = None;
|
||||
ui.chat_model_picker.scroll.offset = 0.0;
|
||||
ui.chat_model_picker.hover = None;
|
||||
ui.chat_model_picker.pressed = None;
|
||||
self.mark_dirty();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn apply_chat_model_picker_caret(&mut self, forward: bool) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ fn chat_model_picker_arrows_move_caret_for_insert_and_backspace() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
{
|
||||
let ui = &mut host.editor_state_mut().editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("abcd");
|
||||
}
|
||||
|
||||
|
|
@ -53,10 +53,10 @@ fn chat_model_picker_clear_button_empties_search() {
|
|||
host.set_now_ms(456);
|
||||
{
|
||||
let ui = &mut host.editor_state_mut().editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("231");
|
||||
ui.chat_model_picker_scroll = 10.0;
|
||||
ui.chat_model_picker_hover = Some(0);
|
||||
ui.chat_model_picker.scroll.offset = 10.0;
|
||||
ui.chat_model_picker.hover = Some(0);
|
||||
}
|
||||
let chat_rect = host.ai_chat_rect(1200.0, 800.0).unwrap();
|
||||
let panel = AIChatPlaceholder::from_editor_at(host.editor_state(), 456);
|
||||
|
|
@ -69,8 +69,8 @@ fn chat_model_picker_clear_button_empties_search() {
|
|||
let ui = &host.editor_state().editor_ui;
|
||||
assert!(ui.chat_model_picker_input.text().is_empty());
|
||||
assert_eq!(ui.chat_model_picker_input.caret(), 0);
|
||||
assert_eq!(ui.chat_model_picker_scroll, 0.0);
|
||||
assert_eq!(ui.chat_model_picker_hover, None);
|
||||
assert!(ui.chat_model_picker_open);
|
||||
assert_eq!(ui.chat_model_picker.scroll.offset, 0.0);
|
||||
assert_eq!(ui.chat_model_picker.hover, None);
|
||||
assert!(ui.chat_model_picker.open);
|
||||
assert_eq!(ui.chat_model_picker_input.next_blink_flip_ms(456), 956);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,44 +190,24 @@ impl WidgetHostNative {
|
|||
AIChatHit::ToggleMaximize => {
|
||||
self.editor_state.chat.maximized = !self.editor_state.chat.maximized;
|
||||
self.editor_state.chat.collapsed = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
AIChatHit::NewChat => {
|
||||
self.editor_state.chat.new_chat();
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
AIChatHit::ToggleModelPicker => {
|
||||
let opening = !self.editor_state.editor_ui.chat_model_picker_open;
|
||||
self.editor_state.editor_ui.chat_model_picker_open = opening;
|
||||
// Reopen the picker un-scrolled / un-hovered so
|
||||
// a stale offset from a prior open never hides
|
||||
// the top of the catalog.
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
let opening = self.editor_state.editor_ui.toggle_chat_model_picker();
|
||||
if opening {
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.touch(self.now_ms);
|
||||
}
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -244,8 +224,9 @@ impl WidgetHostNative {
|
|||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
self.editor_state.editor_ui.chat_model_picker.scroll.offset = 0.0;
|
||||
self.editor_state.editor_ui.chat_model_picker.hover = None;
|
||||
self.editor_state.editor_ui.chat_model_picker.pressed = None;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl WidgetHostNative {
|
|||
changed |= ui.fill_type_picker.hover.take().is_some();
|
||||
changed |= ui.toolbar_hover.take().is_some();
|
||||
changed |= ui.align_toolbar_hover.take().is_some();
|
||||
changed |= ui.chat_model_picker_hover.take().is_some();
|
||||
changed |= ui.chat_model_picker.hover.take().is_some();
|
||||
changed |= ui.chat_design_block_hover.take().is_some();
|
||||
changed |= ui.chat_footer_hover.take().is_some();
|
||||
changed |= ui.export_picker_hover.take().is_some();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ impl WidgetHostNative {
|
|||
|| self.editor_state.editor_ui.effect_param_focus.is_some()
|
||||
|| self.editor_state.editor_ui.agent_settings.focus.is_some()
|
||||
|| self.editor_state.editor_ui.icon_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker.open
|
||||
|| self.editor_state.editor_ui.component_browser_open
|
||||
|| self.editor_state.chat.focused
|
||||
|| self.git_commit_focus_active()
|
||||
|
|
@ -649,11 +649,10 @@ impl WidgetHostNative {
|
|||
}
|
||||
}
|
||||
// Open chat model-picker — track the model row under the
|
||||
// cursor so the dropdown paints a hover wash. `model_at`
|
||||
// returns `None` off the rows (headers / padding / off the
|
||||
// card), which clears any stale highlight.
|
||||
if self.editor_state.editor_ui.chat_model_picker_open && !over_topmost {
|
||||
use op_editor_ui::widgets::ai_chat_model_picker::model_at;
|
||||
// cursor so the dropdown paints a hover wash. Non-row chrome
|
||||
// clears any stale highlight.
|
||||
if self.editor_state.editor_ui.chat_model_picker.open && !over_topmost {
|
||||
use op_editor_ui::widgets::ai_chat_model_picker::{model_picker_hit, SelectHit};
|
||||
use op_editor_ui::widgets::AIChatPlaceholder;
|
||||
let picker = self
|
||||
.ai_chat_rect(self.last_viewport_w, self.last_viewport_h)
|
||||
|
|
@ -662,16 +661,18 @@ impl WidgetHostNative {
|
|||
.model_picker_bounds(chat_rect)
|
||||
});
|
||||
if let Some(picker) = picker {
|
||||
let scroll = self.editor_state.editor_ui.chat_model_picker_scroll;
|
||||
let new_hover = model_at(
|
||||
let new_hover = match model_picker_hit(
|
||||
&self.editor_state.editor_ui.chat_model_picker,
|
||||
picker,
|
||||
Point2D::new(x, y),
|
||||
&self.editor_state.chat.available_models,
|
||||
scroll,
|
||||
self.editor_state.editor_ui.chat_model_picker_input.text(),
|
||||
);
|
||||
if new_hover != self.editor_state.editor_ui.chat_model_picker_hover {
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = new_hover;
|
||||
) {
|
||||
SelectHit::Row(index) => Some(index),
|
||||
SelectHit::Inside | SelectHit::Outside => None,
|
||||
};
|
||||
if new_hover != self.editor_state.editor_ui.chat_model_picker.hover {
|
||||
self.editor_state.editor_ui.chat_model_picker.hover = new_hover;
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1282,7 +1282,7 @@ fn delete_with_model_picker_open_does_not_delete_selected() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut()
|
||||
.set_single_selection(NodeId::new("n10"));
|
||||
host.editor_state_mut().editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker.open = true;
|
||||
|
||||
assert!(!host.apply_delete());
|
||||
assert_eq!(host.editor_state().selection.anchor, NodeId::new("n10"));
|
||||
|
|
@ -1295,7 +1295,7 @@ fn backspace_with_model_picker_open_edits_search_not_selection() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut()
|
||||
.set_single_selection(NodeId::new("n10"));
|
||||
host.editor_state_mut().editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker.open = true;
|
||||
host.editor_state_mut()
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
|
|
@ -1316,7 +1316,7 @@ fn shortcuts_gated_while_model_picker_open() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut()
|
||||
.set_single_selection(NodeId::new("n10"));
|
||||
host.editor_state_mut().editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker.open = true;
|
||||
|
||||
assert!(!host.apply_nudge(1.0, 0.0));
|
||||
assert!(!host.apply_duplicate());
|
||||
|
|
@ -1684,7 +1684,7 @@ fn component_browser_header_buttons_queue_kit_io_requests() {
|
|||
#[test]
|
||||
fn chat_model_picker_open_owns_keyboard_search() {
|
||||
let mut host = WidgetHostNative::new();
|
||||
host.editor_state_mut().editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state_mut().editor_ui.chat_model_picker.open = true;
|
||||
host.editor_state_mut().chat.focused = true;
|
||||
|
||||
assert!(host.input_active_pub());
|
||||
|
|
@ -1701,7 +1701,7 @@ fn chat_model_picker_open_owns_keyboard_search() {
|
|||
"g"
|
||||
);
|
||||
assert!(host.apply_escape());
|
||||
assert!(!host.editor_state().editor_ui.chat_model_picker_open);
|
||||
assert!(!host.editor_state().editor_ui.chat_model_picker.open);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1779,7 +1779,7 @@ fn select_all_in_chat_model_picker_replaces_next_typed_text() {
|
|||
let mut host = WidgetHostNative::new();
|
||||
{
|
||||
let ui = &mut host.editor_state_mut().editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("gpt");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ impl WidgetHostNative {
|
|||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return self.apply_chat_model_picker_text(c);
|
||||
}
|
||||
if self.editor_state.editor_ui.component_browser_open && !c.is_control() {
|
||||
|
|
@ -663,7 +663,7 @@ impl WidgetHostNative {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return self.apply_chat_model_picker_backspace();
|
||||
}
|
||||
if self.editor_state.editor_ui.component_browser_open {
|
||||
|
|
@ -860,7 +860,7 @@ impl WidgetHostNative {
|
|||
if self.editor_state.ui.property_focus.is_some()
|
||||
|| self.editor_state.editor_ui.effect_param_focus.is_some()
|
||||
|| self.editor_state.editor_ui.icon_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker.open
|
||||
|| self.editor_state.editor_ui.component_browser_open
|
||||
|| self.editor_state.chat.focused
|
||||
{
|
||||
|
|
@ -1502,14 +1502,8 @@ impl WidgetHostNative {
|
|||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ impl WidgetHostNative {
|
|||
}
|
||||
// Open chat model-picker — a wheel over its dropdown scrolls
|
||||
// the model list instead of zooming the canvas.
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
use op_editor_ui::widgets::ai_chat_model_picker::max_picker_scroll;
|
||||
use op_editor_ui::widgets::AIChatPlaceholder;
|
||||
let picker = self
|
||||
|
|
@ -311,9 +311,10 @@ impl WidgetHostNative {
|
|||
&self.editor_state.chat.available_models,
|
||||
self.editor_state.editor_ui.chat_model_picker_input.text(),
|
||||
);
|
||||
let next = (self.editor_state.editor_ui.chat_model_picker_scroll - delta_y)
|
||||
let next = (self.editor_state.editor_ui.chat_model_picker.scroll.offset
|
||||
- delta_y)
|
||||
.clamp(0.0, max);
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = next;
|
||||
self.editor_state.editor_ui.chat_model_picker.scroll.offset = next;
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -439,7 +440,7 @@ impl WidgetHostNative {
|
|||
}
|
||||
// Open chat model-picker owns trackpad scroll over its
|
||||
// dropdown, same as the wheel path.
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
use op_editor_ui::widgets::ai_chat_model_picker::max_picker_scroll;
|
||||
use op_editor_ui::widgets::AIChatPlaceholder;
|
||||
let picker = self
|
||||
|
|
@ -454,9 +455,9 @@ impl WidgetHostNative {
|
|||
&self.editor_state.chat.available_models,
|
||||
self.editor_state.editor_ui.chat_model_picker_input.text(),
|
||||
);
|
||||
let next =
|
||||
(self.editor_state.editor_ui.chat_model_picker_scroll - dy).clamp(0.0, max);
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = next;
|
||||
let next = (self.editor_state.editor_ui.chat_model_picker.scroll.offset - dy)
|
||||
.clamp(0.0, max);
|
||||
self.editor_state.editor_ui.chat_model_picker.scroll.offset = next;
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ impl WidgetHostNative {
|
|||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
ui.chat_model_picker_input.select_all();
|
||||
ui.chat_model_picker_input.touch(self.now_ms);
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ fn focused_input(state: &EditorState) -> Option<(String, String, String)> {
|
|||
ui.icon_picker_search.clone(),
|
||||
));
|
||||
}
|
||||
if ui.chat_model_picker_open {
|
||||
if ui.chat_model_picker.open {
|
||||
return Some((
|
||||
"model-search".to_string(),
|
||||
"Model search".to_string(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl WidgetHost {
|
|||
|| eui.variables_variant_rename_value.is_some()
|
||||
|| self.variables_search_active()
|
||||
|| eui.agent_settings.focus.is_some()
|
||||
|| eui.chat_model_picker_open
|
||||
|| eui.chat_model_picker.open
|
||||
|| self.editor_state.chat.focused
|
||||
}
|
||||
|
||||
|
|
@ -50,11 +50,7 @@ impl WidgetHost {
|
|||
let _ = self.editor_state.editor_ui.git_panel.defocus_text_inputs();
|
||||
// Chat input + its model-picker popover (same field set as
|
||||
// the picker-close branch in `apply_escape`).
|
||||
let eui = &mut self.editor_state.editor_ui;
|
||||
eui.chat_model_picker_open = false;
|
||||
eui.chat_model_picker_scroll = 0.0;
|
||||
eui.chat_model_picker_input.set_text("");
|
||||
eui.chat_model_picker_hover = None;
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.editor_state.chat.blur_input(self.now_ms);
|
||||
if was_focused {
|
||||
self.mark_dirty();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const VH: f32 = 800.0;
|
|||
fn top_bar_gap_press_blurs_chat_and_model_picker() {
|
||||
let mut host = WidgetHost::new();
|
||||
host.editor_state.chat.focused = true;
|
||||
host.editor_state.editor_ui.chat_model_picker_open = true;
|
||||
host.editor_state.editor_ui.chat_model_picker.open = true;
|
||||
host.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
|
|
@ -26,7 +26,7 @@ fn top_bar_gap_press_blurs_chat_and_model_picker() {
|
|||
!host.editor_state.chat.focused,
|
||||
"top-bar gap press must blur the chat input"
|
||||
);
|
||||
assert!(!host.editor_state.editor_ui.chat_model_picker_open);
|
||||
assert!(!host.editor_state.editor_ui.chat_model_picker.open);
|
||||
assert!(host
|
||||
.editor_state
|
||||
.editor_ui
|
||||
|
|
|
|||
|
|
@ -2,34 +2,36 @@ use super::WidgetHost;
|
|||
|
||||
impl WidgetHost {
|
||||
pub(in crate::widget_host) fn apply_chat_model_picker_text(&mut self, c: char) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open || c.is_control() {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open || c.is_control() {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
let mut s = [0u8; 4];
|
||||
ui.chat_model_picker_input
|
||||
.insert_str(c.encode_utf8(&mut s), self.now_ms);
|
||||
ui.chat_model_picker_scroll = 0.0;
|
||||
ui.chat_model_picker_hover = None;
|
||||
ui.chat_model_picker.scroll.offset = 0.0;
|
||||
ui.chat_model_picker.hover = None;
|
||||
ui.chat_model_picker.pressed = None;
|
||||
self.mark_dirty();
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::widget_host) fn apply_chat_model_picker_backspace(&mut self) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
ui.chat_model_picker_input.backspace(self.now_ms);
|
||||
ui.chat_model_picker_input.touch(self.now_ms);
|
||||
ui.chat_model_picker_scroll = 0.0;
|
||||
ui.chat_model_picker_hover = None;
|
||||
ui.chat_model_picker.scroll.offset = 0.0;
|
||||
ui.chat_model_picker.hover = None;
|
||||
ui.chat_model_picker.pressed = None;
|
||||
self.mark_dirty();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn apply_chat_model_picker_caret(&mut self, forward: bool) -> bool {
|
||||
if !self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if !self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return false;
|
||||
}
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ fn chat_model_picker_arrows_move_caret_for_insert_and_backspace() {
|
|||
let mut host = WidgetHost::new();
|
||||
{
|
||||
let ui = &mut host.editor_state.editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("abcd");
|
||||
}
|
||||
|
||||
|
|
@ -44,10 +44,10 @@ fn chat_model_picker_clear_button_empties_search() {
|
|||
host.set_now_ms(456);
|
||||
{
|
||||
let ui = &mut host.editor_state.editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("231");
|
||||
ui.chat_model_picker_scroll = 10.0;
|
||||
ui.chat_model_picker_hover = Some(0);
|
||||
ui.chat_model_picker.scroll.offset = 10.0;
|
||||
ui.chat_model_picker.hover = Some(0);
|
||||
}
|
||||
let chat_rect = host.ai_chat_rect(1200.0, 800.0).unwrap();
|
||||
let panel = AIChatPlaceholder::from_editor_at(&host.editor_state, 456);
|
||||
|
|
@ -60,8 +60,8 @@ fn chat_model_picker_clear_button_empties_search() {
|
|||
let ui = &host.editor_state.editor_ui;
|
||||
assert!(ui.chat_model_picker_input.text().is_empty());
|
||||
assert_eq!(ui.chat_model_picker_input.caret(), 0);
|
||||
assert_eq!(ui.chat_model_picker_scroll, 0.0);
|
||||
assert_eq!(ui.chat_model_picker_hover, None);
|
||||
assert!(ui.chat_model_picker_open);
|
||||
assert_eq!(ui.chat_model_picker.scroll.offset, 0.0);
|
||||
assert_eq!(ui.chat_model_picker.hover, None);
|
||||
assert!(ui.chat_model_picker.open);
|
||||
assert_eq!(ui.chat_model_picker_input.next_blink_flip_ms(456), 956);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,35 +76,18 @@ impl WidgetHost {
|
|||
AIChatHit::ToggleMaximize => {
|
||||
self.editor_state.chat.maximized = !self.editor_state.chat.maximized;
|
||||
self.editor_state.chat.collapsed = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
AIChatHit::NewChat => {
|
||||
self.editor_state.chat.new_chat();
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
AIChatHit::ToggleModelPicker => {
|
||||
let opening = !self.editor_state.editor_ui.chat_model_picker_open;
|
||||
self.editor_state.editor_ui.chat_model_picker_open = opening;
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
let opening = self.editor_state.editor_ui.toggle_chat_model_picker();
|
||||
if opening {
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
|
|
@ -127,8 +110,9 @@ impl WidgetHost {
|
|||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
self.editor_state.editor_ui.chat_model_picker.scroll.offset = 0.0;
|
||||
self.editor_state.editor_ui.chat_model_picker.hover = None;
|
||||
self.editor_state.editor_ui.chat_model_picker.pressed = None;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ impl WidgetHost {
|
|||
if let Some(changed) = self.icon_picker_text(c) {
|
||||
return changed;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return self.apply_chat_model_picker_text(c);
|
||||
}
|
||||
if let Some(changed) = self.component_browser_text(c) {
|
||||
|
|
@ -327,7 +327,7 @@ impl WidgetHost {
|
|||
if let Some(changed) = self.icon_picker_backspace() {
|
||||
return changed;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
return self.apply_chat_model_picker_backspace();
|
||||
}
|
||||
if let Some(changed) = self.component_browser_backspace() {
|
||||
|
|
@ -457,7 +457,7 @@ impl WidgetHost {
|
|||
.editor_ui
|
||||
.variables_variant_rename_value
|
||||
.is_some()
|
||||
|| self.editor_state.editor_ui.chat_model_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker.open
|
||||
|| self.editor_state.chat.focused
|
||||
{
|
||||
if self.editor_state.ui.property_focus.is_some()
|
||||
|
|
@ -730,14 +730,8 @@ impl WidgetHost {
|
|||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
self.editor_state.editor_ui.chat_model_picker_open = false;
|
||||
self.editor_state.editor_ui.chat_model_picker_scroll = 0.0;
|
||||
self.editor_state
|
||||
.editor_ui
|
||||
.chat_model_picker_input
|
||||
.set_text("");
|
||||
self.editor_state.editor_ui.chat_model_picker_hover = None;
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
self.editor_state.editor_ui.close_chat_model_picker();
|
||||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ impl WidgetHost {
|
|||
self.mark_dirty();
|
||||
return true;
|
||||
}
|
||||
if self.editor_state.editor_ui.chat_model_picker_open {
|
||||
if self.editor_state.editor_ui.chat_model_picker.open {
|
||||
let ui = &mut self.editor_state.editor_ui;
|
||||
ui.chat_model_picker_input.select_all();
|
||||
ui.chat_model_picker_input.touch(self.now_ms);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl WidgetHost {
|
|||
|| self.variables_search_active()
|
||||
|| self.editor_state.editor_ui.agent_settings.focus.is_some()
|
||||
|| self.editor_state.editor_ui.icon_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker_open
|
||||
|| self.editor_state.editor_ui.chat_model_picker.open
|
||||
|| self.editor_state.editor_ui.component_browser_open
|
||||
|| self.editor_state.chat.focused
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ fn select_all_in_chat_model_picker_replaces_next_typed_text() {
|
|||
let mut host = WidgetHost::new();
|
||||
{
|
||||
let ui = &mut host.editor_state.editor_ui;
|
||||
ui.chat_model_picker_open = true;
|
||||
ui.chat_model_picker.open = true;
|
||||
ui.chat_model_picker_input.set_text("gpt");
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue