mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-06 10:38:39 +00:00
#861: one search field for Pages and Layers; Escape closes it only from the field and goes no further; the search button shows when it's on; filter pages in the row loop
This commit is contained in:
1 parent
be38c68ac5
commit
4cc2230013
2 files changed
+43
-53
No files matched your search
@@ -185,18 +185,16 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
}
|
||||
let sr = Rect::from_center_size(pos2(hr.max.x - 50.0, hr.center().y), vec2(24.0, 24.0));
|
||||
let sresp = ui.interact(sr, ui.id().with("lsearch"), Sense::click()).tip(tr("Search pages"));
|
||||
if sresp.hovered() {
|
||||
if sresp.hovered() || app.ui.page_search {
|
||||
ui.painter().rect_filled(sr, CornerRadius::same(5), t.hover);
|
||||
}
|
||||
icons::paint(ui.painter(), sr.shrink(5.0), Icon::Search, t.icon);
|
||||
let search_id = Id::new("page_search_field");
|
||||
let mut focus_search = false;
|
||||
if sresp.clicked() {
|
||||
app.ui.page_search = !app.ui.page_search;
|
||||
app.ui.page_filter.clear();
|
||||
if app.ui.page_search {
|
||||
app.ui.pages_open = true;
|
||||
focus_search = true;
|
||||
}
|
||||
}
|
||||
let ar = Rect::from_center_size(pos2(hr.max.x - 22.0, hr.center().y), vec2(24.0, 24.0));
|
||||
@@ -213,14 +211,12 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
return;
|
||||
}
|
||||
if app.ui.page_search {
|
||||
page_search_field(app, ui, &t, w, search_id, focus_search);
|
||||
search_field(ui, &t, w, search_id, tr("Search pages"), &mut app.ui.page_filter, &mut app.ui.page_search, sresp.clicked());
|
||||
}
|
||||
let Ok(ds) = app.session.ds() else { return };
|
||||
let pages: Vec<(NodeId, String, bool)> =
|
||||
ds.doc.pages.iter().filter_map(|p| ds.doc.node(*p).map(|n| (*p, n.name.clone(), ds.doc.is_divider(*p)))).collect();
|
||||
let filter = app.ui.page_filter.trim().to_lowercase();
|
||||
let visible_pages: Vec<(NodeId, String, bool)> =
|
||||
pages.iter().filter(|(_, name, divider)| filter.is_empty() || (!divider && name.to_lowercase().contains(&filter))).cloned().collect();
|
||||
let filtering = !filter.is_empty();
|
||||
let cur = ds.page;
|
||||
let uid = ds.uid;
|
||||
@@ -236,7 +232,10 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
// A long list scrolls in its own area, so the layers keep their room below it.
|
||||
let max_h = pages_list_height(app.ui.pages_height, ui.available_height());
|
||||
let mut moved = egui::ScrollArea::vertical().id_salt("pages_list").max_height(max_h).show(ui, |ui| {
|
||||
for (k, (id, name, divider)) in visible_pages.iter().cloned().enumerate() {
|
||||
for (k, (id, name, divider)) in pages.iter().cloned().enumerate() {
|
||||
if filtering && (divider || !name.to_lowercase().contains(&filter)) {
|
||||
continue;
|
||||
}
|
||||
let sense = if filtering { Sense::click() } else { Sense::click_and_drag() };
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, 32.0), sense);
|
||||
if !filtering {
|
||||
@@ -332,36 +331,6 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
pages_edge(app, ui, moved.inner_rect.height().min(moved.content_size.y));
|
||||
}
|
||||
|
||||
/// The Pages panel's inline filter (Escape clears and closes it).
|
||||
fn page_search_field(app: &mut FigApp, ui: &mut Ui, t: &Tokens, w: f32, id: Id, focus: bool) {
|
||||
let (fr, _) = ui.allocate_exact_size(vec2(w, 32.0), Sense::hover());
|
||||
let field = Rect::from_min_max(pos2(fr.min.x + 12.0, fr.min.y + 2.0), pos2(fr.max.x - 12.0, fr.max.y - 6.0));
|
||||
ui.painter().rect_filled(field, CornerRadius::same(5), t.field);
|
||||
icons::paint(ui.painter(), Rect::from_center_size(pos2(field.min.x + 12.0, field.center().y), vec2(12.0, 12.0)), Icon::Search, t.icon_secondary);
|
||||
let te_rect = Rect::from_min_max(pos2(field.min.x + 24.0, field.center().y - 7.0), pos2(field.max.x - 6.0, field.center().y + 7.0));
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(te_rect));
|
||||
let r = child.add(
|
||||
egui::TextEdit::singleline(&mut app.ui.page_filter)
|
||||
.id(id)
|
||||
.frame(egui::Frame::NONE)
|
||||
.font(regular(11.0))
|
||||
.hint_text(tr("Search pages"))
|
||||
.vertical_align(egui::Align::Center)
|
||||
.desired_width(te_rect.width()),
|
||||
);
|
||||
if focus {
|
||||
r.request_focus();
|
||||
}
|
||||
crate::widgets::select_all_on_focus(ui, &r, &app.ui.page_filter);
|
||||
if r.has_focus() {
|
||||
ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, t.accent), egui::StrokeKind::Inside);
|
||||
}
|
||||
if app.ui.page_search && ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
app.ui.page_search = false;
|
||||
app.ui.page_filter.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// The Pages list has the keyboard: a page row (or the add-page button) of the open file was
|
||||
/// clicked last, and the list is still on screen.
|
||||
pub(crate) fn pages_have_keyboard(app: &FigApp) -> bool {
|
||||
@@ -688,8 +657,10 @@ fn set_expanded_deep(doc: &Document, id: NodeId, open: bool, expanded: &mut BTre
|
||||
}
|
||||
}
|
||||
|
||||
/// The search row under the Layers header (live filter; Escape closes it).
|
||||
fn search_field(app: &mut FigApp, ui: &mut Ui, t: &Tokens, w: f32, id: Id) {
|
||||
/// The search row under the Pages or Layers header: a live filter `text`, taking the keyboard when
|
||||
/// `focus` (just opened). Escape in it clears it and closes the row (`open`), and goes no further
|
||||
/// (the canvas keeps its selection).
|
||||
fn search_field(ui: &mut Ui, t: &Tokens, w: f32, id: Id, hint: &str, text: &mut String, open: &mut bool, focus: bool) {
|
||||
let (fr, _) = ui.allocate_exact_size(vec2(w, 32.0), Sense::hover());
|
||||
let field = Rect::from_min_max(pos2(fr.min.x + 12.0, fr.min.y + 2.0), pos2(fr.max.x - 12.0, fr.max.y - 6.0));
|
||||
ui.painter().rect_filled(field, CornerRadius::same(5), t.field);
|
||||
@@ -697,21 +668,25 @@ fn search_field(app: &mut FigApp, ui: &mut Ui, t: &Tokens, w: f32, id: Id) {
|
||||
let te_rect = Rect::from_min_max(pos2(field.min.x + 24.0, field.center().y - 7.0), pos2(field.max.x - 6.0, field.center().y + 7.0));
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(te_rect));
|
||||
let r = child.add(
|
||||
egui::TextEdit::singleline(&mut app.ui.layer_filter)
|
||||
egui::TextEdit::singleline(text)
|
||||
.id(id)
|
||||
.frame(egui::Frame::NONE)
|
||||
.font(regular(11.0))
|
||||
.hint_text(tr("Find layers"))
|
||||
.hint_text(hint)
|
||||
.vertical_align(egui::Align::Center)
|
||||
.desired_width(te_rect.width()),
|
||||
);
|
||||
crate::widgets::select_all_on_focus(ui, &r, &app.ui.layer_filter);
|
||||
// After the field: the click that opened it ends elsewhere, which would take the focus back.
|
||||
if focus {
|
||||
r.request_focus();
|
||||
}
|
||||
crate::widgets::select_all_on_focus(ui, &r, text);
|
||||
if r.has_focus() {
|
||||
ui.painter().rect_stroke(field, CornerRadius::same(5), Stroke::new(1.0, t.accent), egui::StrokeKind::Inside);
|
||||
}
|
||||
if (r.has_focus() || r.lost_focus()) && ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
app.ui.layer_search = false;
|
||||
app.ui.layer_filter.clear();
|
||||
if (r.has_focus() || r.lost_focus()) && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
|
||||
*open = false;
|
||||
text.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,12 +705,9 @@ fn layers(app: &mut FigApp, ui: &mut Ui) {
|
||||
if sresp.clicked() {
|
||||
app.ui.layer_search = !app.ui.layer_search;
|
||||
app.ui.layer_filter.clear();
|
||||
if app.ui.layer_search {
|
||||
ui.memory_mut(|m| m.request_focus(search_id));
|
||||
}
|
||||
}
|
||||
if app.ui.layer_search {
|
||||
search_field(app, ui, &t, w, search_id);
|
||||
search_field(ui, &t, w, search_id, tr("Find layers"), &mut app.ui.layer_filter, &mut app.ui.layer_search, sresp.clicked());
|
||||
}
|
||||
let Ok(ds) = app.session.ds() else { return };
|
||||
let doc = ds.doc.clone();
|
||||
|
||||
@@ -217,16 +217,34 @@ fn pages_search_button_opens_the_filter_and_escape_clears_it() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pages_search_escape_closes_even_when_the_field_is_unfocused() {
|
||||
let mut u = Ui::new(Session::new());
|
||||
fn pages_search_leaves_escape_to_the_canvas_when_the_field_is_unfocused() {
|
||||
let mut s = Session::new();
|
||||
let r = rect(&mut s, "r", 0.0, None);
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
u.app.ui.page_search = true;
|
||||
u.app.ui.page_filter = "COMPON".into();
|
||||
u.frame(vec![]);
|
||||
|
||||
u.frame(vec![Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }]);
|
||||
|
||||
assert!(!u.app.ui.page_search, "Escape closes page search without field focus");
|
||||
assert!(u.app.ui.page_filter.is_empty(), "Escape clears the filter without field focus");
|
||||
assert!(u.app.session.selection().is_empty(), "Escape deselects on the canvas");
|
||||
assert!(u.app.ui.page_search, "the page search stays open");
|
||||
assert_eq!(u.app.ui.page_filter, "COMPON", "and keeps its filter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_in_the_pages_search_does_not_reach_the_canvas() {
|
||||
let mut s = Session::new();
|
||||
let r = rect(&mut s, "r", 0.0, None);
|
||||
s.execute("select.set", &json!({"ids": [r]})).unwrap();
|
||||
let mut u = Ui::new(s);
|
||||
click(&mut u, Pos2::new(246.0, 124.0), Modifiers::NONE);
|
||||
assert!(u.app.ui.page_search);
|
||||
u.frame(vec![Event::Text("x".into())]);
|
||||
u.frame(vec![Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }]);
|
||||
assert!(!u.app.ui.page_search, "Escape closes the page search");
|
||||
assert_eq!(u.app.session.selection(), vec![r], "and leaves the selection alone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user