* docking: fade a dragged native window only when the drag can dock
* docking: narrow the outer edge band to the canvas column and add DockStyle::drop_edges
* docking: drop_edges defaults to all sides; the docking profile's outer band takes only its top and bottom
* panes: preview the slot a dragged pane docks in, also while floating
* docking: a drop that changes only sizes is a no-op; a lone floating group's header drag looks through its window
* docking: a drop beside a frame too narrow to halve takes the room from beyond its divider
* panes: the dock preview counts a pane its drop reveals, and a side pane stays inside the host
With SidePolicy::OneSlot a drop can reveal a pane the frame hides, so the landing slot is
worked out from every open pane. A side pane is no wider than the room the panes outside it
leave, as the preview assumes; egui placed a wider one partly beyond the host's edge.
* docking: anchor an outer edge drop on the area it shows
The edge band and window.panel.dock to an outer edge anchor on the canvas only where the edge area narrows to the canvas column, else on a panel that is not protected, so tree order no longer decides the frame. The renderer tries the drop and offers the band only when it wraps the panels the band shows. A top (bottom) edge also narrows a split of rows to its first (last) row when only that row holds the canvas. to_actions takes the permissions, and FloatSpace records side_edge_zones.
* docking: a sized split gives the new frame its minimum or saved width
* docking: keep a native window opaque during a float-only drag
* docking: DockCommand::to_actions keeps its signature; to_actions_with takes the permissions
FilmCraft calls to_actions. Without permissions no panel is protected, so an outer edge dock
wraps the part between the docks, as before.
* window: title_bar module for app-drawn title bars
The empty part of an app's top strip moves the window on drag and follows
the macOS title-bar double-click setting. A control on the strip or an
in-window Order::Middle panel over it (floating docks, egui::Window) keeps
its own drag: window_movable is false there and, on macOS, sync_movable
sets NSWindow.movable from it. No gesture acts on the window in full
screen.
* window: empty_area returns the empty area's response
* docking: keep a tab's focus outline after Space or Enter
A focused tab activated from the keyboard reported a click, which marked its
focus as pointer focus and hid the outline. Only a primary pointer click marks
it now. The group header and DockArea::monitors docs say how a header widget
starts a drag and where an eframe app gets its monitor list.
* docking: name the area id in the group header's begin_drag note
* ui: panes::Rail, an icon strip that opens one task pane at a time
* ui: panes: a docked pane can go without its title bar
* ui: panes::Rail review fixes
* ui: panes: a docked pane's resize edge works on both sides of the line
Review follow-up. The test asserted `respell(validated, driver_default) == None`.
respell also returns None when it refuses a ticket, and in_drivers_spelling then
keeps the ticket as it is, so a ticket still spelled `ns0000` passed.
The test now compares the vendor namespace declarations of the ticket driver_ticket
returns with those of the driver's own default ticket and fails when a namespace the
driver declares is under another prefix. The comparison is
`ticket::prefixes_unlike_the_drivers` (test-only), unit-tested here: it flags the ticket
as the merge returns it, passes the respelled one and the driver's own, and flags a
ticket respell refused, where `respell(..) == None` can't tell it from a correct one.
Checked against a Brother HL-L2390DW: the test passes, and fails with
`(brother namespace, "ns0000", "brpsk")` when in_drivers_spelling is made to return
the merged ticket.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
The edge band and window.panel.dock to an outer edge anchor on the canvas only where the edge area narrows to the canvas column, else on a panel that is not protected, so tree order no longer decides the frame. The renderer tries the drop and offers the band only when it wraps the panels the band shows. A top (bottom) edge also narrows a split of rows to its first (last) row when only that row holds the canvas. to_actions takes the permissions, and FloatSpace records side_edge_zones.
With SidePolicy::OneSlot a drop can reveal a pane the frame hides, so the landing slot is
worked out from every open pane. A side pane is no wider than the room the panes outside it
leave, as the preview assumes; egui placed a wider one partly beyond the host's edge.
* docking: drop targets follow the pointer over the whole group
Outside side_edge_zones, a group body's outer third on each side docks
beside it and the middle joins as a tab; at a corner the relatively
nearer side wins, and a group that takes no tabs maps its whole body to
its edges. Dock-column groups get the same zones: top and bottom stack,
left and right make a column. Border bands (seam, column edge, new
dock, root edge) are 28 pt and reach only 4 pt into a tab strip that
takes tabs.
Previews are a drop_line_width line along the edge a drop docks to, or
the whole group's outline with a light fill for a tab join, for tab and
group drags alike. side_edge_zones keeps its 40 pt picture-frame zones,
overlay and previews (SIDE_EDGE_ZONE_DEPTH).
DockStyle: side_zone_fraction replaces zone_depth; new column_edge_band
and drop_line_width; edge_band, seam_band and new_dock_band default to
28.
* docking: document the drop zones, bands and previews
* docking: dock groups and the outer edge band keep the split-tree sizes with side edge zones
* docking: drop zones for hosts that resolve drops, lines kept in view, stripless dock groups
resolve_drop_by and DockStyle::zone_rule give hosts the dock area's zones and previews.
A stack line on an edge scrolled out of a dock column shows at the view's edge, and a dock
group without a tab strip takes its zones from the top of its body. A style needs a drop
line wider than zero and a side zone fraction of at most one half.
It came in with the first proofing commit (bcbe09df8) and has never had a caller; `#[allow(dead_code)]` kept the warning quiet. Because it is `pub(crate)`, nothing outside wordcraft-proof can reach it.
SonarCloud's analysis of main at b4da59f0c lists 23 closures such as `|v| v.as_slice()` that hand their argument straight to one method. This passes the method itself: `Vec::as_slice`, `str::to_ascii_lowercase`, `f64::total_cmp` and the like, all forms the repo already uses elsewhere. Behaviour is unchanged.
Five stay. The four in crates/proof/src/es/mod.rs sit in code that storytold/wordcraft#4191 replaces, and its replacement already uses `PoisonError::into_inner`. In crates/engine/src/io/safe_save.rs the path would be `<dyn std::error::Error + Send + Sync>::is::<Guided>`, which reads worse than `|inner| inner.is::<Guided>()`.
Review follow-up. When two vendor namespaces trade prefixes, both prefixes stay
declared, so a name written with one of them as an element or attribute name
(`<ns0000:Feature/>`, `ns0000:flag="1"`) still parsed and resolved after the
root declarations changed, but to the other namespace. respell only rewrites
`name`/`constrained` values and QName values, so such a name would not follow.
respell now returns None when any element or attribute is named in a namespace
whose prefix changes. Names in a namespace that keeps its prefix do not block
the respelling, and the real Brother tickets (vendor names only in `name`
attributes and QName values) are respelled as before.
Tests: regression cases for element and attribute names in a swap and in a
prefix chain, and a control for names in an untouched namespace. Each ticket
is asserted well-formed with every prefix resolving, so the case is refused
for the name, not for a parse failure. The refusal tests fail with the guard
disabled.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
* docking: blocking input hands the keyboard from a native window to the main window
* docking: the keyboard goes to the main window only if a native window still has it after a short wait
Review of #307:
- respell rewrites the root's namespace declarations, so an element below the
root that declares either prefix of a rename again (ns0000 or brpsk) would make
a name resolve to another namespace once the prefixes change. It now returns
None for such a ticket, and a test covers both prefixes.
- The vendor-element test replaced only an opening tag, left the closing tag
behind and so never parsed: it passed because respell refused the broken XML,
not because of what it was meant to check. Every refusal fixture is now
asserted well-formed, and the element is replaced whole. Doing so showed the
element guard was redundant: an element or attribute name in a renamed
namespace leaves an undeclared prefix, which the final parse of the result
already refuses. The guard is removed and the test now exercises that check.
- Doc comments on the new constants and tests.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
Windows' PTMergeAndValidatePrintTicket names a vendor namespace with a generated
prefix (ns0000) as soon as the merged ticket uses a name from it. driver_ticket
merges the driver's own paper option from its capabilities, which carries vendor
properties, so a Brother HL-L2390DW's ticket came back with ns0000 where the
driver's own default ticket says brpsk. XML-wise the two are the same; the
driver's print pipeline reads the prefix as text. The job was accepted and then
ID2D1PrintControl::AddPage (or Close, in older builds) failed with 0x80040003,
reported by winprint as "Render error" / "Failed to close print control: No such
interface supported".
The same ticket sent raw with brpsk prints; with only the prefix changed to
ns0000 it fails the same way, so the prefix is the whole difference.
driver_ticket now re-spells the validated ticket with the prefixes of the driver's
own default ticket (an empty ticket, validated), through ticket::respell: pure
XML, tested on every platform, and a no-op unless a vendor namespace is spelled
differently. It rewrites only the root declarations and the vendor names in name
/ constrained attributes and QName values, handles two namespaces trading
prefixes, and leaves the ticket as it is when a rename could be ambiguous or leave
a prefix undeclared.
Verified on a Brother HL-L2390DW: the job spools, and pdfcraft head's doc_print
that failed at AddPage prints.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
* docking: route app drags in panel bodies across native windows
While an app's drag that began in a panel body lasts, the owner window's
input hook sends pointer moves over another of the app's windows to that
window in its coordinates (PointerMoved, PointerGone, and the primary
release), and the owner sees the pointer gone there. Drop sites in the
target window see hover and release with no app changes.
* docking: wrap the body-drag docs
* docking: a stopped body-drag route takes back the events it queued
* docking: route only body drags that carry a payload, and hold the owner's release
A body drag reaches other windows only with an egui drag-and-drop
payload or when the app calls docking::route_app_drag that frame or the
last; sliders, scrollbars and other plain drags keep the owner's
pointer. When the release goes to another window, the owner's own
release waits for its next input, so a target drawn before the owner
reads the release while egui's payload still exists. A stop never
withdraws that held release.
* docking: routed and held events come before a window's own input
* docking: a held release waits for the main window's next pass, with the pointer events after it
* docking: the owner sees the pointer go in the pass its release is held
The driver's PrintCapabilities tell a driver that dropped a requested value
from one that never had the feature. Grayscale on a driver with no colour
option and uncollated copies on one that can't collate are accepted, like
one-sided on a queue without duplex. Collated copies on a driver that can't
collate are refused with their own message. The capabilities are read only
when the strict check fails.
* docking: native windows ignore input while the app shows a modal
DockArea::input_blocked disables each native floating window's widgets, drops its pointer, key, text and scroll input, covers it with DockStyle::blocked_dim over a layer that takes the pointer, offers no drop targets, forwards no events and cancels its OS close request. Turning it on cancels a drag in progress.
* docking: a blocked native window also drops hovered and dropped files
* docking: blocked palettes drop to the normal window level
* docking: a blocked native window still forwards key releases
* docking: while input is blocked, activation raises the main window last
* docking: blocked palettes stay shown, and the main window comes in front on the app's return
* docking: normal floating windows come forward when the app is activated
* docking: drop windows that became palettes from the raise queue
* docking: the main window comes forward with the normal windows, in focus order
* docking: replacing the workspace cancels a drag in progress
A dock area keeps the layout it drew while a drag is in progress, with
the actions it returned. When the next layout is neither that one nor
that one with the actions applied, the app replaced or changed it, and
the area cancels its tab, group, column, window bar, native bar, divider
and cross-window drags itself. cancel_drag also ends a native window's
bar drag.
* docking: replacing the layout also ends a divider drag in a native window