creator feedback batch: 9 fixes from initial review

1. About box bg → #0A0C1A (matches icon bg)
2. Elements panel collapses when no element selected / scene changes
3. OpacityChip moved from preview overlay into Elements panel
4. Color picker fix: cast to SceneElement not Source (WebcamSceneConfig was silently blocked)
5. Red left accent bar on selected items in Scene/Source lists
6. ✓ character replaced with Path checkmark icon in Elements panel
7. Removed ToolTip='Applied' from green check buttons
8. Created bugs.md with first entry (iTunes audio on mic meter)
9. Full logo in README + About box uses ytLlive-logo.png
This commit is contained in:
2026-08-17 10:16:55 -07:00
parent 2d5d2e9c7a
commit 28765a0dbf
10 changed files with 107 additions and 67 deletions
+14
View File
@@ -0,0 +1,14 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ytLive.Helpers;
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value == null ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is Visibility.Collapsed;
}
+52 -56
View File
@@ -33,6 +33,8 @@
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:NotNullToVisibilityConverter x:Key="NotNullToVis"
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:NullToVisibilityConverter x:Key="NullToVis"
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:HiddenBoolToVisibilityConverter x:Key="HiddenBoolToVis"
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:EnumToBoolConverter x:Key="EnumToBool"
@@ -356,13 +358,40 @@
HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
<!-- ELEMENTS panel: per-element config beneath Sources -->
<Rectangle Grid.Row="5" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"/>
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="0,8,0,0">
<!-- ELEMENTS panel: per-element config beneath Sources.
Collapses entirely when no element is selected. -->
<Rectangle Grid.Row="5" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}"/>
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="0,8,0,0"
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
<TextBlock Text="ELEMENTS" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer Grid.Row="7" VerticalScrollBarVisibility="Auto" Background="Transparent">
<ScrollViewer Grid.Row="7" VerticalScrollBarVisibility="Auto" Background="Transparent"
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
<StackPanel x:Name="ElementsPanel" Margin="0,4,0,0">
<!-- Opacity + mirror/shape (draggable elements: image, webcam) -->
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Opacity" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
<DockPanel Margin="0,0,0,6">
<TextBlock x:Name="OpacityValueText" Text="100%" Foreground="#a0a0b0" FontSize="11"
DockPanel.Dock="Right" VerticalAlignment="Center" Margin="8,0,0,0" Width="32"/>
<Slider x:Name="OpacitySlider" Width="120" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.Opacity, Mode=TwoWay}"
ValueChanged="OpacitySlider_ValueChanged" VerticalAlignment="Center"/>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4"
Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
<Button Content="{Binding SelectedElement.MirrorButtonText}"
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
Click="MirrorButton_Click"/>
<Button Content="{Binding SelectedElement.ShapeButtonText}"
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
Margin="8,0,0,0" Click="ShapeButton_Click"/>
</StackPanel>
<TextBlock Text="Drag to move · drag the corner dot to resize"
Foreground="#a0a0b0" FontSize="10" Margin="0,2,0,0"/>
</StackPanel>
<!-- Webcam border config -->
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Border Color" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
@@ -384,9 +413,11 @@
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Width="22" Height="22"
Background="#22c55e" IsEnabled="False" Margin="4,0,0,0">
<Path Data="M4,9 L8,14 L16,4" Stroke="White" StrokeThickness="2"
Stretch="Uniform" Width="12" Height="12"/>
</Button>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="WebcamBorderRevert_Click" Margin="4,0,0,0"/>
@@ -404,9 +435,11 @@
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Width="22" Height="22"
Background="#22c55e" IsEnabled="False" Margin="4,0,0,0">
<Path Data="M4,9 L8,14 L16,4" Stroke="White" StrokeThickness="2"
Stretch="Uniform" Width="12" Height="12"/>
</Button>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="CountdownRevert_Click" Margin="4,0,0,0"/>
@@ -421,9 +454,11 @@
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
ToolTip="https://example.com"/>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Width="22" Height="22"
Background="#22c55e" IsEnabled="False" Margin="4,0,0,0">
<Path Data="M4,9 L8,14 L16,4" Stroke="White" StrokeThickness="2"
Stretch="Uniform" Width="12" Height="12"/>
</Button>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="WebRevert_Click" Margin="4,0,0,0"/>
@@ -432,21 +467,8 @@
<!-- Placeholder hint when no config available -->
<TextBlock Text="No configurable elements" Foreground="#555" FontSize="11"
HorizontalAlignment="Center" Margin="0,8,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding SelectedElement}" Value="{x:Null}"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
HorizontalAlignment="Center" Margin="0,8,0,0"
Visibility="{Binding IsElementsPanelEmpty, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
</ScrollViewer>
</Grid>
@@ -845,32 +867,6 @@
HorizontalAlignment="Center"/>
</StackPanel>
<Border x:Name="OpacityChip" HorizontalAlignment="Left" VerticalAlignment="Bottom"
Background="#D9000000" CornerRadius="6" Padding="10,8" Margin="12,0,0,12"
Visibility="Collapsed">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Opacity" Foreground="#e0e0e0" FontSize="13" VerticalAlignment="Center"/>
<Slider x:Name="OpacitySlider" Width="120" Minimum="0" Maximum="1" Margin="10,0,0,0"
Value="{Binding SelectedElement.Opacity, Mode=TwoWay}"
ValueChanged="OpacitySlider_ValueChanged" VerticalAlignment="Center"/>
<TextBlock x:Name="OpacityValueText" Text="100%" Foreground="#e0e0e0" FontSize="13"
Width="40" Margin="8,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,10,0,0"
Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
<Button Content="{Binding SelectedElement.MirrorButtonText}"
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
Click="MirrorButton_Click"/>
<Button Content="{Binding SelectedElement.ShapeButtonText}"
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
Margin="8,0,0,0" Click="ShapeButton_Click"/>
</StackPanel>
<TextBlock Text="Drag to move · drag the corner dot to resize"
Foreground="#a0a0b0" FontSize="11" Margin="0,4,0,0"/>
</StackPanel>
</Border>
<!-- Desktop/game audio bar: overlaid at the bottom of the
preview, always visible. Meter + mute + volume control
live here; desktop audio itself is automatic WASAPI
@@ -1177,7 +1173,7 @@
</Button.Template>
</Button>
<Border Background="#1a1a2e" CornerRadius="8" Width="520" MaxHeight="560"
<Border Background="#0A0C1A" CornerRadius="8" Width="520" MaxHeight="560"
VerticalAlignment="Center" HorizontalAlignment="Center"
Padding="24" BorderBrush="#333" BorderThickness="1">
<Grid>
@@ -1256,7 +1252,7 @@
<!-- Hub view -->
<StackPanel Visibility="{Binding IsLicensesOpen, Converter={StaticResource InverseBoolToVis}}">
<Image Source="/ytLive;component/Assets/tyllive-icon.png" Height="140" Stretch="Uniform"
<Image Source="/ytLive;component/Assets/ytLlive-logo.png" Height="100" Stretch="Uniform"
HorizontalAlignment="Center" Margin="0,0,0,12"/>
<TextBlock Text="ytLlive" FontSize="22" FontWeight="Bold" Foreground="#e94560"
HorizontalAlignment="Center"/>
+3 -6
View File
@@ -209,7 +209,7 @@ public partial class MainWindow : Window
if (e.OriginalSource is not DependencyObject original) return;
if (IsDescendantOf(original, PreviewGrid)) return;
if (IsDescendantOf(original, SourceList)) return;
if (IsDescendantOf(original, OpacityChip)) return;
if (IsDescendantOf(original, ElementsPanel)) return;
_viewModel.SelectedElement = null;
}
@@ -224,7 +224,6 @@ public partial class MainWindow : Window
{
var selected = _viewModel.SelectedElement is { } s && IsDraggableElement(s);
SelectionOverlay.Visibility = selected ? Visibility.Visible : Visibility.Collapsed;
OpacityChip.Visibility = selected ? Visibility.Visible : Visibility.Collapsed;
if (selected)
OpacityValueText.Text = $"{Math.Round(_viewModel.SelectedElement!.Opacity * 100)}%";
TakeSnapshot();
@@ -232,7 +231,7 @@ public partial class MainWindow : Window
// Static images and the webcam share the move/resize/selection behavior.
private static bool IsDraggableElement(SceneElement element)
=> element is Source { Type: SourceType.Image } or WebcamSceneConfig;
=> element.IsDraggable;
private void MirrorButton_Click(object sender, RoutedEventArgs e)
{
@@ -304,8 +303,6 @@ public partial class MainWindow : Window
private void Preview_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (OpacityChip.IsMouseOver) return;
var grid = (Grid)sender;
var toCanvas = CanvasGrid.TransformToVisual(grid).Inverse;
var p = toCanvas.Transform(e.GetPosition(grid));
@@ -538,7 +535,7 @@ public partial class MainWindow : Window
private void WebcamBorderColorPicker_Click(object sender, RoutedEventArgs e)
{
if (_viewModel.SelectedElement is not Models.Source src) return;
if (_viewModel.SelectedElement is not SceneElement src) return;
if (sender is FrameworkElement fe && fe.ContextMenu == null)
{
var menu = new System.Windows.Controls.ContextMenu();
+3
View File
@@ -47,6 +47,9 @@ public abstract class SceneElement : INotifyPropertyChanged
/// <summary>True only for singleton resources (webcam). Gates webcam-only UI.</summary>
public virtual bool IsWebcam => false;
/// <summary>True for elements that can be dragged/resized in the preview (image, webcam).</summary>
public virtual bool IsDraggable => false;
/// <summary>True only for image sources — the raw preview renderer's discriminator.</summary>
public virtual bool IsImageSource => false;
+2
View File
@@ -42,6 +42,8 @@ public class Source : SceneElement
}
}
public override bool IsDraggable => Type == SourceType.Image;
/// <summary>The permanent scene backdrop (live desktop/game capture). Never removable/reorderable.</summary>
private bool _isBackdrop;
public bool IsBackdrop { get => _isBackdrop; set => Set(ref _isBackdrop, value); }
+1
View File
@@ -12,5 +12,6 @@ public class WebcamSceneConfig : SceneElement
{
public string WebcamId { get; init; } = string.Empty;
public override bool IsWebcam => true;
public override bool IsDraggable => true;
public override ImageSource? DisplaySource => VideoImageSource;
}
+4
View File
@@ -1,5 +1,9 @@
# ytLlive
<p align="center">
<img src="Assets/ytLlive-logo.png" alt="ytLlive — livestream smarter" height="120"/>
</p>
A creator-proof live-streaming and recording app for YouTube — native Windows (C# / WPF / .NET 8).
## Screenshot
+11 -5
View File
@@ -519,17 +519,23 @@
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Bd" Background="{TemplateBinding Background}"
CornerRadius="3" Padding="{TemplateBinding Padding}" Margin="0,1">
<ContentPresenter VerticalAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<Grid>
<Border x:Name="Accent" Width="3" HorizontalAlignment="Left" CornerRadius="1"
Background="#e94560" Margin="0,2" Opacity="0"
VerticalAlignment="Stretch"/>
<Border x:Name="Bd" Background="{TemplateBinding Background}"
CornerRadius="3" Padding="{TemplateBinding Padding}" Margin="0,1">
<ContentPresenter VerticalAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#22ffffff"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Bd" Property="Background" Value="#1a3a66"/>
<Setter TargetName="Accent" Property="Opacity" Value="1"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
+8
View File
@@ -213,7 +213,10 @@ public class MainViewModel : ViewModelBase
set
{
if (SetProperty(ref _selectedElement, value))
{
OnPropertyChanged(nameof(IsWebcamSelected));
OnPropertyChanged(nameof(IsElementsPanelEmpty));
}
}
}
@@ -263,6 +266,11 @@ public class MainViewModel : ViewModelBase
/// <summary>Shows the mirror / clip-shape row in the source chip when a webcam is selected.</summary>
public bool IsWebcamSelected => SelectedElement is WebcamSceneConfig;
/// <summary>True when the Elements panel should show the "No configurable elements" hint.</summary>
public bool IsElementsPanelEmpty => SelectedElement is { } el
&& !el.IsDraggable && !el.IsWebcam
&& el is not Source { Type: SourceType.Countdown or SourceType.WebSource };
/// <summary>
/// The reason the webcam feed is down (device in use, offline, locked, no frames),
/// or null when it's alive. Surfaced as a red chip so a dead camera is never a
+9
View File
@@ -0,0 +1,9 @@
# Bugs
## iTunes music showing on mic meter
Playing music via iTunes causes the mic meter to show audio levels. The WASAPI
mic capture (`DataFlow.Capture`) and loopback capture (`DataFlow.Loopback`) are
separate devices. If iTunes audio appears on the mic meter, it is likely acoustic
coupling — the mic picking up speaker/headphone output. Verify by muting the mic;
if the meter still shows levels, check Windows Sound settings for stereo mix or
"listen to this device" routing.