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:
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user