Mic audio UX: realtime read-only meter + mic picker

- Meter is a READ-ONLY realtime level display: fill = Math.Min(1, AudioLevel * MicVolume) (AudioLevel = live input from the future mixer, 0 with no input; MicVolume is gain on ambient noise). While the slider is dragged the bar previews the slider position (PreviewMouseLeftButtonDown/Up + LostMouseCapture -> SetVolumeAdjusting); on release it returns to the live level (bounces to 0 with no input). Clicking the meter does nothing.
- MicVolume drives MicMuted (read-only, muted <=> volume 0): sliding to 0 flips the speaker to the red slashed mute icon, sliding up from 0 clears it; speaker button runs volume to 0 or restores the prior level (_volumeBeforeMute, default 0.8) and flashes the meter to the restored position for ~300ms (BeginVolumeFlash/EndVolumeFlash DispatcherTimer, cancelled if the slider is grabbed).
- MIC label opens MicPickerDialog (WinRT audio-capture device enumeration, mirrors camera picker, no new packages); the chosen voice source name shows left-justified INSIDE the meter bar (fill at 75% opacity so text + ruler markings show through).
- New files: Services/IMicrophoneEnumerator, MicrophoneDeviceInfo, WinRtMicrophoneEnumerator; ViewModels/MicPickerViewModel; MicPickerDialog.
- Docs updated (ai.md, TASKS.md, Services/index.md, ViewModels/index.md); build 0 warnings, 65 tests passing
This commit is contained in:
2026-08-07 17:44:16 -07:00
parent 4b3fb04322
commit b00a4cbd5e
14 changed files with 536 additions and 78 deletions
+57 -8
View File
@@ -268,7 +268,34 @@
</Style.Triggers>
</Style>
<!-- Slider -->
<!-- Slider: slim and dimensional — gradient track, beveled fill, gloss-sphere
thumb. The filled side sits on a 5px pill aligned with the track; the
click target stays the full track height. -->
<LinearGradientBrush x:Key="YtSliderTrackBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#0f1b33" Offset="0"/>
<GradientStop Color="#283b60" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="YtSliderFillBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#4fe097" Offset="0"/>
<GradientStop Color="#2dbd7a" Offset="0.5"/>
<GradientStop Color="#1a9e63" Offset="1"/>
</LinearGradientBrush>
<RadialGradientBrush x:Key="YtSliderKnobBrush" Center="0.35,0.3" GradientOrigin="0.35,0.3">
<GradientStop Color="#ffffff" Offset="0"/>
<GradientStop Color="#d7f5e6" Offset="0.22"/>
<GradientStop Color="#3ed588" Offset="0.72"/>
<GradientStop Color="#137a4d" Offset="1"/>
</RadialGradientBrush>
<RadialGradientBrush x:Key="YtSliderKnobHoverBrush" Center="0.35,0.3" GradientOrigin="0.35,0.3">
<GradientStop Color="#ffffff" Offset="0"/>
<GradientStop Color="#e6faef" Offset="0.22"/>
<GradientStop Color="#57e9a1" Offset="0.72"/>
<GradientStop Color="#1b9a63" Offset="1"/>
</RadialGradientBrush>
<Style x:Key="YtSliderTrackButton" TargetType="RepeatButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
@@ -280,19 +307,40 @@
</Setter>
</Style>
<Style x:Key="YtSliderFillButton" TargetType="RepeatButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Grid>
<Border Height="5" VerticalAlignment="Center"
Background="{StaticResource YtSliderFillBrush}"
BorderBrush="#0f7a4e" BorderThickness="1" CornerRadius="2.5"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="YtSliderThumb" TargetType="Thumb">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="Knob" Width="16" Height="16" Fill="#e94560"
Stroke="#ff8fa3" StrokeThickness="1"/>
<Grid Width="13" Height="13">
<Ellipse x:Name="Knob" Fill="{StaticResource YtSliderKnobBrush}"
Stroke="#0b1626" StrokeThickness="1">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="1" Opacity="0.55"/>
</Ellipse.Effect>
</Ellipse>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Knob" Property="Fill" Value="#ff5b78"/>
<Setter TargetName="Knob" Property="Fill" Value="{StaticResource YtSliderKnobHoverBrush}"/>
</Trigger>
<Trigger Property="IsDragging" Value="True">
<Setter TargetName="Knob" Property="Fill" Value="#ff8fa3"/>
<Setter TargetName="Knob" Property="Fill" Value="{StaticResource YtSliderKnobHoverBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
@@ -307,12 +355,13 @@
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid VerticalAlignment="Center">
<Border Height="4" CornerRadius="2" Background="#2a3a5e"
VerticalAlignment="Center"/>
<Border Height="5" CornerRadius="2.5" VerticalAlignment="Center"
Background="{StaticResource YtSliderTrackBrush}"
BorderBrush="#0a1424" BorderThickness="1"/>
<Track x:Name="PART_Track" Height="18">
<Track.DecreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.DecreaseLarge}"
Style="{StaticResource YtSliderTrackButton}" Tag="#e94560"/>
Style="{StaticResource YtSliderFillButton}"/>
</Track.DecreaseRepeatButton>
<Track.IncreaseRepeatButton>
<RepeatButton Command="{x:Static Slider.IncreaseLarge}"