Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ SigningCertificate_Encoded.txt
*.zip
/QPlayerOld
/api
/TestProj
/TestResults
/QPlayer.Tests/CueListTests2.cs
/QPlayer.Tests/CueListTests3.cs
5 changes: 4 additions & 1 deletion QPlayer.MagicQCTRLPlugin/MagicQCTRLPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ private void MagicQCTRLTask()
driver?.OnMessageReceived -= Driver_OnMessageReceived;
MainViewModel.Log($"MagicQCTRL disconnected due to an error: {ex.Message}", MainViewModel.LogLevel.Warning);
}
driver?.Dispose();
finally
{
driver?.Dispose();
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion QPlayer.MagicQCTRLPlugin/QPlayer.MagicQCTRLPlugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<EnableDynamicLoading>true</EnableDynamicLoading>
<UseWPF>true</UseWPF>

<Version>0.1.1</Version>
<Version>0.1.2</Version>
<Authors>Thomas Mathieson</Authors>
<Company>Thomas Mathieson</Company>
<Copyright>©️ Thomas Mathieson 2026</Copyright>
Expand Down
15 changes: 11 additions & 4 deletions QPlayer.MagicQCTRLPlugin/USBDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ internal class USBDriver : IDisposable

public void USBConnectAsync()
{
DeviceList.Local.Changed += (o, e) =>
{
USBConnect();
};
DeviceList.Local.Changed += OnDeviceListChangedHandler;
}

private void OnDeviceListChangedHandler(object? sender, EventArgs e)
{
USBConnect();
}

/// <summary>
Expand Down Expand Up @@ -203,6 +205,11 @@ public void Dispose()
usbRXTask?.Dispose();
}
catch { }
try
{
DeviceList.Local.Changed -= OnDeviceListChangedHandler;
}
catch { }
usbDevice = null;
OnConnectionStatusChanged?.Invoke(false);
isDisposing = false;
Expand Down
15 changes: 14 additions & 1 deletion QPlayer.OSCCuePlugin/OSCCueViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,21 @@ private bool OSCMessageValid_Template
}
}*/

[SkipView]
public override string NamePreview => string.IsNullOrEmpty(Name) ? $"OSC {command}" : Name;

public OSCCueViewModel(MainViewModel mainViewModel) : base(mainViewModel)
{ }
{
PropertyChanged += (o, e) =>
{
switch (e.PropertyName)
{
case nameof(Command):
OnPropertyChanged(nameof(NamePreview));
break;
}
};
}

public override void Go()
{
Expand Down
2 changes: 1 addition & 1 deletion QPlayer.OSCCuePlugin/QPlayer.OSCCuePlugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<UseWPF>true</UseWPF>

<Title>QPlayer OSC Cue Plugin</Title>
<Version>0.1.0</Version>
<Version>0.1.1</Version>
<Authors>Thomas Mathieson</Authors>
<Company>Thomas Mathieson</Company>
<Copyright>©️ Thomas Mathieson 2026</Copyright>
Expand Down
104 changes: 104 additions & 0 deletions QPlayer.PyPlayPlugin/FramingCueView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<UserControl x:Class="QPlayer.PyPlayPlugin.FramingCueView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:util="clr-namespace:QPlayer.Utilities;assembly=QPlayer"
xmlns:vm="clr-namespace:QPlayer.ViewModels;assembly=QPlayer"
xmlns:v="clr-namespace:QPlayer.Views;assembly=QPlayer"
xmlns:local="clr-namespace:QPlayer.PyPlayPlugin"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:FramingCueViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/QPlayer;component/Views/SharedResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<util:TimeSpanStringConverter x:Key="TimeSpanStringConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel>
<StackPanel Margin="2" ToolTip="The pixel coordinates of the four corners of the image, used for perspective correction.">
<TextBlock Text="Corner Pinning" FontStyle="Italic" Margin="4"/>
<Border Background="{DynamicResource AREghZyBrush.Primary.2.Background.Static}"
BorderBrush="{DynamicResource Button.Static.Border}" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="16"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="16"/>
<RowDefinition/>
</Grid.RowDefinitions>
<v:Vec2Field SpinRate="0.001" MinValue="0" MaxValue="1" ClampValue="True" Value="{Binding Corners[0]}" Grid.Column="0" Grid.Row="0"/>
<v:Vec2Field SpinRate="0.001" MinValue="0" MaxValue="1" ClampValue="True" Value="{Binding Corners[1]}" Grid.Column="2" Grid.Row="0"/>
<v:Vec2Field SpinRate="0.001" MinValue="0" MaxValue="1" ClampValue="True" Value="{Binding Corners[2]}" Grid.Column="0" Grid.Row="2"/>
<v:Vec2Field SpinRate="0.001" MinValue="0" MaxValue="1" ClampValue="True" Value="{Binding Corners[3]}" Grid.Column="2" Grid.Row="2"/>
</Grid>
</Border>
</StackPanel>
<StackPanel Margin="2" ToolTip="A set of framing shutters which can be used to mask the overal image.">
<TextBlock Text="Framing Shutters" FontStyle="Italic" Margin="4"/>
<Border Background="{DynamicResource AREghZyBrush.Primary.2.Background.Static}"
BorderBrush="{DynamicResource Button.Static.Border}" BorderThickness="1">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Rotation"/>
<Label Grid.Column="1" Content="Mask Start"/>
<Label Grid.Column="2" Content="Softness"/>
</Grid>
<ItemsControl ItemsSource="{Binding Framing}" HorizontalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<v:TextField Text="{Binding Rotation}" ShowSpinner="True" SpinnerType="Double" SpinRate="0.2" ClampValue="True" MinValue="-360" MaxValue="360"/>
<v:TextField Text="{Binding MaskStart}" ShowSpinner="True" SpinnerType="Double" SpinRate="0.01" ClampValue="True" MinValue="0" MaxValue="1" Grid.Column="1"/>
<v:TextField Text="{Binding Softness}" ShowSpinner="True" SpinnerType="Double" SpinRate="0.01" ClampValue="True" MinValue="0" MaxValue="1" Grid.Column="2"/>
</Grid>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</StackPanel>
<Grid Height="200">
<local:FramingShutterControl DataContext="{Binding Framing[0]}"/>
<local:FramingShutterControl DataContext="{Binding Framing[1]}">
<local:FramingShutterControl.LayoutTransform>
<RotateTransform Angle="-90"/>
</local:FramingShutterControl.LayoutTransform>
</local:FramingShutterControl>
<local:FramingShutterControl DataContext="{Binding Framing[2]}">
<local:FramingShutterControl.LayoutTransform>
<RotateTransform Angle="-180"/>
</local:FramingShutterControl.LayoutTransform>
</local:FramingShutterControl>
<local:FramingShutterControl DataContext="{Binding Framing[3]}">
<local:FramingShutterControl.LayoutTransform>
<RotateTransform Angle="-270"/>
</local:FramingShutterControl.LayoutTransform>
</local:FramingShutterControl>
</Grid>
<DockPanel ToolTip="Fade in time in seconds.">
<Label Content="Fade Time"/>
<v:TextField ShowSpinner="True" SpinnerType="Double" SpinRate="0.1" ClampValue="True" MinValue="0" MaxValue="9999" Text="{Binding FadeTime, UpdateSourceTrigger=Default}" DockPanel.Dock="Right" Margin="8,2,2,2"/>
</DockPanel>
<DockPanel ToolTip="The type of curve to use when fading the volume.">
<Label Content="Fade Type"/>
<ComboBox SelectedItem="{Binding FadeType, UpdateSourceTrigger=Default}" ItemsSource="{Binding FadeTypeVals}" DockPanel.Dock="Right" Margin="8,2,2,2"/>
</DockPanel>
</StackPanel>
</UserControl>
25 changes: 25 additions & 0 deletions QPlayer.PyPlayPlugin/FramingCueView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace QPlayer.PyPlayPlugin;

/// <summary>
/// Interaction logic for FramingCueView.xaml
/// </summary>
public partial class FramingCueView : UserControl
{
public FramingCueView()
{
InitializeComponent();
}
}
31 changes: 31 additions & 0 deletions QPlayer.PyPlayPlugin/FramingCueViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using QPlayer.Audio;
using QPlayer.SourceGenerator;
using QPlayer.Utilities;
using QPlayer.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;

namespace QPlayer.PyPlayPlugin;

[Model(typeof(VideoFramingCue))]
//[GenerateView]
[View(typeof(FramingCueView))]
[DisplayName("Video Framing Cue")]
[Icon("IconPyFramingCue", typeof(Icons))]
public partial class FramingCueViewModel(MainViewModel mainViewModel) : CueViewModel(mainViewModel)
{
[Reactive] private UndoableObservableCollection<Vector2> corners = [..Enumerable.Repeat<Vector2>(default, 4)];
[Reactive] private UndoableObservableCollection<FramingShutterViewModel, FramingShutter> framing = [.. Enumerable.Range(0, 4).Select(x => new FramingShutterViewModel())];
[Reactive] private float fadeTime = 0;
[Reactive] private FadeType fadeType = FadeType.SCurve;
}

public partial class FramingShutterViewModel : BindableViewModel<FramingShutter>
{
[Reactive] private float rotation;
[Reactive] private float maskStart;
[Reactive] private float softness;
}
47 changes: 47 additions & 0 deletions QPlayer.PyPlayPlugin/FramingShutterControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<UserControl x:Class="QPlayer.PyPlayPlugin.FramingShutterControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:util="clr-namespace:QPlayer.Utilities;assembly=QPlayer"
xmlns:local="clr-namespace:QPlayer.PyPlayPlugin"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance Type=local:FramingShutterViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/QPlayer;component/Views/SharedResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Geometry x:Key="FramingShutterGeometry">F0 M50,-20 h-100 v20 A50,50 0 0 0 -35.3553,35.3553 A150,80 0 0 1 -15,75 v20 a5,5 0 0 0 5,5 h20 a5,5 0 0 0 5,-5 v-20 A150,80 0 0 1 35.3553,35.3553 A50,50 0 0 0 50,0 v-20z</Geometry>
<DrawingGroup x:Key="FramingShutterDrawingGroup" ClipGeometry="M-50,-100 v200 h100 v-200 h-100 Z">
<GeometryDrawing Brush="Wheat" Geometry="{StaticResource FramingShutterGeometry}"/>
</DrawingGroup>
<DrawingImage x:Key="FramingShutterDrawing" Drawing="{StaticResource FramingShutterDrawingGroup}" />
<util:MultiplyByFConverter x:Key="MultiplyByFConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid Width="200" Height="200" ClipToBounds="True" x:Name="ContainerGrid">
<Rectangle Width="180" Height="200" StrokeThickness="2" MouseDown="Rectangle_MouseDown" MouseUp="Rectangle_MouseUp" MouseMove="Rectangle_MouseMove" MouseEnter="Rectangle_MouseEnter" MouseLeave="Rectangle_MouseLeave">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,0.5">
<GradientStop Color="{DynamicResource AREghZyColour.Deep.1.Background.Static}" Offset="0.0" />
<GradientStop Color="{DynamicResource AREghZyColour.Foreground.Deeper}" Offset="{Binding Softness}" />
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.Stroke>
<SolidColorBrush Color="{DynamicResource AREghZyColour.Deep.3.Border.Static}"/>
</Rectangle.Stroke>
<Rectangle.OpacityMask>
<DrawingBrush Drawing="{DynamicResource FramingShutterDrawingGroup}"/>
</Rectangle.OpacityMask>
<Rectangle.RenderTransform>
<TransformGroup>
<TranslateTransform Y="180"/>
<TranslateTransform Y="{Binding MaskStart, Mode=OneWay, Converter={StaticResource MultiplyByFConverter}, ConverterParameter=-100}"/>
<RotateTransform Angle="{Binding Rotation, Mode=OneWay}" CenterX="100" CenterY="100"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</UserControl>
97 changes: 97 additions & 0 deletions QPlayer.PyPlayPlugin/FramingShutterControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using QPlayer.Views;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace QPlayer.PyPlayPlugin;

/// <summary>
/// Interaction logic for FramingShutterControl.xaml
/// </summary>
public partial class FramingShutterControl : UserControl
{
bool isDragging = false;
Point lastPos;

public FramingShutterControl()
{
InitializeComponent();
}

private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
{
isDragging = true;
LibraryImports.ShowCursor(false);
lastPos = e.GetPosition(ContainerGrid);
(sender as FrameworkElement)?.CaptureMouse();
//Debug.WriteLine($"down left={e.LeftButton} right={e.RightButton} drag={isDragging}");
}

private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
{
//Debug.WriteLine($"up left={e.LeftButton} right={e.RightButton} drag={isDragging}");
}

private void Rectangle_MouseMove(object sender, MouseEventArgs e)
{
//Debug.WriteLine($"move left={e.LeftButton} right={e.RightButton} drag={isDragging}");
if (!isDragging || DataContext is not FramingShutterViewModel shutter)
return;

if (e.LeftButton == MouseButtonState.Released && e.RightButton == MouseButtonState.Released)
{
(sender as FrameworkElement)?.ReleaseMouseCapture();
isDragging = false;
ContainerGrid.Opacity = 0.7;
// Sometimes the MouseUp call gets eaten, make sure to unhide the mouse here...
int count;
do
{
count = LibraryImports.ShowCursor(true);
} while (count < 0);
while (count > 0)
{
count = LibraryImports.ShowCursor(false);
}
return;
}

var pos = e.GetPosition(ContainerGrid);
var off = pos - lastPos;
lastPos = pos;

var x = off.X / ContainerGrid.ActualWidth;
var y = off.Y / ContainerGrid.ActualHeight;

shutter.Rotation = Math.Clamp(shutter.Rotation - (float)(x * 150), -120, 120);
if (e.RightButton == MouseButtonState.Pressed)
shutter.Softness = Math.Clamp(shutter.Softness + (float)(y*2), 0, 1);
else
shutter.MaskStart = Math.Clamp(shutter.MaskStart - (float)(y*2), 0, 1);
}

private void Rectangle_MouseEnter(object sender, MouseEventArgs e)
{
ContainerGrid.Opacity = 1.0;
}

private void Rectangle_MouseLeave(object sender, MouseEventArgs e)
{
// Debug.WriteLine($"leave left={e.LeftButton} right={e.RightButton} drag={isDragging}");
if (isDragging)
return;

ContainerGrid.Opacity = 0.7;
//LibraryImports.ShowCursor(true);
}
}
Loading
Loading