diff --git a/Helpers/NullToVisibilityConverter.cs b/Helpers/NullToVisibilityConverter.cs
new file mode 100644
index 0000000..06fd9d9
--- /dev/null
+++ b/Helpers/NullToVisibilityConverter.cs
@@ -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;
+}
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 154cccc..9b807d0 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -33,6 +33,8 @@
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
+
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -384,9 +413,11 @@
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
-
+
@@ -404,9 +435,11 @@
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
-
+
@@ -421,9 +454,11 @@
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
ToolTip="https://example.com"/>
-
+
@@ -432,21 +467,8 @@
-
-
-
-
+ HorizontalAlignment="Center" Margin="0,8,0,0"
+ Visibility="{Binding IsElementsPanelEmpty, Converter={StaticResource BoolToVis}}"/>
@@ -845,32 +867,6 @@
HorizontalAlignment="Center"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 1fa2798..ea6848c 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -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();
diff --git a/Models/SceneElement.cs b/Models/SceneElement.cs
index 88e4629..9a9fe97 100644
--- a/Models/SceneElement.cs
+++ b/Models/SceneElement.cs
@@ -47,6 +47,9 @@ public abstract class SceneElement : INotifyPropertyChanged
/// True only for singleton resources (webcam). Gates webcam-only UI.
public virtual bool IsWebcam => false;
+ /// True for elements that can be dragged/resized in the preview (image, webcam).
+ public virtual bool IsDraggable => false;
+
/// True only for image sources — the raw preview renderer's discriminator.
public virtual bool IsImageSource => false;
diff --git a/Models/Source.cs b/Models/Source.cs
index 8ac9331..f31cd22 100644
--- a/Models/Source.cs
+++ b/Models/Source.cs
@@ -42,6 +42,8 @@ public class Source : SceneElement
}
}
+ public override bool IsDraggable => Type == SourceType.Image;
+
/// The permanent scene backdrop (live desktop/game capture). Never removable/reorderable.
private bool _isBackdrop;
public bool IsBackdrop { get => _isBackdrop; set => Set(ref _isBackdrop, value); }
diff --git a/Models/WebcamSceneConfig.cs b/Models/WebcamSceneConfig.cs
index 0a7ec7d..4e8244c 100644
--- a/Models/WebcamSceneConfig.cs
+++ b/Models/WebcamSceneConfig.cs
@@ -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;
}
diff --git a/README.md b/README.md
index 3f7652c..c898127 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
# ytLlive
+
+
+
+
A creator-proof live-streaming and recording app for YouTube — native Windows (C# / WPF / .NET 8).
## Screenshot
diff --git a/Themes/Controls.xaml b/Themes/Controls.xaml
index 0cbbc99..ba38e7b 100644
--- a/Themes/Controls.xaml
+++ b/Themes/Controls.xaml
@@ -519,17 +519,23 @@
-
-
-
+
+
+
+
+
+
+
diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs
index c95789f..51f50d2 100644
--- a/ViewModels/MainViewModel.cs
+++ b/ViewModels/MainViewModel.cs
@@ -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
/// Shows the mirror / clip-shape row in the source chip when a webcam is selected.
public bool IsWebcamSelected => SelectedElement is WebcamSceneConfig;
+ /// True when the Elements panel should show the "No configurable elements" hint.
+ public bool IsElementsPanelEmpty => SelectedElement is { } el
+ && !el.IsDraggable && !el.IsWebcam
+ && el is not Source { Type: SourceType.Countdown or SourceType.WebSource };
+
///
/// 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
diff --git a/bugs.md b/bugs.md
new file mode 100644
index 0000000..6846ef2
--- /dev/null
+++ b/bugs.md
@@ -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.