Panels: the sidebars' width limits are Figma's (left 240 to 450, right 240 to 500)

Measured in Figma's web app: left sidebar 240 default, 240 min, 450 max; right
240 default, 240 min, 500 max; a double-click on the edge resets. FigCraft had
left 200 to 480 and right 240 to 480. Widths saved under the old limits are now
clamped when the app starts, not only when drawn. The narrow-window give-way
(canvas keeps 160 px) is unchanged, with the new 240 floor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
This commit is contained in:
Trevor BrownandClaude Sonnet 5.5 committed 2026-11-07 23:17:00 -05:00
1 parent 3635c23070
commit ad7a886e3c
4 files changed
+33 -6

No files matched your search

+3
View File
@@ -22,7 +22,10 @@ impl FigApp {
/// state.
pub fn new(session: Session, services: Box<dyn Services>) -> Self {
// The main window's panels open as wide as they were left.
// Widths saved under other limits are clamped to the current ones, not rejected.
let (left_width, right_width) = session.prefs.panel_widths;
let within = |w: f32, range: std::ops::RangeInclusive<f32>| if w.is_finite() { w.clamp(*range.start(), *range.end()) } else { PANEL_DEFAULT };
let (left_width, right_width) = (within(left_width, LEFT_PANEL), within(right_width, RIGHT_PANEL));
let pages_height = session.prefs.pages_height;
Self {
ui: UiState { left_width, right_width, pages_height, ..UiState::default() },
+24 -2
View File
@@ -78,7 +78,15 @@ fn panels_fit_the_window() {
((744.0, None, Some(480.0)), (0.0, 480.0), "with the left panel closed the right one has room"),
((300.0, Some(240.0), Some(240.0)), (l0, r0), "a window smaller than any layout: the least widths"),
((1384.0, Some(f32::NAN), Some(f32::INFINITY)), (PANEL_DEFAULT, PANEL_DEFAULT), "junk widths: the default"),
((744.0, Some(480.0), None), (480.0, 0.0), "the right panel closed (a board's): the left one keeps its width"),
((744.0, Some(480.0), None), (l1, 0.0), "the right panel closed (a board's): the left one keeps as much of its width as it may"),
((744.0, Some(450.0), None), (450.0, 0.0), "the right panel closed: the left one at its most"),
((1384.0, Some(200.0), Some(480.0)), (l0, 480.0), "a stored left width under 240 (the old limit was 200) is brought up to 240"),
((1384.0, Some(470.0), Some(520.0)), (l1, r1), "stored widths over the limits (the old ones were 480) are brought down to 450 and 500"),
(
(744.0, Some(300.0), Some(500.0)),
(240.0, 344.0),
"the smallest window with the right panel at its most: the right gives way last, the left to its 240 floor",
),
];
for ((avail, left, right), want, why) in rows {
assert_eq!(fit_panels(avail, left, right), want, "{why}");
@@ -122,14 +130,17 @@ fn dragging_an_edge_sizes_its_panel_within_its_limits_and_keeps_the_views_centre
let (left, right) = edges(&u);
drag(&mut u, pos2(left, 300.0), pos2(left + 700.0, 300.0));
assert_eq!(widths(&u).0, *LEFT_PANEL.end());
assert_eq!(widths(&u).0, 450.0, "Figma's left sidebar stops at 450");
let (left, _) = edges(&u);
drag(&mut u, pos2(left, 300.0), pos2(20.0, 300.0));
assert_eq!(widths(&u).0, *LEFT_PANEL.start());
assert_eq!(widths(&u).0, 240.0, "and at 240 the other way");
drag(&mut u, pos2(right, 300.0), pos2(right + 500.0, 300.0));
assert_eq!(widths(&u).1, *RIGHT_PANEL.start());
let (_, right) = edges(&u);
u.drag_to(pos2(right, 300.0), pos2(right - 900.0, 300.0));
assert_eq!(widths(&u).1, *RIGHT_PANEL.end(), "at its most while the pointer is past it");
assert_eq!(widths(&u).1, 500.0, "Figma's right sidebar stops at 500");
u.frame(vec![Event::PointerMoved(pos2(right - 100.0, 300.0))]);
assert_eq!(widths(&u).1, *RIGHT_PANEL.start() + 100.0, "back with the pointer");
u.button(pos2(right - 100.0, 300.0), false);
@@ -187,6 +198,17 @@ fn the_widths_come_back_with_the_preferences() {
u.frame(vec![]);
let (left, right) = edges(&u);
assert_eq!((left, 1440.0 - right), (56.0 + *LEFT_PANEL.start(), PANEL_DEFAULT));
// Widths saved under the old limits (left 200 to 480, right up to 480) or over the new ones
// are clamped when loaded, not rejected.
for (stored, want) in
[((200.0, 240.0), (240.0, 240.0)), ((239.0, 500.0), (240.0, 500.0)), ((451.0, 480.0), (450.0, 480.0)), ((480.0, 600.0), (450.0, 500.0))]
{
let mut s = Session::new();
s.prefs.panel_widths = stored;
let mut u = Ui::new(s);
u.frame(vec![]);
assert_eq!(widths(&u), want, "stored {stored:?}");
}
}
#[test]
@@ -264,7 +286,7 @@ fn minimize_collapses_the_rail_and_both_sidebars_and_the_right_one_follows_the_s
#[test]
fn the_smallest_window_keeps_its_canvas_whatever_the_widths() {
for asked in [(240.0, 240.0), (480.0, 480.0), (480.0, 240.0), (200.0, 480.0)] {
for asked in [(240.0, 240.0), (450.0, 500.0), (450.0, 240.0), (240.0, 500.0)] {
let mut s = Session::new();
s.prefs.panel_widths = asked;
let ctx = egui::Context::default();
+4 -3
View File
@@ -27,10 +27,11 @@ pub enum TitleBar {
pub type AutoSaveEncoding = std::thread::JoinHandle<(figcraft_engine::cmd::file::FileSnapshot, figcraft_engine::Result<Vec<u8>>)>;
/// The side panels' widths: the least and most a drag of their edge gives, what a double-click on
/// the edge puts back, and how much of the window they leave the canvas.
pub const LEFT_PANEL: std::ops::RangeInclusive<f32> = 200.0..=480.0;
/// the edge puts back, and how much of the window they leave the canvas. The limits are Figma's,
/// measured in the web app: left 240 to 450, right 240 to 500, both 240 by default.
pub const LEFT_PANEL: std::ops::RangeInclusive<f32> = 240.0..=450.0;
pub const RIGHT_PANEL: std::ops::RangeInclusive<f32> = 240.0..=480.0;
pub const RIGHT_PANEL: std::ops::RangeInclusive<f32> = 240.0..=500.0;
pub const PANEL_DEFAULT: f32 = 240.0;
+2 -1
View File
@@ -20,7 +20,7 @@ interaction feel not yet compared.
| UI3 layout: tab strip as title bar, icon rail, file header, pages + layers, floating bottom toolbar with flyouts, Design / Prototype / Inspect panels | done | rail and tab strip icons 20 pt (#553); frameless window on Windows/Linux with drawn buttons; on a Wayland compositor that offers no server-side decorations (GNOME) the window gets winit's Adwaita controls instead (#592; checked by its author on GNOME, KDE not run) |
| Light and dark themes | done | colours from `theme::Tokens`; a file or a page made while the dark theme shows gets a `#1E1E1E` page (`#F5F5F5` in the light theme), its own from then on: changing the theme repaints no page and opened files keep theirs; the file the app opens with takes the theme once it is known; the CLI, MCP and exports have no theme and keep `#F5F5F5`; text drawn with the Text tool is white on a dark page and black on a light one (so is text a command makes in the app without naming a fill; headless sessions keep black) (`theme_pages_tests`, `dark_page_text_tests`) |
| Minimize UI (Shift+Cmd/Ctrl+\\, the Minimize button by the file name) and Show/Hide UI (Cmd/Ctrl+\\) | done | Minimize collapses the navigation bar and both sidebars and keeps the toolbar and the panel widths; "If you select an object while the panels are minimized, the left sidebar remains minimized while the right sidebar expands [...] Once all objects are deselected, the right panel minimizes again" (Explore the navigation bar and left sidebar); a small Expand button at the top left of the canvas puts them back (FigCraft's own, the article names no control for it). Hide UI takes the toolbar too (`panel_width_tests`, `shortcuts_tests`; #1835) |
| Resizable panels | done | each panel's edge drags its width (left 200–480, right 240–480, 240 by default; a double-click on the edge or View ▸ Panels ▸ Reset panel widths puts the default back); the widths are each window's own, the main window's are kept in the preferences when a drag ends and come back at the next start, a new window opens with those of the window it comes from; Minimize UI hides the panels and brings them back as they were; the view keeps its centre and zoom while a panel is resized; in a window too small for them the panels give way so the canvas keeps 160 px (the left one first), down to the smallest window (800×500) (`panel_width_tests`). Not compared with Figma's widths; no snap points |
| Resizable panels | done | each panel's edge drags its width (left 240–450, right 240–500, 240 by default, as measured in Figma's web app on 2026-11-08 (`plan/loop/measurements/scrub-and-panels.md`); a double-click on the edge or View ▸ Panels ▸ Reset panel widths puts the default back); the widths are each window's own, the main window's are kept in the preferences when a drag ends and come back at the next start (a width saved under other limits is clamped to the current ones on load), a new window opens with those of the window it comes from; Minimize UI hides the panels and brings them back as they were; the view keeps its centre and zoom while a panel is resized; in a window too small for them the panels give way so the canvas keeps 160 px (the left one first), down to the smallest window (800×500) (`panel_width_tests`). The double-click reset to 240 matches Figma's; no snap points |
| Design panel's sections on one grid | done | every section lays its rows out on the same grid: the panel is 240 pt, its content 16 pt in from the left and 8 pt from the right, two 88 pt columns at that width with 8 pt gutters and a 24 pt column for the buttons at a row's end (the columns are flex, so they share the panel when it is dragged wider); rows 24 pt in a 32 pt track, 10 pt captions over them; section titles 11 pt semibold and a row's values 11 pt, their buttons 24 pt and 4 pt apart. The panel's edge is drawn inside it: a frame stroke counts as margin and took 1 pt off each side, putting every right margin 1–2 pt out (checked in the app on Windows over the control channel) |
| Actions palette (Cmd+K, Cmd+/) | done | tabs: All (every command, grouped by menu, or Other; a command that needs parameters only through its menu item, and none twice under one label, #1574), Assets (components and styles) and Plugins & widgets; results ranked prefix first, then shortest, at most 60 rows; Up/Down move a highlight and Enter runs it; Recents list what was run from the palette (matching translated labels); one open/toggle path from every entry point (`palette_tests`). Ranking and the Assets results not compared with Figma's |
| Context menus | done | canvas and layers; submenus as in the main menu below, and a right-click in a new spot starts a fresh menu (Select layer closed); Shift+F10 or the keyboard's Menu key opens them from the keyboard (at the focused layer row or below the canvas selection) and the menu takes the keyboard (`layers_keyboard_tests`) |
@@ -187,6 +187,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look
| Date | Change | Summary |
|---|---|---|
| 2026-11-07 | patch | Panels: the sidebars' width limits are Figma's, measured in its web app (left 240 to 450, right 240 to 500, both 240 by default, a double-click on the edge resets), where FigCraft had left 200 to 480 and right 240 to 480; widths saved under the old limits are clamped on load |
| 2026-11-02 | patch | Shortcuts: a Cmd/Ctrl-click on a padding field (the horizontal or vertical pair, or a side with Individual padding open) makes the value typed go to all four sides, in one undo step (Guide to auto layout, Keyboard shortcut guide); a plain click still edits that axis or side alone. A documented gesture row: 134 documented, 133 bound. Test: `padding_individual_tests` |
| 2026-11-01 | patch | Number fields: dragging the field itself with Alt held scrubs it ("You can also hover the input field itself and hold down the modifier key: Mac: ⌥ Option Windows: Alt", Adjust alignment, rotation, position, and dimensions), with the resize cursor and the label's rates (Alt is the enabler there, so it isn't the fine rate); a click with Alt doesn't open the editor. Found by a documentation audit of the ui-parity rows. Test: `field_tests::alt_held_over_a_field_scrubs_it`. Counts unchanged |
| 2026-11-02 | patch | M6, #1839: with a shape or frame tool chosen and nothing selected, the arrows show a placement crosshair in the middle of the view and move it by the nudge amounts (`tool.crosshair`), Enter places the object there (`tool.place`), Escape or a tool change takes it away; the canvas draws the crosshair and `ui.inspect` reports it (`tool.crosshair`), the Keyboard shortcuts panel lists the gesture ("Use the arrow keys to activate the crosshair cursor...", Accessibility at Figma). The board Shapes tool places on it too (its 160 px shape centred, as the tool centres a press), and the view pans by the overshoot when the crosshair is nudged past its edge. Tests: `place_crosshair_tests`, `shortcut_keys_tests::arrows_show_and_move_the_placement_crosshair_with_a_shape_tool`. |