Highlight active speaker row with cyan left border
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
Visual cue for who's currently speaking — operators don't need to watch every VU bar. MainViewModel.OnStatsTick scans enabled participants once per tick, picks the loudest above a 0.05 floor (anti-flicker threshold), sets IsActiveSpeaker on the winner and clears on everyone else. DataGridRow DataTrigger swaps in a 3px cyan-accent left border + CyanMuted background tint when IsActiveSpeaker is true. Plays well with sort modes: LoudestFirst makes the highlighted row always the topmost; other sort modes leave the row position alone, just paints the indicator.
This commit is contained in:
parent
9db0875f9e
commit
b0029a51bf
3 changed files with 45 additions and 0 deletions
|
|
@ -740,6 +740,15 @@
|
|||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource Wd.SurfaceElevated}"/>
|
||||
</Trigger>
|
||||
<!-- Active speaker highlight: set by MainViewModel at the 1Hz
|
||||
stats tick based on DisplayedAudioLevel. Cyan left border
|
||||
(3px) makes whoever's talking pop without changing the
|
||||
background color (which IsMouseOver / IsSelected own). -->
|
||||
<DataTrigger Binding="{Binding IsActiveSpeaker}" Value="True">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Wd.Accent.Cyan}"/>
|
||||
<Setter Property="BorderThickness" Value="3,0,0,1"/>
|
||||
<Setter Property="Background" Value="{StaticResource Wd.Accent.CyanMuted}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
|
|
|
|||
|
|
@ -752,6 +752,29 @@ public sealed class MainViewModel : ObservableObject, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// Active-speaker highlight: find the loudest enabled participant
|
||||
// and mark their IsActiveSpeaker flag. Only one row at a time;
|
||||
// ties broken by enumeration order (first one wins). Threshold of
|
||||
// 0.05 prevents constant flicker between near-silent participants
|
||||
// when nobody's really speaking.
|
||||
ParticipantViewModel? loudest = null;
|
||||
double loudestLevel = 0.05;
|
||||
foreach (var p in Participants)
|
||||
{
|
||||
if (!p.IsEnabled) continue;
|
||||
if (p.DisplayedAudioLevel > loudestLevel)
|
||||
{
|
||||
loudest = p;
|
||||
loudestLevel = p.DisplayedAudioLevel;
|
||||
}
|
||||
}
|
||||
foreach (var p in Participants)
|
||||
{
|
||||
var shouldHighlight = ReferenceEquals(p, loudest);
|
||||
if (p.IsActiveSpeaker != shouldHighlight)
|
||||
p.IsActiveSpeaker = shouldHighlight;
|
||||
}
|
||||
|
||||
// If sort mode is LoudestFirst, refresh the view so the new audio
|
||||
// peaks re-evaluate the sort. Skipped for the other sort modes
|
||||
// since their keys (name, online state) don't change every tick —
|
||||
|
|
|
|||
|
|
@ -44,6 +44,19 @@ public sealed class ParticipantViewModel : ObservableObject
|
|||
/// <summary>True when <see cref="Thumbnail"/> is non-null. Bound to Visibility in XAML.</summary>
|
||||
public bool HasThumbnail => _thumbnail is not null;
|
||||
|
||||
/// <summary>
|
||||
/// True when this participant is currently the loudest among the live
|
||||
/// set — set by MainViewModel at the 1Hz stats tick. Bound to a cyan
|
||||
/// border accent on the DataGrid row so operators can spot who's
|
||||
/// speaking without watching every VU bar individually.
|
||||
/// </summary>
|
||||
public bool IsActiveSpeaker
|
||||
{
|
||||
get => _isActiveSpeaker;
|
||||
internal set => SetField(ref _isActiveSpeaker, value);
|
||||
}
|
||||
private bool _isActiveSpeaker;
|
||||
|
||||
public ParticipantViewModel(IIsoController controller, Participant participant, ToastViewModel? toast = null)
|
||||
{
|
||||
_controller = controller;
|
||||
|
|
|
|||
Loading…
Reference in a new issue