Settings: group preferences and open from gear

Group App Settings by purpose and let right-click on the gear open settings directly while preserving the left-click menu.

WPF event reference: https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmouserightbuttondown?view=windowsdesktop-10.0
This commit is contained in:
2026-10-28 17:57:02 -07:00
parent 769c616706
commit ee3c59210d
6 changed files with 66 additions and 49 deletions
+36 -31
View File
@@ -38,47 +38,52 @@
<!-- App Settings: broadcast fields live in the preview's
Text pull-out now — this panel keeps app-level prefs only.
(Currently empty; the overlay shell stays for future use.) -->
<StackPanel Grid.Row="1" Margin="0,16,0,0"
<StackPanel Grid.Row="1" Margin="0,8,0,0"
Visibility="{Binding IsSettingsOpen, Converter={StaticResource BoolToVis}}">
<TextBlock Text="Default Location for Recordings" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
<TextBox Style="{StaticResource YtTextBox}"
Text="{Binding RecordFolderDisplay, Mode=OneWay}"
IsReadOnly="True" Margin="0,0,0,12" Foreground="#a0a0b0"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
<Button Content="Browse…" Command="{Binding ChooseRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Margin="0,0,8,0"/>
<Button Content="Use Downloads" Command="{Binding ResetRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}"/>
</StackPanel>
<TextBlock Text="Zoom View Shape" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
<ComboBox Style="{StaticResource YtComboBox}" SelectedValue="{Binding ZoomShape}" SelectedValuePath="Tag">
<TextBlock Text="Recordings" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Default location" Foreground="#a0a0b0" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,8">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding ChooseRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Use Downloads" Command="{Binding ResetRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding RecordFolderDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="#a0a0b0" VerticalAlignment="Center"/>
</DockPanel>
<TextBlock Text="Brand logo" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Image file (shown on every scene)" Foreground="#a0a0b0" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,8">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding BrowseBrandLogoCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding BrandLogoFileDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="#a0a0b0" VerticalAlignment="Center"/>
</DockPanel>
<TextBlock Text="Preview" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Zoom view shape" Foreground="#a0a0b0" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding ZoomShape}" SelectedValuePath="Tag">
<ComboBoxItem Content="Circle" Tag="{x:Static Models:ZoomShape.Circle}"/>
<ComboBoxItem Content="Phone (Vertical Rectangle)" Tag="{x:Static Models:ZoomShape.VerticalRectangle}"/>
<ComboBoxItem Content="Tablet (Horizontal Rectangle)" Tag="{x:Static Models:ZoomShape.HorizontalRectangle}"/>
</ComboBox>
<TextBlock Text="Brand Logo" Style="{StaticResource YtLabel}" Margin="0,12,0,4"/>
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Style="{StaticResource YtTextBox}" Text="{Binding BrandLogoFileDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="#a0a0b0"/>
<Button Grid.Column="1" Content="Browse…" Command="{Binding BrowseBrandLogoCommand}"
Style="{StaticResource YtButtonSecondary}" Margin="8,0,0,0"/>
</Grid>
<TextBlock Text="Scene Transition" Style="{StaticResource YtLabel}" Margin="0,12,0,4"/>
<ComboBox Style="{StaticResource YtComboBox}" SelectedValue="{Binding SelectedTransitionType}" SelectedValuePath="Tag">
<TextBlock Text="Scene transition" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Type" Foreground="#a0a0b0" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding SelectedTransitionType}" SelectedValuePath="Tag">
<ComboBoxItem Content="Cut" Tag="{x:Static services:TransitionType.Cut}"/>
<ComboBoxItem Content="Fade" Tag="{x:Static services:TransitionType.Fade}"/>
<ComboBoxItem Content="Move" Tag="{x:Static services:TransitionType.Move}"/>
<ComboBoxItem Content="Wipe" Tag="{x:Static services:TransitionType.Wipe}"/>
</ComboBox>
<TextBlock Style="{StaticResource YtLabel}" Margin="0,8,0,4">
<Run Text="Transition length: "/><Run Text="{Binding TransitionDurationMs, StringFormat={}{0:0} ms}"/>
</TextBlock>
<Slider Minimum="100" Maximum="2000" TickFrequency="50" IsSnapToTickEnabled="True"
Value="{Binding TransitionDurationMs, Mode=TwoWay}"/>
<DockPanel Margin="10,0,0,6">
<TextBlock DockPanel.Dock="Right" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="56">
<Run Text="{Binding TransitionDurationMs, StringFormat={}{0:0}}"/><Run Text=" ms"/>
</TextBlock>
<Slider Minimum="100" Maximum="2000" TickFrequency="50" IsSnapToTickEnabled="True"
Value="{Binding TransitionDurationMs, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
</StackPanel>
<!-- Report Bug -->
+1 -1
View File
@@ -33,7 +33,7 @@
nudged right a couple more places 2026-09-22 (build 77 feedback).
Moved up from the bottom bar — one menu: App Settings / Bug / Feature / About. -->
<Button Style="{StaticResource IconButton}" Margin="18,0,0,2" VerticalAlignment="Center"
ToolTip="Menu" Click="GearButton_Click">
ToolTip="Menu" Click="GearButton_Click" PreviewMouseRightButtonDown="GearButton_PreviewMouseRightButtonDown">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="App Settings" Command="{Binding OpenSettingsCommand}"/>
+8
View File
@@ -26,6 +26,14 @@ public partial class TopBar : UserControl
_ = LoadAvatarImageAsync();
}
private void GearButton_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// Right-click goes straight to App Settings; the menu stays on left-click.
e.Handled = true;
if (DataContext is MainViewModel { OpenSettingsCommand: { } open } && open.CanExecute(null))
open.Execute(null);
}
private void GearButton_Click(object sender, RoutedEventArgs e)
{
// One menu (Settings / Bug / Feature / About), opened on click — moved up
+1 -1
View File
@@ -9,7 +9,7 @@ See [`schema.md`](../schema.md) for the memory-map conventions.
|------|---------|
| `BottomBar.xaml(.cs)` | Footer (Row 3): intentionally empty, height-preserving spacer; stream readouts and resolution picker moved to the top-left cell in the 2026-10-26 surface redesign |
| `HealthBanner.xaml(.cs)` | Row 1 status banner + the 7 value→visibility converters live in the app theme, not here |
| `TopBar.xaml(.cs)` | Row 0 session surface: brand + gear menu (left), explicit `EDIT` toggle beside the REC|ON-AIR mode switch and world actions (Record: Start Recording · Stream: Go Live + Test), running-state reality line + End (center), account zone (right). Left-click gear opens the menu; direct right-click-to-settings is active uncommitted work. `RefreshAvatar()`, `GearButton_Click` |
| `TopBar.xaml(.cs)` | Row 0 session surface: brand + gear menu (left), explicit `EDIT` toggle beside the REC|ON-AIR mode switch and world actions (Record: Start Recording · Stream: Go Live + Test), running-state reality line + End (center), account zone (right). Left-click gear opens the menu; right-click opens App Settings directly. `RefreshAvatar()`, `GearButton_Click` |
| `PreviewPane.xaml(.cs)` | Row 2 Col 1 center: 1920×1080 `OverlayCanvas`/`SelectionOverlay`/`BrandFlashLayer`/`SocialBarElement`, text drawer, outgoing-picture transition overlay, right-edge Stream Info/YPP/Socials/TEST tabs and drawers, and live controls (TRAX / game / mic meters + sliders + speakers). The list-selected draggable layer is temporarily raised in preview z-order and custom hit-testing; deselection restores the scene's unchanged order (`SelectedLayerPreviewHitTestingTests`). **TASK 57:** dragging/resizing a webcam in the vertical tier routes through `MainViewModel.MoveWebcamInVerticalTier` / `ResizeWebcamInVerticalTier` for the six-spot snap or Chat camera's two sizes. |
| `SceneThumbnailStrip.xaml(.cs)` | Top row over the preview column: five fixed-size scene cards with scene name, hotkey, staged `PROGRAM` badge, orange staged/live border |
| `LeftPanel.xaml(.cs)` | Row 2 Col 0 left panel: explicit EditingActive toggle on → Scene Layers+properties; off → YouTube chat, independent of stream status. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept. **TASK 51:** the webcam properties carry the Background section — `RemoveBackgroundPill` / `BlurBackgroundPill` (`PillToggle`, radio-exclusive via `WebcamSceneConfig.RemoveBackground`/`BlurBackground`) + `BackgroundStrengthSlider` (0..1, 5% snaps, shown only while an effect is on). **TASK 57:** the webcam properties carry a Geometry section (X/Y/W/H) — manual placement of the camera, bound to the **live** placement (the one the current tier shows), gated on `IsWebcamSelected` (not `SelectedElement.IsWebcam` — the TASK 51 test finds that panel by binding text and would match twice); the Shape button's `Visibility` is bound to `CanToggleClipShape`, so the circle option is NOT OFFERED in the vertical Chat layout. **TASK 58:** every drop-down, check box and field in the properties panel carries the theme's style (`YtComboBox`/`YtCheckBox`/`YtTextBoxCompact`) and paints no colour of its own — the guard is `ytLive.Tests/PropertiesStyleTests.cs`. **TASK 59:** the webcam's properties are a HIERARCHY — a `Shape` title owns the mirror switch and the shape choice (`Rect` | `Circle` as two `SegmentToggle` options, Rect selected by default, the circle option hidden where `CanToggleClipShape` is false), and every titled block (Geometry, Border Color, Border Thickness, Background) owns its content one indent deeper (Strength sits under Blur). **TASK 60:** the border-colour drop-down builds its menu from `Models/BorderColorPalette.cs` — each row is the hex plus the colour's NAME (`#e94560 Crimson`, tooltip "Crimson (#e94560)"); the swatch icon is unchanged. **TASK 61:** the four geometry fields (X/Y/W/H) share one fixed width (44px — the largest value, 1920, plus a sign) and their rows set `LastChildFill="False"`, so Y/H no longer stretch to fill the row. **TASK 62:** the border-colour field shows the palette's name beside the hex (`SelectedElement.BorderColorName`, empty for a custom hex) and keeps a fixed width so the name stays next to the hex. **TASK 63:** the border colour is picked with the **app's own drop-down** (`YtComboBox`) — the `...` button and its code-built `ContextMenu` are gone; the closed box shows the swatch + the colour's NAME, the rows show swatch + hex + name (the panel's own `DataTemplate`s + `Helpers/ClosedBoxTemplateSelector.cs`), and the hex field stays for a custom colour; `IsDescendantOf` falls back to `LogicalTreeHelper.GetParent` / `TemplatedParent` when `VisualTreeHelper.GetParent` returns `null` (at `PopupRoot`) so clicking a popup row does not trigger `MainWindow`'s deselection guard |
+19 -15
View File
@@ -1,10 +1,11 @@
# HANDOFF — 2026-10-28 (selected-layer preview stacking; suite green)
# HANDOFF — 2026-10-28 (selected-layer stacking and App Settings UX; suite green)
## Current state
- **Branch `main`; `origin/main` remains at `7ae30d4`.** The five
feature commits since that point are `90cd039` (fade starts from outgoing picture), `f67a263`
- **Branch `main`, tracking `origin/main`; the selected-layer fix and test refresh were pushed as
`769c616`.** The five
feature commits since the previous `7ae30d4` checkpoint are `90cd039` (fade starts from outgoing picture), `f67a263`
(transition picker), `b86b6bb` (Wipe), `841c905` (brand logo layer), and `7da2872` (preview and
program transitions). They are not pushed; no push was requested.
program transitions); they are included in the pushed history.
- **Four UI-redesign commits already on `origin/main` were missing from the older handoff:**
`24aca7d` moved the scene strip above the preview and removed the default CRT/on-air image seeds;
`3239808` added the Scene Layers header/count and layer icons plus scene-card names, hotkeys, and
@@ -28,16 +29,18 @@
thumbnail cases persist explicit tile assets instead of relying on removed default seed files;
SourceNaming only considers user-created Text overlays; ZoomBox locates its placeholder by type,
not a list index changed by Brand logo.
- **The worktree already had three uncommitted user edits before this docs sync; they are preserved:**
- **The App Settings UX update is now committed and pushed:** `Controls/OverlayHost.xaml` groups
settings into Recordings, Brand logo, Preview, and Scene transition sections with compact controls.
Right-clicking the gear opens App Settings directly; left-click still opens the existing menu.
The on-screen verification is still pending.
- **The three edits that were previously uncommitted are now included in that update:**
`Controls/OverlayHost.xaml` groups App Settings into Recordings, Brand logo, Preview, and Scene
transition sections with compact controls; `Controls/TopBar.xaml` wires right-click on the gear;
`Controls/TopBar.xaml.cs` opens App Settings directly on right-click while left-click keeps the
menu. These are active UX-redesign work, not part of the selected-layer fix. On-screen verification is
still pending.
- **This work unit is committed locally, not pushed.** The three pre-existing user edits above
remain uncommitted and untouched.
- **Next:** continue the creator's UX redesign and verify the current App Settings/gear changes,
transitions, and Brand logo on-screen. The earlier vertical-webcam hands-on check is also still
menu.
- **Worktree is clean after the push.**
- **Next:** verify the App Settings/gear changes, transitions, and Brand logo on-screen. The earlier
vertical-webcam hands-on check is also still
pending unless the creator confirms it was completed. Move's persistent-layer behavior and Cube
remain queued; distribution remains deferred until near v1.
- **Verification state:** `verify.sh` passed: clean build with 0 warnings, **444/444 tests passed**,
@@ -49,10 +52,11 @@
Socials surface a modal dialog in its method comment; the committed UI is now a preview-rail drawer.
Correct that source comment separately if the file is next in scope. The new deterministic WPF
hit-test recipe is in `MyMistakes.md`; the final commit cites its Microsoft reference.
- **WPF stacking reference:** [`Panel.ZIndex`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.panel.zindex?view=windowsdesktop-10.0)
governs preview container order. The code also prioritizes the selected element in `HitElementAt`,
because the custom hit test does not consult WPF's visual z-order. The deterministic test recipe and
hit-testing reference are recorded in `MyMistakes.md`; cite that Microsoft reference in the commit.
- **WPF references:** [`Panel.ZIndex`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.panel.zindex?view=windowsdesktop-10.0)
governs preview container order; [`PreviewMouseRightButtonDown`](https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.previewmouserightbuttondown?view=windowsdesktop-10.0)
is used for the gear's direct settings action. The selected-layer fix also prioritizes the selected
element in `HitElementAt`, because the custom hit test does not consult WPF's visual z-order. The
deterministic test recipe and reference are recorded in `MyMistakes.md`.
---
+1 -1
View File
@@ -360,7 +360,7 @@ The app is a **director's control surface**, not an editor. This replaces the OB
**Current layout (2026-10-26 redesign commits):** `MainWindow` has a top bar and health banner, then a two-column work area with a fixed 360px left column and preview column. Its top row holds stream readouts/resolution on the left and the five-scene strip above the preview; the lower row holds the left panel and central preview. The bottom bar is now only a height-preserving spacer.
- **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/gear and account surfaces remain at the sides. The gear's left-click menu remains; direct right-click-to-settings is in the current uncommitted UX work (see HANDOFF).
- **Top bar:** the `EDIT` toggle sits beside the REC/ON-AIR session controls; the brand/gear and account surfaces remain at the sides. The gear's left-click menu remains; right-click opens App Settings directly.
- **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.
- **Preview rail:** stream info, YPP, Socials, and TEST are icon/color tabs on the preview's right edge. Socials opens an animated in-preview drawer; it is no longer a standalone modal window. The bottom live-controls row retains TRAX and the game/mic meters and controls.
- **Central monitor:** shows the staged scene while editing and the program scene when editing is off. Preview transitions snapshot and animate the outgoing picture in every mode; output transitions run while streaming or recording. Go Live itself remains a Cut.