diff --git a/Controls/PreviewPane.xaml.cs b/Controls/PreviewPane.xaml.cs index e37ce85..e2c58eb 100644 --- a/Controls/PreviewPane.xaml.cs +++ b/Controls/PreviewPane.xaml.cs @@ -402,6 +402,18 @@ public partial class PreviewPane : UserControl e.Handled = true; } + /// Is this canvas point inside the social bar? It is a full-width strip at the top or + /// bottom of the frame and it consumes its own click, so the preview's tunnelling handler has + /// to step aside (see Preview_MouseLeftButtonDown). + private bool IsOverSocialBar(Point canvasPoint) + { + if (DataContext is not MainViewModel { SocialBarVisible: true }) return false; + if (SocialBarElement.ActualHeight <= 0) return false; + + var topLeft = SocialBarElement.TranslatePoint(new Point(0, 0), CanvasGrid); + return new Rect(topLeft, SocialBarElement.RenderSize).Contains(canvasPoint); + } + private void SocialsButton_MouseEnter(object sender, MouseEventArgs e) { if (SocialBarElement == null) return; @@ -453,6 +465,16 @@ public partial class PreviewPane : UserControl var vm = DataContext as MainViewModel; if (vm is null) return; + var grid = (Grid)sender; + var toCanvas = CanvasGrid.TransformToVisual(grid).Inverse; + var p = toCanvas.Transform(e.GetPosition(grid)); + + // The social bar owns its own left-click (toggle top ⇄ bottom). This handler is the + // TUNNELLING preview event, so it fires before the bar's bubbling handler: without this + // guard the bar would be unclickable in preview mode (zoom would win) and, in edit mode, + // it would drag whatever layer sat underneath it. + if (IsOverSocialBar(p)) return; + // Preview mode (EDIT off): the left button drives the zoom box — the common gesture in // the mode where nothing is editable. Dragging it afterwards is handled by MouseMove. if (!vm.IsEditMode) @@ -461,10 +483,6 @@ public partial class PreviewPane : UserControl e.Handled = true; return; } - - var grid = (Grid)sender; - var toCanvas = CanvasGrid.TransformToVisual(grid).Inverse; - var p = toCanvas.Transform(e.GetPosition(grid)); var selected = vm.SelectedElement; if (selected is { } sel && IsDraggableElement(sel) && HitHandle(e.GetPosition(grid), sel)) @@ -603,7 +621,9 @@ public partial class PreviewPane : UserControl vm.ToggleZoom(); e.Handled = true; - } private void Preview_MouseRightButtonUp(object sender, MouseButtonEventArgs e) + } + + private void Preview_MouseRightButtonUp(object sender, MouseButtonEventArgs e) { if (_isZoomDragging) { @@ -614,6 +634,13 @@ public partial class PreviewPane : UserControl e.Handled = true; return; } + + // WPF's PopupControlService opens a context menu on right-button-UP, not down — so + // handling the Down event alone still let a menu appear under a Ctrl+right-click (found + // by the creator in edit mode, 2026-11-01). Swallow the Up event too, in both modes: + // Down decides the action, Up must not undo that decision. + if ((Keyboard.Modifiers & ModifierKeys.Control) != 0) + e.Handled = true; } private void EndPreviewDrag(Grid grid) diff --git a/docs/ai/ui-surface.md b/docs/ai/ui-surface.md index 105982a..c78b7e0 100644 --- a/docs/ai/ui-surface.md +++ b/docs/ai/ui-surface.md @@ -104,7 +104,7 @@ The app is a **director's control surface**, not an editor. This replaces the OB - **Left panel:** `EditingActive` is an explicit `EDIT` toggle, not a mode inferred from `StreamStatus`. On = scene layers + properties; off = YouTube chat. Go Live turns editing off and stopping restores it; recording does not automatically switch it off. The staged scene is the preview/edit target; `LiveScene` is the program output. - **Top bar:** the `EDIT` toggle sits beside the REC/ON-AIR session controls; the brand and account surfaces remain at the sides. Its **right edge aligns with the layers panel below** (`TopBar` column 0 is a fixed **352** = work-area margin 8 + 360px column − the bar's own 16px padding), so the button and the panel share an edge. The **settings gear moved to the preview rail** (2026-11-01): first tab, orange (`Brush.Attention`), gear icon only — left-click opens the app menu (Settings / Bug / Feature / About), right-click goes straight to App Settings. -- **Preview gestures (2026-11-01):** *edit mode* — left selects/drags/resizes, right opens the context menu, **Ctrl+right toggles the zoom box**. *Preview mode* — **left toggles the zoom box**, right opens the context menu; **Ctrl+right is swallowed** (no chord while live or recording — one hand, always). Menus exist on the webcam layer and the backdrop; every other layer right-clicks through to the backdrop menu. Left-dragging with the zoom box open moves it, in both modes. +- **Preview gestures (2026-11-01):** *edit mode* — left selects/drags/resizes, right opens the context menu, **Ctrl+right toggles the zoom box**. *Preview mode* — **left toggles the zoom box**, right opens the context menu; **Ctrl+right does nothing at all** (no chord while live or recording — one hand, always). ⚠️ **Both the Down and the Up event must be handled for a Ctrl+right-click**: WPF's `PopupControlService` opens a context menu on right-button-**up**, so suppressing only the down event leaves the menu appearing under the gesture (found 2026-11-01 in edit mode). Menus exist on the webcam layer and the backdrop; every other layer right-clicks through to the backdrop menu. Left-dragging with the zoom box open moves it, in both modes. The social bar consumes its own click (toggle top ⇄ bottom), and the preview's tunnelling handler steps aside for it — otherwise the bar is unclickable in preview mode and drags the layer beneath it in edit mode. - **Preview undo/redo (2026-11-01):** `Ctrl+Z` / `Ctrl+Shift+Z`, window-level bindings, live only while `EditingActive` — undo with the preview showing program output would rewrite a scene nobody is looking at. `Models/PreviewHistory` snapshots element **references** plus geometry, visibility, order and membership, so move / resize / reorder / add / remove / hide all undo in one step per gesture (`PushPreviewUndo` is called once at drag or resize start, never inside the move loop). Property edits (colour, opacity, border, font) are **not** captured yet — stated in the type's doc comment so nobody assumes otherwise. The stack clears on layout load. Guard: `ytLive.Tests/PreviewHistoryTests.cs`. - **Developer affordances are compiled out of release, not hidden.** Any control that must never ship (today: the all-resolutions override) lives in its own `UserControl` whose `Page` and `Compile` items are removed by `ytLive.csproj` when `$(Configuration) != 'Debug'`, and is added to its host at runtime inside `#if DEBUG` (`OverlayHost`). A collapsed row still exists in the visual tree of every shipped copy; "not compiled" is the only claim worth making. Label such rows with `Brush.Attention` (orange) while they are visible in Debug. Guard: `ytLive.Tests/OutputProfileTests.cs` asserts the build rule, and `Helpers/DevBuild.cs` is the single source for "is this a dev build". - **Scene strip:** five fixed-size cards above the preview show the scene name and hotkey; the staged scene carries a `PROGRAM` badge, and staged/live borders use orange. Click stages a scene while idle; while streaming or recording it changes program output and starts the selected transition.