Skip to content

Studio docking

Studio should dock the way the engine’s level editor does. Any panel can be split, tabbed or floated, and the result is remembered as one line. The approach is one dock brain with two skins. Phase 1 moved the editor’s model and rules into Halcyon as Halcyon.Docking (see Docking), and nothing in that namespace draws. Phase 2 is Studio drawing the same model with Avalonia controls. It starts after the Studio refresh lane lands, because that lane is editing MainWindow now.

What Studio takes from the brain, and what it builds

Section titled “What Studio takes from the brain, and what it builds”

Studio adds a project reference to Halcyon. The assembly references nothing and targets net10.0 like Studio, and Studio uses only the Docking namespace. No Halcyon widget, DrawList or UiTree enters Studio.

Shared (Halcyon)Studio’s own (Avalonia)
DockTree edits: tab, reorder, split a pane, split an edge, remove and collapseThe pane host control that arranges views over solved rectangles
DockLayoutSolver, with per-pane minimum widthsTab strips, measured by Avalonia’s own text layout
DockDragging.Resolve, InsertIndex, Tears, TearsFromStripThe drop indicator and the drag ghost
DockTree.ResizeDivider, DividerTravel, DividerGrabThe divider strips and their cursors
DockTree.SizedSplitFraction, the share a docked window arrives atFloating panes as OS windows
DockLayoutText, the one-line spelling and its readerWhere the line is stored (StudioSettings)
DockTabs, for tabs keyed by what they showWhich window ids exist, and what each one shows

Every panel becomes a window with an id, and the tab group on the right becomes an ordinary tabbed pane.

TodayWindow idNotes
Pallet list, the left columnpalletsIts header’s folder and refresh buttons move to the right end of the pane’s strip, so the pane keeps one header row.
Asset browser, the centerassetsThe required window, the way gameView is the engine’s. Selection starts here, so a stored layout that lost it reads as malformed and falls back to the default.
Inspector tabinspectorKeeps its preview drawer.
Visual tab (the patch editor)visualStays in the layout when the asset has no visual editor and shows that as its empty state. Today the tab hides, but a tab that leaves the tree loses the place a person gave it. The auto-switch setting becomes DockTabs.Focus.
Raw Edit tabrawEdit
Diagnostics tabdiagnosticsThe status bar’s error link and a tapped toast still open it, now by focusing it wherever it is docked.
Preview pop-out (PreviewWindow)previewBecomes a dockable window. Floating it is the pop-out, so the special window goes away.

These stay as they are: the toolbar with build and the Edit/View switch, the status bar, toasts, the build progress line, OmniSearch, the modal dialogs (new asset, folder or pallet, move and rename, Blender path, workspace setup), the Settings window and the cache viewer.

The default is today’s three columns in the layout spelling:

h[0.18:pallets,0.58:assets,0.24:inspector+visual+rawEdit+diagnostics]

Before anybody drags a tab, Studio looks as it does now. The strips replace the headers the panels already have, the right column’s tabs are the tabs it already shows, and the dividers keep today’s panelSplitter look (an 11-pixel grab area around a 1-pixel line). The preview starts closed, as the pop-out does now.

There is one change in behavior. Today the side columns are fixed pixel widths around a star column. The tree stores shares, so a wider window widens them too. The solver’s per-pane minimum widths (pallets 180, inspector 260, in Avalonia’s device-independent pixels) stop them collapsing. If mltn wants the old pixel-pinned feel back, that is a host choice about which fraction to write after a resize. It is not a change to the brain.

A small layout button in the toolbar opens a list of every window with an open/close check and a Reset layout row. Studio has no menu bar, so this stands in for the editor’s View menu.

Docked panes have no close X, in Studio or in the editor. A tab closes by a middle-click or from its right-click menu’s Close row, both built by the shared DockChrome; Studio’s first window of a mode is the one a mode cannot do without, so its Close row is disabled. Only a floating window carries an X.

The pane host. A Panel subclass overrides ArrangeOverride. It solves the tree into its bounds and arranges one pane control per DockPaneRect and one divider per DockDividerRect. Avalonia’s device-independent pixels are already scale-free, which is the same rule as the engine’s “floors in logical units”. The minimum widths pass straight through as DIPs.

Views are cached by window id. Moving a tab reparents the existing view and does not rebuild it, so tree expansion, scroll position and the Raw Edit caret survive a drag. Avalonia refuses a control that still has a parent, so the host detaches before it attaches.

Tab strips are a horizontal ItemsControl of tab headers with a front tab and a close glyph. A drag inside the strip reorders through DockDragging.InsertIndex, fed the right edges of the laid-out tab containers. It tears once TearsFromStrip says so. From then on, every pointer move calls Resolve with a DockDragOptions filled from Studio’s settings.

The drop indicator and ghost draw in an overlay layer above the pane host: a rectangle at the target’s PreviewPosition and PreviewSize, and the tab’s title riding the pointer. Avalonia blends in gamma space, and the engine blends in linear light. The engine’s indicator alpha therefore does not carry over, and Studio keeps its own number for it.

Dividers are thin controls with the resize cursor for their axis. They drive DockTree.ResizeDivider from a DividerGrab anchored at the press, which replaces the GridSplitters.

Floating panes are OS windows. Each one is an owned Avalonia Window (not in the taskbar) that hosts the same pane control. The brain already keeps floating panes outside the tree for exactly this reason. The drag controller works in screen coordinates and converts to the main window’s client space with each window’s own render scaling. It then passes Resolve the floating windows as DockFloatingTargets, so dropping on a floating window’s strip merges into it. Floating geometry is stored in screen DIPs and pulled back onto a visible screen when restored. Some platforms ignore a requested window position (Wayland does). There a restored window opens where the system puts it, and that is accepted rather than worked around.

Tuning. Studio starts from the engine’s numbers: an edge band of 16, a center zone of 0.3, pane and root splits of 0.5 and 0.25, and a tear distance of 12. If both hosts should always agree, the defaults move into the brain as named constants that the engine’s preferences and Studio’s settings both read, rather than being written twice.

  1. Reference Halcyon from Studio and replace the Grid with the pane host. It shows the default layout with no dragging, and a screenshot should match today’s.
  2. Add the dividers.
  3. Add tab reorder, tear, drop zones and the indicator inside the main window.
  4. Add floating OS windows and drops between windows.
  5. Store the layout in StudioSettings, restore it, and add the layout button with Reset.
  6. Fold the preview pop-out into the preview window.

Each step leaves Studio usable, and none of them changes Halcyon.Docking except where Studio finds a rule the engine never needed. Such a rule lands in the brain with a Halcyon test, so the editor gets it too.