From ee77ecb9ffca2dc3f9eb6d5321921f2525265cd5 Mon Sep 17 00:00:00 2001 From: Antoine Aflalo Date: Tue, 24 Aug 2021 15:12:39 -0400 Subject: [PATCH] refactor(Notification::Menu): Implement property updated for the DataContainer of the menu item --- .../Audio/Lister/CachedAudioDeviceLister.cs | 8 +- SoundSwitch/Framework/Banner/BannerManager.cs | 2 +- .../UI/Forms/Components/AudioDeviceBox.cs | 32 ----- ...x.Designer.cs => IconMenuItem.Designer.cs} | 11 +- .../UI/Forms/Components/IconMenuItem.cs | 118 ++++++++++++++++++ ...{AudioDeviceBox.resx => IconMenuItem.resx} | 0 SoundSwitch/UI/Forms/DeviceSelectorMenu.cs | 63 ++++++++-- 7 files changed, 180 insertions(+), 54 deletions(-) delete mode 100644 SoundSwitch/UI/Forms/Components/AudioDeviceBox.cs rename SoundSwitch/UI/Forms/Components/{AudioDeviceBox.Designer.cs => IconMenuItem.Designer.cs} (89%) create mode 100644 SoundSwitch/UI/Forms/Components/IconMenuItem.cs rename SoundSwitch/UI/Forms/Components/{AudioDeviceBox.resx => IconMenuItem.resx} (100%) diff --git a/SoundSwitch/Framework/Audio/Lister/CachedAudioDeviceLister.cs b/SoundSwitch/Framework/Audio/Lister/CachedAudioDeviceLister.cs index 8537902eef..7c008f2e9d 100644 --- a/SoundSwitch/Framework/Audio/Lister/CachedAudioDeviceLister.cs +++ b/SoundSwitch/Framework/Audio/Lister/CachedAudioDeviceLister.cs @@ -28,10 +28,10 @@ namespace SoundSwitch.Framework.Audio.Lister public class CachedAudioDeviceLister : IAudioDeviceLister { /// - public DeviceReadOnlyCollection PlaybackDevices { get; private set; } = new(Enumerable.Empty()); + public DeviceReadOnlyCollection PlaybackDevices { get; private set; } = new(Enumerable.Empty(), DataFlow.Render); /// - public DeviceReadOnlyCollection RecordingDevices { get; private set; } = new(Enumerable.Empty()); + public DeviceReadOnlyCollection RecordingDevices { get; private set; } = new(Enumerable.Empty(), DataFlow.Capture); private readonly DeviceState _state; private readonly DebounceDispatcher _dispatcher = new(); @@ -87,8 +87,8 @@ public void Refresh() } } - PlaybackDevices = new DeviceReadOnlyCollection(playbackDevices.Values); - RecordingDevices = new DeviceReadOnlyCollection(recordingDevices.Values); + PlaybackDevices = new DeviceReadOnlyCollection(playbackDevices.Values, DataFlow.Render); + RecordingDevices = new DeviceReadOnlyCollection(recordingDevices.Values, DataFlow.Capture); Log.Information("[{@State}] Refreshed all devices. {@Recording}/rec, {@Playback}/play", _state, recordingDevices.Count, playbackDevices.Count); diff --git a/SoundSwitch/Framework/Banner/BannerManager.cs b/SoundSwitch/Framework/Banner/BannerManager.cs index 5ebad9d48c..8d3b189fdb 100644 --- a/SoundSwitch/Framework/Banner/BannerManager.cs +++ b/SoundSwitch/Framework/Banner/BannerManager.cs @@ -48,7 +48,7 @@ public void ShowNotification(BannerData data) menu.Disposed += (sender, args) => menu = null; } - menu.SetData(AppModel.Instance.AvailablePlaybackDevices.Select(info => new AudioDeviceBox.Data(info.LargeIcon.ToBitmap(), info.NameClean, defaultDevice.Id == info.Id))); + menu.SetData(AppModel.Instance.AvailablePlaybackDevices.Select(info => new IconMenuItem.DataContainer(info.LargeIcon, info.NameClean, defaultDevice.Id == info.Id, info.Id))); if (banner == null) { banner = new BannerForm(); diff --git a/SoundSwitch/UI/Forms/Components/AudioDeviceBox.cs b/SoundSwitch/UI/Forms/Components/AudioDeviceBox.cs deleted file mode 100644 index 0d1e2fac9a..0000000000 --- a/SoundSwitch/UI/Forms/Components/AudioDeviceBox.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Drawing; -using System.Windows.Forms; -using SoundSwitch.Util; - -namespace SoundSwitch.UI.Forms.Components -{ - public partial class AudioDeviceBox : UserControl - { - public Data CurrentData { get; } - - public record Data(Image Image, string Label, bool Selected) - { - public Color Color => Selected ? Color.RoyalBlue.WithOpacity(0x70) : Color.Black.WithOpacity(0x70); - } - - - public AudioDeviceBox(Data data) - { - CurrentData = data; - InitializeComponent(); - - base.CreateParams.ExStyle |= 0x20; - SetStyle(ControlStyles.SupportsTransparentBackColor, true); - iconBox.BackColor = Color.Transparent; - deviceName.BackColor = Color.Transparent; - - iconBox.DataBindings.Add(nameof(PictureBox.Image), CurrentData, nameof(CurrentData.Image), false, DataSourceUpdateMode.OnPropertyChanged); - deviceName.DataBindings.Add(nameof(Label.Text), CurrentData, nameof(CurrentData.Label), false, DataSourceUpdateMode.OnPropertyChanged); - DataBindings.Add(nameof(BackColor), CurrentData, nameof(CurrentData.Color), false, DataSourceUpdateMode.OnPropertyChanged); - } - } -} \ No newline at end of file diff --git a/SoundSwitch/UI/Forms/Components/AudioDeviceBox.Designer.cs b/SoundSwitch/UI/Forms/Components/IconMenuItem.Designer.cs similarity index 89% rename from SoundSwitch/UI/Forms/Components/AudioDeviceBox.Designer.cs rename to SoundSwitch/UI/Forms/Components/IconMenuItem.Designer.cs index 47ab34d8e7..6d84368792 100644 --- a/SoundSwitch/UI/Forms/Components/AudioDeviceBox.Designer.cs +++ b/SoundSwitch/UI/Forms/Components/IconMenuItem.Designer.cs @@ -1,7 +1,7 @@  namespace SoundSwitch.UI.Forms.Components { - partial class AudioDeviceBox + partial class IconMenuItem { /// /// Required designer variable. @@ -49,10 +49,11 @@ private void InitializeComponent() this.deviceName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Right))); this.deviceName.AutoSize = true; + this.deviceName.Font = new System.Drawing.Font("Segoe UI Semibold", 11.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); this.deviceName.ForeColor = System.Drawing.SystemColors.Control; - this.deviceName.Location = new System.Drawing.Point(64, 19); + this.deviceName.Location = new System.Drawing.Point(57, 17); this.deviceName.Name = "deviceName"; - this.deviceName.Size = new System.Drawing.Size(80, 15); + this.deviceName.Size = new System.Drawing.Size(106, 20); this.deviceName.TabIndex = 1; this.deviceName.Text = "Speaker (USB)"; // @@ -64,8 +65,8 @@ private void InitializeComponent() this.Controls.Add(this.deviceName); this.Controls.Add(this.iconBox); this.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); - this.Name = "AudioDeviceBox"; - this.Size = new System.Drawing.Size(278, 54); + this.Name = "IconMenuItem"; + this.Size = new System.Drawing.Size(295, 54); ((System.ComponentModel.ISupportInitialize)(this.iconBox)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); diff --git a/SoundSwitch/UI/Forms/Components/IconMenuItem.cs b/SoundSwitch/UI/Forms/Components/IconMenuItem.cs new file mode 100644 index 0000000000..afc97774a1 --- /dev/null +++ b/SoundSwitch/UI/Forms/Components/IconMenuItem.cs @@ -0,0 +1,118 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Windows.Forms; +using JetBrains.Annotations; +using SoundSwitch.Util; + +namespace SoundSwitch.UI.Forms.Components +{ + public partial class IconMenuItem : UserControl + { + public DataContainer CurrentDataContainer { get; } + + public class DataContainer : INotifyPropertyChanged + { + private bool _selected; + private Image _image; + private string _label; + private Icon _icon; + + public bool Selected + { + get => _selected; + set + { + _selected = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(Color)); + } + } + + public Image Image + { + get { return _image ??= _icon.ToBitmap(); } + } + + public string Label + { + get => _label; + set + { + _label = value; + OnPropertyChanged(); + } + } + + public Icon Icon + { + get => _icon; + set + { + _icon = value; + OnPropertyChanged(); + var oldImage = _image; + _image = null; + OnPropertyChanged(nameof(Image)); + oldImage?.Dispose(); + } + } + + public string Id { get; } + public Color Color => Selected ? Color.RoyalBlue.WithOpacity(0x80) : Color.Black.WithOpacity(0x70); + + public DataContainer(Icon icon, string label, bool selected, string id) + { + Selected = selected; + Icon = icon; + Label = label; + Id = id; + } + + /// + /// Override the metadata part + /// + /// + public void OverrideMetadata(DataContainer dataContainer) + { + if (dataContainer.Id != Id) + { + throw new ArgumentException("Need to have the same ID", nameof(dataContainer)); + } + + if (Selected != dataContainer.Selected) + Selected = dataContainer.Selected; + if (Icon != dataContainer.Icon) + Icon = dataContainer.Icon; + if (Label != dataContainer.Label) + Label = dataContainer.Label; + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + public IconMenuItem(DataContainer dataContainer) + { + CurrentDataContainer = dataContainer; + InitializeComponent(); + + base.CreateParams.ExStyle |= 0x20; + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + iconBox.BackColor = Color.Transparent; + deviceName.BackColor = Color.Transparent; + Name = CurrentDataContainer.Id; + + iconBox.DataBindings.Add(nameof(PictureBox.Image), CurrentDataContainer, nameof(CurrentDataContainer.Image), false, DataSourceUpdateMode.OnPropertyChanged); + deviceName.DataBindings.Add(nameof(Label.Text), CurrentDataContainer, nameof(CurrentDataContainer.Label), false, DataSourceUpdateMode.OnPropertyChanged); + DataBindings.Add(nameof(BackColor), CurrentDataContainer, nameof(CurrentDataContainer.Color), false, DataSourceUpdateMode.OnPropertyChanged); + } + } +} \ No newline at end of file diff --git a/SoundSwitch/UI/Forms/Components/AudioDeviceBox.resx b/SoundSwitch/UI/Forms/Components/IconMenuItem.resx similarity index 100% rename from SoundSwitch/UI/Forms/Components/AudioDeviceBox.resx rename to SoundSwitch/UI/Forms/Components/IconMenuItem.resx diff --git a/SoundSwitch/UI/Forms/DeviceSelectorMenu.cs b/SoundSwitch/UI/Forms/DeviceSelectorMenu.cs index 58d0456079..6e02acac50 100644 --- a/SoundSwitch/UI/Forms/DeviceSelectorMenu.cs +++ b/SoundSwitch/UI/Forms/DeviceSelectorMenu.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Windows.Forms; using SoundSwitch.UI.Forms.Components; @@ -6,6 +7,8 @@ namespace SoundSwitch.UI.Forms { public partial class DeviceSelectorMenu : Form { + private readonly Dictionary _currentPayloads = new(); + private readonly object _lock = new(); protected override bool ShowWithoutActivation => true; /// @@ -28,21 +31,57 @@ public DeviceSelectorMenu() InitializeComponent(); } - public void SetData(IEnumerable payloads) + public void SetData(IEnumerable payloads) { - Hide(); - SetLocationToCursor(); - Controls.Clear(); - Height = 0; - var top = 5; - foreach (var payload in payloads) + lock (_lock) { - var control = new AudioDeviceBox(payload); - control.Top = top; - Controls.Add(control); - top += control.Height; + SetLocationToCursor(); + var top = 5; + + var payloadsArray = payloads.ToArray(); + var newPayloads = payloadsArray.ToDictionary(container => container.Id); + var toRemove = _currentPayloads.Keys.Except(newPayloads.Keys); + var toAdd = newPayloads.Keys.Except(_currentPayloads.Keys); + var toModify = _currentPayloads.Keys.Intersect(newPayloads.Keys); + + var needRearrange = false; + + foreach (var id in toRemove) + { + Controls.RemoveByKey(id); + _currentPayloads.Remove(id); + needRearrange = true; + } + + foreach (var key in toModify) + { + _currentPayloads[key].OverrideMetadata(newPayloads[key]); + } + + + foreach (var key in toAdd) + { + var payload = newPayloads[key]; + var control = new IconMenuItem(payload); + Controls.Add(control); + _currentPayloads.Add(payload.Id, payload); + needRearrange = true; + } + + if (needRearrange) + { + foreach (var payload in payloadsArray) + { + var control = Controls[payload.Id]; + control.Top = top; + top += control.Height; + } + Height = 0; + } + + + Show(); } - Show(); } private void SetLocationToCursor()