Commit Graph
95 Commits
Author SHA1 Message Date
aiongg 8b19c44267 docking: a dragged window fades only while it highlights a drop target (#321)
* docking: a dragged native window fades only while a drop target is highlighted

The dragged window stays opaque over empty space and goes to 75% only
while the bar, only-tab or only-tab-row drag shows a drop preview.
paint_group_drag reports whether it highlighted a target; end_cross keeps
that for the next frame, since the dragged window sets its opacity before
the preview is drawn.

* docking: a dragged in-window floating window fades only while a drop target is highlighted

The rule for native windows covers in-window floating windows too. A
title bar drag draws its window at DRAG_OPACITY (75%) while the drag
highlights a drop target, and opaque over empty space and after a drop,
Escape or focus loss. finish_group_drag keeps paint_group_drag's result
for the next frame, since the window draws before the preview.
FloatingFrame takes an opacity for the whole frame, contents included.

* docking: a title-bar drag also fades over a preview the host draws
2026-10-25 12:21:53 -04:00
aiongg da5bc8df94 docking: native palettes stay shown while the app is inactive (#326)
* docking: keep native palettes shown while the app is inactive

Palettes no longer hide when none of the application's windows has focus.
They drop to WindowLevel::Normal while the application is inactive, so
they do not cover other applications, and go back to AlwaysOnTop when it
is active again. Files dragged from another application reach them, OS
sheets attached to them stay open, and they stay shown, dimmed, while an
app modal blocks input.

* docking: on Windows an inactive app's palettes go behind other windows

* docking: the viewer level test expects the inactive palette level
2026-10-25 12:18:10 -04:00
aiongg 200b68fd6a docking: a macOS window bar drag follows AppKit's cursor (#325)
AppKit reports a native window's new place as soon as a move is
commanded, but measures the pointer events it sends against where the
window server had the window, which can be a move behind. Placing the
window from those events made it overshoot and step back while the
cursor moved smoothly.

On macOS the window now goes to NSEvent::mouseLocation, flipped against
the primary screen's height and divided by egui's zoom factor, less the
pressed point; drop targets are hit at the same point. Other platforms
keep placing the window from the frame's pointer move.

The cursor source is a DesktopCursor the test harness fixes. NativeHost
can tell the cursor (tells_cursor) and measure a window's pointer events
against its place some frames back (stale), through window_pointer.
2026-10-25 12:09:34 -04:00
aiongg c74116411d docking: a group drops on its own column's window edge, and window-edge drops preview a line (#324)
A group bar drag to a dock band named by one of the moving panels, such as the window edge of the column the group heads, anchors on another panel of that column, as a tab drag does; with none, the drop is a no-op. Before, the model rejected the move and the drop did nothing.

Drops on the bands along the layout's outer edge (a new dock, a new outermost column, the outer edge band) preview the line along that edge instead of the rect the panels take; drops beside groups keep the rect. The window-edge column line sits inside the window, like the new-dock line.
2026-10-25 12:08:55 -04:00
aiongg bab74e1f4c docking: a canvas body takes no drop in the docking profile (#323)
With new_dock_zones (and without side_edge_zones), the body of a frame or
floating group that takes no tabs is no drop target, whatever its
accepts_splits. A floating window released there stays floating and a
docked panel floats as over empty space; the outer edge band, dock columns,
new docks and drops beside other groups still dock. Without new_dock_zones
the whole canvas body still goes to the nearest of drop_edges.
2026-10-25 12:07:15 -04:00
aiongg 2ccf7ea3a2 Merge remote-tracking branch 'upstream/main' into dock-result-preview
# Conflicts:
#	crates/ui/src/docking/drop_tests.rs
#	crates/ui/src/docking/render/preview.rs
2026-10-25 14:01:23 +00:00
aiongg edef7915d2 docking: fixes from the first Mac test round (edge band, drop_edges, no-op drops, narrow frames, pane previews) (#312)
* 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.
2026-10-25 09:59:17 -04:00
aiongg ea4c823abb docking: save every floating window's id (#317) 2026-10-25 09:50:13 -04:00
aiongg cdc6713130 Merge branch 'mac-b1-edges' into dock-result-preview 2026-10-25 07:01:22 +00:00
aiongg 91aaa294d0 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.
2026-10-25 06:18:34 +00:00
aiongg d004f40c25 docking: preview a drop beside something with the rect the panel takes 2026-10-25 05:56:38 +00:00
aiongg 950b9644e8 docking: a drop beside a frame too narrow to halve takes the room from beyond its divider 2026-10-25 05:34:35 +00:00
aiongg 63b3e4e2e8 docking: a drop that changes only sizes is a no-op; a lone floating group's header drag looks through its window 2026-10-25 05:34:35 +00:00
aiongg 876d2539f9 docking: drop_edges defaults to all sides; the docking profile's outer band takes only its top and bottom 2026-10-25 05:20:27 +00:00
aiongg 889f0367f2 docking: narrow the outer edge band to the canvas column and add DockStyle::drop_edges 2026-10-25 05:18:09 +00:00
aiongg 8a503cfe79 docking: drop targets follow the pointer over the whole group (#310)
* 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.
2026-10-24 23:51:43 -04:00
aiongg e95bc0d2a2 docking: blocking input hands the keyboard to the main window (#306)
* 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
2026-10-24 22:10:52 -04:00
aiongg 869928d2a0 docking: app drags in panel bodies reach other native windows (#302)
* 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
2026-10-24 20:36:23 -04:00
aiongg 38c4ecab17 docking: native windows ignore input while the app shows a modal (#298)
* 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
2026-10-24 18:27:19 -04:00
aiongg acb7290804 docking: normal floating windows come forward when the app is activated (#301)
* 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
2026-10-24 18:12:06 -04:00
aiongg f9181cc7b9 docking: replacing the workspace cancels a drag in progress (#294)
* 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
2026-10-24 18:11:42 -04:00
aiongg d12a6db750 docking: an input hook and dropped files for every native window (#293)
* docking: an input hook and dropped files for every native window

eframe runs raw_input_hook only for the root viewport, and each native window's
dropped files arrive only in that window's input. DockArea::native_input_hook runs
an app hook on each native window's RawInput through an egui plugin, before egui
reads it, and DockOutput::window_files reports files hovered or dropped on native
windows with the window and the panel under the pointer. The test host takes
hovered and dropped files per window.

* docking: a discarded pass reports no hovered files; share hook state through a mutex

* docking: one file report per native window when its pass is discarded
2026-10-24 17:53:29 -04:00
aiongg 75da958619 docking: panels closed one by one reopen into one group (#290)
* docking: panels closed one by one reopen into one group

Showing a hidden panel whose anchor is hidden too follows the chain of
hidden anchors to its far end, so a floating location no longer opens a
second window. The hidden panels the restore passed then anchor back on
the panel before them in the chain, on the recorded side, so reopening
them joins the restored panel instead of rebuilding their group or
window. A restored tab whose next tab is not in the anchor's group goes
last.

* docking: a panel beside an edge frame keeps its own location on reopen

* docking: a panel reopened through a hidden neighbour keeps its own group state; the chain stops reversing at an edge frame
2026-10-24 17:52:34 -04:00
aiongg c75bf618f0 docking: viewer panels float as normal windows (#296)
* docking: viewer panels float as normal windows

DockContent::window_kind marks panels as palettes (the default) or normal
windows. A native floating window holding any Normal panel gets the normal
window level and stays shown while the app is in the background; drags
treat it as stacked with the main window by focus.

* docs: a normal window behind the main window takes drags outside it
2026-10-24 17:13:37 -04:00
aiongg c98ee15a7f docking: tabs shrink before they overflow (#295)
* docking: tabs shrink before they overflow

Tabs in a narrow strip shrink, widest first, down to DockStyle::min_tab_width (54 pt at the
default font), cutting their labels with "…" and naming them in a tooltip. Only tabs that do
not fit at that width go to the overflow menu.

* docking: the drag bar narrows once the strip is narrower than the header plus the bar
2026-10-24 17:13:12 -04:00
aiongg 36db71c5c3 docking: floating windows stay in the app window on Wayland (#292)
* docking: floating windows stay in the app window on Wayland

The compositor reports no window positions, so native windows could not
dock again by drag or reopen where they were. The dock area detects
Wayland (WAYLAND_DISPLAY or WAYLAND_SOCKET set and a main window that never
reported its outer rect) and draws floats in-window, keeping their native
rects. DockArea::native_windows(bool) overrides the choice.

* docking: a missing DISPLAY also counts as a possible Wayland backend

* Revert "docking: a missing DISPLAY also counts as a possible Wayland backend"

This reverts commit a804afb1d3.

* docking: the Wayland session is a pure function of the environment; without a Wayland variable only a real window counts
2026-10-24 17:13:00 -04:00
aiongg 6fdd2efbba docking: showing a panel with no known place opens it in the first dock group or at an edge (#286)
* docking: open an unanchored panel in the first dock group or at an edge

* docking: an unanchored open prefers the end-side dock; an edge frame is a quarter wide

* docking: drop the redundant edge resize; an edge frame is already a quarter wide

* docking: an unanchored open passes over minimized groups and iconic columns
2026-10-24 16:52:51 -04:00
aiongg 17744430be docking: a divider drag keeps the split's fixed or ratio size (#291) 2026-10-24 16:18:39 -04:00
aiongg 5a371c978e docking: cancel the OS close of a native window that may not close (#288) 2026-10-24 16:14:58 -04:00
aiongg a0679c1dfc docking: dock column groups give way to the filler from their default heights (DockContent::dock_heights) (#287)
* docking: default, compact and fill heights for dock column groups

DockContent::dock_heights gives a panel's body heights in a dock column while the
user has not resized its group. Groups at their default give way down to their
compact heights so the filling group gets its fill height; then every group gives
way down to its minimum. Saved heights stay. Without it, layouts are unchanged.

* docking: dock column defaults give way nearest the filler first

The group nearest the filling group shrinks to its compact height before the next
one gives way, so groups further up keep their defaults.

* docking: shared groups with different minimums split the rest evenly above their minimums

* docking: a group asking for a fill height fills the column beside groups without dock heights
2026-10-24 16:14:38 -04:00
aiongg 6680968a83 docking: DockArea::locked draws a locked workspace; a lock still records native windows 2026-10-24 13:12:58 +00:00
aiongg 1564d54959 docking: a native window moved by its only tab or group joins a body's center
Dragging a native window's only tab, or its only group, moves the
window and docks it on release. It took a frame's edges and its tab
strip but not its body's center, a rule meant for the bar, so the
center rectangle was neither a target nor filled under the pointer.
Such a move now joins the center as the tab or group drag would; the
bar still does not.
2026-10-24 04:49:32 +00:00
aiongg 3250dd2305 docking: the macOS × sits 8 pt from the corner, its hit area reaching away from it
The ×'s glyph inset, size and hit area are set in one place (MACOS_CLOSE,
OTHER_CLOSE in render/native.rs). On macOS the glyph sits where macOS puts its
close button, and the hit area reaches 10 pt toward the leading edge and down
to the bar's bottom, past AppKit's corner resize area. Elsewhere the layout is
the same as before. CRAFT_DOCK_DEBUG_CLOSE=1 outlines the hit area and AppKit's
resize areas.
2026-10-24 03:53:42 +00:00
aiongg 09e14347d3 Merge branch 'docking' into dock-windows 2026-10-24 03:44:56 +00:00
aiongg 2f90228e10 docking: a narrow group splits when its neighbours have room for both halves
A side split that leaves a half at its minimum is taken when the
dividers along that half can still travel, the groups beyond giving
way as they do for a dragged divider. An 80 pt group, whose halves
need 84, splits on all four sides and takes the rest from its
neighbour; only when the groups beyond are at their minimums too does
the drop join the group's tabs.
2026-10-24 03:41:27 +00:00
aiongg 308163bcb5 docking: macOS resizes native windows itself, bands elsewhere are 6 pt with 16 pt corners
On macOS the native window is resizable by AppKit again, with its own areas
and cursors. The bar's buttons sit 19 pt in from the trailing edge, clear of
AppKit's corner area (about 15 pt into the window, a 19 pt square measured on
macOS 26), and the window sets no cursor of its own over AppKit's areas.
Elsewhere the bands are 6 pt deep with 16 pt corner Ls, and hand the resize
to the window system; the manual resize path is gone.
2026-10-24 03:40:17 +00:00
aiongg 1c24845a17 Merge branch 'docking' into dock-windows 2026-10-24 03:13:34 +00:00
aiongg 1ccdad9512 docking: Floating::with_id for layouts built in code; debug builds warn about rebuilt layouts
Floating::new, single and stack draw a fresh id on each call, so a layout
built anew each frame opened a new OS window each frame. with_id gives such
a layout the same windows every frame. Debug builds log a warning once when
three frames running replace every native window with new ones holding the
same panels.
2026-10-24 03:09:14 +00:00
aiongg cb352b39ff docking: all four drop zones of a group share one depth
Each side zone reaches zone_depth into the body, or a quarter of the
body's smaller side when that is less. A tall narrow group's top and
bottom zones are no deeper than its left and right ones.
2026-10-24 03:06:11 +00:00
aiongg 99d8fe537e docking: a dragged divider shrinks the groups beyond it in turn
A divider dragged past the minimum of the group it shrinks moves on to
the next group along the axis, and so on until every group on that side
is at its minimum. The line follows same-axis splits up the tree and
ends at a split across the axis or beside a dock. Groups touching the
divider on the other side grow, up to their maximums, as before.

Stacked panels and the groups of a dock column cascade the same way.
2026-10-24 03:05:32 +00:00
aiongg b892ec92e2 docking: native window bar buttons win over the resize bands, corners are 4 pt Ls
The bands along a native window's edges leave out the bar's buttons, which
sit 4 pt in from the window's edge. Each corner is an L, 4 pt deep and 12 pt
along both edges. macOS windows are not resizable by AppKit, whose resize
areas reach over the ×; the bands resize them by InnerSize and OuterPosition,
since winit has no BeginResize there.
2026-10-24 03:05:03 +00:00
aiongg 0008f89212 Merge branch 'docking' into dock-windows
Native windows draw a frame's drop zones through the destination window's
painter, and the bar-docking test aims inside the 40 pt right band.
2026-10-24 00:00:43 +00:00
aiongg e6c76f5151 docking: drop zones are 40 pt bands below the tab strip, all outlined while dragging
A frame's side zones reach DockStyle::zone_depth (40 pt) into the body below
the tab strip, or a third of a smaller body, so a frame keeps its center.
drop_zone, zone_shape and resolve_drop take the depth. In the Frame style a
drag over a frame outlines every zone a drop there would change something in
and fills the one under the pointer, without the outline of the resulting rect.
2026-10-23 23:57:43 +00:00
aiongg 909410258b docking: document native window placement, moves and mixed scale factors 2026-10-23 18:06:10 +00:00
aiongg 783793cbbc docking: document where a native window's bar docks it 2026-10-23 17:31:11 +00:00
aiongg 619c93ea29 docking: document drags between OS windows 2026-10-23 17:24:25 +00:00
aiongg 99f4511e6d docking: document native windows 2026-10-23 08:27:19 +00:00
aiongg 01ce7162a4 docking: Floating::stack is public, and four-way handles take the pointer only
Floating::stack builds a window from groups stacked top to bottom, so an
app's own stacked layouts migrate without building the split chain. The
four-way handle no longer takes keyboard focus: it has no label or key
action, and the two dividers it joins take arrow keys.
2026-10-23 06:11:03 +00:00
aiongg 281ea71f5b docking: the drop preview shows the zone under the pointer
With side_edge_zones, a drop on a group fills the zone the pointer is in
(a side's trapezoid, or the center rectangle with the tab strip) and
outlines the rect the drop gives what moves. zone_shape gives a zone's
outline; DockDriver::filled_polygon checks it. The dock-and-canvas
arrangement keeps its filled half and insertion lines.
2026-10-22 20:20:26 +00:00
aiongg 809bf90936 docking: a floating window holds a tree like the docked layout
Floating::root replaces the stack of groups. A floating window takes
splits in any direction, tab groups and accordions; its groups split,
join and resize like frames, with the Sizer, four-way handles
(Divider::WindowSplit, Divider::WindowIntersection,
Action::ResizeWindowSplit) and the room check. Tabs and drag bars inside
it show the targets of every window and dock; Undock Panel Group floats
one group of a window that has several. Floating::minimized replaces
per-group minimizing in a window.

Saved layouts keep loading: a stored stack of groups becomes a vertical
split sized by the groups' heights. Placement::Group on a floating anchor
splits its group above or below. A window moved onto a dock keeps its
groups as stacked groups.
2026-10-22 20:19:09 +00:00