Commit 28630aa
feat(045): cross-window tab dock-in (Center) + floating tab chrome (#388)
* feat(045): cross-window tab dock-in (Center) + floating tab chrome
Adds support for dragging a tab from any DockHost (main window or
floating) into another floating window's tab group, materializing it
as an appended tab. Also lands the §4.2 `TitleBar`-zone tab chrome
for floating windows (Edge / Files / modern-VS pattern).
## Cross-window dock-in (§4.6 / §2.4)
WinUI's `TabView.CanDragTabs` drag pipeline is **window-local**: drag
events never reach AllowDrop elements in another XAML island, so the
P3 live-overlay path cannot deliver Center-only dock-in across HWNDs.
Workaround: drop-completion cursor hit-test.
- `src/Reactor/Docking/Native/DockFloatingPaneRouter.cs` (new):
global `ReactorWindow -> append-as-tab` registry guarded by a lock.
`Register` / `Unregister` / `TryAppendUnderCursor` /
`HasRegisteredWindows`. Uses Win32 `GetCursorPos` +
`GetWindowRect` + `SetForegroundWindow` (P/Invoke) for the
HWND-boundary hit-test.
- `DockHostNativeComponent.HandleTabDragCompleted`: now consults the
router *before* falling back to tear-out, so a tab dropped over a
floating window's chrome appends as a new tab in that window.
- `DockFloatingWindowComponent`: self-registers with the router on
mount via `UseEffect` deferred through `DispatcherQueue.TryEnqueue`
(so the post-Open `windowHolder[0]` assignment lands first), and
unregisters on close. Floating-to-floating redirect lives in the
component's `onTabDragCompleted` closure.
- `DockDropTargetOverlayControl`: reserved `DockDropOverlayMode.CenterOnly`
enum value + `ApplyModeVisibility` branch for the future live-overlay UX.
- `DockHostModel.OnExternalCrossWindowDrop`: internal hook for adapters.
## Floating tab chrome (§4.2 / §4.3)
- `DockFloatingWindow`: `Open()` now mounts `DockFloatingWindowComponent`
with `WindowSpec.ExtendsContentIntoTitleBar = true`. `BuildChrome`
renders `DockTabGroup` at the root + a transparent `BorderElement`
in `TabStripFooter` whose `OnMount` calls `window.SetTitleBar(self)`.
Brush construction is deferred to `OnMount` to keep `BuildFloatingRoot`
headless-safe for unit tests.
- Component owns panes via `UseReducer<IReadOnlyList<DockableContent>>`
(functional updater avoids stale-closure bugs); subscribes to
`DockDragSession.SessionChanged`.
## Spec / tests
- Spec 045: tick §4.2 / §4.3 / §2.4 cross-window dock-in bullets;
document the drop-completion hit-test and the WinUI window-local
limitation.
- `tests/Reactor.Tests/Docking/Native/DockFloatingWindowTests.cs` (new):
5 unit tests pinning the `BuildFloatingRoot` element-tree shape.
## Verification
- `dotnet build Reactor.slnx -c Debug -p:Platform=x64`: 0 errors.
- Full unit suite (`tests/Reactor.Tests`): 8773 passed, 0 failed, 46 skipped.
- Selftests (`Reactor.AppTests.Host --self-test`): 826/827 pass
(1 flake in `DynDock_OpenWelcomeButton_Mounted` is a pre-existing
render-timing flake — passes in isolation).
- E2E (`tests/Reactor.AppTests`): 64/64 pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* review: address Copilot CR feedback on #388
Six inline comments from the automated reviewer:
1. `DockFloatingWindow.cs` (~L80) — Comment near
`ExtendsContentIntoTitleBar = true` referenced the old
`TitleBarElement` / `Microsoft.UI.Xaml.Controls.TitleBar` path
that the PR explicitly moved away from. Rewrote to describe the
actual implementation (root TabView + transparent `BorderElement`
in `TabStripFooter` wired via `Window.SetTitleBar`) and call
out why `Microsoft.UI.Xaml.Controls.TitleBar` is intentionally
NOT used here.
2. `DockFloatingWindow.cs` (~L270) — `fe.Loaded += (_, _) => Apply();`
never unsubscribed; WinUI `Loaded` re-fires on reparent/reload
which would re-call `SetTitleBar` and leak the delegate. Replaced
with a one-shot `RoutedEventHandler` that detaches itself before
calling `Apply()` (mirrors the pattern in
`DockSideStripRenderer.cs:260`).
3. `DockFloatingWindow.cs` (~L470) — The temporary `reAppend`
callback used during the unregister/append/re-register gap (the
floating→floating drag-completed path) did not update the
`AppWindow.Title` on first-tab append, unlike the main router
`append` closure built in `UseEffect`. A pane appended during
that small gap would leave the title stale until the next render
re-registered the canonical `append`. Refactored by hoisting
`append` to a local function shared by both registration sites.
4. `DockFloatingWindow.cs` (~L501) — Dead-code `ContainsByRef`
helper removed.
5. `DockHostNativeComponent.cs` (~L387) — Cross-window dock-in into
an existing floating window was being logged as `DragTearOut`
even though it appends as a tab (no new floating window is opened).
That makes the operation log / replay analysis misleading. Switched
to `DragConfirm` to match the semantics used by the
`OnExternalCrossWindowDrop` adapter path.
6. `samples/apps/dock-showcase/App.cs` (~L1144) — Typo in
persistence-note string: literal TAB + `"abChrome"` (missing the
leading `t`). Fixed to `"tabChrome"` so the showcase UI reads
correctly.
Verification:
- `dotnet build Reactor.slnx -c Debug -p:Platform=x64` — 0 errors.
- `dotnet test tests/Reactor.Tests --filter Docking` — 350/350 pass.
- `NativeDocking_FloatingWindow_*` selftest fixtures — 4/4 pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* review: address CodeQL feedback on #388
Second-round review from `github-code-quality[bot]` flagged 10
issues across 3 files (with several near-duplicate comments on the
same lines):
1. **Floating-point equality (CornerRadius == 0)** —
`NativeDockingSmokeFixture.cs:1507-1509`. Production sets
`new CornerRadius(0)` which yields exact 0.0, but CodeQL flags
any `== 0.0` on a `double`. Switched to `Math.Abs(x) < 1e-9`
with a documented epsilon constant. Same intent, no CodeQL noise.
2. **Foreach-with-type-guard → `OfType<T>()`** —
`NativeDockingSmokeFixture.cs:1562-1573` (HasHeader helper).
Replaced two nested `foreach (var x in seq) if (x is T t)` with
`foreach (var t in seq.OfType<T>())` for `TabItems` and
`StackPanel.Children`. Implicit usings already give us LINQ.
3. **Same-variable branch → ternary** —
`DockDropTargetOverlayControl.cs:289-294`. Collapsed the
`if/else` in the `centerOnly` mode (both branches assigning
`entry.Button.Visibility`) into one ternary assignment.
4. **Generic catch clauses (3 sites)** —
`DockFloatingPaneRouter.cs:87, 91, 101`. Replaced bare
`catch { … }` with typed catches for the expected recoverable
exceptions when probing a floating window during shutdown:
`COMException` / `ObjectDisposedException` /
`InvalidOperationException`. Unknown exceptions now surface
instead of being silently swallowed.
Verification:
- `dotnet build Reactor.slnx -c Debug -p:Platform=x64` — 0 errors.
- `dotnet test tests/Reactor.Tests --filter Docking` — 350/350 pass.
- `NativeDocking_TabChromePresetsApplyAndClear` selftest — 14/14
assertions pass (including the epsilon CornerRadius check).
- `NativeDocking_FloatingWindow_*` selftests — 0 failures.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>1 parent e72e4d8 commit 28630aa
19 files changed
Lines changed: 1626 additions & 107 deletions
File tree
- docs/specs/tasks
- plugins/reactor/skills/reactor-dsl/references
- samples/apps/dock-showcase
- skills
- src/Reactor/Docking
- Native
- Persistence
- tests
- Reactor.AppTests.Host/SelfTest
- Fixtures
- Reactor.Tests/Docking
- Native
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
596 | 596 | | |
597 | 597 | | |
598 | 598 | | |
599 | | - | |
600 | | - | |
601 | | - | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
602 | 615 | | |
603 | 616 | | |
604 | 617 | | |
| |||
2061 | 2074 | | |
2062 | 2075 | | |
2063 | 2076 | | |
2064 | | - | |
2065 | | - | |
2066 | | - | |
2067 | | - | |
2068 | | - | |
2069 | | - | |
2070 | | - | |
2071 | | - | |
| 2077 | + | |
| 2078 | + | |
| 2079 | + | |
| 2080 | + | |
| 2081 | + | |
| 2082 | + | |
| 2083 | + | |
| 2084 | + | |
| 2085 | + | |
| 2086 | + | |
| 2087 | + | |
| 2088 | + | |
| 2089 | + | |
| 2090 | + | |
| 2091 | + | |
| 2092 | + | |
| 2093 | + | |
| 2094 | + | |
| 2095 | + | |
| 2096 | + | |
| 2097 | + | |
| 2098 | + | |
| 2099 | + | |
| 2100 | + | |
| 2101 | + | |
| 2102 | + | |
2072 | 2103 | | |
2073 | 2104 | | |
2074 | 2105 | | |
2075 | | - | |
2076 | | - | |
2077 | | - | |
| 2106 | + | |
| 2107 | + | |
| 2108 | + | |
| 2109 | + | |
| 2110 | + | |
2078 | 2111 | | |
2079 | 2112 | | |
2080 | | - | |
2081 | | - | |
2082 | | - | |
| 2113 | + | |
| 2114 | + | |
2083 | 2115 | | |
2084 | | - | |
2085 | | - | |
2086 | | - | |
2087 | | - | |
| 2116 | + | |
| 2117 | + | |
| 2118 | + | |
| 2119 | + | |
| 2120 | + | |
| 2121 | + | |
2088 | 2122 | | |
2089 | | - | |
| 2123 | + | |
| 2124 | + | |
2090 | 2125 | | |
2091 | 2126 | | |
2092 | 2127 | | |
2093 | | - | |
2094 | | - | |
| 2128 | + | |
| 2129 | + | |
| 2130 | + | |
| 2131 | + | |
| 2132 | + | |
2095 | 2133 | | |
2096 | 2134 | | |
2097 | 2135 | | |
2098 | | - | |
2099 | | - | |
2100 | | - | |
2101 | | - | |
| 2136 | + | |
| 2137 | + | |
| 2138 | + | |
| 2139 | + | |
| 2140 | + | |
| 2141 | + | |
| 2142 | + | |
| 2143 | + | |
2102 | 2144 | | |
2103 | 2145 | | |
2104 | 2146 | | |
| |||
2113 | 2155 | | |
2114 | 2156 | | |
2115 | 2157 | | |
2116 | | - | |
2117 | | - | |
| 2158 | + | |
| 2159 | + | |
| 2160 | + | |
| 2161 | + | |
| 2162 | + | |
| 2163 | + | |
| 2164 | + | |
| 2165 | + | |
| 2166 | + | |
| 2167 | + | |
2118 | 2168 | | |
2119 | 2169 | | |
2120 | 2170 | | |
| |||
2124 | 2174 | | |
2125 | 2175 | | |
2126 | 2176 | | |
2127 | | - | |
2128 | | - | |
2129 | | - | |
| 2177 | + | |
| 2178 | + | |
| 2179 | + | |
| 2180 | + | |
| 2181 | + | |
| 2182 | + | |
| 2183 | + | |
| 2184 | + | |
| 2185 | + | |
2130 | 2186 | | |
2131 | 2187 | | |
2132 | 2188 | | |
| |||
Lines changed: 3 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
346 | 347 | | |
347 | 348 | | |
348 | 349 | | |
| 350 | + | |
349 | 351 | | |
350 | 352 | | |
351 | 353 | | |
| |||
918 | 920 | | |
919 | 921 | | |
920 | 922 | | |
| 923 | + | |
921 | 924 | | |
922 | 925 | | |
923 | 926 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
| 67 | + | |
67 | 68 | | |
68 | 69 | | |
69 | 70 | | |
| |||
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
79 | 81 | | |
80 | 82 | | |
81 | 83 | | |
| |||
1040 | 1042 | | |
1041 | 1043 | | |
1042 | 1044 | | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
0 commit comments