Skip to content

Commit 68aff4e

Browse files
committed
UI Refinements
1 parent 4b416d5 commit 68aff4e

17 files changed

Lines changed: 845 additions & 471 deletions

AutoMidiPlayer.WPF/App.xaml

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
<Setter Property="BorderThickness"
3434
Value="0"/>
3535
<Setter Property="Padding"
36-
Value="12"/>
36+
Value="8"/>
3737
<Setter Property="Margin"
38-
Value="0,8"/>
38+
Value="0,4"/>
3939
<Setter Property="Foreground"
4040
Value="{DynamicResource TextFillColorPrimaryBrush}"/>
4141
<Setter Property="Template">
@@ -74,6 +74,8 @@
7474
BasedOn="{StaticResource {x:Type ComboBox}}">
7575
<Setter Property="MinWidth"
7676
Value="200"/>
77+
<Setter Property="Cursor"
78+
Value="Hand"/>
7779
</Style>
7880
<Style TargetType="TextBox"
7981
BasedOn="{StaticResource {x:Type TextBox}}">
@@ -84,6 +86,8 @@
8486
BasedOn="{StaticResource {x:Type Button}}">
8587
<Setter Property="Background"
8688
Value="Transparent"/>
89+
<Setter Property="Cursor"
90+
Value="Hand"/>
8791
<Style.Triggers>
8892
<Trigger Property="IsHitTestVisible"
8993
Value="False">
@@ -95,6 +99,19 @@
9599
</Style.Resources>
96100
</Style>
97101

102+
<!-- Remove focus border visuals on window activation -->
103+
<Style TargetType="{x:Type Control}">
104+
<Setter Property="FocusVisualStyle"
105+
Value="{x:Null}"/>
106+
</Style>
107+
108+
<!-- Reserve space for scrollbars so content does not overlap -->
109+
<Style TargetType="ScrollViewer"
110+
BasedOn="{StaticResource {x:Type ScrollViewer}}">
111+
<Setter Property="Padding"
112+
Value="0,0,12,0"/>
113+
</Style>
114+
98115
<!-- Shared FontIcon style for MDL2 Assets icons -->
99116
<Style x:Key="MdlFontIcon"
100117
TargetType="ui:FontIcon">
@@ -107,6 +124,51 @@
107124
<!-- FontFamily resource for direct use -->
108125
<FontFamily x:Key="MdlFontFamily">Segoe MDL2 Assets</FontFamily>
109126

127+
<!-- Ghost icon button style: transparent bg, faded, opaque on hover, NO default WPF-UI chrome -->
128+
<Style x:Key="GhostIconButton"
129+
TargetType="Button">
130+
<Setter Property="Background"
131+
Value="Transparent"/>
132+
<Setter Property="BorderThickness"
133+
Value="0"/>
134+
<Setter Property="Padding"
135+
Value="6"/>
136+
<Setter Property="Cursor"
137+
Value="Hand"/>
138+
<Setter Property="Foreground"
139+
Value="{DynamicResource TextFillColorPrimaryBrush}"/>
140+
<Setter Property="Opacity"
141+
Value="0.4"/>
142+
<Setter Property="Template">
143+
<Setter.Value>
144+
<ControlTemplate TargetType="Button">
145+
<Border Background="Transparent"
146+
Padding="{TemplateBinding Padding}">
147+
<ContentPresenter HorizontalAlignment="Center"
148+
VerticalAlignment="Center"/>
149+
</Border>
150+
</ControlTemplate>
151+
</Setter.Value>
152+
</Setter>
153+
<Style.Triggers>
154+
<Trigger Property="IsMouseOver"
155+
Value="True">
156+
<Setter Property="Opacity"
157+
Value="1"/>
158+
</Trigger>
159+
<Trigger Property="IsHitTestVisible"
160+
Value="False">
161+
<Setter Property="Opacity"
162+
Value="0.2"/>
163+
</Trigger>
164+
<Trigger Property="IsEnabled"
165+
Value="False">
166+
<Setter Property="Opacity"
167+
Value="0.2"/>
168+
</Trigger>
169+
</Style.Triggers>
170+
</Style>
171+
110172
<!-- Player control button style with clean hover (no background, icon turns white) -->
111173
<Style x:Key="PlayerControlButton"
112174
TargetType="Button">
@@ -241,7 +303,7 @@
241303
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType=Button}}"
242304
Value="True">
243305
<Setter Property="Fill"
244-
Value="White"/>
306+
Value="{DynamicResource TextFillColorPrimaryBrush}"/>
245307
<DataTrigger.EnterActions>
246308
<BeginStoryboard>
247309
<Storyboard>
@@ -325,6 +387,8 @@
325387
Value="32"/>
326388
<Setter Property="Padding"
327389
Value="0"/>
390+
<Setter Property="Cursor"
391+
Value="Hand"/>
328392
<Setter Property="Background"
329393
Value="{DynamicResource TextFillColorPrimaryBrush}"/>
330394
<Setter Property="Foreground"
@@ -435,7 +499,7 @@
435499
<ControlTemplate TargetType="RepeatButton">
436500
<Border x:Name="FilledBorder"
437501
Height="4"
438-
Background="White"
502+
Background="{DynamicResource TextFillColorPrimaryBrush}"
439503
CornerRadius="2"/>
440504
</ControlTemplate>
441505
</Setter.Value>
@@ -455,7 +519,7 @@
455519
<Setter.Value>
456520
<ControlTemplate TargetType="RepeatButton">
457521
<Border Height="4"
458-
Background="#4DFFFFFF"
522+
Background="{DynamicResource ControlFillColorTertiaryBrush}"
459523
CornerRadius="0,2,2,0"/>
460524
</ControlTemplate>
461525
</Setter.Value>
@@ -475,6 +539,8 @@
475539
Value="8,0"/>
476540
<Setter Property="MinHeight"
477541
Value="20"/>
542+
<Setter Property="Cursor"
543+
Value="Hand"/>
478544
<Setter Property="Template">
479545
<Setter.Value>
480546
<ControlTemplate TargetType="Slider">
@@ -514,7 +580,7 @@
514580
<Grid Background="Transparent">
515581
<Ellipse Width="12"
516582
Height="12"
517-
Fill="White"/>
583+
Fill="{DynamicResource TextFillColorPrimaryBrush}"/>
518584
</Grid>
519585
</ControlTemplate>
520586
</Thumb.Template>

AutoMidiPlayer.WPF/AutoMidiPlayer.WPF.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<UseWPF>true</UseWPF>
77
<StartupObject>AutoMidiPlayer.WPF.App</StartupObject>
88
<ApplicationManifest>app.manifest</ApplicationManifest>
9-
<Version>6.7.4</Version>
9+
<Version>6.7.5</Version>
1010
<ApplicationIcon>logo.ico</ApplicationIcon>
1111
<Nullable>enable</Nullable>
1212
<RepositoryUrl>https://github.com/Jed556/AutoMidiPlayer</RepositoryUrl>

AutoMidiPlayer.WPF/Bootstrapper.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Windows.Storage.Streams;
1414
using AutoMidiPlayer.Data;
1515
using AutoMidiPlayer.Data.Properties;
16+
using AutoMidiPlayer.WPF.Errors;
1617
using AutoMidiPlayer.WPF.ViewModels;
1718
using Microsoft.EntityFrameworkCore;
1819
using Stylet;
@@ -53,17 +54,19 @@ private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledEx
5354
CrashLogger.Log("=== DISPATCHER UNHANDLED EXCEPTION ===");
5455
CrashLogger.LogException(e.Exception);
5556

56-
// Show modern WPF-UI message box with log path
57-
var messageBox = new Wpf.Ui.Controls.MessageBox
57+
try
5858
{
59-
Title = "AutoMidiPlayer Error",
60-
Content = $"An error occurred. Log saved to:\n{CrashLogger.GetLogPath()}\n\nError: {e.Exception.Message}",
61-
CloseButtonText = "OK",
62-
CloseButtonAppearance = ControlAppearance.Primary
63-
};
64-
65-
// Run the async dialog synchronously to block and show the error
66-
_ = messageBox.ShowDialogAsync();
59+
CrashMessageBox.Show(e.Exception, CrashLogger.GetLogPath());
60+
}
61+
catch
62+
{
63+
// Fallback if the themed dialog itself fails
64+
System.Windows.MessageBox.Show(
65+
$"An error occurred. Log saved to:\n{CrashLogger.GetLogPath()}\n\nError: {e.Exception.Message}",
66+
"AutoMidiPlayer Error",
67+
System.Windows.MessageBoxButton.OK,
68+
MessageBoxImage.Error);
69+
}
6770
}
6871

6972
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)

AutoMidiPlayer.WPF/Controls/HotkeyEditControl/HotkeyEditControl.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Windows;
34
using System.Windows.Controls;
45
using System.Windows.Input;
@@ -26,6 +27,18 @@ public partial class HotkeyEditControl : UserControl
2627
typeof(HotkeyEditControl),
2728
new PropertyMetadata(true));
2829

30+
public static readonly DependencyProperty EditingPartsProperty = DependencyProperty.Register(
31+
nameof(EditingParts),
32+
typeof(List<HotkeyPart>),
33+
typeof(HotkeyEditControl),
34+
new PropertyMetadata(new List<HotkeyPart>()));
35+
36+
public static readonly DependencyProperty IsGlowActiveProperty = DependencyProperty.Register(
37+
nameof(IsGlowActive),
38+
typeof(bool),
39+
typeof(HotkeyEditControl),
40+
new PropertyMetadata(false));
41+
2942
public HotkeyBinding? HotkeyBinding
3043
{
3144
get => (HotkeyBinding?)GetValue(HotkeyBindingProperty);
@@ -44,17 +57,38 @@ public bool IsNotEditing
4457
set => SetValue(IsNotEditingProperty, value);
4558
}
4659

60+
public List<HotkeyPart> EditingParts
61+
{
62+
get => (List<HotkeyPart>)GetValue(EditingPartsProperty);
63+
set => SetValue(EditingPartsProperty, value);
64+
}
65+
66+
public bool IsGlowActive
67+
{
68+
get => (bool)GetValue(IsGlowActiveProperty);
69+
set => SetValue(IsGlowActiveProperty, value);
70+
}
71+
4772
public event EventHandler<HotkeyChangedEventArgs>? HotkeyChanged;
4873
public event EventHandler<string>? HotkeyCleared;
4974
public event EventHandler? EditStarted;
5075
public event EventHandler? EditEnded;
5176

5277
private Key _pendingKey = Key.None;
5378
private ModifierKeys _pendingModifiers = ModifierKeys.None;
79+
private readonly System.Windows.Threading.DispatcherTimer _glowTimer = new()
80+
{
81+
Interval = TimeSpan.FromMilliseconds(500)
82+
};
5483

5584
public HotkeyEditControl()
5685
{
5786
InitializeComponent();
87+
_glowTimer.Tick += (_, _) =>
88+
{
89+
_glowTimer.Stop();
90+
IsGlowActive = false;
91+
};
5892
}
5993

6094
private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
@@ -79,6 +113,8 @@ private void EditButton_Click(object sender, RoutedEventArgs e)
79113
{
80114
_pendingKey = Key.None;
81115
_pendingModifiers = ModifierKeys.None;
116+
EditingParts = HotkeyBinding?.DisplayParts ?? new List<HotkeyPart> { new("Not Set", true) };
117+
IsGlowActive = false;
82118
EditStarted?.Invoke(this, EventArgs.Empty);
83119
IsEditing = true;
84120
}
@@ -117,6 +153,7 @@ private void EditBorder_PreviewKeyDown(object sender, KeyEventArgs e)
117153
key == Key.LeftShift || key == Key.RightShift ||
118154
key == Key.LWin || key == Key.RWin)
119155
{
156+
EditingParts = BuildEditingParts(Keyboard.Modifiers, Key.None);
120157
return;
121158
}
122159

@@ -133,12 +170,15 @@ private void EditBorder_PreviewKeyDown(object sender, KeyEventArgs e)
133170
_pendingKey = key;
134171
_pendingModifiers = modifiers;
135172

173+
EditingParts = BuildEditingParts(modifiers, key);
174+
136175
// Apply the hotkey
137176
if (HotkeyBinding != null)
138177
{
139178
HotkeyChanged?.Invoke(this, new HotkeyChangedEventArgs(HotkeyBinding.Name, key, modifiers));
140179
}
141180

181+
TriggerGlow();
142182
IsEditing = false;
143183
EditEnded?.Invoke(this, EventArgs.Empty);
144184
}
@@ -153,6 +193,36 @@ private static bool IsFunctionKey(Key key)
153193
{
154194
return key >= Key.F1 && key <= Key.F24;
155195
}
196+
197+
private static List<HotkeyPart> BuildEditingParts(ModifierKeys modifiers, Key key)
198+
{
199+
var parts = new List<HotkeyPart>();
200+
201+
if (modifiers.HasFlag(ModifierKeys.Control)) parts.Add(new HotkeyPart("Ctrl", parts.Count == 0));
202+
if (modifiers.HasFlag(ModifierKeys.Alt)) parts.Add(new HotkeyPart("Alt", parts.Count == 0));
203+
if (modifiers.HasFlag(ModifierKeys.Shift)) parts.Add(new HotkeyPart("Shift", parts.Count == 0));
204+
if (modifiers.HasFlag(ModifierKeys.Windows)) parts.Add(new HotkeyPart("Win", parts.Count == 0));
205+
206+
if (key != Key.None)
207+
{
208+
var text = HotkeyBinding.GetKeyDisplayName(key);
209+
parts.Add(new HotkeyPart(text, parts.Count == 0));
210+
}
211+
212+
if (parts.Count == 0)
213+
{
214+
parts.Add(new HotkeyPart("Not Set", true));
215+
}
216+
217+
return parts;
218+
}
219+
220+
private void TriggerGlow()
221+
{
222+
IsGlowActive = true;
223+
_glowTimer.Stop();
224+
_glowTimer.Start();
225+
}
156226
}
157227

158228
public class HotkeyChangedEventArgs : EventArgs

0 commit comments

Comments
 (0)