Pinned chat message + brand wordmark assets. Chat can pin one message to a banner above the feed: ChatMessage.IsPinned, MainViewModel.PinnedChatMessage + Pin/ClearPinnedChatMessageCommand (Helpers/RelayCommandGeneric.cs), LeftPanel pinned-message border bound via NotNullToVis with the feed nudged down.

This commit is contained in:
2026-10-10 14:54:08 -07:00
parent 8e1d6f44db
commit 931ab46783
9 changed files with 56 additions and 1 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

+11 -1
View File
@@ -596,8 +596,18 @@
HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/>
</StackPanel>
<!-- Pinned message -->
<Border Grid.Row="1" VerticalAlignment="Top" Margin="4,4,4,0" Padding="6,4" CornerRadius="4"
Background="#1a2a3a" BorderBrush="#38bdf8" BorderThickness="1"
Visibility="{Binding PinnedChatMessage, Converter={StaticResource NotNullToVis}}">
<StackPanel>
<TextBlock Text="{Binding PinnedChatMessage.AuthorName}" Foreground="#38bdf8" FontWeight="SemiBold" FontSize="11"/>
<TextBlock Text="{Binding PinnedChatMessage.Message}" Foreground="#e0e0e0" FontSize="11" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- Message feed (shown when live) -->
<ListBox Grid.Row="1" Background="Transparent" BorderThickness="0"
<ListBox Grid.Row="1" Background="Transparent" BorderThickness="0" Margin="0,40,0,0"
ItemsSource="{Binding ChatMessages}"
ItemContainerStyle="{StaticResource YtListBoxItem}"
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource InverseBoolToVis}}"
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Windows.Input;
namespace ytLive.Helpers;
public class RelayCommand<T> : ICommand
{
private readonly Action<T?> _execute;
private readonly Predicate<T?>? _canExecute;
public RelayCommand(Action<T?> execute, Predicate<T?>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object? parameter)
{
if (parameter is T t) return _canExecute?.Invoke(t) ?? true;
return _canExecute?.Invoke(default) ?? true;
}
public void Execute(object? parameter)
{
if (parameter is T t) _execute(t);
else _execute(default);
}
}
+1
View File
@@ -70,4 +70,5 @@ public class ChatMessage
/// subscriber events locally (YouTube's insert API only supports text), so the
/// buffer can label them honestly — simulated events never reach YouTube.</summary>
public bool IsSimulated { get; set; }
public bool IsPinned { get; set; }
}
+10
View File
@@ -22,6 +22,16 @@ public partial class MainViewModel : ViewModelBase
/// left panel can subscribe to its changes.</summary>
public ObservableCollection<ChatMessage> ChatMessages => _chatLayer.Messages;
private ChatMessage? _pinnedChatMessage;
public ChatMessage? PinnedChatMessage
{
get => _pinnedChatMessage;
set => SetProperty(ref _pinnedChatMessage, value);
}
public RelayCommand<ChatMessage> PinChatMessageCommand => new((m) => { PinnedChatMessage = m; });
public RelayCommand ClearPinnedChatMessageCommand => new(_ => { PinnedChatMessage = null; });
/// <summary>The Add → YouTube Chat menu item: enabled only while no chat layer exists
/// anywhere — one chat overlay per layout.</summary>
public bool CanAddYouTubeChat