When the app self-manages the display transform (OAK_MACOS_LAYER_
COLORSPACE=display set at startup), the CAMetalLayer is tagged with the
main display's colorspace so ColorSync passes the already-transformed
pixels through — without the tag the OS would re-correct them (double
correction). The CoreGraphics FFI lives in its own module because the
build script forwards metal_renderer.rs FFI declarations into the
Metal shader header.
- effect_stack: the params view is cached per effect (recreation per
render killed in-progress slider drags and clicks); new
LibraryEffectDrag payload + AddTypeRequested event let an effect
dragged from the app's effect library drop onto the stack at the
indicator position
- timeline: left-press selects clips (plain press selects an
unselected clip, keeps multi-selection on a selected one for group
drags, Ctrl/Cmd toggles membership)
- node_graph: graph_position_at() converts a window-space point (drop
position) to graph space
- Menu popups (menu bar, context menus, submenus) now occlude hover
beneath them via a BlockMouse hitbox, so items under an open popup no
longer stay highlighted.
- Menu bar dropdowns anchor to the opened title's bottom-left (local
anchoring) instead of the raw pointer position plus a fixed offset;
submenus are absolute children of the popup with separator-aware row
alignment.
- Selected timeline clips render one luma notch darker so the
selection is visible against the green clip body.
- oak theme palette to a denser professional dark (near-black base,
container/window surfaces, subtler secondary text).
- Dock tabs: selected tab solid accent with white text, hairline
separators between panels.
- Timeline: green clip blocks, blue playhead, neutral track headers,
brighter ruler labels, deep-green waveforms.
- Viewer: green timecode readout, solid-accent play button, chip-style
fit/safe-margin buttons.
- Effect stack cards and node cards get rounded borders; spinbox
values in amber per the design.
NodeGraphView::set_selection selects nodes programmatically (emitting
SelectionChanged); EffectStackDataSource::selected_effect highlights
the chosen card and EffectStackEvent::CardSelected reports card clicks
— the two-way clip/node selection sync between the timeline, node
editor and inspector.
- Tab close buttons are now visible and right-aligned with a hover
background (they were nearly invisible, causing accidental panel
closes).
- Tear-off: dragging a tab outside the dock opens the panel in its own
floating window; closing the window re-docks the panel to its last
group (or closes it outright from the Window menu).
- Timeline clip dragging shows a translucent ghost at the target track
and frame, including cross-track moves.
- Project explorer icon view renders real thumbnail images when the
engine provides them, with debug selectors and a structural test.
ViewerEvent gains InteractPointer/InteractKey variants and the picture
area captures pointer and key input for them (the OFX Interact overlay
integration forwards these to the plugin).
The right-click ContextMenu never tracked hover (the listener was a
no-op), so rows did not highlight and parent items never opened their
submenus. Hover now drives the highlight and opens the parent's
submenu, with Left/Right and Enter keyboard parity.
The submenu is an absolutely positioned child of the popup instead of
a second window-anchored popup computed from the raw click point, so
it follows the popup's final snapped/switched bounds and can never
detach from its parent row.
The right-click ContextMenu's hover listener was a no-op: it never
tracked the hovered row and never rendered a submenu popup, so every
menu item with a submenu (Proxy, Cache, Color, Show Thumbnails, Zoom,
Playback Resolution...) was a dead end. Hover now drives the row
highlight and opens the parent's submenu anchored to its right (the
menu bar's model), with keyboard Left/Right to close/open it and Enter
expanding a parent row instead of firing it.
BGRA atlas uploads (every viewer video frame on macOS) were staged
through swizzle_upload_data's to_vec() into pending_uploads — a
full-frame CPU copy per upload. Non-swizzled formats now write_texture
directly from the borrowed bytes; only the RGBA swizzle and the
not-yet-allocated fallback keep the owned staging path.
TimelineView emits ContextMenuRequested with hit target (clip/empty/
track head/ruler marker), NodeGraphView emits it for nodes and the
background, and ProjectExplorer emits it for entries and blank areas,
so the app can open context menus aligned with the C++ version.
The handle mixed the hitbox-local drag origin with window-space
drag-move positions, so the first move snapped the ratio to a clamp
boundary (a panel suddenly grew and further drags did nothing). Drags
are now incremental window-space deltas applied to the freshly-read
ratio; drag-move routing is filtered to the owning split container so
nested splits no longer double-apply with wrong geometry; pair extent
excludes the divider itself so the boundary tracks the pointer 1:1.
- node_graph: card geometry stays graph-space; paint/hit-test/wire
endpoints scale by zoom so nodes no longer overlap at zoom != 1
- project_explorer: icon grid aligns items to start (no full-height
selection stretch), loads thumbnails from filesystem paths and
observes the data source for late arrivals
The strip handed the drag to the dock area via cx.emit, which is
deferred — the dock's drag state was created a dispatch too late, so
the hovered target was never recorded and drops were no-ops. The dock
area now lazily begins the drag in its own capture-phase handler;
drops onto the root edge from the panel's own group split the tree
instead of being swallowed as self-drops.
- MenuBar: hover another title with a menu open to switch (scrubbing)
- menu popup width computed from the longest label; ROW_HEIGHT 22
- dock tab bar 26px / smaller tab text; viewer transport tightened
- MenuBar: mouse hover now sets the hovered row and opens submenus next
to the hovered item (previously hover state was never set from the
mouse, so '>' rows never opened their submenu)
- Menu popup: debug selectors for test automation
- viewer: transport bar renders 16px icon buttons with localized
tooltips via a host-registered icon resolver (glyph fallback kept)
- new icons.rs (theme-aware icon resolver) and tooltip.rs helpers
- gpui::i18n: minimal string-table override (set_table/tr/clear_table) with
built-in defaults; effect-stack and viewer strings now go through it so
hosts can localize widget-baked labels without a full i18n framework.
- gpui_widgets::i18n re-exports the hook (gpui_widgets::i18n::set_table).
- node_graph: GraphViewState::fit_to_rect for fit-window/initial viewports
(unit tested); NodeGraphView::viewport_size accessor for fit targets.
- menu: add runtime set-checked path (MenuItem::set_checked/clear_checked,
Menu::set_item_checked recursive, MenuBar::set_item_checked)
- dock: Split now stores per-child ratios (sum 1.0) so 3+ panels on one
axis keep distinct sizes; resize_split_child adjusts a single boundary,
resize_split keeps its two-arg whole-share semantics; one handle per
boundary; DockLayoutState VERSION bumped to 2 for the new ratios field
- audio_meter: MeterOrientation::Vertical for the 26px transport strip,
segments lit bottom to top
- viewer: ViewerFrameSource::CpuFrame + set_cpu_frame (BGRA8 RenderImage
via the sprite atlas) for platforms without CVPixelBuffer
- timeline: TimeDisplay::TimecodeDropFrame (SMPTE drop-frame for NTSC
rates, non-drop fallback otherwise)
- gpui_widgets: regression test pinning the fractional spacing helpers
(py_0p5/py_1p5/py_2p5/py_3p5 already exist; no _0_5 aliases added)
Adds interaction/render tests for the untested view layers: menu bar popup
open/click/keyboard-nav/escape, project explorer expand/open/view-switch,
scope widgets and keyable diamond rendering in windows, progress and file
dialog content rendering, and viewer transport buttons. gpui_widgets line
coverage rises from 75.4% to 89.4% (menu/mod 3.7%->71.9%, project_explorer
21.4%->91.3%, scopes/mod 0%->96.7%, keyable 69%->98.4%); every module is now
>=70%. 115 widget tests pass.
W7 of the oak task list. OakTheme::olive_dark/olive_light translate oak's
palette.ini files into a structured theme (window/base/alternate/accent/
link/disabled plus a derived border). apply_theme swaps gpui's GlobalColors
so every widget reading cx.default_colors() re-themes immediately, and
stores the full theme in a ThemeGlobal. Palette contrast and mapping are
unit-tested, with a gpui::test verifying the runtime switch;
examples/themes.rs toggles between the two themes live. 101 widget tests
pass.
W6 of the oak task list. scopes::math is a pure, unit-tested core
(histogram binning, waveform min/max envelopes, vectorscope chroma
projection, meter segment math, peak-hold decay) feeding three canvas
widgets - Histogram, Waveform, Vectorscope - generic over LumaDataSource /
ChromaDataSource traits the host implements over its frame buffers, plus an
AudioLevelMeter over AudioMeterDataSource with lit segments and a peak
marker. examples/scopes.rs drives all four from mock signals. 96 widget
tests pass.
W5 companion widget. ProjectExplorer is data-agnostic over a
ProjectDataSource trait: a tree view with expandable folders and
double-click-to-open, an icon grid with thumbnails (img/placeholder
swatch), a view switcher, and file-drop import via on_drop (platform
FileDrop becomes an internal drag), emitting OpenRequested /
FileDropRequested / ViewChanged requests. flatten_tree (pure) is
unit-tested; examples/project_explorer.rs demos a mock project. 89 widget
tests pass.
W5 timeline supplements: clicking a track header toggles the track in a
selected_tracks set and emits TimelineEvent::TrackSelected; clips with in/out
transitions get a resize handle at the wedge edge that emits
TimelineEvent::TransitionChanged with the clamped new length (delta-scaled
by zoom). Both follow the request-only contract. 182 gpui tests pass.
Two changes landing W4 of the oak task list:
gpui_macos: the Surface renderer only accepted biplanar YUV 4:2:0 pixel
buffers, so engine BGRA frames could not be displayed. SurfaceBounds gains an
is_bgra flag and draw_surfaces now creates a single BGRA8Unorm texture (and
the fragment shader swizzles it to RGBA) for 32BGRA buffers, keeping the
YUV path unchanged. Verified by running the W3 surface_bridge demo.
gpui_widgets::viewer: ViewerWidget with a pure TransportState machine
(advance/loop/step/in-out, unit-tested), a PlaybackClock trait the host
implements over the engine, a ~60Hz polling ticker, a transport bar emitting
ViewerEvent requests, timecode via gpui::timeline::time, and safe-frame/zoom
toggles. examples/viewer.rs shows a mock clock with generated macOS test
frames. 87 widget + 182 gpui tests pass.
W3 of the oak task list. SurfaceBridge wraps an engine wgpu (Metal) texture
into an IOSurface-backed CVPixelBuffer for gpui's Surface element: a reused
pixel buffer created with kCVPixelBufferIOSurfacePropertiesKey +
MetalCompatibility, aliased to a Metal texture via gpui_media's
CVMetalTextureCache, with a GPU-to-GPU MTLBlitCommandEncoder copy (a
device.poll(Wait) before the blit keeps the v1 path correct and
synchronous). A CPU readback fallback (stage_readback/finish_readback)
works on any backend. The surface_bridge demo (demo feature) streams a
moving test pattern through the bridge into a window and prints FPS;
smoke-verified on macOS at 1280x720. Windows/Linux paths are left for a
follow-up (noted in docs/zh). 2 tests pass.
W2 of the oak task list. A pure menu model (nesting, disabled items,
separators, checked state, wrap-around keyboard navigation arithmetic) feeds
two views: a MenuBar with drop-down popups and a right-click ContextMenu,
both built on anchored+deferred with keyboard navigation (up/down/enter/
escape), hover-opened submenus, check marks, shortcut labels and request
events. The Modal framework provides the mask, title bar, content slot and
button row with escape/enter defaults; message_box (info/warning/error),
progress_dialog (driven by ProgressContent) and file_dialog (native gpui
fallback - the fork has no prompt_for_paths platform API) are built on it.
A menus_dialogs example demonstrates everything. 79 tests pass.
Adds a parameter-panel example (cargo run -p gpui_widgets --example controls)
that demonstrates every form control: float/rational/angle sliders with
keying diamonds, a spinbox, a combo box, checkboxes, a radio group, a color
picker and a curve editor, printing each edit as a request event. Marks all
W1 checklist items done in docs/zh/oak-app-rewrite.md. 65 tests pass.
CurveEditor edits a normalized x->y keyframe curve: cubic bezier segments
with per-point in/out control handles. Points are dragged with x clamped to
keep the list sorted; handles drag as offsets; double-click inserts a new
point at the cursor. Every gesture emits CurveEditorEvent requests while the
widget keeps a local working copy for fluid dragging. Pure geometry
(bezier evaluation, sampling via binary search, polyline approximation, hit
tests) is unit-tested; interaction tests cover point dragging and adding.
65 tests pass.
ColorPicker is a swatch button that opens a popup picker built from
anchored+deferred: an SV square (layered linear gradients over the hue) and
a 48-segment hue strip, both click- and drag-editable, plus RGBA spinboxes
reusing the SpinBox control. Pure HsvColor math (hue wrapping, saturation/
value extremes, u8 conversion) is unit-tested; an interaction test verifies
clicking the SV square emits ColorChanged. The eyedropper is a placeholder.
55 tests pass.
ComboBox renders a field showing the selection and a drop-down menu built
entirely with gpui's anchored + deferred elements (no platform primitives).
Menu dismissal is mouse-up-out based so an option click is never swallowed
by a capture-phase close that would redraw the menu away; the field toggles
from the open state recorded at mouse-down. Arrow keys cycle the selection
with wrap-around, escape closes. 48 tests pass.
Completes the basic form controls of W1: a SpinBox reusing SliderModel for
range/step/clamping with an editable_text field (enter/blur commits,
escape reverts, invalid input rejected) and up/down buttons; a CheckBox with
optional tri-state and request-only Toggled events; a RadioGroup that emits
Selected requests. All three follow the request-only contract: the host
applies changes and updates the widget's display state. 45 tests pass.
Implements W1's slider: a pure SliderModel state machine (float, integer,
rational and angle kinds with clamping, step snapping, fine adjustment and
drag math) plus the Slider view with vertical-drag scrubbing (shift for
fine), double-click direct entry backed by editable_text (enter commits,
escape cancels, click-away commits, invalid text rejected), wheel stepping,
middle-click reset and arrow-key navigation. Every edit is emitted as a
SliderEvent request; keyable controls expose a Keying diamond. 37 unit and
interaction tests pass.
New workspace crate implementing W1 of the oak task list. This first
slice lands the pure numeric core (ValueKind / SliderValue /
RationalValue / ValueFormatter with round-trip tests) and the keying
diamond button (KeyingState / KeyingRequest) that keyable controls
embed. 12 unit tests pass.
* Add text spacing and transform style plumbing
* Apply byte-stable transforms and spacing-aware truncation
* Add platform letter spacing support
* Fix scheduler clippy warning
* Refresh dependency lockfile
* Fix CI for text spacing changes
* Add text transform preview example
* temportalflux/elements-input: (117 commits)
remove legacy input example now that gpui_elements::editable_text exists
remove stop_propagation on_mouse_up to avoid unintended consequence of input always highlighting/selecting while mousing over
add gpui_elements root documentation so its not weird that the docs page is empty
add an extremely basic example for editable_text which only shows a subset of what is documented in the module
fix caret getting stuck as visible without focus when the text field pauses blinking while it is without focus
stop propagation of mouse events when clicking on editable text, so that users which consume click events on wrapping elements do not receive events when they are used to focus/update the editable text state
add simpler api for configuring the caret blink interval via element
add external api for access to caret by routing it through keyed element data
fixup typos
refactor text position queries by factoring out common logic
first pass at simplifying text position queries
rework caret implementaiton to increase clarity and reduce code duplication
reorganize element implementation to be more readable
assign default editable text colors as consts
reduce complexity of mouse click handler and cut/copy handlers
reduce bounds_for_range to be more readable
remove EditableTextState::storage() in favor of direct access to the member
resolve clippy errors from using Range in reverse. Add CaretSelection to represent a bidirectional inclusive range
collapse styling of conditions
reorganize blocks in editable text element
...