Skip to content

craft-ui: extend direction with body/window/popup/dialog helpers, sizing-pass LTR, direction-aware grids, geometry and paint submodules - #507

Draft
ahmedhosnypro wants to merge 1 commit into
storytold:mainfrom
ahmedhosnypro:craft-ui/direction-core-promote
Draft

ahmedhosnypro wants to merge 1 commit into
storytold:mainfrom
ahmedhosnypro:craft-ui/direction-core-promote

Conversation

@ahmedhosnypro

@ahmedhosnypro ahmedhosnypro commented Nov 2, 2026 •

Copy link
Copy Markdown

Summary

Adds a large set of RTL direction helpers to craft-ui's crates/ui/src/direction module and splits the hand-painted part of it into two new submodules, geometry.rs and paint.rs. Apps built on craft-ui can mirror their whole chrome for right-to-left languages (Arabic, Hebrew, Persian, Urdu): dialog, window, popup and settings bodies lay out in reading order, resizable windows measure a direction-independent minimum size, forms and grids emit label/editor pairs in reading order, and hand-painted rows, labels and popups place themselves from reading edges instead of left/right. Existing LTR layouts are byte-identical.

Motivation

egui has no whole-context interface direction: panels, popups, grids and hand-painted positions all name left and right explicitly, and the right_to_left style flag alone does not mirror nested rows of a plain top_down(Align::Min) body, egui::Grid cell order, or painted shapes. Every app would otherwise re-derive the same mirroring rules; this lands them once in the shared library. Refs #198 (language support across the craft apps, track 6 "RTL interface", item D1: row and trailing layouts, popup alignment, direction-aware arrow keys in craft-ui direction).

Changes

  • crates/ui/src/direction/mod.rs (+421 lines):
    • Body/window/popup/dialog helpers: body_layout (vertical + Align::Max in RTL, so nested ui.horizontal rows mirror; egui's default in LTR), trailing_min, dialog_row, dialog_content, window_body, popup_body, and sizing_pass_ltr — the sizing pass of a resizable host measures left-to-right (force guard + cleared flag + top_down(Align::Min)), so the enforced window minimum is direction-independent instead of ballooning to the offered width.
    • Direction-blind forms and grids: settings_row, settings_grid, form_grid, two_col_grid (shared body), ltr, ltr_vertical, ltr_grid, scroll_grid, grid_cells, row_width, hscroll_row (mirrors a centred row's region about the scroll viewport so the reading-end is not cropped), full_row, full_trailing, trailing_row, body_label (pins paragraph base direction and aligns the galley to the reading-start edge so mixed Arabic/Latin lines keep their phrase order), text_direction.
    • Window helpers: clamp_window_min_size (clamps a resizable window's rect to exactly min while keeping the anchored edge fixed, so shrink drags are honoured), float_window_x (first-show x of a cascading panel from the canvas's reading-end edge), horizontal_nav_keys (thin tuple over the existing Step for raw-key machines: (ArrowRight, ArrowLeft) LTR, swapped RTL).
    • direction_scope: a #[must_use] per-thread force guard with correct nesting and !Send by construction; Context::direction reads it first, then the stored context direction, so a single frame or widget can be forced even where apply cannot.
    • The module docs gain "Bodies, popups and dialogs" and "Hand-painted geometry and grids" sections, including the mirror-exactly-once rule: the flag, body_layout and an explicit row/trailing pin each flip a row once — never combine two on the same row.
    • API widening: start_x/end_x gain a width/offset parameter (content offset from the edge); forward doc updated.
  • crates/ui/src/direction/geometry.rs (new, 169 lines): hand-painted geometry from reading edges — start_x_w, after_x, start_rect, start_cell, advance, start_span, end_span, name_span, start_edge, row_end_edge, split_end, start_align2, end_align2, start_side, glyph_x, start_text_anchor, end_text_anchor, end_corner_anchor (screen-corner Area anchors), float_window_x, clamp_window_min_size.
  • crates/ui/src/direction/paint.rs (new, 133 lines): direction-aware painting — elided_galley (binary search over whole-character cuts; an RTL string keeps its start and takes "…" at its logical end), PaintText, paint_label_span, paint_elided, popup_area_near (an Area hanging off an anchor's reading-end edge), popup_anchor_reading_end (a PopupAnchor translated past the reading end).
  • crates/ui/src/direction/tests.rs: updated for the widened start_x/end_x signature and a new style_flag helper (split from style_rtl so the chrome-mirror test inspects a plain Style); all 10 existing direction tests stay green.
  • Call sites for the widened API: crates/font-manager/src/panel/browse.rs, crates/font-manager/src/panel/details.rs (start_x(ui, rect, 0.0)); crates/ui/src/docking/render/preview.rs folds a boolean guard into one ! expression (no behaviour change).
  • No changes to craft-i18n; no new dependencies; the no-op SetRightToLeft trait fallback for the crates.io egui build is preserved, so craft-ui still builds against stock egui.

Testing

On rust 1.95 (the workspace rust-version), all exit 0:

  • cargo fmt --all -- --check
  • cargo check --workspace
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p craft-ui -p craft-font-manager — all passed (craft-ui 457 lib tests including the 10 direction tests, craft-font-manager suites, 7 doctests)
  • cargo check -p craft-ui --target wasm32-unknown-unknown

Changed tests: positions_and_popups_mirror covers the new start_x/end_x offsets; the style_flag/style_rtl split lets the chrome test build against both the patched and the crates.io egui API surface.

Checklist

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings (no unwrap/expect/panic, no unsafe)
  • cargo test -p craft-ui -p craft-font-manager
  • cargo check -p craft-ui --target wasm32-unknown-unknown

Promote the app-proven, app-agnostic helpers from PhotoCraft's
i18n/rtl-layout direction.rs into crates/ui/src/direction, keeping
craft-ui's Context-temp HasDirection model as the single source of
truth (signatures take &impl HasDirection instead of the app's
thread-local):

- body/window/popup/dialog scopes: body_layout, trailing_min,
  popup_body, dialog_row, dialog_content, window_body, sizing_pass_ltr
- direction-blind forms and grids: settings_row, settings_grid,
  form_grid, two_col_grid, ltr_grid, scroll_grid, grid_cells, row_width,
  hscroll_row, ltr/ltr_vertical, body_label
- window helpers: clamp_window_min_size, float_window_x,
  horizontal_nav_keys (thin tuple over Step for raw-key machines)
- hand-painted geometry family in direction/geometry.rs (start_x_w,
  after_x, start_rect, start_cell, advance, start_span, name_span,
  start_edge, row_end_edge, end_span, split_end, start_align2,
  end_align2, start_side, glyph_x, text anchors, end_corner_anchor);
  start_x/end_x gain a width parameter (0.0 = bare edge)
- painters in direction/paint.rs: elided_galley, paint_label_span,
  paint_elided, popup_area_near, popup_anchor_reading_end,
  text_direction()
- direction_scope: per-Context/thread force guard alongside apply
  (precedence FORCED > env > context), since apply cannot force one frame
- mirror-exactly-once rule documented: flag + body_layout + explicit
  row/trailing pin each flip a row once; two on one row double-flip

Regression twins ported into direction/tests.rs; existing 10 tests
kept. crates.io-egui no-op SetRightToLeft fallback in apply preserved.

Also:
- font-manager: adapt the two start_x/end_x call sites to the new
  width parameter
- docking/render/preview.rs: simplify a boolean expression flagged by
  clippy 1.95 nonminimal_bool at the base commit (De Morgan-identical)

Gate (rust 1.95.0): cargo check --workspace, clippy -p craft-ui
-p craft-font-manager --all-targets -- -D warnings, cargo test -p
craft-ui / -p craft-font-manager, cargo fmt --check, and wasm32 check
of craft-ui all green.

Source: plan/rtl/01-direction-core-spec.md (branch plan/rtl-core,
0149e7e); battle-tested reference photocraft i18n/rtl-layout
9a446cfea. Refs: craft-libs#198, emilk/egui#8577, #8698.
@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant