From 927ceb06383a15f4a9e38dc4dbc1bd3caec09569 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 28 Oct 2026 16:00:05 +0000 Subject: [PATCH] refactor(ui): simplify panels and menus --- crates/ui-egui/src/menus.rs | 10 +- crates/ui-egui/src/menus/clipboard.rs | 2 +- crates/ui-egui/src/menus/palette.rs | 5 +- crates/ui-egui/src/menus/select_layer.rs | 24 +- crates/ui-egui/src/menus/tree.rs | 26 +- crates/ui-egui/src/panels/component_panel.rs | 2 +- crates/ui-egui/src/panels/export_panel.rs | 2 +- crates/ui-egui/src/panels/layers.rs | 485 ++++++++++-------- crates/ui-egui/src/panels/layout_panel.rs | 2 +- crates/ui-egui/src/panels/proto_editor.rs | 68 ++- crates/ui-egui/src/panels/proto_panel.rs | 75 ++- crates/ui-egui/src/panels/right.rs | 6 +- crates/ui-egui/src/panels/right/effects.rs | 6 +- crates/ui-egui/src/panels/right/inspect.rs | 5 +- crates/ui-egui/src/panels/right/menu.rs | 2 +- crates/ui-egui/src/panels/right/paints.rs | 6 +- crates/ui-egui/src/panels/right/stroke.rs | 5 +- crates/ui-egui/src/panels/typography.rs | 5 +- crates/ui-egui/src/panels/typography/axes.rs | 5 +- .../ui-egui/src/panels/typography/fields.rs | 6 +- .../ui-egui/src/panels/typography/glyphs.rs | 5 +- .../ui-egui/src/panels/typography/picker.rs | 2 +- .../ui-egui/src/panels/typography/spacing.rs | 5 +- 23 files changed, 463 insertions(+), 296 deletions(-) diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 549a55c0..b7785e08 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -27,7 +27,7 @@ pub use select_layer::open_select_layer; use select_layer::{SELECT_LAYER, select_layer_ask_id, select_layer_menu}; #[cfg(test)] pub(crate) use tree::{AIM_REST, HOVER_DELAY, heading_for, in_triangle}; -use tree::{At, menu_frame, plain_row_hover, set_open, submenu_at}; +use tree::{At, SubmenuOptions, menu_frame, plain_row_hover, set_open, submenu_at}; pub(crate) use tree::{forget_tree, item, separator, submenu}; /// Commands the UI layer implements itself (dialogs and platform actions). @@ -403,8 +403,12 @@ pub fn popups(app: &mut FigApp, ctx: &egui::Context) { } ui.add_space(6.0); for top in TOP_MENUS { - if submenu_at(ui, Id::unique(("menu", *top)), top, tr(top), 12.0, false, &t, |ui| menu_level(app, ui, top).then_some(())) - .is_some() + if submenu_at( + ui, + SubmenuOptions { area: Id::unique(("menu", *top)), key: top, label: tr(top), gap: 12.0, constrain: false, tokens: &t }, + |ui| menu_level(app, ui, top).then_some(()), + ) + .is_some() { close = true; } diff --git a/crates/ui-egui/src/menus/clipboard.rs b/crates/ui-egui/src/menus/clipboard.rs index dd775297..3089ee3e 100644 --- a/crates/ui-egui/src/menus/clipboard.rs +++ b/crates/ui-egui/src/menus/clipboard.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{FigApp, dev_mode_refuses, json, trn}; /// Run a menu item: some open dialogs or use platform services, the rest go to the engine. /// Paste: the system clipboard wins when it holds something newer than our own copy (layers copied diff --git a/crates/ui-egui/src/menus/palette.rs b/crates/ui-egui/src/menus/palette.rs index 4890f8ad..d5f7e810 100644 --- a/crates/ui-egui/src/menus/palette.rs +++ b/crates/ui-egui/src/menus/palette.rs @@ -1,4 +1,7 @@ -use super::*; +use super::{ + Align2, Color32, CornerRadius, FigApp, Id, MENU, Rect, SEP, Sense, Stroke, StrokeKind, Tokens, Ui, available, i18n, invoke, json, pos2, + pretty_shortcut, record_recent, regular, tr, vec2, +}; /// The Actions palette's tab. #[derive(Clone, Copy, PartialEq, Eq, Default)] diff --git a/crates/ui-egui/src/menus/select_layer.rs b/crates/ui-egui/src/menus/select_layer.rs index 59389c41..73571f38 100644 --- a/crates/ui-egui/src/menus/select_layer.rs +++ b/crates/ui-egui/src/menus/select_layer.rs @@ -1,5 +1,5 @@ -use super::tree::{indented_item, separator, submenu_at}; -use super::*; +use super::tree::{SubmenuOptions, indented_item, separator, submenu_at}; +use super::{FigApp, Id, Tokens, Ui, json, tr}; /// The context menu's Select layer submenu (its key in the context menu's tree). pub(super) const SELECT_LAYER: &str = "Select layer"; @@ -35,15 +35,19 @@ pub(super) fn select_layer_menu(app: &mut FigApp, ui: &mut Ui, pos: egui::Pos2, return false; } let sel = ds.selection.clone(); - let picked = submenu_at(ui, Id::unique("ctx_select_layer"), SELECT_LAYER, tr("Select layer"), 4.0, true, t, |ui| { - let mut picked = None; - for (id, depth, name) in &layers { - if indented_item(ui, name, "", true, sel.contains(id), 12.0 * (*depth).min(8) as f32, t) { - picked = Some(*id); + let picked = submenu_at( + ui, + SubmenuOptions { area: Id::unique("ctx_select_layer"), key: SELECT_LAYER, label: tr("Select layer"), gap: 4.0, constrain: true, tokens: t }, + |ui| { + let mut picked = None; + for (id, depth, name) in &layers { + if indented_item(ui, name, "", true, sel.contains(id), 12.0 * (*depth).min(8) as f32, t) { + picked = Some(*id); + } } - } - picked - }); + picked + }, + ); separator(ui); match picked { Some(id) => { diff --git a/crates/ui-egui/src/menus/tree.rs b/crates/ui-egui/src/menus/tree.rs index 4f181b3f..f0c64550 100644 --- a/crates/ui-egui/src/menus/tree.rs +++ b/crates/ui-egui/src/menus/tree.rs @@ -1,10 +1,10 @@ -use super::*; +use super::{Color32, CornerRadius, Icon, Id, Rect, Sense, Stroke, Tokens, Ui, a11y, i18n, icons, pos2, pretty_shortcut, regular, vec2}; /// A submenu row `label` in the menu being drawn; while it's open, `add` draws its items beside /// it. `key` names the submenu (its menu path, unique in its menu tree). Returns what `add` /// returned. pub(crate) fn submenu(ui: &mut Ui, key: &str, label: &str, t: &Tokens, add: impl FnOnce(&mut Ui) -> Option) -> Option { - submenu_at(ui, Id::unique(("submenu", key)), key, label, 4.0, false, t, add) + submenu_at(ui, SubmenuOptions { area: Id::unique(("submenu", key)), key, label, gap: 4.0, constrain: false, tokens: t }, add) } // --------------------------------------------------------------------------------------------- @@ -240,17 +240,17 @@ pub(super) fn plain_row_hover(ui: &Ui, resp: &egui::Response) -> bool { } /// A submenu row whose panel (area `area`, `gap` right of the row) shows while it's open. -#[allow(clippy::too_many_arguments)] -pub(super) fn submenu_at( - ui: &mut Ui, - area: Id, - key: &str, - label: &str, - gap: f32, - constrain: bool, - t: &Tokens, - add: impl FnOnce(&mut Ui) -> Option, -) -> Option { +pub(super) struct SubmenuOptions<'a> { + pub(super) area: Id, + pub(super) key: &'a str, + pub(super) label: &'a str, + pub(super) gap: f32, + pub(super) constrain: bool, + pub(super) tokens: &'a Tokens, +} + +pub(super) fn submenu_at(ui: &mut Ui, options: SubmenuOptions<'_>, add: impl FnOnce(&mut Ui) -> Option) -> Option { + let SubmenuOptions { area, key, label, gap, constrain, tokens: t } = options; let at = level_at(ui); let aim = aiming(ui); let w = ui.available_width(); diff --git a/crates/ui-egui/src/panels/component_panel.rs b/crates/ui-egui/src/panels/component_panel.rs index 54bc0974..0ab3e5cc 100644 --- a/crates/ui-egui/src/panels/component_panel.rs +++ b/crates/ui-egui/src/panels/component_panel.rs @@ -11,7 +11,7 @@ //! (preferred ones first), select it, reset it to the main's default content. use egui::{Id, Sense, Ui, vec2}; -use figcraft_doc::*; +use figcraft_doc::{Document, Node, NodeId, NodeKind, PropDef, VariableType}; use figcraft_engine::cmd::component::{can_add_variant, can_combine_variants}; use figcraft_engine::components::{ PROP_BINDING, default_variant, inside_instance, owning_component, owning_instance, slot_def, slot_edited, slots_of, variant_conflicts, diff --git a/crates/ui-egui/src/panels/export_panel.rs b/crates/ui-egui/src/panels/export_panel.rs index 5a9c4ae2..cec8ebc0 100644 --- a/crates/ui-egui/src/panels/export_panel.rs +++ b/crates/ui-egui/src/panels/export_panel.rs @@ -6,7 +6,7 @@ use std::collections::BTreeSet; use egui::{Align2, Color32, CornerRadius, Id, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2}; -use figcraft_doc::*; +use figcraft_doc::{Document, ExportFormat, ExportSetting, ExportSize, NodeId, NodeKind, PrintSettings, VdCompat}; use figcraft_engine::cmd::file::export_jobs; use serde_json::{Value, json}; diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index b5217678..1be4d28c 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -728,6 +728,259 @@ fn search_field(ui: &mut Ui, t: &Tokens, w: f32, id: Id, hint: &str, text: &mut } } +/// Dragging near the list's top or bottom edge scrolls it, faster closer to the edge. +fn drag_autoscroll(ui: &mut Ui, dragging: bool) { + if dragging && let Some(p) = ui.input(|i| i.pointer.hover_pos()) { + const EDGE: f32 = 28.0; + let view = ui.clip_rect(); + let dy = if p.y < view.min.y + EDGE { + (view.min.y + EDGE - p.y).min(EDGE) + } else if p.y > view.max.y - EDGE { + -(p.y - (view.max.y - EDGE)).min(EDGE) + } else { + 0.0 + }; + if dy != 0.0 && view.x_range().contains(p.x) { + ui.scroll_with_delta(vec2(0.0, dy * 0.5)); + ui.ctx().request_repaint(); + } + } +} + +/// A row's icon or name colour: the component tint (faded when hidden), else `normal` or `faded`. +fn row_color(component: bool, shown: bool, normal: Color32, faded: Color32) -> Color32 { + if component { + COMPONENT.gamma_multiply(if shown { 1.0 } else { 0.5 }) + } else if shown { + normal + } else { + faded + } +} + +/// The pointer `p` over row `rects` = [row, padded row]: the drop zone it shows (-1 above, 0 into, +/// 1 below), whether holding there should open the collapsed row, and whether the button was released. +fn drop_zone(ui: &mut Ui, p: egui::Pos2, rects: [Rect; 2], x0: f32, id: NodeId, collapsed: bool, n: &figcraft_doc::Node) -> (i8, bool, bool) { + let [r, rr] = rects; + let mut open = false; + let rel = (p.y - r.min.y) / r.height(); + let into = n.kind.is_container() && !matches!(n.kind, NodeKind::Instance { .. }) && (0.25..0.75).contains(&rel); + let zone: i8 = if into { + 0 + } else if rel < 0.5 { + -1 + } else { + 1 + }; + let y = match zone { + -1 => r.min.y, + 1 => r.max.y, + _ => r.center().y, + }; + if zone == 0 { + ui.painter().rect_stroke(rr, CornerRadius::same(5), Stroke::new(2.0, ACCENT), egui::StrokeKind::Inside); + // Holding over a collapsed group or frame opens it, to drop deeper inside. + if collapsed { + open = hold_opens(ui, id); + ui.ctx().request_repaint_after(std::time::Duration::from_millis(100)); + } + } else { + crate::widgets::insertion_line(ui.painter(), x0, rr.max.x, y); + } + (zone, open, ui.input(|i| i.pointer.any_released())) +} + +/// True once the pointer has stayed over row `id` for 0.6 s (the timer then restarts). +fn hold_opens(ui: &mut Ui, id: NodeId) -> bool { + let hold_id = Id::unique("layer_drag_hold"); + let now = ui.input(|i| i.time); + match ui.data(|d| d.get_temp::<(NodeId, f64)>(hold_id)) { + Some((held, since)) if held == id => { + if now - since > 0.6 { + ui.data_mut(|d| d.remove::<(NodeId, f64)>(hold_id)); + return true; + } + } + _ => { + ui.data_mut(|d| d.insert_temp(hold_id, (id, now))); + } + } + false +} + +/// Applies a finished sweep across the eyes or padlocks, or keeps it going while the button is down. +fn finish_sweep(app: &mut FigApp, ui: &mut Ui, sw: Sweep) { + let (released, down) = ui.input(|i| (i.pointer.any_released(), i.pointer.primary_down())); + if released { + let (cmd, key) = if sw.lock { ("object.toggleLock", "locked") } else { ("object.toggleVisible", "visible") }; + // A sweep started in the design can end after a switch to Dev Mode. + if !crate::menus::dev_mode_refuses(app, cmd) { + app.run(cmd, json!({"ids": sw.ids, key: if sw.lock { sw.on } else { !sw.on }})); + } + } + // A sweep whose release the panel never saw (it wasn't shown) is dropped. + if released || !down { + ui.data_mut(|d| d.remove::(sweep_id())); + } else { + ui.data_mut(|d| d.insert_temp(sweep_id(), sw)); + } +} + +/// A click on row `id`: plain selects it, Shift extends from the anchor, Cmd (or Shift without an +/// anchor) toggles it. +fn select_clicked(app: &mut FigApp, rows: &[Row], sel: &[NodeId], id: NodeId, m: egui::Modifiers) { + app.ui.layers_focus = true; + app.ui.pages_focus = None; + let order: Vec = rows.iter().map(|r| r.id).collect(); + let pos = |x: NodeId| order.iter().position(|o| *o == x); + let anchor = app.ui.layer_anchor.filter(|a| sel.contains(a)).and_then(pos); + if m.shift + && let (Some(ai), Some(bi)) = (anchor, pos(id)) + { + // Shift: the visible rows from the anchor to here (added to the selection with Cmd). + let (lo, hi) = (ai.min(bi), ai.max(bi)); + let mut s: Vec = if m.command { sel.to_vec() } else { vec![] }; + for x in order.get(lo..=hi).unwrap_or_default() { + if !s.contains(x) { + s.push(*x); + } + } + app.run("select.set", json!({"ids": s})); + } else if m.shift || m.command { + let mut s = sel.to_vec(); + if let Some(pos) = s.iter().position(|x| *x == id) { + s.remove(pos); + } else { + s.push(id); + } + app.ui.layer_anchor = Some(id); + app.run("select.set", json!({"ids": s})); + } else { + app.ui.layer_anchor = Some(id); + app.run("select.set", json!({"ids": [id]})); + } + // A click in the panel needs no reveal scroll. + app.ui.layers_seen_sel = app.session.selection(); + if let Ok(ds) = app.session.ds_mut() + && ds.text_edit.is_some() + { + let _ = figcraft_engine::textedit::end(&mut app.session); + } +} + +/// Drops `moving` on `target` (`zone`: 0 into it, -1 above, 1 below), unless Dev Mode refuses +/// (a drag started in the design can end after a switch to it). +fn drop_layers(app: &mut FigApp, doc: &Document, moving: &[NodeId], target: NodeId, zone: i8) { + if crate::menus::dev_mode_refuses(app, "node.reparent") { + return; + } + let ids = moving.to_vec(); + match zone { + 0 => { + app.run("node.reparent", json!({"ids": ids, "parent": target})); + } + z => { + // Above in the list = higher z = after the target among siblings. + if let Some(parent) = doc.node(target).and_then(|n| n.parent) { + let idx = doc.index_in_parent(target).unwrap_or(0); + let gap = if z < 0 { idx + 1 } else { idx }; + // `index` is the final position: the dragged layers below the gap leave it. + let below = doc.node(parent).map_or(0, |p| p.children.iter().take(gap).filter(|c| ids.contains(c)).count()); + app.run("node.reparent", json!({"ids": ids, "parent": parent, "index": gap - below})); + } + } + } +} + +/// The eye and padlock of a row: painted when hovered (`over`, false in Dev Mode) or set, and, outside +/// Dev Mode, clickable; a drag over them starts a sweep. Returns the layers whose eye / padlock was clicked. +fn eye_lock( + ui: &mut Ui, + t: &Tokens, + rects: [Rect; 2], + row: &Row, + n: &figcraft_doc::Node, + over: bool, + dev: bool, + sweep: &mut Option, +) -> (Option, Option) { + let [r, rr] = rects; + let (mut vis, mut lock) = (None, None); + // A sweep across the eyes or padlocks takes in the rows the pointer passes. + if let Some(sw) = sweep.as_mut() + && !sw.ids.contains(&row.id) + && ui.input(|i| i.pointer.hover_pos()).is_some_and(|p| r.y_range().contains(p.y)) + { + sw.ids.push(row.id); + } + let swept = |lock: bool| sweep.as_ref().filter(|sw| sw.lock == lock && sw.ids.contains(&row.id)).map(|sw| sw.on); + // Lock / eye on hover (or when set); a layer in a locked one shows a faint padlock. + let hidden = swept(false).unwrap_or(!n.visible); + let locked = swept(true).unwrap_or(n.locked); + // In Dev Mode they only show what is set. + let show_lock = over || locked || row.locked_above; + let show_eye = over || hidden; + let er = Rect::from_center_size(pos2(rr.max.x - 14.0, r.center().y), vec2(20.0, 20.0)); + let lr = Rect::from_center_size(pos2(rr.max.x - 36.0, r.center().y), vec2(20.0, 20.0)); + if show_eye { + icons::paint(ui.painter(), er.shrink(3.0), if hidden { Icon::EyeOff } else { Icon::Eye }, t.icon_secondary); + } + if show_eye && !dev { + let eresp = ui.interact(er, ui.scope_id().with(("eye", row.id)), Sense::click_and_drag()); + a11y::toggle(&eresp, tr("Hidden"), hidden); + if eresp.clicked() { + vis = Some(row.id); + } + if eresp.drag_started() { + *sweep = Some(Sweep { lock: false, on: n.visible, ids: vec![row.id] }); + } + } + if show_lock { + let col = if locked || !row.locked_above { t.icon_secondary } else { t.text_tertiary }; + icons::paint(ui.painter(), lr.shrink(3.0), if locked || row.locked_above { Icon::Lock } else { Icon::Unlock }, col); + } + if show_lock && !dev { + let lresp = ui.interact(lr, ui.scope_id().with(("lock", row.id)), Sense::click_and_drag()); + a11y::toggle(&lresp, tr("Locked"), locked || row.locked_above); + if lresp.clicked() { + lock = Some(row.id); + } + if lresp.drag_started() { + *sweep = Some(Sweep { lock: true, on: !n.locked, ids: vec![row.id] }); + } + } + (vis, lock) +} + +/// Expands the ancestors of a selection not seen yet (once, so manual collapsing sticks); whether it was new. +fn reveal_selection(app: &mut FigApp, doc: &Document, page: NodeId, sel: &[NodeId]) -> bool { + let reveal = sel != app.ui.layers_seen_sel; + if reveal { + for s in sel { + for a in doc.ancestors(*s) { + if a != page { + app.ui.expanded.insert(a); + } + } + } + app.ui.layers_seen_sel = sel.to_vec(); + } + reveal +} + +/// A row's background: selected, inside a selected layer, or outlined when hovered. +/// `state` is [selected, in_selected, component]. +fn paint_row_bg(ui: &Ui, t: &Tokens, rr: Rect, state: [bool; 3], hovered: bool) { + let [selected, in_selected, is_comp] = state; + if selected { + ui.painter().rect_filled(rr, CornerRadius::same(5), if is_comp { t.component_row } else { t.selected_row }); + } else if in_selected { + ui.painter().rect_filled(rr, CornerRadius::ZERO, t.selected_row_child); + } else if hovered { + ui.painter().rect_stroke(rr, CornerRadius::same(5), Stroke::new(1.0, if is_comp { COMPONENT } else { ACCENT }), egui::StrokeKind::Inside); + } +} + /// Draw the layer tree with search, cached rows, reveal-selection expansion and scrolling, /// rename/Tab navigation, selection and drag/drop, sweep toggles and Dev Mode restrictions. fn layers(app: &mut FigApp, ui: &mut Ui) { @@ -761,17 +1014,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { let hover = ds.hover; // Reveal a new selection: expand its ancestors and scroll its row into view, once, so // "Collapse layers" and manual collapsing stick. - let reveal = sel != app.ui.layers_seen_sel; - if reveal { - for s in &sel { - for a in doc.ancestors(*s) { - if a != page { - app.ui.expanded.insert(a); - } - } - } - app.ui.layers_seen_sel = sel.clone(); - } + let reveal = reveal_selection(app, &doc, page, &sel); let filter = app.ui.layer_filter.trim().to_lowercase(); let rows = rows_for(ui, &doc, page, &app.ui.expanded, &filter); let mut scroll_to = if reveal { sel.first().and_then(|s| rows.iter().position(|r| r.id == *s)) } else { None }; @@ -831,24 +1074,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { ui.add(egui::Label::new(egui::RichText::new(hint).font(regular(11.0)).color(t.text_secondary)).wrap()); }); } - // Dragging near the list's top or bottom edge scrolls it, faster closer to the edge. - if dragging.is_some() - && let Some(p) = ui.input(|i| i.pointer.hover_pos()) - { - const EDGE: f32 = 28.0; - let view = ui.clip_rect(); - let dy = if p.y < view.min.y + EDGE { - (view.min.y + EDGE - p.y).min(EDGE) - } else if p.y > view.max.y - EDGE { - -(p.y - (view.max.y - EDGE)).min(EDGE) - } else { - 0.0 - }; - if dy != 0.0 && view.x_range().contains(p.x) { - ui.scroll_with_delta(vec2(0.0, dy * 0.5)); - ui.ctx().request_repaint(); - } - } + drag_autoscroll(ui, dragging.is_some()); let first = ((viewport.min.y / pitch).floor().max(0.0) as usize).min(rows.len()); let last = ((viewport.max.y / pitch).ceil().max(0.0) as usize).saturating_add(1).min(rows.len()); let band = Rect::from_x_y_ranges(ui.max_rect().x_range(), (top + first as f32 * pitch)..=(top + last as f32 * pitch)); @@ -879,18 +1105,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { a11y::ring_around(ui, rr, &resp); // Over the row, its eye and padlock included (they sit on top of it). let over = resp.contains_pointer(); - if selected { - ui.painter().rect_filled(rr, CornerRadius::same(5), if is_comp { t.component_row } else { t.selected_row }); - } else if in_selected { - ui.painter().rect_filled(rr, CornerRadius::ZERO, t.selected_row_child); - } else if over || hover == Some(row.id) { - ui.painter().rect_stroke( - rr, - CornerRadius::same(5), - Stroke::new(1.0, if is_comp { COMPONENT } else { ACCENT }), - egui::StrokeKind::Inside, - ); - } + paint_row_bg(ui, &t, rr, [selected, in_selected, is_comp], over || hover == Some(row.id)); if over { hovered_row = Some(row.id); } @@ -912,13 +1127,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { } let icon = node_icon(&doc, row.id); let shown = n.visible && !row.dimmed; - let icon_col = if is_comp || icon == Icon::Slot { - COMPONENT.gamma_multiply(if shown { 1.0 } else { 0.5 }) - } else if shown { - t.icon - } else { - t.text_tertiary - }; + let icon_col = row_color(is_comp || icon == Icon::Slot, shown, t.icon, t.text_tertiary); icons::paint(ui.painter(), Rect::from_center_size(pos2(x0 + 22.0, r.center().y), vec2(14.0, 14.0)), icon, icon_col); let name_x = x0 + 36.0; if app.ui.rename.is_some_and(|rn| rn.id == row.id) { @@ -926,68 +1135,18 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { rename_done = Some((row.id, new)); } } else { - let col = if is_comp { - COMPONENT.gamma_multiply(if shown { 1.0 } else { 0.5 }) - } else if shown { - t.text - } else { - t.text_tertiary - }; - let font = if doc.node(n.parent.unwrap_or(page)).is_some_and(|p| p.is_page()) - && (n.kind.is_frame_like() || matches!(n.kind, NodeKind::Section { .. })) - { - semibold(11.0) - } else { - regular(11.0) - }; + let col = row_color(is_comp, shown, t.text, t.text_tertiary); + let top_level = doc.node(n.parent.unwrap_or(page)).is_some_and(|p| p.is_page()); + let font = + if top_level && (n.kind.is_frame_like() || matches!(n.kind, NodeKind::Section { .. })) { semibold(11.0) } else { regular(11.0) }; let name = if n.name.is_empty() { tr(n.kind.label()).to_string() } else { n.name.clone() }; // Room left of the lock and eye buttons. let max_w = (rr.max.x - 56.0 - name_x).max(20.0); crate::bidi::paint_name(ui.painter(), pos2(name_x, r.center().y), &name, font, col, max_w); } - // A sweep across the eyes or padlocks takes in the rows the pointer passes. - if let Some(sw) = sweep.as_mut() - && !sw.ids.contains(&row.id) - && ui.input(|i| i.pointer.hover_pos()).is_some_and(|p| r.y_range().contains(p.y)) - { - sw.ids.push(row.id); - } - let swept = |lock: bool| sweep.as_ref().filter(|sw| sw.lock == lock && sw.ids.contains(&row.id)).map(|sw| sw.on); - // Lock / eye on hover (or when set); a layer in a locked one shows a faint padlock. - let hidden = swept(false).unwrap_or(!n.visible); - let locked = swept(true).unwrap_or(n.locked); - // In Dev Mode they only show what is set. - let show_lock = (over && !dev) || locked || row.locked_above; - let show_eye = (over && !dev) || hidden; - let er = Rect::from_center_size(pos2(rr.max.x - 14.0, r.center().y), vec2(20.0, 20.0)); - let lr = Rect::from_center_size(pos2(rr.max.x - 36.0, r.center().y), vec2(20.0, 20.0)); - if show_eye { - icons::paint(ui.painter(), er.shrink(3.0), if hidden { Icon::EyeOff } else { Icon::Eye }, t.icon_secondary); - } - if show_eye && !dev { - let eresp = ui.interact(er, ui.scope_id().with(("eye", row.id)), Sense::click_and_drag()); - a11y::toggle(&eresp, tr("Hidden"), hidden); - if eresp.clicked() { - vis = Some(row.id); - } - if eresp.drag_started() { - sweep = Some(Sweep { lock: false, on: n.visible, ids: vec![row.id] }); - } - } - if show_lock { - let col = if locked || !row.locked_above { t.icon_secondary } else { t.text_tertiary }; - icons::paint(ui.painter(), lr.shrink(3.0), if locked || row.locked_above { Icon::Lock } else { Icon::Unlock }, col); - } - if show_lock && !dev { - let lresp = ui.interact(lr, ui.scope_id().with(("lock", row.id)), Sense::click_and_drag()); - a11y::toggle(&lresp, tr("Locked"), locked || row.locked_above); - if lresp.clicked() { - lock = Some(row.id); - } - if lresp.drag_started() { - sweep = Some(Sweep { lock: true, on: !n.locked, ids: vec![row.id] }); - } - } + let (v, l) = eye_lock(ui, &t, [r, rr], row, n, over && !dev, dev, &mut sweep); + vis = v.or(vis); + lock = l.or(lock); if resp.clicked() { clicked = Some((row.id, ui.input(|i| i.modifiers))); } @@ -1016,43 +1175,12 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { && let Some(p) = ui.input(|i| i.pointer.hover_pos()) && r.contains(p) { - let rel = (p.y - r.min.y) / r.height(); - let into = n.kind.is_container() && !matches!(n.kind, NodeKind::Instance { .. }) && (0.25..0.75).contains(&rel); - let zone: i8 = if into { - 0 - } else if rel < 0.5 { - -1 - } else { - 1 - }; - let y = match zone { - -1 => r.min.y, - 1 => r.max.y, - _ => r.center().y, - }; - if zone == 0 { - ui.painter().rect_stroke(rr, CornerRadius::same(5), Stroke::new(2.0, ACCENT), egui::StrokeKind::Inside); - // Holding over a collapsed group or frame opens it, to drop deeper inside. - if row.has_kids && !app.ui.expanded.contains(&row.id) { - let hold_id = Id::unique("layer_drag_hold"); - let now = ui.input(|i| i.time); - match ui.data(|d| d.get_temp::<(NodeId, f64)>(hold_id)) { - Some((id, since)) if id == row.id => { - if now - since > 0.6 { - toggle = Some(row.id); - ui.data_mut(|d| d.remove::<(NodeId, f64)>(hold_id)); - } - } - _ => { - ui.data_mut(|d| d.insert_temp(hold_id, (row.id, now))); - } - } - ui.ctx().request_repaint_after(std::time::Duration::from_millis(100)); - } - } else { - crate::widgets::insertion_line(ui.painter(), x0, rr.max.x, y); + let collapsed = row.has_kids && !app.ui.expanded.contains(&row.id); + let (zone, open, released) = drop_zone(ui, p, [r, rr], x0, row.id, collapsed, n); + if open { + toggle = Some(row.id); } - if ui.input(|i| i.pointer.any_released()) { + if released { drop = Some((row.id, zone)); } } @@ -1087,46 +1215,15 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { } } if let Some(sw) = sweep { - let (released, down) = ui.input(|i| (i.pointer.any_released(), i.pointer.primary_down())); - if released { - let (cmd, key) = if sw.lock { ("object.toggleLock", "locked") } else { ("object.toggleVisible", "visible") }; - // A sweep started in the design can end after a switch to Dev Mode. - if !crate::menus::dev_mode_refuses(app, cmd) { - app.run(cmd, json!({"ids": sw.ids, key: if sw.lock { sw.on } else { !sw.on }})); - } - } - // A sweep whose release the panel never saw (it wasn't shown) is dropped. - if released || !down { - ui.data_mut(|d| d.remove::(sweep_id())); - } else { - ui.data_mut(|d| d.insert_temp(sweep_id(), sw)); - } + finish_sweep(app, ui, sw); } if ui.input(|i| i.pointer.any_released()) { ui.data_mut(|d| { d.remove::(drag_id); d.remove::<(NodeId, f64)>(Id::unique("layer_drag_hold")); }); - // A drag started in the design can end after a switch to Dev Mode. - if let (Some(_), Some((target, zone))) = (dragging, drop) - && !crate::menus::dev_mode_refuses(app, "node.reparent") - { - let ids = moving.clone(); - match zone { - 0 => { - app.run("node.reparent", json!({"ids": ids, "parent": target})); - } - z => { - // Above in the list = higher z = after the target among siblings. - if let Some(parent) = doc.node(target).and_then(|n| n.parent) { - let idx = doc.index_in_parent(target).unwrap_or(0); - let gap = if z < 0 { idx + 1 } else { idx }; - // `index` is the final position: the dragged layers below the gap leave it. - let below = doc.node(parent).map_or(0, |p| p.children.iter().take(gap).filter(|c| ids.contains(c)).count()); - app.run("node.reparent", json!({"ids": ids, "parent": parent, "index": gap - below})); - } - } - } + if let (Some(_), Some((target, zone))) = (dragging, drop) { + drop_layers(app, &doc, &moving, target, zone); } } if let Ok(ds) = app.session.ds_mut() { @@ -1159,43 +1256,7 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { a11y::refocus_row(&mut app.ui.nav); } if let Some((id, m)) = clicked { - app.ui.layers_focus = true; - app.ui.pages_focus = None; - let order: Vec = rows.iter().map(|r| r.id).collect(); - let pos = |x: NodeId| order.iter().position(|o| *o == x); - let anchor = app.ui.layer_anchor.filter(|a| sel.contains(a)).and_then(pos); - if m.shift - && let (Some(ai), Some(bi)) = (anchor, pos(id)) - { - // Shift: the visible rows from the anchor to here (added to the selection with Cmd). - let (lo, hi) = (ai.min(bi), ai.max(bi)); - let mut s: Vec = if m.command { sel.clone() } else { vec![] }; - for x in order.get(lo..=hi).unwrap_or_default() { - if !s.contains(x) { - s.push(*x); - } - } - app.run("select.set", json!({"ids": s})); - } else if m.shift || m.command { - let mut s = sel.clone(); - if let Some(pos) = s.iter().position(|x| *x == id) { - s.remove(pos); - } else { - s.push(id); - } - app.ui.layer_anchor = Some(id); - app.run("select.set", json!({"ids": s})); - } else { - app.ui.layer_anchor = Some(id); - app.run("select.set", json!({"ids": [id]})); - } - // A click in the panel needs no reveal scroll. - app.ui.layers_seen_sel = app.session.selection(); - if let Ok(ds) = app.session.ds_mut() - && ds.text_edit.is_some() - { - let _ = figcraft_engine::textedit::end(&mut app.session); - } + select_clicked(app, &rows, &sel, id, m); } let _ = Color32::TRANSPARENT; } diff --git a/crates/ui-egui/src/panels/layout_panel.rs b/crates/ui-egui/src/panels/layout_panel.rs index 0874da2f..c958007a 100644 --- a/crates/ui-egui/src/panels/layout_panel.rs +++ b/crates/ui-egui/src/panels/layout_panel.rs @@ -5,7 +5,7 @@ //! commands. use egui::{Align2, CornerRadius, Id, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2}; -use figcraft_doc::*; +use figcraft_doc::{AutoLayout, CellAlign, ContentAlign, CrossAlign, Document, LayoutMode, MainAlign, Node, NodeId, NodeKind, Sizing}; use serde_json::json; use crate::FigApp; diff --git a/crates/ui-egui/src/panels/proto_editor.rs b/crates/ui-egui/src/panels/proto_editor.rs index 7163807e..32817e84 100644 --- a/crates/ui-egui/src/panels/proto_editor.rs +++ b/crates/ui-egui/src/panels/proto_editor.rs @@ -165,7 +165,7 @@ pub fn action_list(app: &mut FigApp, ui: &mut Ui, cx: &Cx, list: &[Action], pref for (i, a) in list.iter().enumerate() { let path = [prefix, &[i]].concat(); let is_open = single || open.starts_with(&path); - let (header, extent) = action_item(app, ui, cx, a, path, list.len(), is_open, single); + let (header, extent) = action_item(app, ui, ActionItem { context: cx, action: a, path, count: list.len(), open: is_open, single }); if let Some(d) = &mut drag { d.row(ui, i, header, extent, || action_summary(cx.doc, a)); } @@ -222,8 +222,17 @@ fn add_action_row(app: &mut FigApp, ui: &mut Ui, cx: &Cx, path: Vec) { /// One action: its kind (open) or a summary (closed) with a menu to move or remove it, and when /// open its settings below. Returns its row and the area it covers with its settings. -#[allow(clippy::too_many_arguments)] -fn action_item(app: &mut FigApp, ui: &mut Ui, cx: &Cx, a: &Action, path: Vec, count: usize, open: bool, single: bool) -> (Rect, Rect) { +struct ActionItem<'context, 'document> { + context: &'context Cx<'document>, + action: &'context Action, + path: Vec, + count: usize, + open: bool, + single: bool, +} + +fn action_item(app: &mut FigApp, ui: &mut Ui, item: ActionItem<'_, '_>) -> (Rect, Rect) { + let ActionItem { context: cx, action: a, path, count, open, single } = item; let t = Tokens::get(ui.ctx()); let i = path.last().copied().unwrap_or(0); let removable = path.len() > 1 || count > 1; @@ -451,7 +460,17 @@ fn condition_editor(app: &mut FigApp, ui: &mut Ui, cx: &Cx, slot: &Slot, b: usiz Some(s) => builder_row(app, ui, cx, slot, b, &s), None => { let id = slot.id("proto_cond_expr").with(b); - if let Some(e) = expr_field(ui, id, vars, Some(c), tr("e.g. count >= 3 and not done"), |e| e.check_condition(vars), None::) { + if let Some(e) = expr_field( + ui, + ExprInput { + id, + variables: vars, + current: Some(c), + placeholder: tr("e.g. count >= 3 and not done"), + check: |e: &Expr| e.check_condition(vars), + trailing: None::, + }, + ) { set_condition(app, slot, b, &e); } } @@ -575,14 +594,16 @@ fn set_variable_fields(app: &mut FigApp, ui: &mut Ui, cx: &Cx, slot: &Slot, vari Some(e) => { let edited = expr_field( ui, - slot.id("proto_set_expr"), - vars, - Some(e), - tr("e.g. count + 1"), - |e| e.check_value(vars, var.kind), - Some(|ui: &mut Ui| { - toggled = toggle(ui); - }), + ExprInput { + id: slot.id("proto_set_expr"), + variables: vars, + current: Some(e), + placeholder: tr("e.g. count + 1"), + check: |e: &Expr| e.check_value(vars, var.kind), + trailing: Some(|ui: &mut Ui| { + toggled = toggle(ui); + }), + }, ); if let Some(e) = edited { set(app, value.clone(), Some(e)); @@ -616,16 +637,21 @@ fn set_variable_fields(app: &mut FigApp, ui: &mut Ui, cx: &Cx, slot: &Slot, vari /// An expression typed as text in a row (with a `trailing` button after the field): what `check` /// accepts is returned when committed; otherwise the text stays and why shows under it. -#[allow(clippy::too_many_arguments)] -fn expr_field( - ui: &mut Ui, +struct ExprInput<'a, Check, Trailing> { id: Id, - vars: &Variables, - cur: Option<&Expr>, - placeholder: &str, - check: impl Fn(&Expr) -> Result<(), String>, - trailing: Option, -) -> Option { + variables: &'a Variables, + current: Option<&'a Expr>, + placeholder: &'a str, + check: Check, + trailing: Option, +} + +fn expr_field(ui: &mut Ui, input: ExprInput<'_, Check, Trailing>) -> Option +where + Check: Fn(&Expr) -> Result<(), String>, + Trailing: FnOnce(&mut Ui), +{ + let ExprInput { id, variables: vars, current: cur, placeholder, check, trailing } = input; let t = Tokens::get(ui.ctx()); let shown = cur.map(|e| e.display(vars)).unwrap_or_default(); let key = id.with("draft"); diff --git a/crates/ui-egui/src/panels/proto_panel.rs b/crates/ui-egui/src/panels/proto_panel.rs index 86dae3a8..e43815bd 100644 --- a/crates/ui-egui/src/panels/proto_panel.rs +++ b/crates/ui-egui/src/panels/proto_panel.rs @@ -8,7 +8,7 @@ use figcraft_doc::proto::{ Action, ConditionalBlock, Direction, Easing, GAMEPAD_BUTTONS, Interaction, KeyMods, Overflow, OverlayAnchor, OverlayPosition, ScrollPosition, Transition, Trigger, is_gamepad_button, }; -use figcraft_doc::*; +use figcraft_doc::{Document, Node, NodeId, NodeKind, VariableValue, proto}; use serde_json::{Value, json}; use crate::FigApp; @@ -640,15 +640,24 @@ fn interactions(app: &mut FigApp, ui: &mut Ui, doc: &Document, page: NodeId, nod { let panel_left = ui.clip_rect().min.x; let y = anchor.map(|a| a.min.y).unwrap_or(ui.clip_rect().min.y + 80.0); - editor_popover(app, ui.ctx(), doc, page, id, k, it, pos2(panel_left - 8.0, y)); + editor_popover(app, ui.ctx(), InteractionPopover { doc, page, node: id, index: k, interaction: it, anchor: pos2(panel_left - 8.0, y) }); } else if open.is_some_and(|(n, _)| n == id) { open_editor(ui.ctx(), None); } } /// The interaction editor, floating left of the panel (`at` is its top-right corner). -#[allow(clippy::too_many_arguments)] -fn editor_popover(app: &mut FigApp, ctx: &egui::Context, doc: &Document, page: NodeId, node: NodeId, k: usize, it: &Interaction, at: Pos2) { +struct InteractionPopover<'a> { + doc: &'a Document, + page: NodeId, + node: NodeId, + index: usize, + interaction: &'a Interaction, + anchor: Pos2, +} + +fn editor_popover(app: &mut FigApp, ctx: &egui::Context, popover: InteractionPopover<'_>) { + let InteractionPopover { doc, page, node, index: k, interaction: it, anchor: at } = popover; let t = Tokens::get(ctx); let width = 280.0; let screen = ctx.content_rect(); @@ -832,17 +841,20 @@ fn trigger_rows(app: &mut FigApp, ui: &mut Ui, node: NodeId, k: usize, it: &Inte const PAD_MENU_W: f32 = 92.0; /// A destination picker for the action at `slot`: `make` builds the action for the destination. -#[allow(clippy::too_many_arguments)] -fn dest_dropdown( - app: &mut FigApp, - ui: &mut Ui, - doc: &Document, - options: &[NodeId], - cur: Option, - label: &str, - make: impl Fn(NodeId) -> Action, - slot: &Slot, -) { +struct DestinationPicker<'a, Make> { + doc: &'a Document, + options: &'a [NodeId], + current: Option, + label: &'a str, + make: Make, + slot: &'a Slot, +} + +fn dest_dropdown(app: &mut FigApp, ui: &mut Ui, picker: DestinationPicker<'_, Make>) +where + Make: Fn(NodeId) -> Action, +{ + let DestinationPicker { doc, options, current: cur, label, make, slot } = picker; widgets::sublabels(ui, label, None); row(ui, 32.0, |ui| { let text = cur.map(|d| name_of(doc, d)).unwrap_or_else(|| tr("None").into()); @@ -871,22 +883,45 @@ pub(crate) fn action_fields(app: &mut FigApp, ui: &mut Ui, doc: &Document, page: Action::Navigate { dest, preserve_scroll } => { let (dest, preserve_scroll) = (*dest, *preserve_scroll); let mk = |d| Action::Navigate { dest: d, preserve_scroll }; - dest_dropdown(app, ui, doc, &nav_targets(doc, page), Some(dest), tr("Destination"), mk, slot); + dest_dropdown( + app, + ui, + DestinationPicker { doc, options: &nav_targets(doc, page), current: Some(dest), label: tr("Destination"), make: mk, slot }, + ); row(ui, 28.0, |ui| { if widgets::checkbox(ui, preserve_scroll, tr("Preserve scroll position")) { set_action(app, slot, Action::Navigate { dest, preserve_scroll: !preserve_scroll }); } }); } - Action::SwapOverlay { dest } => dest_dropdown(app, ui, doc, &frames, Some(*dest), tr("Overlay"), |d| Action::SwapOverlay { dest: d }, slot), + Action::SwapOverlay { dest } => dest_dropdown( + app, + ui, + DestinationPicker { doc, options: &frames, current: Some(*dest), label: tr("Overlay"), make: |d| Action::SwapOverlay { dest: d }, slot }, + ), Action::ChangeTo { dest } => { let vs = variants_for(doc, node); - dest_dropdown(app, ui, doc, &vs, Some(*dest), tr("Variant"), |d| Action::ChangeTo { dest: d }, slot); + dest_dropdown( + app, + ui, + DestinationPicker { doc, options: &vs, current: Some(*dest), label: tr("Variant"), make: |d| Action::ChangeTo { dest: d }, slot }, + ); } Action::ScrollTo { dest, offset } => { let (dest, offset) = (*dest, *offset); let layers: Vec = doc.top_level_frame(node).map(|f| doc.subtree(f).into_iter().skip(1).take(400).collect()).unwrap_or_default(); - dest_dropdown(app, ui, doc, &layers, Some(dest), tr("Scroll to layer"), move |d| Action::ScrollTo { dest: d, offset }, slot); + dest_dropdown( + app, + ui, + DestinationPicker { + doc, + options: &layers, + current: Some(dest), + label: tr("Scroll to layer"), + make: move |d| Action::ScrollTo { dest: d, offset }, + slot, + }, + ); // Where the layer ends up from the top-left of what scrolls (room for a fixed header). widgets::sublabels(ui, tr("Offset"), None); row(ui, 32.0, |ui| { @@ -920,7 +955,7 @@ pub(crate) fn action_fields(app: &mut FigApp, ui: &mut Ui, doc: &Document, page: Action::OpenOverlay { dest, position, close_on_outside, background, offset, anchor } => { let (dest, position, close_on_outside, background, offset, anchor) = (*dest, *position, *close_on_outside, *background, *offset, *anchor); let mk = move |d: NodeId| Action::OpenOverlay { dest: d, position, close_on_outside, background, offset, anchor }; - dest_dropdown(app, ui, doc, &frames, Some(dest), tr("Overlay"), mk, slot); + dest_dropdown(app, ui, DestinationPicker { doc, options: &frames, current: Some(dest), label: tr("Overlay"), make: mk, slot }); widgets::sublabels(ui, tr("Overlay position"), None); row(ui, 32.0, |ui| { let resp = widgets::dropdown(ui, tr(position.label()), ui.available_width(), None); diff --git a/crates/ui-egui/src/panels/right.rs b/crates/ui-egui/src/panels/right.rs index f00a5821..b0c0e995 100644 --- a/crates/ui-egui/src/panels/right.rs +++ b/crates/ui-egui/src/panels/right.rs @@ -2,7 +2,11 @@ use egui::{Align2, Color32, CornerRadius, Id, Pos2, Rect, Sense, Stroke, Ui, pos2, vec2}; use figcraft_color::{BlendMode, Paint, PaintKind}; -use figcraft_doc::*; +use figcraft_doc::{ + Blur, BlurType, BoolOp, Brush, Document, Effect, EffectKind, ExportFormat, ExportSetting, Glass, GridAlign, GridKind, LayoutGrid, + MAX_BLUR_RADIUS, MaskType, Node, NodeId, NodeKind, Noise, NoiseMode, Shadow, StrokeAlign, StrokeCap, StrokeJoin, StrokeProps, Texture, + VariableType, WidthPoint, WidthPreset, WidthProfile, +}; use figcraft_engine::cmd::lists::{moved_index, removed_index}; use serde_json::{Value, json}; diff --git a/crates/ui-egui/src/panels/right/effects.rs b/crates/ui-egui/src/panels/right/effects.rs index ef62bbbc..f2d48eba 100644 --- a/crates/ui-egui/src/panels/right/effects.rs +++ b/crates/ui-egui/src/panels/right/effects.rs @@ -1,5 +1,9 @@ use super::menu::{context_popup, menu_check, menu_item, menu_popup, row, row_context_menu, row_rect}; -use super::*; +use super::{ + Blur, BlurType, Document, Effect, EffectKind, FieldEdit, FigApp, Glass, GridAlign, GridKind, Icon, Id, LayoutGrid, Lead, ListDrag, + MAX_BLUR_RADIUS, NodeId, Noise, NoiseMode, PAD, Paint, Sense, Shadow, StyleSlot, Texture, Ui, VariableType, common, icons, json, move_flags, + moved_index, remove_flags, removed_index, style_picker, tr, trf, trn, vec2, widgets, +}; /// Draw the effect list with add/style/type/toggle/remove/reorder, open settings, and keep an /// open picker and settings flags aligned when rows move or are removed. diff --git a/crates/ui-egui/src/panels/right/inspect.rs b/crates/ui-egui/src/panels/right/inspect.rs index 658028f9..53403a5f 100644 --- a/crates/ui-egui/src/panels/right/inspect.rs +++ b/crates/ui-egui/src/panels/right/inspect.rs @@ -1,5 +1,8 @@ use super::menu::{menu_item, menu_popup, row}; -use super::*; +use super::{ + Align2, Color32, CornerRadius, Document, ExportFormat, ExportSetting, FigApp, Icon, Id, NodeId, PAD, Paint, Rect, Sense, Stroke, Tip, Tokens, Ui, + a11y, icons, pos2, regular, semibold, tr, trf, vec2, widgets, +}; /// One group of inspected properties: label, value (with a swatch for colours) and the style or /// variable it comes from. Clicking a row copies its value. diff --git a/crates/ui-egui/src/panels/right/menu.rs b/crates/ui-egui/src/panels/right/menu.rs index fed4a960..fb51d857 100644 --- a/crates/ui-egui/src/panels/right/menu.rs +++ b/crates/ui-egui/src/panels/right/menu.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{BlendMode, Color32, CornerRadius, FIELD_H, Id, PAD, Rect, Sense, Tokens, Ui, a11y, pos2, regular, tr, vec2}; /// The smallest width a popup menu is measured in (see [`menu_popup`]). const MENU_MIN_W: f32 = 48.0; diff --git a/crates/ui-egui/src/panels/right/paints.rs b/crates/ui-egui/src/panels/right/paints.rs index 66db55dd..b947e296 100644 --- a/crates/ui-egui/src/panels/right/paints.rs +++ b/crates/ui-egui/src/panels/right/paints.rs @@ -1,6 +1,10 @@ use super::menu::{blend_menu, context_popup, menu_check, menu_item, menu_popup, row, row_context_menu, row_rect}; use super::stroke::stroke_settings; -use super::*; +use super::{ + Align2, BlendMode, CornerRadius, Document, FIELD_H, FieldEdit, FigApp, Icon, Id, Lead, ListDrag, MaskType, Node, NodeId, NodeKind, PAD, Paint, + PaintKind, Rect, Sense, Stroke, StrokeAlign, StyleSlot, Tip, Tokens, Ui, Value, VariableType, common, icons, json, moved_index, pos2, regular, + removed_index, style_picker, tr, trf, vec2, widgets, +}; /// Draw visibility/blend/mask, opacity, uniform or per-corner radius, corner smoothing and /// the blend-mode row. diff --git a/crates/ui-egui/src/panels/right/stroke.rs b/crates/ui-egui/src/panels/right/stroke.rs index c3bc8642..8576e642 100644 --- a/crates/ui-egui/src/panels/right/stroke.rs +++ b/crates/ui-egui/src/panels/right/stroke.rs @@ -1,5 +1,8 @@ use super::menu::{menu_item, menu_item_with, menu_popup, row}; -use super::*; +use super::{ + Brush, Color32, Document, FigApp, Icon, Id, Lead, NodeId, NodeKind, PAD, Pos2, Rect, Stroke, StrokeAlign, StrokeCap, StrokeJoin, StrokeProps, + Tokens, Ui, Value, WidthPoint, WidthPreset, WidthProfile, a11y, common, icons, json, pos2, tr, widgets, +}; /// A width profile drawn as a filled band across `rect` (thickest point = full height); `None` /// draws a uniform stroke. diff --git a/crates/ui-egui/src/panels/typography.rs b/crates/ui-egui/src/panels/typography.rs index 046428f7..a95af934 100644 --- a/crates/ui-egui/src/panels/typography.rs +++ b/crates/ui-egui/src/panels/typography.rs @@ -8,7 +8,10 @@ use std::sync::Arc; use egui::{Align2, Color32, CornerRadius, Id, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2}; -use figcraft_doc::*; +use figcraft_doc::{ + DecorationLength, Document, LeadingTrim, LetterSpacing, LineHeight, ListStyle, NodeId, NodeKind, TextAlignH, TextAlignV, TextAutoResize, + TextCase, TextData, TextDecoration, TextStyle, VariableType, trim_num, +}; use serde_json::{Value, json}; use crate::FigApp; diff --git a/crates/ui-egui/src/panels/typography/axes.rs b/crates/ui-egui/src/panels/typography/axes.rs index 6c042dec..953d6fa1 100644 --- a/crates/ui-egui/src/panels/typography/axes.rs +++ b/crates/ui-egui/src/panels/typography/axes.rs @@ -1,5 +1,8 @@ use super::fields::menu_item_hover; -use super::*; +use super::{ + Color32, CornerRadius, FIELD_H, FieldEdit, FigApp, Id, Lead, MIXED, NodeId, Rect, Sense, Stroke, TextStyle, Tokens, Ui, Value, commit_text, json, + pos2, regular, text_style_params, tr, trf, trim_num, uniform, vec2, widgets, +}; /// Type settings ▸ Variable: the font's named instances, then a slider and a field per visible /// axis (values the style name implies show until changed; "Mixed" when the shown styles differ). diff --git a/crates/ui-egui/src/panels/typography/fields.rs b/crates/ui-egui/src/panels/typography/fields.rs index 86fae14e..7104072f 100644 --- a/crates/ui-egui/src/panels/typography/fields.rs +++ b/crates/ui-egui/src/panels/typography/fields.rs @@ -2,7 +2,11 @@ use super::axes::variable_axes; use super::glyphs::{LeadGlyph, caption, feature_row, text_seg}; use super::picker::{font_picker, remember_family}; use super::spacing::{SpacingField, commit_spacing, parse_letter_spacing, parse_line_height, spacing_field, spacing_input}; -use super::*; +use super::{ + Arc, DecorationLength, Document, FEATURES, FIELD_H, FieldEdit, FigApp, Icon, Id, Lead, LeadingTrim, ListStyle, MIXED, NodeId, PAD, SIZES, Sense, + TABS, TextAlignH, TextAlignV, TextAutoResize, TextCase, TextData, TextDecoration, TextStyle, Tokens, Ui, Value, VariableType, common, field_row, + icons, json, live, menu_item, menu_popup, regular, row, semibold, text_style_params, tr, tr_noop, trf, trim_num, uniform, vec2, widgets, +}; /// The font rows: family, style and size, line height and letter spacing. pub(super) fn font_rows( diff --git a/crates/ui-egui/src/panels/typography/glyphs.rs b/crates/ui-egui/src/panels/typography/glyphs.rs index 3b71b7c4..5b379f4f 100644 --- a/crates/ui-egui/src/panels/typography/glyphs.rs +++ b/crates/ui-egui/src/panels/typography/glyphs.rs @@ -4,7 +4,7 @@ use super::axes::round_axis; use super::picker::{FontPick, font_picker}; #[cfg(test)] use super::spacing::{parse_letter_spacing, parse_line_height}; -use super::*; +use super::{Align2, Arc, Color32, CornerRadius, FIELD_H, Icon, Id, Rect, Sense, Stroke, StrokeKind, Tokens, Ui, icons, pos2, regular, tr, vec2}; pub(super) fn caption(ui: &mut Ui, s: &str) { let t = Tokens::get(ui.ctx()); @@ -147,6 +147,9 @@ pub(super) fn paint_lead(p: &egui::Painter, r: Rect, g: LeadGlyph, col: Color32) #[cfg(test)] mod tests { use super::*; + use crate::panels::typography::{target_styles, uniform}; + use figcraft_doc::{Document, LetterSpacing, LineHeight, Node, NodeKind, TextData, TextStyle}; + use serde_json::json; #[test] fn small_caps_sample_draws_only_letters_the_ui_font_has() { diff --git a/crates/ui-egui/src/panels/typography/picker.rs b/crates/ui-egui/src/panels/typography/picker.rs index c7d8bea2..1dc88456 100644 --- a/crates/ui-egui/src/panels/typography/picker.rs +++ b/crates/ui-egui/src/panels/typography/picker.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{Align2, CornerRadius, Icon, Id, Rect, Sense, Tokens, Ui, icons, pos2, regular, semibold, tr, vec2, widgets}; // ---- Font picker ---- diff --git a/crates/ui-egui/src/panels/typography/spacing.rs b/crates/ui-egui/src/panels/typography/spacing.rs index 971a3c0f..c6bc7a51 100644 --- a/crates/ui-egui/src/panels/typography/spacing.rs +++ b/crates/ui-egui/src/panels/typography/spacing.rs @@ -1,5 +1,8 @@ use super::glyphs::{LeadGlyph, paint_lead}; -use super::*; +use super::{ + Align2, CornerRadius, FIELD_H, FieldEdit, FigApp, Id, LetterSpacing, LineHeight, MIXED, NodeId, Rect, Sense, Stroke, StrokeKind, Tokens, Ui, + Value, VariableType, commit_text, json, live, pos2, regular, tr, trim_num, vec2, widgets, +}; /// Line height, letter spacing or paragraph spacing: property, field id, glyph and tooltip. pub(super) struct SpacingField {