refactor(editor): migrate icon picker to shared select

This commit is contained in:
Kayshen-X 2026-06-14 00:56:40 +08:00
parent f6628e2b28
commit f142bb90d4
25 changed files with 158 additions and 110 deletions

View file

@ -896,8 +896,8 @@ pub struct EditorUiState {
/// Last-selected shape tool — drives the toolbar shape slot's
/// icon. Always one of Rect / Ellipse / Polygon / Line / Pen.
pub shape_tool: Tool,
/// Whether the Toolbar's Icon action picker is open.
pub icon_picker_open: bool,
/// Toolbar/property icon picker interaction state.
pub icon_picker: jian_widgets::components::select::SelectState,
/// True when the icon picker should replace the selected icon
/// instead of inserting a new icon at the canvas centre.
pub icon_picker_replace_selection: bool,
@ -909,9 +909,6 @@ pub struct EditorUiState {
/// True after Cmd/Ctrl+A in the icon search box. The next edit
/// replaces the whole search query.
pub icon_picker_select_all: bool,
/// Which icon-picker target the cursor is over (close / row /
/// load-more) — drives the `theme.button_hover` wash.
pub icon_picker_hover: Option<crate::icon_picker_button_state::IconPickerButton>,
/// Remote Iconify search results appended by the desktop host.
pub icon_picker_remote: crate::icon_picker_state::IconPickerRemoteState,
/// Queued "load more" request drained asynchronously by desktop.
@ -1227,12 +1224,11 @@ impl Default for EditorUiState {
shape_picker: jian_widgets::components::select::SelectState::default(),
toolbar_hover: None,
shape_tool: Tool::Rect,
icon_picker_open: false,
icon_picker: jian_widgets::components::select::SelectState::default(),
icon_picker_replace_selection: false,
icon_picker_panel_pos: None,
icon_picker_search: String::new(),
icon_picker_select_all: false,
icon_picker_hover: None,
icon_picker_remote: crate::icon_picker_state::IconPickerRemoteState::default(),
icon_picker_load_more_request: None,
chat_model_picker: jian_widgets::components::select::SelectState::default(),
@ -1352,6 +1348,30 @@ impl EditorUiState {
changed
}
pub fn open_icon_picker(&mut self, replace_selection: bool) {
self.close_icon_picker();
self.icon_picker.open = true;
self.icon_picker_replace_selection = replace_selection;
}
pub fn close_icon_picker(&mut self) -> bool {
let changed = self.icon_picker.open
|| self.icon_picker.hover.is_some()
|| self.icon_picker.pressed.is_some()
|| self.icon_picker.scroll.offset != 0.0
|| self.icon_picker_replace_selection
|| !self.icon_picker_search.is_empty()
|| self.icon_picker_select_all;
self.icon_picker.open = false;
self.icon_picker.hover = None;
self.icon_picker.pressed = None;
self.icon_picker.scroll.offset = 0.0;
self.icon_picker_replace_selection = false;
self.icon_picker_search.clear();
self.icon_picker_select_all = false;
changed
}
pub fn toggle_font_picker(&mut self) {
let opening = !self.font_picker.open;
self.close_font_picker();
@ -1703,4 +1723,35 @@ mod tests {
assert_eq!(ui.chat_model_picker.pressed, None);
assert_eq!(ui.chat_model_picker.scroll.offset, 0.0);
}
#[test]
fn icon_picker_helpers_reset_select_interaction_state_and_search() {
let mut ui = EditorUiState::new();
ui.icon_picker_replace_selection = true;
ui.icon_picker_search = "home".to_string();
ui.icon_picker_select_all = true;
ui.icon_picker.hover = Some(1);
ui.icon_picker.pressed = Some(1);
ui.open_icon_picker(false);
assert!(ui.icon_picker.open);
assert!(!ui.icon_picker_replace_selection);
assert!(ui.icon_picker_search.is_empty());
assert!(!ui.icon_picker_select_all);
assert_eq!(ui.icon_picker.hover, None);
assert_eq!(ui.icon_picker.pressed, None);
ui.icon_picker_replace_selection = true;
ui.icon_picker_search = "settings".to_string();
ui.icon_picker_select_all = true;
ui.icon_picker.hover = Some(0);
ui.icon_picker.pressed = Some(0);
assert!(ui.close_icon_picker());
assert!(!ui.icon_picker.open);
assert!(!ui.icon_picker_replace_selection);
assert!(ui.icon_picker_search.is_empty());
assert!(!ui.icon_picker_select_all);
assert_eq!(ui.icon_picker.hover, None);
assert_eq!(ui.icon_picker.pressed, None);
}
}

View file

@ -1,18 +0,0 @@
//! State-layer mirror of the icon-picker panel's buttons.
//!
//! The panel's click enum (`IconPickerHit`) carries owned `String`
//! collection/name ids for a row select, so it isn't `Copy`. The row
//! list is index-addressable (paint + hit-test share the same row
//! order), so `Row(usize)` captures the hovered row without strings.
//! Same wasm32-clean discipline as the other `*_state` mirrors.
/// Which icon-picker target the cursor is over. `None` = no hover wash.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IconPickerButton {
/// The header `✕` close button.
Close,
/// An icon row, by its index into the visible list.
Row(usize),
/// The "load more" row at the foot of a remote result set.
LoadMore,
}

View file

@ -42,7 +42,6 @@ pub mod git_button_state;
pub mod grouping;
pub mod history;
pub mod host_support;
pub mod icon_picker_button_state;
pub mod icon_picker_state;
pub mod image_node_props;
pub mod image_panel_state;
@ -173,7 +172,6 @@ pub use fills::{
pub use geometry::{aggregate_bounds, own_bounds, union_aggregate_bounds, DocRect};
pub use git_button_state::GitButton;
pub use history::{EditorSnapshot, History, HISTORY_CAP};
pub use icon_picker_button_state::IconPickerButton;
pub use icon_picker_state::{IconPickerRemoteIcon, IconPickerRemoteState, IconifyLoadMoreRequest};
pub use image_node_props::image_node_summary;
pub use instance_override::{

View file

@ -11,6 +11,8 @@ use op_editor_core::{EditorState, IconPickerRemoteIcon, Locale};
pub const ICON_PICKER_PANEL_W: f32 = 320.0;
pub const ICON_PICKER_PANEL_H: f32 = 420.0;
pub const ICON_PICKER_CLOSE_HOVER: usize = usize::MAX - 1;
pub const ICON_PICKER_LOAD_MORE_HOVER: usize = usize::MAX;
const PAD: f32 = 14.0;
const HEADER_H: f32 = 40.0;
const SEARCH_H: f32 = 42.0;
@ -57,36 +59,31 @@ pub struct IconPickerPanel<'a> {
theme: Theme,
locale: Locale,
/// Which target the cursor is over — drives the hover wash.
hover: Option<op_editor_core::IconPickerButton>,
hover: Option<usize>,
}
impl<'a> IconPickerPanel<'a> {
pub fn for_editor(state: &'a EditorState) -> Option<IconPickerPanel<'a>> {
if !state.editor_ui.icon_picker_open {
if !state.editor_ui.icon_picker.open {
return None;
}
Some(IconPickerPanel {
state,
theme: theme_for(&state.editor_ui),
locale: state.editor_ui.locale,
hover: state.editor_ui.icon_picker_hover,
hover: state.editor_ui.icon_picker.hover,
})
}
/// Resolve a pointer to a hoverable target (close / row / load-more).
/// Mirrors [`Self::hit_test`]'s row math; rows are index-addressable
/// so no string ids leak into the hover state.
pub fn hover_at(
&self,
panel: Rect,
point: Point2D,
) -> Option<op_editor_core::IconPickerButton> {
use op_editor_core::IconPickerButton as B;
pub fn hover_at(&self, panel: Rect, point: Point2D) -> Option<usize> {
if !(panel).contains(point) {
return None;
}
if (Self::close_rect(panel)).contains(point) {
return Some(B::Close);
return Some(ICON_PICKER_CLOSE_HOVER);
}
if point.y <= panel.origin.y + HEADER_H {
return None;
@ -99,10 +96,10 @@ impl<'a> IconPickerPanel<'a> {
let item_cap = capacity.saturating_sub(usize::from(has_more));
let items = self.rows(item_cap);
if row < items.len() {
return Some(B::Row(row));
return Some(row);
}
if has_more && row == items.len() && !self.state.editor_ui.icon_picker_remote.loading {
return Some(B::LoadMore);
return Some(ICON_PICKER_LOAD_MORE_HOVER);
}
}
None
@ -245,7 +242,7 @@ impl<'a> IconPickerPanel<'a> {
Point2D::new(panel.origin.x + PAD, panel.origin.y + 25.0),
);
let close = Self::close_rect(panel);
let close_hovered = self.hover == Some(op_editor_core::IconPickerButton::Close);
let close_hovered = self.hover == Some(ICON_PICKER_CLOSE_HOVER);
if close_hovered {
cx.backend
.fill_round_rect(close, 6.0, self.theme.button_hover);
@ -332,7 +329,7 @@ impl<'a> IconPickerPanel<'a> {
size: Point2D::new(panel.size.x - 12.0, ROW_H),
};
cx.backend.fill_round_rect(row, 6.0, self.theme.popover);
if self.hover == Some(op_editor_core::IconPickerButton::Row(idx)) {
if self.hover == Some(idx) {
cx.backend
.fill_round_rect(row, 6.0, self.theme.button_hover);
}
@ -381,7 +378,7 @@ impl<'a> IconPickerPanel<'a> {
size: Point2D::new(panel.size.x - 12.0, ROW_H - 4.0),
};
cx.backend.fill_round_rect(row, 6.0, self.theme.muted);
if self.hover == Some(op_editor_core::IconPickerButton::LoadMore) {
if self.hover == Some(ICON_PICKER_LOAD_MORE_HOVER) {
cx.backend
.fill_round_rect(row, 6.0, self.theme.button_hover);
}
@ -420,3 +417,35 @@ fn truncate(text: &str, max_chars: usize) -> String {
out.push_str("...");
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hover_at_uses_select_state_indices_for_close_and_rows() {
let mut state = EditorState::default();
state.editor_ui.open_icon_picker(false);
let panel = IconPickerPanel::for_editor(&state).unwrap();
let rect = Rect::xywh(0.0, 0.0, ICON_PICKER_PANEL_W, ICON_PICKER_PANEL_H);
let close = IconPickerPanel::close_rect(rect);
assert_eq!(
panel.hover_at(
rect,
Point2D::new(close.origin.x + close.size.x / 2.0, close.origin.y + 4.0)
),
Some(ICON_PICKER_CLOSE_HOVER)
);
assert_eq!(
panel.hover_at(
rect,
Point2D::new(
rect.origin.x + 20.0,
IconPickerPanel::list_top(rect) + ROW_H / 2.0
)
),
Some(0)
);
}
}

View file

@ -24,10 +24,7 @@ impl WidgetHostNative {
};
match hit {
IconPickerHit::Close => {
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_hover = None;
self.editor_state.editor_ui.close_icon_picker();
}
IconPickerHit::DragHeader => {
self.icon_picker_drag = Some(IconPickerDragState {
@ -84,10 +81,7 @@ impl WidgetHostNative {
doc.x as f64,
doc.y as f64,
);
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_hover = None;
self.editor_state.editor_ui.close_icon_picker();
if inserted.is_some() {
self.mark_dirty();
}

View file

@ -32,7 +32,7 @@ impl WidgetHostNative {
|| ui.variable_row_focus.is_some()
|| self.editor_state.ui.property_focus.is_some()
|| self.editor_state.chat.focused
|| ui.icon_picker_open
|| ui.icon_picker.open
|| ui.component_browser_open
}

View file

@ -38,7 +38,7 @@ impl WidgetHostNative {
|| self.variables_search_active()
|| 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.icon_picker.open
|| self.editor_state.editor_ui.chat_model_picker.open
|| self.editor_state.editor_ui.component_browser_open
|| self.editor_state.chat.focused
@ -472,15 +472,15 @@ impl WidgetHostNative {
return true;
}
// Icon-picker panel hover (close / icon rows / load-more).
if self.editor_state.editor_ui.icon_picker_open {
if self.editor_state.editor_ui.icon_picker.open {
use op_editor_ui::widgets::icon_picker_panel::IconPickerPanel;
if let Some(panel_rect) =
self.icon_picker_panel_rect(self.last_viewport_w, self.last_viewport_h)
{
let new_hover = IconPickerPanel::for_editor(&self.editor_state)
.and_then(|p| p.hover_at(panel_rect, Point2D::new(x, y)));
if new_hover != self.editor_state.editor_ui.icon_picker_hover {
self.editor_state.editor_ui.icon_picker_hover = new_hover;
if new_hover != self.editor_state.editor_ui.icon_picker.hover {
self.editor_state.editor_ui.icon_picker.hover = new_hover;
self.mark_dirty();
return true;
}

View file

@ -1824,13 +1824,13 @@ fn shape_picker_icon_row_opens_icon_picker() {
assert!(host.apply_press(x, icon_row_y, viewport_w, viewport_h));
assert!(!host.editor_state().editor_ui.shape_picker.open);
assert!(host.editor_state().editor_ui.icon_picker_open);
assert!(host.editor_state().editor_ui.icon_picker.open);
}
#[test]
fn icon_picker_open_owns_keyboard_search() {
let mut host = WidgetHostNative::new();
host.editor_state_mut().editor_ui.icon_picker_open = true;
host.editor_state_mut().editor_ui.icon_picker.open = true;
assert!(host.input_active_pub());
assert!(host.apply_text('h'));
@ -1839,7 +1839,7 @@ fn icon_picker_open_owns_keyboard_search() {
assert!(host.apply_backspace());
assert_eq!(host.editor_state().editor_ui.icon_picker_search, "h");
assert!(host.apply_escape());
assert!(!host.editor_state().editor_ui.icon_picker_open);
assert!(!host.editor_state().editor_ui.icon_picker.open);
}
#[test]
@ -1847,7 +1847,7 @@ fn icon_picker_click_inserts_icon_font_node() {
let mut host = WidgetHostNative::new();
let viewport_w = 1440.0;
let viewport_h = 900.0;
host.editor_state_mut().editor_ui.icon_picker_open = true;
host.editor_state_mut().editor_ui.icon_picker.open = true;
host.editor_state_mut().editor_ui.icon_picker_search = "home".to_string();
let panel = host
@ -1856,7 +1856,7 @@ fn icon_picker_click_inserts_icon_font_node() {
let row_y = panel.origin.y + 40.0 + 42.0 + 20.0;
assert!(host.apply_press(panel.origin.x + 40.0, row_y, viewport_w, viewport_h));
assert!(!host.editor_state().editor_ui.icon_picker_open);
assert!(!host.editor_state().editor_ui.icon_picker.open);
let icon = host
.editor_state()
.doc
@ -1877,7 +1877,7 @@ fn icon_picker_header_drag_moves_the_panel() {
let mut host = WidgetHostNative::new();
let viewport_w = 1440.0;
let viewport_h = 900.0;
host.editor_state_mut().editor_ui.icon_picker_open = true;
host.editor_state_mut().editor_ui.icon_picker.open = true;
let start = host
.icon_picker_panel_rect(viewport_w, viewport_h)

View file

@ -178,7 +178,7 @@ fn context_menu_detach_component_sheds_reusable_flag() {
fn remote_icon_insert_bakes_svg_d_as_path_node() {
let mut host = seeded_host();
let state = host.editor_state_mut();
state.editor_ui.icon_picker_open = true;
state.editor_ui.icon_picker.open = true;
state.editor_ui.icon_picker_replace_selection = false;
state.editor_ui.icon_picker_panel_pos = Some((0.0, 0.0));
state.editor_ui.icon_picker_search = "zwxq".to_string();

View file

@ -341,12 +341,16 @@ impl WidgetHostNative {
if self.apply_image_panel_text(c) {
return true;
}
if self.editor_state.editor_ui.icon_picker_open && !c.is_control() {
if self.editor_state.editor_ui.icon_picker.open && !c.is_control() {
if self.editor_state.editor_ui.icon_picker_select_all {
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
}
self.editor_state.editor_ui.icon_picker_search.push(c);
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
return true;
}
@ -644,10 +648,12 @@ impl WidgetHostNative {
if self.apply_image_panel_backspace() {
return true;
}
if self.editor_state.editor_ui.icon_picker_open {
if self.editor_state.editor_ui.icon_picker.open {
if self.editor_state.editor_ui.icon_picker_select_all {
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
return true;
}
@ -658,6 +664,8 @@ impl WidgetHostNative {
.pop()
.is_some()
{
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
return true;
}
@ -859,7 +867,7 @@ impl WidgetHostNative {
// would silently drop the node behind the focused field.
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.icon_picker.open
|| self.editor_state.editor_ui.chat_model_picker.open
|| self.editor_state.editor_ui.component_browser_open
|| self.editor_state.chat.focused
@ -1493,12 +1501,8 @@ impl WidgetHostNative {
self.mark_dirty();
return true;
}
if self.editor_state.editor_ui.icon_picker_open {
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker_hover = None;
if self.editor_state.editor_ui.icon_picker.open {
self.editor_state.editor_ui.close_icon_picker();
self.mark_dirty();
return true;
}

View file

@ -190,7 +190,7 @@ impl WidgetHostNative {
viewport_w: f32,
viewport_h: f32,
) -> Option<Rect> {
if !self.editor_state.editor_ui.icon_picker_open {
if !self.editor_state.editor_ui.icon_picker.open {
return None;
}
let ui = &self.editor_state.editor_ui;

View file

@ -162,9 +162,7 @@ impl WidgetHostNative {
}
A::OpenSelectedIconPicker => {
let ui = &mut self.editor_state.editor_ui;
ui.icon_picker_open = true;
ui.icon_picker_replace_selection = true;
ui.icon_picker_search.clear();
ui.open_icon_picker(true);
ui.close_fill_type_picker();
ui.image_fill_popover_open = false;
ui.close_font_picker();

View file

@ -31,9 +31,7 @@ impl WidgetHostNative {
self.editor_state.tool = tool;
}
ShapeChoice::OpenIconPicker => {
self.editor_state.editor_ui.icon_picker_open = true;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.open_icon_picker(false);
}
ShapeChoice::ImportImageOrSvg => {
self.editor_state.editor_ui.pending_file_action =

View file

@ -205,7 +205,7 @@ impl WidgetHostNative {
self.mark_dirty();
return true;
}
if self.editor_state.editor_ui.icon_picker_open {
if self.editor_state.editor_ui.icon_picker.open {
self.editor_state.editor_ui.icon_picker_select_all = true;
self.mark_dirty();
return true;

View file

@ -535,7 +535,7 @@ fn focused_input(state: &EditorState) -> Option<(String, String, String)> {
state.ui.property_input.text().to_owned(),
));
}
if ui.icon_picker_open {
if ui.icon_picker.open {
return Some((
"icon-search".to_string(),
"Icon search".to_string(),
@ -698,7 +698,7 @@ mod tests {
#[test]
fn focused_input_reports_open_search_popovers() {
let mut state = EditorState::new();
state.editor_ui.icon_picker_open = true;
state.editor_ui.icon_picker.open = true;
state.editor_ui.icon_picker_search = "arrow".to_string();
let (key, _label, value) = focused_input(&state).expect("icon search");
assert_eq!(key, "icon-search");

View file

@ -33,10 +33,7 @@ impl WidgetHost {
};
match hit {
IconPickerHit::Close => {
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_hover = None;
self.editor_state.editor_ui.close_icon_picker();
}
IconPickerHit::DragHeader => {
self.icon_picker_drag = Some(PanelDragState {
@ -93,10 +90,7 @@ impl WidgetHost {
doc.x as f64,
doc.y as f64,
);
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_hover = None;
self.editor_state.editor_ui.close_icon_picker();
if inserted.is_some() {
self.mark_dirty();
}

View file

@ -688,12 +688,8 @@ impl WidgetHost {
self.mark_dirty();
return true;
}
if self.editor_state.editor_ui.icon_picker_open {
self.editor_state.editor_ui.icon_picker_open = false;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker_hover = None;
if self.editor_state.editor_ui.icon_picker.open {
self.editor_state.editor_ui.close_icon_picker();
self.mark_dirty();
return true;
}

View file

@ -135,7 +135,7 @@ impl WidgetHost {
self.mark_dirty();
return true;
}
if self.editor_state.editor_ui.icon_picker_open {
if self.editor_state.editor_ui.icon_picker.open {
self.editor_state.editor_ui.icon_picker_select_all = true;
self.mark_dirty();
return true;

View file

@ -25,7 +25,7 @@ impl WidgetHost {
.is_some()
|| 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.icon_picker.open
|| self.editor_state.editor_ui.chat_model_picker.open
|| self.editor_state.editor_ui.component_browser_open
|| self.editor_state.chat.focused

View file

@ -89,15 +89,15 @@ impl WidgetHost {
return true;
}
// Icon-picker panel hover (close / icon rows / load-more).
if self.editor_state.editor_ui.icon_picker_open {
if self.editor_state.editor_ui.icon_picker.open {
use op_editor_ui::widgets::icon_picker_panel::IconPickerPanel;
if let Some(panel_rect) =
self.icon_picker_panel_rect(self.last_viewport_w, self.last_viewport_h)
{
let new_hover = IconPickerPanel::for_editor(&self.editor_state)
.and_then(|p| p.hover_at(panel_rect, Point2D::new(x, y)));
if new_hover != self.editor_state.editor_ui.icon_picker_hover {
self.editor_state.editor_ui.icon_picker_hover = new_hover;
if new_hover != self.editor_state.editor_ui.icon_picker.hover {
self.editor_state.editor_ui.icon_picker.hover = new_hover;
self.mark_dirty();
return true;
}

View file

@ -14,14 +14,18 @@ impl WidgetHost {
/// Icon-picker search box typing — owns every printable char
/// while the panel is open.
pub(in crate::widget_host) fn icon_picker_text(&mut self, c: char) -> Option<bool> {
if !self.editor_state.editor_ui.icon_picker_open || c.is_control() {
if !self.editor_state.editor_ui.icon_picker.open || c.is_control() {
return None;
}
if self.editor_state.editor_ui.icon_picker_select_all {
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
}
self.editor_state.editor_ui.icon_picker_search.push(c);
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
Some(true)
}
@ -29,12 +33,14 @@ impl WidgetHost {
/// Icon-picker search box backspace — swallows the key while the
/// panel is open even when the draft is already empty.
pub(in crate::widget_host) fn icon_picker_backspace(&mut self) -> Option<bool> {
if !self.editor_state.editor_ui.icon_picker_open {
if !self.editor_state.editor_ui.icon_picker.open {
return None;
}
if self.editor_state.editor_ui.icon_picker_select_all {
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.icon_picker_select_all = false;
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
return Some(true);
}
@ -45,6 +51,8 @@ impl WidgetHost {
.pop()
.is_some()
{
self.editor_state.editor_ui.icon_picker.hover = None;
self.editor_state.editor_ui.icon_picker.pressed = None;
self.mark_dirty();
return Some(true);
}

View file

@ -267,7 +267,7 @@ fn shape_picker_icon_row_opens_icon_picker_panel() {
let row_y = row_y.expect("icon row present in the shape picker");
assert!(host.apply_press(x, row_y, W, H));
assert!(host.editor_state.editor_ui.icon_picker_open);
assert!(host.editor_state.editor_ui.icon_picker.open);
assert!(!host.editor_state.editor_ui.shape_picker.open);
// The icon-picker panel paints at its centred rect.

View file

@ -186,7 +186,7 @@ impl WidgetHost {
viewport_w: f32,
viewport_h: f32,
) -> Option<Rect> {
if !self.editor_state.editor_ui.icon_picker_open {
if !self.editor_state.editor_ui.icon_picker.open {
return None;
}
let ui = &self.editor_state.editor_ui;

View file

@ -122,9 +122,7 @@ impl WidgetHost {
// Property-panel icon section → replace-selection
// picker (mirrors the native host's arm).
let ui = &mut self.editor_state.editor_ui;
ui.icon_picker_open = true;
ui.icon_picker_replace_selection = true;
ui.icon_picker_search.clear();
ui.open_icon_picker(true);
ui.close_fill_type_picker();
ui.image_fill_popover_open = false;
ui.close_font_picker();

View file

@ -34,9 +34,7 @@ impl WidgetHost {
self.editor_state.tool = tool;
}
ShapeChoice::OpenIconPicker => {
self.editor_state.editor_ui.icon_picker_open = true;
self.editor_state.editor_ui.icon_picker_replace_selection = false;
self.editor_state.editor_ui.icon_picker_search.clear();
self.editor_state.editor_ui.open_icon_picker(false);
}
ShapeChoice::ImportImageOrSvg => {
// No file-picker service on web yet — raise the same