Floating windows:
- A title bar (TITLE 14) drags the whole window live; Escape and focus
loss put it back. A release over a tab strip, a floating stack's band
or seam, or the outer edge emits MoveWindow; elsewhere the window
stays and hosts get group_drop. Double-click minimizes or expands
every group; the close button closes every panel (CloseWindow).
- Stacked groups with a 3 pt bar between them and dividers that resize
both neighbours (ResizeGroup). The top and bottom quarter of a group
and the seam between two groups insert a new group there, with a line
preview (P3, P4). The title bar occludes what lies behind it.
- The only group of a window carries the window rect when dragged by its
bar, so floating it again keeps the window's size.
Frame profile (DockStyle::profile):
- Ctrl/Cmd held at the start of a tab drag undocks the panel (S9).
- The outer edge band splits at the root: top and bottom in both
profiles, left and right in the frame profile (P8).
- Menu tail: Undock Panel, Close Panel Group, Undock Panel Group, Close
Other Panels in Group and Maximize Frame (DockEvent::ToggleMaximize).
DockArea::maximized draws only the docked frame holding the panel;
other frames and floating windows do not render or take input.
DockArea::hide_panels leaves out every panel, or every panel except a
host-chosen set; protected panels stay and the layout is unchanged.
Feature native-windows: a floating window with a native rect gets its
own OS window through show_viewport_immediate when the backend does not
embed viewports, and stays in-window otherwise. Closing it emits
SetNative None, so the group returns to its in-window rect; moves and
resizes are read back as SetNative with bounded sizes and positions.
Tabs and groups do not drag between OS windows. Move Panel to New Window
is in the menu tail where native windows are available.
A floating panel whose window closed as a whole restores at its last
rect.
Behaviour follows vectorcraft floating.rs (show_set, draw_groups, track),
effectcraft dock_ui.rs (panel_window, set_panel_window, toggle_maximize)
and dock.rs (PanelWindow::sanitized), filmcraft dock_ui.rs (workspace_edge,
EDGE_BAND, maximized) and storytold/filmcraft#2118 (saved_native_rect,
bounded_inner_size).
docking::chrome holds the parts of a floating window that hosts other
than DockArea need, such as office task panes: FloatingFrame (opaque
frame on its own layer, anchored resize from both sides, the bottom and
both bottom corners, arrow keys on a focused handle) and TitleBar (two
grip lines or a title, an optional menu button, and a close button 10 pt
in from the right). They return responses and requested rects; the
caller turns them into its own commands.
Behaviour follows vectorcraft floating.rs (show_set: TITLE 14, grip
lines, close position).
Behaviour tests with real egui input: seam, band, column edge and new
dock drops with their line previews, tab drops, a column taking groups
anywhere, scrolling, column bar drags and their cancellation, dividers,
minimize, iconic toggles, occlusion by floating windows and the mirrored
right-to-left layout.
Behaviour follows vectorcraft crates/ui-egui/src/floating.rs tests.
Node::Dock renders as side docks: columns from the window edge inwards,
each with a column bar (drag, »/« button, double-click and menu toggle
iconic) above stacked groups. A split beside a dock gives the dock its
columns' width. Every column's inner edge resizes it; dividers between
open groups save both heights; minimized groups keep theirs. A column
whose groups' minimum heights do not fit scrolls.
Drops resolve dock targets before group targets: column edges (new
column), seams and the top or bottom quarter of a group body (new group),
the room under a minimized column (below its last group) and the left or
right edge of the docked area without a dock (new dock, offered once the
layout has a dock). They show a horizontal or full-height vertical line.
Floating windows and host overlays occlude them. Tab drags use the same
targets with Move. A column bar drag moves the column with a ghost and
floats its groups away from a dock; Escape, focus loss, pointer exit and
cancel_drag cancel it like a group drag.
Side::Start maps to the left edge, or the right with
DockStyle::right_to_left. New DockStyle fields: column_bar_height,
seam_band, edge_band, right_to_left and the column labels.
Behaviour follows vectorcraft crates/ui-egui/src/floating.rs (drop
geometry, column layout and scrolling) and photocraft
crates/ui-egui/src/dock.rs (group heights).
A frame along one edge of the docked layout, a quarter of its size. In a
layout with docks the split wraps the part between them, so a bottom
Timeline or top bar stays inside the docks.
Behaviour follows filmcraft dock_ui.rs (workspace_edge).
Every group with a tab strip has a drag bar between its tabs and the
host's header, at least drag_bar_width wide: tabs shrink and overflow
before it narrows. The bar and the 4 pt top border move the whole group
with its order and active tab. While dragging, a ghost outline follows
the pointer and one destination preview paints on the Tooltip layer. On
release DockArea resolves the target and emits one MoveGroup: joined as
tabs, split beside a frame, or floated at the group's own size.
group_drop is filled only for releases without a target. Ctrl or Cmd
held at release always floats, for tab drags too. Escape, focus loss,
leaving the window and cancel_drag end the drag without a change. A
release over the group's own docked position is a no-op; a floating
group released over itself moves there.
Double-clicking a tab or the bar of a floating or dock-column group
minimizes it to its strip; clicking another tab expands it. The bar is
focusable; its menu (right-click, Enter or Space) and the tab menu end
with Close Tab Group, Minimize or Expand, Float Tab Group and Group
with. Floating windows paint floating_background and floating_stroke
under their groups, so they stay opaque over a transparent host. A lone
bare panel, like a lone protected one, draws no strip and no bar.
The group handles and their tests build on storytold/craft-libs#110 by
@Zenodeon.
Co-authored-by: Zenodeon <aswinmani2001@gmail.com>
Floating windows resize from both side edges, the bottom edge and both
bottom corners; the opposite edges stay fixed and a resize past the
viewport waits for the pointer to come back. A release over a tab's
current slot or its own group body is a no-op instead of a float.
Insertion markers stay inside their strip. Previews paint on the
Tooltip layer, and a reorder inside the source strip shows only the
insertion line. resolve_drag lets the host own a gesture. A panel
floated from a dock uses floating_size or its limits instead of the
column height. ResizeHandle gives focus back after a pointer drag.
node_limits is a free function for the render modules. is_dragging and
tab_insertion_marker are public.
Ported from storytold/craft-libs#110 by @Zenodeon.
Co-authored-by: Zenodeon <aswinmani2001@gmail.com>
The last segment of a panel-menu entry matches alone only when the panel
name is a segment of some catalogue path. An entry without keys matched
only as a panel-menu item leaves the command's shortcuts alone and is
listed in ImportReport::matched_without_change. Tests cover an entry
without keys next to one with keys for the same command, in both orders.
DockContent gains default methods for the logical label, permissions,
limits, floating size, icon, native-window permission, bare panels and a
host drop hook. show_content reads all of them from the content; the
closures passed to the older show functions override the label,
permission and limit defaults.
floating_size, resolve_drag, DropRequest and DropFeedback are ported
from storytold/craft-libs#110 by @Zenodeon.
Co-authored-by: Zenodeon <aswinmani2001@gmail.com>
DockArea, DockContent, DockStyle, DockOutput and the drag state stay in
surface.rs. The Renderer moves to docking/render: the node tree, limits
and helpers in mod.rs, groups and tabs in group.rs, floating windows in
floating.rs, and drop resolution and previews in preview.rs.
Group, column and window moves, close and restore round trips, dock validation, Lock Workspace, reconcile and reset, the random action sequence over dock layouts, and one v1 fixture per app shape that must parse and write back unchanged.
Workspace<P> keeps the current layout, where hidden panels go back, the
user's saved arrangement and Lock Workspace. It reads the {layout,
hidden} files lightcraft, pdfcraft, cadcraft and soundcraft write today.
Close actions record a location per closed panel; set_visible follows
hidden anchors when a panel's own anchor is hidden too. reconcile moves
panels the app does not register now to hidden and places missing
registered panels at their default place; reset returns to the saved or
default arrangement.
Node::Dock holds columns of groups on a logical window edge (Side::Start
or Side::End). Floating windows hold a stack of groups and an optional
native rect; the v1 {panels, active, rect} shape still parses, and a
window with one plain group writes it. Placement gains Group and Column,
Target names where a group, column or window goes, and the new actions
move, close, minimize, collapse and resize groups, columns and windows
atomically. Location records group and column state so a panel closed
from a dock or floating stack restores to the same group, seam, column
or dock. Layout gains apply_all, closed_by and panel_state; Permissions
gains locked. Node, Action, Placement and DockError are non_exhaustive.
The renderer draws each floating window's groups stacked and a dock's
columns side by side until the dock and floating-stack profiles land.
- ForeignEntry::in_panel_menu marks an item whose leading segments name a panel
(InDesign's "Character: All Caps"); it also matches a catalogue path ending
with its segments, then its last segment as a unique label.
- Aliases count in the menu steps too, joined segments are tried as a label or
alias anywhere, and normalize_label reads a colon as a space, so the alias
"Preferences: General…" matches "Edit > Preferences > General...".
- A leading Show, Hide or Show/Hide is dropped only for commands marked
Bindable::toggle, and only after the names as written found nothing.
- Keys in skipped contexts are left out of unmatched rows, so entries with only
such keys no longer use up the suggestion cap.
reset, and an edit that leaves a command with its defaults, removed
only the entry the command read. A variant of its key
(window.panel { "panel": "layers" }) stayed and took effect on the
next load. Both remove every entry that resolves to the command.
conflicts_with lists the commands that share one command's chords, with
that command as a, without filtering every conflict. The README shows
set_bindings with one binding for an editor with one shortcut per
command; assign adds a binding.
Apps that stored { command: text } before read the field in older builds
again. Overrides with several bindings for a command still serialize as
the { command, keys } array. keymap::overrides_list always writes the
array, for craft-settings files, where only an array lets a reset entry
leave the file.
Hit carries the trigger that matched and whether the press matched it
exactly. Dispatcher::dispatch_filtered takes a predicate; a declined hit
leaves its event and text in place.
egui's Copy, Cut and Paste events stay with the app unless
with_clipboard_events(true): egui-winit sends them in place of the
clipboard presses, and a Paste event consumed as a hit lost its text.
With them on, Hit::pasted holds that text, and a web clipboard press
and the event it causes run once for every clipboard chord, not only
Cmd+C/X/V. The Hit doc line moves from DOUBLE_PRESS to Hit.
A movable panel's context menu always offered 'Move to group', even
when the layout held no other panel that accepts tabs — one open panel
showed an empty submenu. Layout gains move_targets() (the same filter
the menu applies: every other panel that accepts_tabs), the menu renders
only when it is non-empty, and a test pins the lone-panel and
protected-anchor cases.
(cherry picked from commit 671463c1af)
A tab squeezed below its natural width painted its whole label centred and only clipped it, so long names ran over the tab's edges and under its neighbours. The label is now cut with '…' inside the same padding the natural width is measured with, and a cut tab names its panel in full on hover.
(cherry picked from commit 5c28add6cf)
Discard a renderer-owned group gesture when its pointer disappears so a
missing outside release cannot resume dragging, while retaining touch drops.
Closes#95
Co-Authored-By: OpenAI Codex <noreply@openai.com>
(cherry picked from commit 9a6f159fc5)
On X11 (including XWayland) and Windows, the pointer leaving the window
reaches egui as PointerGone, and egui-winit then drops the button release
because it has no pointer position. finish_drag never saw the drag end:
the panel stayed in its dock with the drop outline following the pointer
until the next click inside the window.
Leaving the window now ends the drag as a drop. interact_pos still holds
the last position on that frame, and the panel floats there (clamped to
the window) instead of docking into whatever target sits at the edge.
Test: tab_dragged_out_of_the_window_floats_where_it_left. It fails without
the fix (no action at all).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit aecdf819ba)
craft-keymap holds the data and logic for keyboard bindings: egui-free
chords with one canonical text form, bindings to commands with params,
several bindings per command, scopes, tool groups, conflicts, overrides
kept in a craft-settings type, an import pipeline for other applications'
shortcut sets, and an optional egui dispatcher. Apps keep their command
catalogue, file readers and UI.
Skylar's review of 886a0b3:
1. tc/tid go locale by locale: in each locale the context or @id row,
then the plain row, so pt-br's plain row beats pt's context row.
2. pt (and pt-* other than pt-br) uses pt-PT plural rules: 0 is plural.
3. A script subtag (sr-Latn-RS, sr_RS@latin, be_BY@latin) never picks a
code of another script; sr-Cyrl-RS still picks sr.
4. A row defined twice is reported and the first kept, as in the checker.
5. The pseudo locale is debug-only via from_code, get marks too, and
looked_up() lists what was looked up for an app's audit.
6. intl_pluralrules pinned to =7.0.2; plural_form_counts copied from the
checker, plus pt.
7. I18n::new takes the app's offered codes: languages(), from_code,
match_tag and system_lang only return those; others stay fallbacks.
Smaller: en/pseudo/repeated codes are skipped and reported,
system_lang is cached, zh-hant-hk falls back to zh-hant, rtl adds ckb,
dv, ps, sd, ug and yi.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Proofing: say when a dictionary is installed but unreadable, with file and reason (storytold/wordcraft#2830)
* proof test: use SHIFT_JIS as the unsupported encoding (ISO 8859-1 is decoded since storytold/wordcraft#2904)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* storytold/wordcraft#1741: WIP
* storytold/wordcraft#1741: list items no longer get the sentence-capital grammar squiggle
The English "Capitalize the first word of a sentence" rule skips the first
word of a list paragraph (own or style numbering); later sentences are still
checked. check_text_with and paragraph_issues take a list_item flag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* storytold/wordcraft#1741: review fixes
Thread the capital-first flag into the Polish, Croatian, Italian, Turkish and
Portuguese grammar rules; test the drawn squiggles through layout() for own,
style and num 0 numbering; tidy the proof test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* storytold/wordcraft#1741: main's Vietnamese proofing test passes the new list-item argument
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* storytold/wordcraft#1741: the third call in main's Vietnamese proofing test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* storytold/wordcraft#1694: -ically adverbs are no longer flagged as misspelled
English spelling accepts "-ally" adverbs built on an "-ic" adjective in the
word list (specific → specifically, scientific → scientifically), as Word does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* storytold/wordcraft#1694: only -ic adjectives take -ally
Review (FredApps): the "-ally" → stem rule applied to every known word,
so "desktopally" was accepted. The rule is now "-ically" → "-ic"
(specifically → specific); other words don't take "-ally".
Test: non_ic_words_do_not_take_ally (desktop, house, final, green).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Review fixes (CodeRabbit): a glibc @latin/@cyrillic modifier becomes the
script subtag and only codes with that script are tried. sys-locale
drops the modifier, so on desktop Unix system_lang reads LANGUAGE,
LC_ALL, LC_MESSAGES and LANG raw, in its order. README: tl! qualifies
CURRENT with $crate; get follows the locale chain but never the plain
row or English.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Final review fixes: a European Portuguese locale wins over pt-br when both
are embedded; sr-latn no longer falls back to Cyrillic sr; get() looks up
one context with no plain or English fallback (key-cap names).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F9fM3mv1PUyKpk5rdwUQrW
Review follow-ups: the German patterns' MIT notice goes into NOTICE with
the other pattern sets; the pattern reader skips other commands'
arguments, so hyph-de-1996.tex's \message{} line no longer adds junk
patterns and exception words; a load test for the German set; the
status bar names a bare "de" tag; the docs list the languages with
their own hyphenation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runs in German (de, de-DE, de-AT, de-CH) hyphenated with the English
patterns, giving breaks such as "unterlieg-ende" and "rechtli-cher".
They now use hyph-utf8's German patterns for the reformed spelling
(MIT, bundled unmodified as assets/hyphenation/de.pat). German gets a
ProofLang of its own that, like Bulgarian, hyphenates only: no spelling
or grammar marks yet (storytold/wordcraft#1564). Portuguese, Italian and German share one
pattern lookup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Indonesian spelling reads the installed Hunspell id_ID dictionary at runtime
(Debian/Ubuntu hunspell-id, LibreOffice dict-id, the Dictionaries folder or
WORDCRAFT_DICTIONARIES), like Czech, Polish and Portuguese: nothing is bundled,
and without a dictionary Indonesian text gets no spelling marks.
- ProofLang::Id for id, id-ID, ind and the legacy in tag
- suggestions reuse the dictionary-driven Czech ranking (MAP/REP/TRY/KEY of
the loaded dictionary), nothing Indonesian hard-coded
- hyphenated reduplication accepted whole or by parts (kupu-kupu, anak-anak);
hyphenated clitics after capitals/abbreviations (KTP-nya, se-Indonesia)
- generic grammar only: repeated word (offers anak-anak first), space before
punctuation, double space, with Indonesian messages
- Review > Language, status bar, Set Proofing Language, File > Options >
Proofing list Indonesian; name translated in every catalog
- tests use a hand-written synthetic dictionary; engine end-to-end test
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>