Add port config, quick-switch toolbar, and dark mode styling
- Add Config button to each monitor for hiding ports, custom labels, and quick-switch selection - Add quick-switch toolbar at top of popup for one-click input switching - Add dark mode styling for all controls (buttons, combobox, dropdown items) - Add loading spinner animation - Add Exit button in header - Fix dropdown selection to correctly show current input 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using CMM.Library.Method;
|
||||
using CMM.Library.Config;
|
||||
using CMM.Library.Method;
|
||||
using CMM.Library.ViewModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -7,26 +8,32 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace DellMonitorControl;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private List<(XMonitor Monitor, List<InputSourceOption> Options)> _loadedMonitors = new();
|
||||
private Storyboard? _spinnerStoryboard;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_spinnerStoryboard = (Storyboard)FindResource("SpinnerAnimation");
|
||||
}
|
||||
|
||||
public void ShowNearTray()
|
||||
{
|
||||
var workArea = SystemParameters.WorkArea;
|
||||
Left = workArea.Right - Width - 10;
|
||||
// Use estimated height since ActualHeight is 0 before render
|
||||
Top = workArea.Bottom - 350;
|
||||
Show();
|
||||
Activate();
|
||||
|
||||
// Reposition after layout is complete
|
||||
// Start spinner
|
||||
_spinnerStoryboard?.Begin(this, true);
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
Top = workArea.Bottom - ActualHeight - 10;
|
||||
@@ -36,11 +43,18 @@ public partial class MainWindow : Window
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
Hide();
|
||||
_spinnerStoryboard?.Stop(this);
|
||||
}
|
||||
|
||||
private void ExitButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
|
||||
public async Task LoadMonitors()
|
||||
{
|
||||
var newChildren = new List<UIElement>();
|
||||
_loadedMonitors.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -68,21 +82,19 @@ public partial class MainWindow : Window
|
||||
var inputOptions = await CMMCommand.GetInputSourceOptions(m.SerialNumber);
|
||||
var powerStatus = await CMMCommand.GetMonPowerStatus(m.SerialNumber) ?? "Unknown";
|
||||
|
||||
// Monitor name header
|
||||
newChildren.Add(new TextBlock
|
||||
{
|
||||
Text = m.MonitorName,
|
||||
Foreground = Brushes.White,
|
||||
FontSize = 13,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Margin = new Thickness(0, 8, 0, 6)
|
||||
});
|
||||
_loadedMonitors.Add((m, inputOptions));
|
||||
|
||||
// Apply config to filter hidden ports and use custom labels
|
||||
var filteredOptions = MonitorConfigManager.ApplyConfigToOptions(m.SerialNumber, inputOptions);
|
||||
|
||||
// Monitor name header with Config button
|
||||
newChildren.Add(CreateMonitorHeader(m, inputOptions));
|
||||
|
||||
newChildren.Add(CreateSliderRow("Brightness", brightness, m.SerialNumber, CMMCommand.SetBrightness));
|
||||
newChildren.Add(CreateSliderRow("Contrast", contrast, m.SerialNumber, CMMCommand.SetContrast));
|
||||
|
||||
if (inputOptions.Count > 0)
|
||||
newChildren.Add(CreateInputRow(inputSource, inputOptions, m.SerialNumber));
|
||||
if (filteredOptions.Count > 0)
|
||||
newChildren.Add(CreateInputRow(inputSource, filteredOptions, m.SerialNumber));
|
||||
|
||||
newChildren.Add(CreatePowerRow(powerStatus, m.SerialNumber));
|
||||
|
||||
@@ -94,6 +106,9 @@ public partial class MainWindow : Window
|
||||
if (newChildren.Count > 0 && newChildren.Last() is Border)
|
||||
newChildren.RemoveAt(newChildren.Count - 1);
|
||||
}
|
||||
|
||||
// Load quick-switch toolbar
|
||||
LoadQuickSwitchToolbar();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -101,12 +116,14 @@ public partial class MainWindow : Window
|
||||
newChildren.Add(new TextBlock { Text = $"Error: {ex.Message}", Foreground = Brushes.OrangeRed, FontSize = 11, TextWrapping = TextWrapping.Wrap });
|
||||
}
|
||||
|
||||
// Stop spinner
|
||||
_spinnerStoryboard?.Stop(this);
|
||||
|
||||
sp.VerticalAlignment = VerticalAlignment.Top;
|
||||
sp.Children.Clear();
|
||||
foreach (var child in newChildren)
|
||||
sp.Children.Add(child);
|
||||
|
||||
// Reposition after content changes
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
var workArea = SystemParameters.WorkArea;
|
||||
@@ -114,6 +131,112 @@ public partial class MainWindow : Window
|
||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void LoadQuickSwitchToolbar()
|
||||
{
|
||||
quickSwitchPanel.Children.Clear();
|
||||
var quickItems = MonitorConfigManager.GetQuickSwitchItems();
|
||||
|
||||
if (quickItems.Count == 0)
|
||||
{
|
||||
quickSwitchPanel.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
quickSwitchPanel.Visibility = Visibility.Visible;
|
||||
|
||||
foreach (var item in quickItems)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Content = $"{item.PortLabel}",
|
||||
ToolTip = $"{item.MonitorName}: {item.PortLabel}",
|
||||
Margin = new Thickness(0, 0, 6, 6),
|
||||
Style = (Style)FindResource("QuickSwitchButton"),
|
||||
Tag = item
|
||||
};
|
||||
|
||||
btn.Click += async (s, e) =>
|
||||
{
|
||||
if (s is Button b && b.Tag is QuickSwitchItem qsi)
|
||||
{
|
||||
await CMMCommand.SetInputSource(qsi.MonitorSerialNumber, qsi.PortVcpValue);
|
||||
UpdateInputDropdown(qsi.MonitorSerialNumber, qsi.PortVcpValue);
|
||||
}
|
||||
};
|
||||
|
||||
quickSwitchPanel.Children.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInputDropdown(string serialNumber, int newValue)
|
||||
{
|
||||
foreach (var child in sp.Children)
|
||||
{
|
||||
if (child is StackPanel row)
|
||||
{
|
||||
foreach (var element in row.Children)
|
||||
{
|
||||
if (element is ComboBox cb && cb.Tag is string sn && sn == serialNumber)
|
||||
{
|
||||
var options = cb.ItemsSource as List<InputSourceOption>;
|
||||
if (options != null)
|
||||
{
|
||||
var index = options.FindIndex(o => o.Value == newValue);
|
||||
if (index >= 0)
|
||||
cb.SelectedIndex = index;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StackPanel CreateMonitorHeader(XMonitor monitor, List<InputSourceOption> allOptions)
|
||||
{
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 8, 0, 6) };
|
||||
|
||||
row.Children.Add(new TextBlock
|
||||
{
|
||||
Text = monitor.MonitorName,
|
||||
Foreground = Brushes.White,
|
||||
FontSize = 13,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
|
||||
var configBtn = new Button
|
||||
{
|
||||
Content = "Config",
|
||||
Margin = new Thickness(10, 0, 0, 0),
|
||||
FontSize = 11,
|
||||
Style = (Style)FindResource("DarkButton"),
|
||||
Tag = (monitor, allOptions)
|
||||
};
|
||||
|
||||
configBtn.Click += ConfigButton_Click;
|
||||
row.Children.Add(configBtn);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private async void ConfigButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button btn && btn.Tag is ValueTuple<XMonitor, List<InputSourceOption>> data)
|
||||
{
|
||||
var (monitor, options) = data;
|
||||
var configWindow = new ConfigWindow(monitor.SerialNumber, monitor.MonitorName, options);
|
||||
configWindow.Owner = this;
|
||||
configWindow.ShowDialog();
|
||||
|
||||
if (configWindow.ConfigChanged)
|
||||
{
|
||||
MonitorConfigManager.ClearCache();
|
||||
await LoadMonitors();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StackPanel CreateSliderRow(string label, int value, string serialNumber, Func<string, int, Task> setCommand)
|
||||
{
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
@@ -139,17 +262,34 @@ public partial class MainWindow : Window
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
row.Children.Add(new TextBlock { Text = "Input", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
|
||||
var combo = new ComboBox { Width = 170, ItemsSource = options, DisplayMemberPath = "Name", Tag = serialNumber };
|
||||
if (currentInput.HasValue)
|
||||
combo.SelectedItem = options.Find(o => o.Value == currentInput.Value);
|
||||
|
||||
combo.SelectionChanged += async (s, e) =>
|
||||
var combo = new ComboBox
|
||||
{
|
||||
if (s is ComboBox cb && cb.Tag is string sn && cb.SelectedItem is InputSourceOption opt)
|
||||
await CMMCommand.SetInputSource(sn, opt.Value);
|
||||
Width = 170,
|
||||
ItemsSource = options,
|
||||
DisplayMemberPath = "Name",
|
||||
Tag = serialNumber,
|
||||
Style = (Style)FindResource("DarkComboBox"),
|
||||
ItemContainerStyle = (Style)FindResource("DarkComboBoxItem")
|
||||
};
|
||||
|
||||
row.Children.Add(combo);
|
||||
|
||||
// Set selection AFTER adding to visual tree, BEFORE adding event handler
|
||||
if (currentInput.HasValue)
|
||||
{
|
||||
var index = options.FindIndex(o => o.Value == currentInput.Value);
|
||||
if (index >= 0)
|
||||
combo.SelectedIndex = index;
|
||||
}
|
||||
|
||||
// Add event handler AFTER setting the initial selection
|
||||
combo.SelectionChanged += async (s, e) =>
|
||||
{
|
||||
// Only trigger if user actually changed the selection (not initial load)
|
||||
if (s is ComboBox cb && cb.Tag is string sn && cb.SelectedItem is InputSourceOption opt && e.AddedItems.Count > 0 && e.RemovedItems.Count > 0)
|
||||
await CMMCommand.SetInputSource(sn, opt.Value);
|
||||
};
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -158,7 +298,13 @@ public partial class MainWindow : Window
|
||||
var row = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 3, 0, 3) };
|
||||
row.Children.Add(new TextBlock { Text = "Power", Foreground = Brushes.LightGray, Width = 70, FontSize = 12, VerticalAlignment = VerticalAlignment.Center });
|
||||
|
||||
var btn = new Button { Content = status, Width = 170, Tag = serialNumber };
|
||||
var btn = new Button
|
||||
{
|
||||
Content = status,
|
||||
Width = 170,
|
||||
Tag = serialNumber,
|
||||
Style = (Style)FindResource("DarkButton")
|
||||
};
|
||||
btn.Click += async (s, e) =>
|
||||
{
|
||||
if (s is Button b && b.Tag is string sn)
|
||||
|
||||
Reference in New Issue
Block a user