diff --git a/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml b/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml index c404e4c..c1a3058 100644 --- a/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml +++ b/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml @@ -15,13 +15,16 @@ d:DesignHeight="450" d:DesignWidth="800"> - + - - - - + + + +  + + @@ -36,6 +39,15 @@ Background="#83000000" VerticalAlignment="Top" Padding="5" FontSize="20" Visibility="{Binding Name, Converter={StaticResource StringToVis}}" /> - + + + + + + + + \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml.cs b/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml.cs index d94c9ea..4a0d3b8 100644 --- a/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml.cs +++ b/BetterMultiview/ObsMultiview/Controls/SceneSlot.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Printing; using System.Windows; using System.Windows.Controls; @@ -67,6 +68,14 @@ public partial class SceneSlot : UserControl { set { SetValue(SlotConfiguringProperty, value); } } + public static readonly DependencyProperty SetProperty = DependencyProperty.Register( + nameof(Set), typeof(Set), typeof(SceneSlot), new PropertyMetadata(default(Set))); + + public Set Set { + get { return (Set)GetValue(SetProperty); } + set { SetValue(SetProperty, value); } + } + public SceneSlot(UserProfile.DSlot slot, StreamView owner) { _slot = slot; _owner = owner; @@ -113,10 +122,13 @@ public partial class SceneSlot : UserControl { private void LoadSlot() { Unconfigured = string.IsNullOrEmpty(_slot.Obs.Scene); Name = _slot.Name; + Set = _profile.ActiveProfile?.SceneView?.Sets.FirstOrDefault(x => x.Id == _slot.SetId); } private void SceneSlot_OnMouseRightButtonUp(object sender, MouseButtonEventArgs e) { - var config = new SlotConfig(_slot); + if (_profile.ActiveProfile == null) return; + + var config = new SlotConfig(_slot, _profile.ActiveProfile.SceneView); config.Owner = Window.GetWindow(this); _plugins.PausePlugins(null, true); diff --git a/BetterMultiview/ObsMultiview/Converters/StringToVis.cs b/BetterMultiview/ObsMultiview/Converters/StringToVis.cs index 364f4e1..ba51a8c 100644 --- a/BetterMultiview/ObsMultiview/Converters/StringToVis.cs +++ b/BetterMultiview/ObsMultiview/Converters/StringToVis.cs @@ -8,7 +8,7 @@ namespace ObsMultiview.Converters { /// /// Convert a string to visibility (null or whitespace => hidden) /// - public class StringToVis : IValueConverter { + public class NullToVis : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string s) { return string.IsNullOrWhiteSpace(s) ? Visibility.Collapsed : Visibility.Visible; diff --git a/BetterMultiview/ObsMultiview/Data/IPluginSettingsProvider.cs b/BetterMultiview/ObsMultiview/Data/IPluginSettingsProvider.cs new file mode 100644 index 0000000..1a33a88 --- /dev/null +++ b/BetterMultiview/ObsMultiview/Data/IPluginSettingsProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Newtonsoft.Json.Linq; + +namespace ObsMultiview.Data { + public interface IPluginSettingsProvider { + JObject GetPluginSettings(string pluginId); + + void SetPluginSettings(string pluginId, JObject pluginSettings); + + Guid Id { get; } + + string Name { get; set; } + } +} diff --git a/BetterMultiview/ObsMultiview/Data/Set.cs b/BetterMultiview/ObsMultiview/Data/Set.cs new file mode 100644 index 0000000..808df38 --- /dev/null +++ b/BetterMultiview/ObsMultiview/Data/Set.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using JetBrains.Annotations; +using Newtonsoft.Json.Linq; + +namespace ObsMultiview.Data { + public class Set : INotifyPropertyChanged, IPluginSettingsProvider { + private Color _color; + public string Name { get; set; } + + /// + /// Plugin configs + /// + public Dictionary PluginConfigs { get; set; } = new(); + + public void SetPluginSettings(string pluginId, JObject pluginSettings) { + if (pluginSettings == null) { + PluginConfigs.Remove(pluginId); + } else { + PluginConfigs[pluginId] = pluginSettings; + } + } + + public Guid? Id { get; set; } + + Guid IPluginSettingsProvider.Id => Id ?? Guid.Empty; + + public Color Color { + get => _color; + set { + if (value.Equals(_color)) return; + _color = value; + OnPropertyChanged(); + } + } + + public Set() { + Id = Guid.NewGuid(); + PluginConfigs = new Dictionary(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public JObject GetPluginSettings(string pluginId) { + if (PluginConfigs.ContainsKey(pluginId)) { + return PluginConfigs[pluginId]; + } else { + return null; + } + } + } +} \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Data/UserProfile.cs b/BetterMultiview/ObsMultiview/Data/UserProfile.cs index 39d7621..5f780a6 100644 --- a/BetterMultiview/ObsMultiview/Data/UserProfile.cs +++ b/BetterMultiview/ObsMultiview/Data/UserProfile.cs @@ -1,5 +1,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Windows.Media; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -11,11 +17,28 @@ public class UserProfile { /// /// Config options for a single slot /// - public class DSlot { + public class DSlot : INotifyPropertyChanged, IPluginSettingsProvider { + private string _name; + /// /// Name of the slot /// - public string Name { get; set; } + public string Name { + get => _name; + set { + if (value == _name) return; + _name = value; + OnPropertyChanged(); + } + } + + public void SetPluginSettings(string pluginId, JObject pluginSettings) { + if (pluginSettings == null) { + PluginConfigs.Remove(pluginId); + } else { + PluginConfigs[pluginId] = pluginSettings; + } + } /// /// internal id of the slot. Not persistent during restarts @@ -23,6 +46,11 @@ public class DSlot { [JsonIgnore] public Guid Id { get; } + /// + /// The id of the set this slot belongs to + /// + public Guid? SetId { get; set; } + /// /// OBS config /// @@ -36,6 +64,12 @@ public class DSlot { public DSlot() { Obs = new DSlotObs(); Id = Guid.NewGuid(); + PluginConfigs = new(); + } + + [OnDeserialized] + private void OnDeserialized(StreamingContext context) { + if (PluginConfigs == null) PluginConfigs = new(); } public static bool operator ==(DSlot a, DSlot b) { @@ -45,6 +79,21 @@ public class DSlot { public static bool operator !=(DSlot a, DSlot b) { return !(a == b); } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public JObject GetPluginSettings(string pluginId) { + if (PluginConfigs.ContainsKey(pluginId)) { + return PluginConfigs[pluginId]; + } else { + return null; + } + } } /// @@ -72,9 +121,21 @@ public class DSceneViewConfig { public int Columns { get; set; } = 6; public List Slots { get; set; } + public List Sets { get; set; } public DSceneViewConfig() { Slots = new List(); + Sets = new List(); + Sets.Add(new Set { + Color = Colors.DarkGray, + Name = "Neutral", + Id = Guid.Empty + }); + } + + [OnDeserialized] + internal void OnDeserialized(StreamingContext context) { + Sets = Sets.DistinctBy(x => x.Id).ToList(); } } @@ -102,7 +163,7 @@ public class DObsProfile { public DObsProfile(string id, int rows, int columns) { Id = id; - SceneView = new DSceneViewConfig {Rows = rows, Columns = columns}; + SceneView = new DSceneViewConfig { Rows = rows, Columns = columns }; } } diff --git a/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml b/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml new file mode 100644 index 0000000..cff2cc2 --- /dev/null +++ b/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml.cs b/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml.cs new file mode 100644 index 0000000..b1589d0 --- /dev/null +++ b/BetterMultiview/ObsMultiview/Dialogs/SetConfig.xaml.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using Autofac; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ObsMultiview.Data; +using ObsMultiview.Plugins; +using ObsMultiview.Services; + +namespace ObsMultiview.Dialogs { + /// + /// Config Dialog for a scene slot + /// + public partial class SetConfig : Window { + private readonly ILogger _logger; + + public static readonly DependencyProperty SetProperty = DependencyProperty.Register( + nameof(Set), typeof(Set), typeof(SetConfig), new PropertyMetadata(default(Set))); + + public Set Set { + get { return (Set)GetValue(SetProperty); } + set { SetValue(SetProperty, value); } + } + + public static readonly DependencyProperty AvailableScenesProperty = DependencyProperty.Register( + nameof(AvailableScenes), typeof(ObservableCollection), typeof(SetConfig), + new PropertyMetadata(default(ObservableCollection))); + + public ObservableCollection AvailableScenes { + get { return (ObservableCollection)GetValue(AvailableScenesProperty); } + set { SetValue(AvailableScenesProperty, value); } + } + public static readonly DependencyProperty PluginStateProperty = DependencyProperty.Register( + nameof(PluginState), typeof(Dictionary), typeof(SetConfig), + new PropertyMetadata(default(Dictionary))); + + public Dictionary PluginState { + get { return (Dictionary)GetValue(PluginStateProperty); } + set { SetValue(PluginStateProperty, value); } + } + + private string _originalConfig; + private readonly ObsWatchService _obs; + private readonly PluginService _plugins; + private readonly List<(PluginBase plugin, SettingsControl settings)> _pluginSettings = new(); + + public SetConfig(Set set) { + _originalConfig = JsonConvert.SerializeObject(set); + Set = set; + + InitializeComponent(); + + _obs = App.Container.Resolve(); + _plugins = App.Container.Resolve(); + _logger = App.Container.Resolve>(); + + var scenes = _obs.WebSocket.GetSceneList().Scenes.Select(x => x.Name) + .Where(x => x != "multiview" && x != "preview"); + AvailableScenes = new ObservableCollection(scenes); + PluginState = new Dictionary(); + + if (Set.PluginConfigs == null) + Set.PluginConfigs = new Dictionary(); + + // load config controls for all active plugins + foreach (var plugin in _plugins.Plugins.Where(x => x.Active && x.Plugin.HasSlotSettings)) { + PluginState.Add(plugin.Plugin.Name, Set.PluginConfigs.ContainsKey(plugin.Plugin.Name)); + + var expander = new Expander(); + + var title = new CheckBox(); + title.Content = new TextBlock { + Text = plugin.Plugin.Name, + Style = TryFindResource("Title") as Style, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + title.HorizontalAlignment = HorizontalAlignment.Stretch; + var bind = new Binding(); + bind.Path = new PropertyPath($"PluginState[{plugin.Plugin.Name}].Value"); + bind.Mode = BindingMode.TwoWay; + bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; + BindingOperations.SetBinding(title, ToggleButton.IsCheckedProperty, bind); + expander.Header = title; + expander.IsExpanded = PluginState[plugin.Plugin.Name]; + + var slotSettings = plugin.Plugin.GetSlotSettings(Set.Id); + if (slotSettings == null) return; + + slotSettings.FetchSettings(); + slotSettings.Margin = new Thickness(0, 0, 0, 10); + bind = new Binding(); + bind.Source = this; + bind.Path = new PropertyPath($"PluginState[{plugin.Plugin.Name}].Value"); + bind.Mode = BindingMode.OneWay; + BindingOperations.SetBinding(slotSettings, UserControl.IsEnabledProperty, bind); + + _pluginSettings.Add((plugin.Plugin, slotSettings)); + expander.Content = slotSettings; + + ConfigPanel.Children.Add(expander); + } + + input.Focus(); + } + + private void Ok_OnClick(object sender, RoutedEventArgs e) { + DialogResult = true; + + foreach (var item in _pluginSettings) { + try { + item.settings.WriteSettings(); + } catch (Exception ex) { + _logger.LogError(ex, "Failed to write slot settings for " + item.plugin.Name); + } + } + + foreach (var plugin in PluginState.Where(x => !x.Value).Select(x => x.Key)) { + Set.PluginConfigs.Remove(plugin); + } + + Close(); + } + + private void Cancel_OnClick(object sender, RoutedEventArgs e) { + DialogResult = false; + JsonConvert.PopulateObject(_originalConfig, Set, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); + + Close(); + } + + private void SlotConfig_OnClosing(object sender, CancelEventArgs e) { + if (DialogResult == null) { + DialogResult = false; + JsonConvert.PopulateObject(_originalConfig, Set, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); + } + } + + private void Unlink_OnClick(object sender, RoutedEventArgs e) { + // Reset this slot & delete all configs + JsonConvert.PopulateObject(JsonConvert.SerializeObject(new UserProfile.DSlot()), Set, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); + DialogResult = true; + Close(); + } + + public class ObservableBoolean : INotifyPropertyChanged { + private bool _value; + + public bool Value { + get => _value; + set { + if (value == _value) return; + _value = value; + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public static implicit operator ObservableBoolean(bool value) { + return new ObservableBoolean { Value = value }; + } + + public static implicit operator bool(ObservableBoolean value) { + return value.Value; + } + } + } +} \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml b/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml new file mode 100644 index 0000000..abecfed --- /dev/null +++ b/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml.cs b/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml.cs new file mode 100644 index 0000000..a6b47e0 --- /dev/null +++ b/BetterMultiview/ObsMultiview/Dialogs/SetEditor.xaml.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Input; +using ObsMultiview.Data; + +namespace ObsMultiview.Dialogs { + /// + /// Interaktionslogik für SetEditor.xaml + /// + public partial class SetEditor : Window { + public static readonly DependencyProperty SetsProperty = DependencyProperty.Register( + nameof(Sets), typeof(ObservableCollection), typeof(SetEditor), + new PropertyMetadata(default(ObservableCollection))); + + public ObservableCollection Sets { + get { return (ObservableCollection)GetValue(SetsProperty); } + set { SetValue(SetsProperty, value); } + } + + public static RoutedUICommand EditSet { get; } + + static SetEditor() { + EditSet = new RoutedUICommand(); + } + + public SetEditor() { + InitializeComponent(); + } + + private void Ok_OnClick(object sender, RoutedEventArgs e) { + DialogResult = true; + Close(); + } + + private void Cancel_OnClick(object sender, RoutedEventArgs e) { + DialogResult = false; + Close(); + } + + private void EditSet_OnExecuted(object sender, ExecutedRoutedEventArgs e) { + var set = e.Parameter as Set; + + if (set != null) { + var editor = new SetConfig(set); + editor.Owner = this; + editor.ShowDialog(); + } + } + + private void EditSet_OnCanExecute(object sender, CanExecuteRoutedEventArgs e) { + var set = e.Parameter as Set; + + if (set != null) { + e.CanExecute = set.Id != null && set.Id != Guid.Empty; + } + } + } +} \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml b/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml index bf7cda8..6f3e6af 100644 --- a/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml +++ b/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml @@ -5,6 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ObsMultiview" xmlns:lex="http://wpflocalizeextension.codeplex.com" + xmlns:data="clr-namespace:ObsMultiview.Data" + xmlns:converters="clr-namespace:ObsMultiview.Converters" mc:Ignorable="d" Icon="pack://application:,,,/ObsMultiview;component/Images/Icon.png" Closing="SlotConfig_OnClosing" @@ -17,6 +19,8 @@ Title="{lex:Loc SlotConfig}" Height="450" Width="345"> + + @@ -46,6 +50,14 @@ OBS + + + + + + + + diff --git a/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml.cs b/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml.cs index 373d0a7..1b5b54e 100644 --- a/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml.cs +++ b/BetterMultiview/ObsMultiview/Dialogs/SlotConfig.xaml.cs @@ -3,13 +3,18 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; using Autofac; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ObsMultiview.Data; +using ObsMultiview.Extensions; using ObsMultiview.Plugins; using ObsMultiview.Services; @@ -25,7 +30,7 @@ public partial class SlotConfig : Window { new PropertyMetadata(default(UserProfile.DSlot))); public UserProfile.DSlot Slot { - get { return (UserProfile.DSlot) GetValue(SlotProperty); } + get { return (UserProfile.DSlot)GetValue(SlotProperty); } set { SetValue(SlotProperty, value); } } @@ -34,18 +39,38 @@ public partial class SlotConfig : Window { new PropertyMetadata(default(ObservableCollection))); public ObservableCollection AvailableScenes { - get { return (ObservableCollection) GetValue(AvailableScenesProperty); } + get { return (ObservableCollection)GetValue(AvailableScenesProperty); } set { SetValue(AvailableScenesProperty, value); } } + public static readonly DependencyProperty SetsProperty = DependencyProperty.Register( + nameof(Sets), typeof(ObservableCollection), typeof(SlotConfig), + new PropertyMetadata(default(ObservableCollection))); + + public ObservableCollection Sets { + get { return (ObservableCollection)GetValue(SetsProperty); } + set { SetValue(SetsProperty, value); } + } + + public static readonly DependencyProperty PluginStateProperty = DependencyProperty.Register( + nameof(PluginState), typeof(Dictionary), typeof(SlotConfig), + new PropertyMetadata(default(Dictionary))); + + public Dictionary PluginState { + get { return (Dictionary)GetValue(PluginStateProperty); } + set { SetValue(PluginStateProperty, value); } + } + private string _originalConfig; private readonly ObsWatchService _obs; private readonly PluginService _plugins; private readonly List<(PluginBase plugin, SettingsControl settings)> _pluginSettings = new(); - public SlotConfig(UserProfile.DSlot slot) { + public SlotConfig(UserProfile.DSlot slot, UserProfile.DSceneViewConfig config) { _originalConfig = JsonConvert.SerializeObject(slot); Slot = slot; + Sets = new ObservableCollection(config.Sets); + Sets.Insert(0, new Set { Name = Localizer.Localize("Dialogs","NoSet"), Id = null }); InitializeComponent(); @@ -56,25 +81,42 @@ public partial class SlotConfig : Window { var scenes = _obs.WebSocket.GetSceneList().Scenes.Select(x => x.Name) .Where(x => x != "multiview" && x != "preview"); AvailableScenes = new ObservableCollection(scenes); + PluginState = new Dictionary(); if (Slot.PluginConfigs == null) Slot.PluginConfigs = new Dictionary(); // load config controls for all active plugins foreach (var plugin in _plugins.Plugins.Where(x => x.Active && x.Plugin.HasSlotSettings)) { + PluginState.Add(plugin.Plugin.Name, Slot.PluginConfigs.ContainsKey(plugin.Plugin.Name)); + var expander = new Expander(); - var title = new TextBlock(); - title.Text = plugin.Plugin.Name; - title.Style = TryFindResource("Title") as Style; + var title = new CheckBox(); + title.Content = new TextBlock { + Text = plugin.Plugin.Name, + Style = TryFindResource("Title") as Style, + HorizontalAlignment = HorizontalAlignment.Stretch + }; title.HorizontalAlignment = HorizontalAlignment.Stretch; + var bind = new Binding(); + bind.Path = new PropertyPath($"PluginState[{plugin.Plugin.Name}].Value"); + bind.Mode = BindingMode.TwoWay; + bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; + BindingOperations.SetBinding(title, ToggleButton.IsCheckedProperty, bind); expander.Header = title; + expander.IsExpanded = PluginState[plugin.Plugin.Name]; var slotSettings = plugin.Plugin.GetSlotSettings(slot.Id); if (slotSettings == null) return; slotSettings.FetchSettings(); slotSettings.Margin = new Thickness(0, 0, 0, 10); + bind = new Binding(); + bind.Source = this; + bind.Path = new PropertyPath($"PluginState[{plugin.Plugin.Name}].Value"); + bind.Mode = BindingMode.OneWay; + BindingOperations.SetBinding(slotSettings, UserControl.IsEnabledProperty, bind); _pluginSettings.Add((plugin.Plugin, slotSettings)); expander.Content = slotSettings; @@ -96,27 +138,63 @@ public partial class SlotConfig : Window { } } + foreach (var plugin in PluginState.Where(x => !x.Value).Select(x => x.Key)) { + Slot.PluginConfigs.Remove(plugin); + } + Close(); } private void Cancel_OnClick(object sender, RoutedEventArgs e) { DialogResult = false; - JsonConvert.PopulateObject(_originalConfig, Slot); + JsonConvert.PopulateObject(_originalConfig, Slot, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); + Close(); } private void SlotConfig_OnClosing(object sender, CancelEventArgs e) { if (DialogResult == null) { DialogResult = false; - JsonConvert.PopulateObject(_originalConfig, Slot); + JsonConvert.PopulateObject(_originalConfig, Slot, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); } } private void Unlink_OnClick(object sender, RoutedEventArgs e) { // Reset this slot & delete all configs - JsonConvert.PopulateObject(JsonConvert.SerializeObject(new UserProfile.DSlot()), Slot); + JsonConvert.PopulateObject(JsonConvert.SerializeObject(new UserProfile.DSlot()), Slot, + new() { ObjectCreationHandling = ObjectCreationHandling.Replace }); DialogResult = true; Close(); } + + public class ObservableBoolean : INotifyPropertyChanged { + private bool _value; + + public bool Value { + get => _value; + set { + if (value == _value) return; + _value = value; + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public static implicit operator ObservableBoolean(bool value) { + return new ObservableBoolean { Value = value }; + } + + public static implicit operator bool(ObservableBoolean value) { + return value.Value; + } + } } } \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/MainWindow.xaml b/BetterMultiview/ObsMultiview/MainWindow.xaml index 20e77b3..60c6f12 100644 --- a/BetterMultiview/ObsMultiview/MainWindow.xaml +++ b/BetterMultiview/ObsMultiview/MainWindow.xaml @@ -17,7 +17,7 @@ Title="Stream Deck" Height="450" Width="800"> - + diff --git a/BetterMultiview/ObsMultiview/ObsMultiview.csproj b/BetterMultiview/ObsMultiview/ObsMultiview.csproj index ee2226a..7e0cfba 100644 --- a/BetterMultiview/ObsMultiview/ObsMultiview.csproj +++ b/BetterMultiview/ObsMultiview/ObsMultiview.csproj @@ -4,8 +4,9 @@ WinExe net6.0-windows true - 1.0.0.0 + 2.0.0.0 Icon.ico + 2.0.0.0 @@ -22,6 +23,7 @@ + @@ -43,6 +45,9 @@ + + Code + True True @@ -72,4 +77,11 @@ + + + $(DefaultXamlRuntime) + Designer + + + diff --git a/BetterMultiview/ObsMultiview/Services/PluginService.cs b/BetterMultiview/ObsMultiview/Services/PluginService.cs index fb5e8cd..05d58c1 100644 --- a/BetterMultiview/ObsMultiview/Services/PluginService.cs +++ b/BetterMultiview/ObsMultiview/Services/PluginService.cs @@ -235,11 +235,17 @@ private class CommandFacadeBound : CommandFacade { _logger.LogDebug($"Requesting slot config for slot {guid}"); var slot = _profile.ActiveProfile.SceneView.Slots.FirstOrDefault(x => x.Id == guid); - if (slot != null && slot.PluginConfigs != null) { - return slot.PluginConfigs.FirstOrDefault(x => x.Key == _plugin.Name).Value; + if (slot != null && slot.PluginConfigs.ContainsKey(_plugin.Name)) { + return slot.PluginConfigs[_plugin.Name]; } else { - return new JObject(); + var set = _profile.ActiveProfile.SceneView.Sets.FirstOrDefault(x => x.Id == guid); + + if (set != null && set.PluginConfigs.ContainsKey(_plugin.Name)) { + return set.PluginConfigs[_plugin.Name]; + } } + + return null; } protected override IEnumerable<(Guid id, JObject config)> RequestSlotSettings() { @@ -254,10 +260,13 @@ private class CommandFacadeBound : CommandFacade { var slot = _profile.ActiveProfile.SceneView.Slots.FirstOrDefault(x => x.Id == guid); if (slot != null) { - if (slot.PluginConfigs == null) - slot.PluginConfigs = new Dictionary(); - slot.PluginConfigs[_plugin.Name] = config; + } else { + var set = _profile.ActiveProfile.SceneView.Sets.FirstOrDefault(x => x.Id == guid); + + if (set != null) { + set.PluginConfigs[_plugin.Name] = config; + } } } } diff --git a/BetterMultiview/ObsMultiview/Services/SceneService.cs b/BetterMultiview/ObsMultiview/Services/SceneService.cs index e326d20..c9f89cd 100644 --- a/BetterMultiview/ObsMultiview/Services/SceneService.cs +++ b/BetterMultiview/ObsMultiview/Services/SceneService.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using ObsMultiview.Data; using ObsMultiview.Plugins; @@ -16,6 +18,7 @@ public class SceneService { private UserProfile.DSlot _previewScene; private UserProfile.DSlot _liveScene; + private Dictionary _activeSettings = new(); /// /// Fired when the active preview changes @@ -93,15 +96,18 @@ public class SceneService { _logger.LogDebug($"Unapplying scene {slot?.Name} to {next?.Name}"); foreach (var plugin in - _plugins.Plugins.Where(x => - (slot?.PluginConfigs?.ContainsKey(x.Plugin.Name) ?? false) && - x.Plugin.TriggerType == PluginTriggerType.Change)) { - + _plugins.Plugins.Where(x => x.Active && x.Plugin.TriggerType == PluginTriggerType.Change)) { if (plugin.Plugin is ChangePluginBase cplug) { - try { - cplug.OnSlotExit(slot.Id, next?.Id); - } catch (Exception ex) { - _logger.LogError(ex,$"Failed to trigger SlotExit for plugin {cplug.Name}"); + var current = ResolveActiveSettings(cplug, slot); + var nextS = ResolveActiveSettings(cplug, next); + + if (_activeSettings[cplug] != nextS) { + try { + cplug.OnSlotExit(current.GetPluginSettings(cplug.Name), + nextS.GetPluginSettings(cplug.Name)); + } catch (Exception ex) { + _logger.LogError(ex, $"Failed to trigger SlotExit for plugin {cplug.Name}"); + } } } } @@ -123,30 +129,66 @@ public class SceneService { } foreach (var plugin in - _plugins.Plugins.Where(x => (slot?.PluginConfigs?.ContainsKey(x.Plugin.Name) ?? false) - && (x.Plugin.TriggerType == PluginTriggerType.Change))) { + _plugins.Plugins.Where(x => x.Active && x.Plugin.TriggerType == PluginTriggerType.Change)) { if (plugin.Plugin is ChangePluginBase cplug) { - try { - cplug.OnSlotEnter(slot.Id, prev?.Id); - } catch (Exception ex) { - _logger.LogError(ex,$"Failed to trigger SlotEnter for plugin {cplug.Name}"); + var next = ResolveActiveSettings(cplug, slot); + var prevS = ResolveActiveSettings(cplug, prev); + + if (_activeSettings[cplug] != next) { + try { + cplug.OnSlotEnter(next.GetPluginSettings(cplug.Name), prevS.GetPluginSettings(cplug.Name)); + } catch (Exception ex) { + _logger.LogError(ex, $"Failed to trigger SlotEnter for plugin {cplug.Name}"); + } + + _activeSettings[cplug] = next; } } } foreach (var plugin in - _plugins.Plugins.Where(x => (slot?.PluginConfigs?.ContainsKey(x.Plugin.Name) ?? false) - && (x.Plugin.TriggerType == PluginTriggerType.State))) { + _plugins.Plugins.Where(x => x.Active && x.Plugin.TriggerType == PluginTriggerType.State)) { if (plugin.Plugin is StatePluginBase splug) { - try { - splug.ActiveSlotChanged(slot.Id); - } catch (Exception ex) { - _logger.LogError(ex,$"Failed to apply slot state for plugin {splug.Name}"); + var config = ResolveActiveSettings(splug, slot); + + if (_activeSettings[splug] != config) { + try { + splug.ActiveSettingsChanged(config.GetPluginSettings(splug.Name)); + } catch (Exception ex) { + _logger.LogError(ex, $"Failed to apply slot state for plugin {splug.Name}"); + } + + _activeSettings[splug] = config; } } } } + private IPluginSettingsProvider ResolveActiveSettings(PluginBase plugin, UserProfile.DSlot slot) { + if (slot == null) return null; + + if (!_activeSettings.ContainsKey(plugin)) + _activeSettings.Add(plugin, null); + + if (slot.PluginConfigs.ContainsKey(plugin.Name) && slot.PluginConfigs[plugin.Name] != null) { + return slot; + } else if (slot.SetId != null) { + var set = _profile.ActiveProfile.SceneView.Sets.FirstOrDefault(x => x.Id == slot.SetId); + + if (set != null && set.Id == Guid.Empty) { + _activeSettings.TryGetValue(plugin, out var config); + return config; + } else if (set != null && set.PluginConfigs.ContainsKey(plugin.Name) && + set.PluginConfigs[plugin.Name] != null) { + return set; + } else { + return null; + } + } else { + return null; + } + } + public void ClearPreview() { OnPreviewChanged(null); } diff --git a/BetterMultiview/ObsMultiview/StreamView.xaml b/BetterMultiview/ObsMultiview/StreamView.xaml index 5add5ba..880a9b7 100644 --- a/BetterMultiview/ObsMultiview/StreamView.xaml +++ b/BetterMultiview/ObsMultiview/StreamView.xaml @@ -92,6 +92,10 @@ FontSize="16" FontFamily="Segoe MDL2 Assets" Margin="5" Click="ProfileSettings_OnClick">  + diff --git a/BetterMultiview/ObsMultiview/StreamView.xaml.cs b/BetterMultiview/ObsMultiview/StreamView.xaml.cs index 49d79d3..75e4b21 100644 --- a/BetterMultiview/ObsMultiview/StreamView.xaml.cs +++ b/BetterMultiview/ObsMultiview/StreamView.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Numerics; @@ -290,5 +291,25 @@ public partial class StreamView : Window { private void ReplaceRunningConfig(UserProfile.DSceneViewConfig config) { _watcher.ReplaceProfile(config); } + + private void ConfigSets_OnClick(object sender, RoutedEventArgs e) { + if (_watcher?.ActiveProfile == null) return; + + var dlg = new SetEditor(); + dlg.Owner = this; + dlg.Sets = new ObservableCollection(_watcher.ActiveProfile.SceneView.Sets ?? new List()); + + if (dlg.ShowDialog() == true) { + _watcher.ActiveProfile.SceneView.Sets = dlg.Sets?.ToList(); + + foreach (var slot in _watcher.ActiveProfile.SceneView.Slots) { + if (slot.SetId != null && !dlg.Sets.Any(x=>x.Id == slot.SetId.Value)) { + slot.SetId = null; + } + } + + SceneCollectionChanged(_watcher.ActiveProfile); + } + } } } \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Strings/Dialogs.Designer.cs b/BetterMultiview/ObsMultiview/Strings/Dialogs.Designer.cs index ceba6e2..02ecc18 100644 --- a/BetterMultiview/ObsMultiview/Strings/Dialogs.Designer.cs +++ b/BetterMultiview/ObsMultiview/Strings/Dialogs.Designer.cs @@ -105,6 +105,15 @@ internal class Dialogs { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Set ähnelt. + /// + internal static string Group { + get { + return ResourceManager.GetString("Group", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Import ähnelt. /// @@ -123,6 +132,15 @@ internal class Dialogs { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die No Set ähnelt. + /// + internal static string NoSet { + get { + return ResourceManager.GetString("NoSet", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Ok ähnelt. /// @@ -204,6 +222,24 @@ internal class Dialogs { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Set Config ähnelt. + /// + internal static string SetConfig { + get { + return ResourceManager.GetString("SetConfig", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Edit Sets ähnelt. + /// + internal static string SetEditor { + get { + return ResourceManager.GetString("SetEditor", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Slot Config ähnelt. /// diff --git a/BetterMultiview/ObsMultiview/Strings/Dialogs.de-DE.resx b/BetterMultiview/ObsMultiview/Strings/Dialogs.de-DE.resx index a0ff3cd..c4bf141 100644 --- a/BetterMultiview/ObsMultiview/Strings/Dialogs.de-DE.resx +++ b/BetterMultiview/ObsMultiview/Strings/Dialogs.de-DE.resx @@ -168,4 +168,16 @@ Exportieren + + Gruppen Bearbeiten + + + Gruppe Konfigurieren + + + Gruppe + + + Keine Gruppe + \ No newline at end of file diff --git a/BetterMultiview/ObsMultiview/Strings/Dialogs.resx b/BetterMultiview/ObsMultiview/Strings/Dialogs.resx index c3b54b7..191fc01 100644 --- a/BetterMultiview/ObsMultiview/Strings/Dialogs.resx +++ b/BetterMultiview/ObsMultiview/Strings/Dialogs.resx @@ -148,4 +148,16 @@ Export + + Edit Sets + + + Set Config + + + Set + + + No Set + \ No newline at end of file diff --git a/BetterMultiview/Plugins/CommandFacade.cs b/BetterMultiview/Plugins/CommandFacade.cs new file mode 100644 index 0000000..06a0c92 --- /dev/null +++ b/BetterMultiview/Plugins/CommandFacade.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace ObsMultiview.Plugins { + /// + /// A facade to interface with the normal application + /// + public abstract class CommandFacade { + /// + /// Fired when the settings have changed + /// + public event Action SettingsChanged; + + /// + /// Fired when a slot config has changed + /// + public event Action SlotConfigChanged; + + /// + /// The logging instance for this plugin + /// + public ILogger Logger { get; protected set; } + + /// + /// Request settings + /// + /// The settings class + /// The subtype to fetch or null for the default settings + /// + public T RequestSettings(string subtype = null) { + var json = RequestSettings(subtype); + + if (json != null) { + return json.ToObject(); + } else { + return new JObject().ToObject(); + } + } + + /// + /// Save settings + /// + /// The settings class + /// The global settings + /// The subtype of the settings + public void WriteSettings(T settings, string subtype = null) { + var json = JObject.FromObject(settings); + WriteSettings(json, subtype); + OnSettingsChanged(subtype); + } + + /// + /// Request settings for a slot + /// + /// The settings class + /// The slot id + /// + public T RequestSlotSetting(Guid? slot) { + if (slot == null) return default(T); + + var json = RequestSlotSetting(slot.Value); + + if (json != null) { + return json.ToObject(); + } else { + return new JObject().ToObject(); + } + } + + /// + /// Request all slot settings with their IDs + /// + /// The settings class + /// + public IEnumerable<(Guid id, T config)> RequestSlotSettings() { + foreach (var item in RequestSlotSettings()) { + if (item.Item2 != null) { + yield return (item.Item1, item.Item2.ToObject()); + } + } + } + + /// + /// Save slot settings + /// + /// The settings class + /// The slot ID + /// The slot settings + public void WriteSlotSettings(Guid id, T config) { + WriteSlotSettings(id, JObject.FromObject(config)); + OnSlotConfigChanged(id); + } + + /// + /// Activate a scene in preview mode + /// + /// The scene ID + public abstract void ActivateScene(Guid scene); + + /// + /// Switch the currently live and preview scenes + /// + public abstract void SwitchLive(); + + /// + protected abstract JObject RequestSettings(string subtype = null); + + /// + protected abstract void WriteSettings(JObject settings, string subtype = null); + + /// + protected abstract JObject RequestSlotSetting(Guid slot); + + /// + protected abstract IEnumerable<(Guid id, JObject config)> RequestSlotSettings(); + + /// + protected abstract void WriteSlotSettings(Guid id, JObject config); + + private void OnSettingsChanged(string obj) { + SettingsChanged?.Invoke(obj); + } + + private void OnSlotConfigChanged(Guid obj) { + SlotConfigChanged?.Invoke(obj); + } + } +} \ No newline at end of file diff --git a/BetterMultiview/Plugins/KNX/KnxPlugin.cs b/BetterMultiview/Plugins/KNX/KnxPlugin.cs index d2d3225..45fb58e 100644 --- a/BetterMultiview/Plugins/KNX/KnxPlugin.cs +++ b/BetterMultiview/Plugins/KNX/KnxPlugin.cs @@ -8,7 +8,7 @@ namespace ObsMultiview.Plugins.KNX { /// /// A plugin to talk to a KNX/IP Interface /// - public class KnxPlugin : ChangePluginBase { + public class KnxPlugin : ChangePluginBase { public override string Name => "KNX"; public override string Author => "Nathanael Schneider"; @@ -65,21 +65,17 @@ public class KnxPlugin : ChangePluginBase { return new GlobalSettings(CommandFacade); } - public override SettingsControl GetSlotSettings(Guid slot) { + public override SettingsControl GetSlotSettings(Guid? slot) { return new SlotSettings(this, CommandFacade, slot); } - public override void OnSlotExit(Guid slot, Guid? next) { - var config = CommandFacade.RequestSlotSetting(slot); - + protected override void OnSlotExit(KnxSlotSettings config, KnxSlotSettings? next) { foreach (var group in config.Groups.Where(x => x.OnExit != null && x.OnExit.Length > 0)) { _knx.Action(group.Group.GroupAddress, group.OnExit); } } - public override void OnSlotEnter(Guid slot, Guid? previous) { - var config = CommandFacade.RequestSlotSetting(slot); - + protected override void OnSlotEnter(KnxSlotSettings config, KnxSlotSettings? previous) { foreach (var group in config.Groups.Where(x => x.OnEntry != null && x.OnEntry.Length > 0)) { _knx.Action(group.Group.GroupAddress, group.OnEntry); } diff --git a/BetterMultiview/Plugins/KNX/SlotSettings.xaml.cs b/BetterMultiview/Plugins/KNX/SlotSettings.xaml.cs index 64eb74a..7c9ac15 100644 --- a/BetterMultiview/Plugins/KNX/SlotSettings.xaml.cs +++ b/BetterMultiview/Plugins/KNX/SlotSettings.xaml.cs @@ -16,7 +16,7 @@ public partial class SlotSettings : SlotSettingsControl { set { SetValue(PluginProperty, value); } } - public SlotSettings(KnxPlugin plugin, CommandFacade commandFacade, Guid slotID) : base(commandFacade, slotID) { + public SlotSettings(KnxPlugin plugin, CommandFacade commandFacade, Guid? slotID) : base(commandFacade, slotID) { Plugin = plugin; InitializeComponent(); } diff --git a/BetterMultiview/Plugins/Keyboard/KeyboardPlugin.cs b/BetterMultiview/Plugins/Keyboard/KeyboardPlugin.cs index 0f70cbd..6cd50d8 100644 --- a/BetterMultiview/Plugins/Keyboard/KeyboardPlugin.cs +++ b/BetterMultiview/Plugins/Keyboard/KeyboardPlugin.cs @@ -131,7 +131,7 @@ public class KeyboardPlugin : TriggerPluginBase { return new GlobalSettings(CommandFacade); } - public override SettingsControl GetSlotSettings(Guid id) { + public override SettingsControl GetSlotSettings(Guid? id) { return new SlotSettings(CommandFacade, id); } } diff --git a/BetterMultiview/Plugins/Keyboard/SlotSettings.xaml.cs b/BetterMultiview/Plugins/Keyboard/SlotSettings.xaml.cs index 3cd4285..144bf4b 100644 --- a/BetterMultiview/Plugins/Keyboard/SlotSettings.xaml.cs +++ b/BetterMultiview/Plugins/Keyboard/SlotSettings.xaml.cs @@ -6,7 +6,7 @@ namespace ObsMultiview.Plugins.Keyboard { /// public partial class SlotSettings : SlotSettingsControl { - public SlotSettings(CommandFacade commandFacade, Guid slotID) : base(commandFacade, slotID) { + public SlotSettings(CommandFacade commandFacade, Guid? slotID) : base(commandFacade, slotID) { InitializeComponent(); Grabber.SetCommandFacade(commandFacade); } diff --git a/BetterMultiview/Plugins/ObsMultiview.Plugins.csproj b/BetterMultiview/Plugins/ObsMultiview.Plugins.csproj index 7aed0b2..3d5ecc0 100644 --- a/BetterMultiview/Plugins/ObsMultiview.Plugins.csproj +++ b/BetterMultiview/Plugins/ObsMultiview.Plugins.csproj @@ -3,6 +3,8 @@ net6.0-windows True + 2.0.0.0 + 2.0.0.0 diff --git a/BetterMultiview/Plugins/PelcoD/PelcoPlugin.cs b/BetterMultiview/Plugins/PelcoD/PelcoPlugin.cs index f79ed94..e0bc3ef 100644 --- a/BetterMultiview/Plugins/PelcoD/PelcoPlugin.cs +++ b/BetterMultiview/Plugins/PelcoD/PelcoPlugin.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace ObsMultiview.Plugins.PelcoD { - public class PelcoPlugin : StatePluginBase { + public class PelcoPlugin : StatePluginBase { public override string Name => "Pelco-D"; public override string Author => "Nathanael Schneider"; @@ -47,13 +47,11 @@ public class PelcoPlugin : StatePluginBase { return new GlobalSettings(CommandFacade); } - public override SettingsControl GetSlotSettings(Guid slot) { + public override SettingsControl GetSlotSettings(Guid? slot) { return new SlotSettings(CommandFacade, slot); } - public override void ActiveSlotChanged(Guid slot) { - var settings = CommandFacade.RequestSlotSetting(slot); - + protected override void ActiveSettingsChanged(PelcoSlotSettings settings) { if (settings.Presets != null && _port.IsOpen) { foreach (var preset in settings.Presets) { if (preset.CameraID > 0) { @@ -71,5 +69,9 @@ public class PelcoPlugin : StatePluginBase { } } } + + protected override void PrepareSettings(PelcoSlotSettings preview, PelcoSlotSettings live) { + + } } } \ No newline at end of file diff --git a/BetterMultiview/Plugins/PelcoD/SlotSettings.xaml.cs b/BetterMultiview/Plugins/PelcoD/SlotSettings.xaml.cs index 6eea4d5..6b780c4 100644 --- a/BetterMultiview/Plugins/PelcoD/SlotSettings.xaml.cs +++ b/BetterMultiview/Plugins/PelcoD/SlotSettings.xaml.cs @@ -32,7 +32,7 @@ public partial class SlotSettings : SlotSettingsControl { set { SetValue(PresetsProperty, value); } } - public SlotSettings(CommandFacade commandFacade, Guid slotID) : base(commandFacade, slotID) { + public SlotSettings(CommandFacade commandFacade, Guid? slotID) : base(commandFacade, slotID) { Presets = new List(); var settings = commandFacade.RequestSettings(); diff --git a/BetterMultiview/Plugins/PluginBase.cs b/BetterMultiview/Plugins/PluginBase.cs index 2593b01..b578756 100644 --- a/BetterMultiview/Plugins/PluginBase.cs +++ b/BetterMultiview/Plugins/PluginBase.cs @@ -6,6 +6,7 @@ using System.Windows.Controls; using JetBrains.Annotations; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace ObsMultiview.Plugins { @@ -27,10 +28,12 @@ public enum PluginTriggerType { /// Is not triggered by slots but instead triggers a slot /// Trigger, + /// /// Gets triggered when a slot is activated /// State, + /// /// Gets triggered when a slot gets activated or deactivated /// @@ -64,7 +67,7 @@ public abstract class SettingsControl : SettingsControl { /// The global settings of this plugin /// public T Settings { - get { return (T) GetValue(SettingsProperty); } + get { return (T)GetValue(SettingsProperty); } set { SetValue(SettingsProperty, value); } } @@ -97,7 +100,7 @@ public abstract class SlotSettingsControl : SettingsControl { nameof(Settings), typeof(T), typeof(SlotSettingsControl), new PropertyMetadata(default(T))); public T Settings { - get { return (T) GetValue(SettingsProperty); } + get { return (T)GetValue(SettingsProperty); } set { SetValue(SettingsProperty, value); } } @@ -112,9 +115,11 @@ public abstract class SlotSettingsControl : SettingsControl { /// ID is not persistent through restarts protected Guid SlotID { get; } - protected SlotSettingsControl(CommandFacade commandFacade, Guid slotID) { + protected SlotSettingsControl(CommandFacade commandFacade, Guid? slotID) { + if(slotID == null) throw new ArgumentNullException(nameof(slotID)); + CommandFacade = commandFacade; - SlotID = slotID; + SlotID = slotID.Value; } /// @@ -232,7 +237,7 @@ public abstract class PluginBase : INotifyPropertyChanged { /// /// The slot id /// - public abstract SettingsControl GetSlotSettings(Guid slot); + public abstract SettingsControl GetSlotSettings(Guid? slot); /// /// Set the command facade for this plugin. Can only be set once (during initialization) @@ -267,158 +272,68 @@ public abstract class TriggerPluginBase : PluginBase { public sealed override PluginTriggerType TriggerType => PluginTriggerType.Trigger; } - /// - /// Plugin base for State-Type plugins - /// public abstract class StatePluginBase : PluginBase { public sealed override PluginTriggerType TriggerType => PluginTriggerType.State; - /// - /// Gets triggered when the live-slot changes - /// - /// The name of the slot - public abstract void ActiveSlotChanged(Guid slot); - } + public abstract void ActiveSettingsChanged(JObject settings); - public abstract class ChangePluginBase : PluginBase { - public sealed override PluginTriggerType TriggerType => PluginTriggerType.Change; - - /// - /// Triggered when exiting a live slot - /// - /// - /// - public abstract void OnSlotExit(Guid slot, Guid? next); - - /// - /// Triggered when entering a new slot - /// - /// - /// - public abstract void OnSlotEnter(Guid slot, Guid? previous); + public abstract void PrepareSettings(JObject preview, JObject live); } /// - /// A facade to interface with the normal application + /// Plugin base for State-Type plugins /// - public abstract class CommandFacade { - /// - /// Fired when the settings have changed - /// - public event Action SettingsChanged; - - /// - /// Fired when a slot config has changed - /// - public event Action SlotConfigChanged; - - /// - /// The logging instance for this plugin - /// - public ILogger Logger { get; protected set; } - - /// - /// Request settings - /// - /// The settings class - /// The subtype to fetch or null for the default settings - /// - public T RequestSettings(string subtype = null) { - var json = RequestSettings(subtype); + public abstract class StatePluginBase : StatePluginBase where T : class { + public override void ActiveSettingsChanged(JObject settings) { + ActiveSettingsChanged(settings?.ToObject()); + } - if (json != null) { - return json.ToObject(); - } else { - return new JObject().ToObject(); - } + public override void PrepareSettings(JObject preview, JObject live) { + PrepareSettings(preview?.ToObject(), live?.ToObject()); } /// - /// Save settings + /// Gets triggered when the live-slot changes /// - /// The settings class - /// The global settings - /// The subtype of the settings - public void WriteSettings(T settings, string subtype = null) { - var json = JObject.FromObject(settings); - WriteSettings(json, subtype); - OnSettingsChanged(subtype); - } + /// The name of the slot + protected abstract void ActiveSettingsChanged(T slot); /// - /// Request settings for a slot + /// Gets triggered when the preview slot changes to prepare for transitions /// - /// The settings class - /// The slot id - /// - public T RequestSlotSetting(Guid? slot) { - if (slot == null) return default(T); + /// + /// + protected abstract void PrepareSettings(T preview, T live); + } - var json = RequestSlotSetting(slot.Value); + public abstract class ChangePluginBase : PluginBase { + public sealed override PluginTriggerType TriggerType => PluginTriggerType.Change; - if (json != null) { - return json.ToObject(); - } else { - return new JObject().ToObject(); - } - } + public abstract void OnSlotExit(JObject slot, JObject next); + public abstract void OnSlotEnter(JObject slot, JObject previous); + } - /// - /// Request all slot settings with their IDs - /// - /// The settings class - /// - public IEnumerable<(Guid id, T config)> RequestSlotSettings() { - foreach (var item in RequestSlotSettings()) { - if (item.Item2 != null) { - yield return (item.Item1, item.Item2.ToObject()); - } - } + public abstract class ChangePluginBase : ChangePluginBase where T : class { + public sealed override void OnSlotExit(JObject slot, JObject next) { + OnSlotExit(slot?.ToObject(), next?.ToObject()); } - /// - /// Save slot settings - /// - /// The settings class - /// The slot ID - /// The slot settings - public void WriteSlotSettings(Guid id, T config) { - WriteSlotSettings(id, JObject.FromObject(config)); - OnSlotConfigChanged(id); + public sealed override void OnSlotEnter(JObject slot, JObject previous) { + OnSlotEnter(slot?.ToObject(), previous?.ToObject()); } /// - /// Activate a scene in preview mode + /// Triggered when exiting a live slot /// - /// The scene ID - public abstract void ActivateScene(Guid scene); + /// + /// + protected abstract void OnSlotExit(T slot, T next); /// - /// Switch the currently live and preview scenes + /// Triggered when entering a new slot /// - public abstract void SwitchLive(); - - /// - protected abstract JObject RequestSettings(string subtype = null); - - /// - protected abstract void WriteSettings(JObject settings, string subtype = null); - - /// - protected abstract JObject RequestSlotSetting(Guid slot); - - /// - protected abstract IEnumerable<(Guid id, JObject config)> RequestSlotSettings(); - - /// - protected abstract void WriteSlotSettings(Guid id, JObject config); - - private void OnSettingsChanged(string obj) { - SettingsChanged?.Invoke(obj); - } - - private void OnSlotConfigChanged(Guid obj) { - SlotConfigChanged?.Invoke(obj); - } + /// + /// + protected abstract void OnSlotEnter(T slot, T previous); } } \ No newline at end of file diff --git a/BetterMultiview/Plugins/qlc/QlcPlugin.cs b/BetterMultiview/Plugins/qlc/QlcPlugin.cs index c9fb5f0..8adec9f 100644 --- a/BetterMultiview/Plugins/qlc/QlcPlugin.cs +++ b/BetterMultiview/Plugins/qlc/QlcPlugin.cs @@ -20,22 +20,17 @@ namespace ObsMultiview.Plugins.qlc { /// - Can't easily fetch button state, because it's a) async and b) the response has no identifier as to which control it belongs to /// - Can't easily fetch widget type, because a) there is no identifier for the control that is associated with the response and b) they are localized (wtf?) /// - public class QlcPlugin : ChangePluginBase { - public override void OnSlotExit(Guid slot, Guid? next) { - var settings = CommandFacade.RequestSlotSetting(slot); - var nextSettings = CommandFacade.RequestSlotSetting(next); - + public class QlcPlugin : ChangePluginBase { + protected override void OnSlotExit(QlcSlotSettings settings, QlcSlotSettings? next) { foreach (var fkt in settings.ExitFunctions) { // Only turn off functions that don't get triggered in the next scene - if (!(nextSettings?.EntryFunctions.Any(x => + if (!(next?.EntryFunctions.Any(x => x.Function.Type == fkt.Function.Type && x.Function.ID == fkt.Function.ID) ?? false)) SetFkt(fkt.Function, fkt.Value); } } - public override void OnSlotEnter(Guid slot, Guid? previous) { - var settings = CommandFacade.RequestSlotSetting(slot); - + protected override void OnSlotEnter(QlcSlotSettings settings, QlcSlotSettings? previous) { foreach (var fkt in settings.EntryFunctions) { SetFkt(fkt.Function, fkt.Value); } @@ -168,7 +163,7 @@ public class QlcPlugin : ChangePluginBase { return new GlobalSettings(CommandFacade); } - public override SettingsControl GetSlotSettings(Guid slot) { + public override SettingsControl GetSlotSettings(Guid? slot) { return new SlotSettings(this, CommandFacade, slot); } diff --git a/BetterMultiview/Plugins/qlc/SlotSettings.xaml.cs b/BetterMultiview/Plugins/qlc/SlotSettings.xaml.cs index 4ad4ef2..848366a 100644 --- a/BetterMultiview/Plugins/qlc/SlotSettings.xaml.cs +++ b/BetterMultiview/Plugins/qlc/SlotSettings.xaml.cs @@ -16,7 +16,7 @@ public partial class SlotSettings : SlotSettingsControl { set { SetValue(PluginProperty, value); } } - public SlotSettings(QlcPlugin plugin, CommandFacade commandFacade, Guid slotID) : base(commandFacade, slotID) { + public SlotSettings(QlcPlugin plugin, CommandFacade commandFacade, Guid? slotID) : base(commandFacade, slotID) { Plugin = plugin; plugin.FetchInfo(); InitializeComponent();