Refactored menu window fading

Improve TaskbarLogo (still flickers on load but only very shortly)
This commit is contained in:
Peter Kirmeier 2022-12-04 01:24:30 +01:00
parent 813912d08c
commit 81e20d618b
5 changed files with 135 additions and 263 deletions

View file

@ -386,6 +386,16 @@ namespace SystemTrayMenu.Business
return menuData; return menuData;
} }
internal static void OpenFolder(string? path = null)
{
if (string.IsNullOrEmpty(path))
{
path = Config.Path;
}
Log.ProcessStart(path);
}
internal void SwitchOpenCloseByTaskbarItem() internal void SwitchOpenCloseByTaskbarItem()
{ {
SwitchOpenClose(true); SwitchOpenClose(true);
@ -476,16 +486,6 @@ namespace SystemTrayMenu.Business
} }
} }
internal static void OpenFolder(string? path = null)
{
if (string.IsNullOrEmpty(path))
{
path = Config.Path;
}
Log.ProcessStart(path);
}
private static void LoadMenu(object senderDoWork, DoWorkEventArgs eDoWork) private static void LoadMenu(object senderDoWork, DoWorkEventArgs eDoWork)
{ {
string path; string path;
@ -1000,21 +1000,24 @@ namespace SystemTrayMenu.Business
if (menuPrevious != null) if (menuPrevious != null)
{ {
// Clean up menu status IsMenuOpen for previous one // Clean up menu status IsMenuOpen for previous one
ListView dgvPrevious = menuPrevious.GetDataGridView(); ListView? dgvPrevious = menuPrevious.GetDataGridView();
foreach (ListViewItemData item in dgvPrevious.Items) if (dgvPrevious != null)
{ {
RowData rowDataToClear = item.data; foreach (ListViewItemData item in dgvPrevious.Items)
if (rowDataToClear == (RowData)menuToShow.Tag)
{ {
rowDataToClear.IsMenuOpen = keepOrSetIsMenuOpen; RowData rowDataToClear = item.data;
if (rowDataToClear == (RowData)menuToShow.Tag)
{
rowDataToClear.IsMenuOpen = keepOrSetIsMenuOpen;
}
else
{
rowDataToClear.IsMenuOpen = false;
}
} }
else
{
rowDataToClear.IsMenuOpen = false;
}
}
RefreshSelection(dgvPrevious); RefreshSelection(dgvPrevious);
}
// Hide old menu // Hide old menu
foreach (Menu menuToClose in menus.Where( foreach (Menu menuToClose in menus.Where(

View file

@ -1,157 +0,0 @@
// <copyright file="Fading.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.Helper
{
using System;
using System.Windows.Threading;
public class Fading
{
private const int Interval100FPS = 10; // 100fps=>1s/100fps=~10ms
private const double StepIn = 0.20;
private const double StepOut = 0.10;
private const double Transparent = 0.80;
private const double TransparentMinus = 0.60; // Transparent - StepIn
private const double TransparentPlus = 0.85; // Transparent + StepOut
private const double Shown = 1.00;
private const double ShownMinus = 0.80; // Shown - StepIn
private readonly DispatcherTimer timer = new(DispatcherPriority.Render);
private FadingState state = FadingState.Idle;
private double opacity;
private bool visible;
internal Fading()
{
timer.Interval = TimeSpan.FromMilliseconds(Interval100FPS);
timer.Tick += (sender, e) => FadeStep();
}
internal event Action Hide;
internal event Action Show;
internal event EventHandler<double> ChangeOpacity;
internal enum FadingState
{
Idle,
Show,
ShowTransparent,
Hide,
}
internal bool IsHiding => state == FadingState.Hide;
internal void Fade(FadingState state)
{
StartStopTimer(state);
}
private void StartStopTimer(FadingState newState)
{
if (newState == FadingState.Idle)
{
state = newState;
timer.Stop();
}
else
{
state = newState;
timer.Start();
}
}
private void FadeStep()
{
switch (state)
{
case FadingState.Show:
if (!visible)
{
visible = true;
Show?.Invoke();
opacity = 0;
ChangeOpacity?.Invoke(this, opacity);
}
else if (Properties.Settings.Default.UseFading &&
opacity < ShownMinus)
{
opacity += StepIn;
ChangeOpacity?.Invoke(this, opacity);
}
else
{
if (!Properties.Settings.Default.UseFading)
{
// #393 provoke a redraw for the CS_DROPSHADOW to work
opacity = ShownMinus;
ChangeOpacity?.Invoke(this, opacity);
}
opacity = Shown;
ChangeOpacity?.Invoke(this, opacity);
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.ShowTransparent:
if (!visible)
{
visible = true;
Show?.Invoke();
opacity = 0;
ChangeOpacity?.Invoke(this, opacity);
}
else if (Properties.Settings.Default.UseFading &&
opacity < TransparentMinus)
{
opacity += StepIn;
ChangeOpacity?.Invoke(this, opacity);
}
else if (Properties.Settings.Default.UseFading &&
opacity > TransparentPlus)
{
opacity -= StepOut;
ChangeOpacity?.Invoke(this, opacity);
}
else
{
opacity = Transparent;
ChangeOpacity?.Invoke(this, opacity);
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.Hide:
if (Properties.Settings.Default.UseFading &&
opacity > StepOut)
{
opacity -= StepOut;
ChangeOpacity?.Invoke(this, opacity);
}
else if (visible)
{
opacity = 0;
ChangeOpacity?.Invoke(this, opacity);
visible = false;
Hide?.Invoke();
StartStopTimer(FadingState.Idle);
}
else
{
StartStopTimer(FadingState.Idle);
}
break;
case FadingState.Idle:
default:
StartStopTimer(FadingState.Idle);
break;
}
}
}
}

View file

@ -16,13 +16,23 @@
</Window.Effect> </Window.Effect>
<Window.Triggers> <Window.Triggers>
<EventTrigger RoutedEvent="Loaded"> <EventTrigger RoutedEvent="local:Menu.FadeIn">
<BeginStoryboard> <BeginStoryboard>
<Storyboard> <Storyboard>
<DoubleAnimation <DoubleAnimation
Storyboard.TargetProperty="Opacity" Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1" /> From="0.0" To="1.0" Duration="0:0:0.5"
<!-- Completed="fadeCompleted" --> Completed="FadeIn_Completed"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="local:Menu.FadeOut">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:0.25"
Completed="FadeOut_Completed"/>
</Storyboard> </Storyboard>
</BeginStoryboard> </BeginStoryboard>
</EventTrigger> </EventTrigger>

View file

@ -26,13 +26,18 @@ namespace SystemTrayMenu.UserInterface
/// </summary> /// </summary>
public partial class Menu : Window public partial class Menu : Window
{ {
private const int CornerRadius = 10;
private static readonly RoutedEvent FadeInEvent = EventManager.RegisterRoutedEvent(
nameof(FadeIn), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
private static readonly RoutedEvent FadeOutEvent = EventManager.RegisterRoutedEvent(
nameof(FadeOut), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
#if TODO // SEARCH #if TODO // SEARCH
public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'"; public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'";
#endif #endif
private const int CornerRadius = 10; private bool isFading;
private readonly Fading fading = new();
private bool isShowing;
private bool directionToRight; private bool directionToRight;
private bool mouseDown; private bool mouseDown;
private Point lastLocation; private Point lastLocation;
@ -48,56 +53,10 @@ namespace SystemTrayMenu.UserInterface
timerUpdateIcons.Tick += TimerUpdateIcons_Tick; timerUpdateIcons.Tick += TimerUpdateIcons_Tick;
Closed += (_, _) => Closed += (_, _) =>
{ {
fading.Fade(Fading.FadingState.Idle);
timerUpdateIcons.Stop(); timerUpdateIcons.Stop();
isClosed = true; // TODO WPF Replace Forms wrapper isClosed = true; // TODO WPF Replace Forms wrapper
}; };
Opacity = 0D;
fading.ChangeOpacity += Fading_ChangeOpacity;
void Fading_ChangeOpacity(object? sender, double newOpacity)
{
if (newOpacity != Opacity && !IsDisposed && !Disposing)
{
Opacity = newOpacity;
}
}
fading.Show += Fading_Show;
void Fading_Show()
{
try
{
isShowing = true;
Visibility = Visibility.Visible;
isShowing = false;
timerUpdateIcons.Start();
}
catch (ObjectDisposedException)
{
Visibility = Visibility.Hidden;
isShowing = false;
Log.Info($"Could not open menu, old menu was disposing," +
$" IsDisposed={IsDisposed}");
}
if (Visibility == Visibility.Visible)
{
if (Level == 0)
{
Activate();
Show();
}
else
{
ShowActivated = false;
Show();
}
}
}
fading.Hide += Hide;
InitializeComponent(); InitializeComponent();
Level = level; Level = level;
@ -220,6 +179,8 @@ namespace SystemTrayMenu.UserInterface
Loaded += (sender, e) => Loaded += (sender, e) =>
{ {
NativeMethods.HideFromAltTab(this); NativeMethods.HideFromAltTab(this);
RaiseEvent(new(routedEvent: FadeInEvent));
}; };
Closed += (sender, e) => Closed += (sender, e) =>
@ -263,6 +224,18 @@ namespace SystemTrayMenu.UserInterface
internal event Action<ListView, int, MouseButtonEventArgs>? CellMouseClick; internal event Action<ListView, int, MouseButtonEventArgs>? CellMouseClick;
private event RoutedEventHandler FadeIn
{
add { AddHandler(FadeInEvent, value); }
remove { RemoveHandler(FadeInEvent, value); }
}
private event RoutedEventHandler FadeOut
{
add { AddHandler(FadeOutEvent, value); }
remove { RemoveHandler(FadeOutEvent, value); }
}
internal enum StartLocation internal enum StartLocation
{ {
Predecessor, Predecessor,
@ -283,13 +256,14 @@ namespace SystemTrayMenu.UserInterface
internal string? FolderPath { get; set; } internal string? FolderPath { get; set; }
internal bool IsUsable => Visibility == Visibility.Visible && !fading.IsHiding && !IsDisposed && !Disposing; internal bool IsUsable => Visibility == Visibility.Visible && !isFading && !IsDisposed && !Disposing;
#if TODO // TOUCH #if TODO // TOUCH
internal bool ScrollbarVisible { get; private set; } internal bool ScrollbarVisible { get; private set; }
private ListView tableLayoutPanelDgvAndScrollbar => dgv; // TODO WPF Remove and replace with dgv private ListView tableLayoutPanelDgvAndScrollbar => dgv; // TODO WPF Remove and replace with dgv
#endif #endif
internal void ResetSearchText() internal void ResetSearchText()
{ {
textBoxSearch.Text = string.Empty; textBoxSearch.Text = string.Empty;
@ -450,21 +424,58 @@ namespace SystemTrayMenu.UserInterface
} }
} }
internal void ShowWithFade() internal void ShowWithFade() => Fading_Show(false);
{
fading.Fade(Fading.FadingState.Show);
}
internal void ShowTransparent() internal void ShowTransparent() => Fading_Show(true);
internal void Fading_Show(bool transparency)
{ {
fading.Fade(Fading.FadingState.ShowTransparent); timerUpdateIcons.Start();
if (Level == 0)
{
Activate();
}
else
{
ShowActivated = false;
}
Opacity = 0D;
Show();
if (Settings.Default.UseFading)
{
isFading = true;
if (transparency)
{
// TODO: FADING: Instead setting of opacity 100% only go up to 80% (Temporarily go to 100% as well)
RaiseEvent(new(routedEvent: FadeInEvent));
}
else
{
RaiseEvent(new(routedEvent: FadeInEvent));
}
}
else
{
Opacity = transparency ? 0.80D : 1D;
FadeIn_Completed(this, new());
}
} }
internal void HideWithFade() internal void HideWithFade()
{ {
if (!isShowing) if (Settings.Default.UseFading)
{ {
fading.Fade(Fading.FadingState.Hide); isFading = true;
// TODO: FADING: Instead starting at opacity 100% it should start with 80% due to transparency setting
RaiseEvent(new(routedEvent: FadeOutEvent));
}
else
{
FadeOut_Completed(this, new());
} }
} }
@ -645,7 +656,7 @@ namespace SystemTrayMenu.UserInterface
case StartLocation.Predecessor: case StartLocation.Predecessor:
RowData trigger = (RowData)Tag; RowData trigger = (RowData)Tag;
ListView dgv = menuPredecessor!.GetDataGridView()!; ListView dgv = menuPredecessor!.GetDataGridView() !;
// Set position on same height as the selected row from predecessor // Set position on same height as the selected row from predecessor
y = menuPredecessor.Location.Y; y = menuPredecessor.Location.Y;
@ -758,7 +769,7 @@ namespace SystemTrayMenu.UserInterface
UpdateLayout(); UpdateLayout();
SizeToContent = SizeToContent.Manual; SizeToContent = SizeToContent.Manual;
#if TODO // SEARCH #if TODO // SEARCH
dgvHeightSet = false; dgvHeightSet = false;
#endif #endif
} }
} }
@ -770,9 +781,20 @@ namespace SystemTrayMenu.UserInterface
labelStatus.Content = $"{filesAndFoldersCount} {Translator.GetText(elements)}"; labelStatus.Content = $"{filesAndFoldersCount} {Translator.GetText(elements)}";
} }
private void FadeIn_Completed(object sender, EventArgs e)
{
isFading = false;
}
private void FadeOut_Completed(object sender, EventArgs e)
{
isFading = false;
Hide();
}
private void HandlePreviewKeyDown(object sender, KeyEventArgs e) private void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{ {
searchPanel.Visibility= Visibility.Visible; searchPanel.Visibility = Visibility.Visible;
ModifierKeys modifiers = Keyboard.Modifiers; ModifierKeys modifiers = Keyboard.Modifiers;
switch (e.Key) switch (e.Key)
@ -881,6 +903,7 @@ namespace SystemTrayMenu.UserInterface
} }
double factorIconSizeInPercent = Properties.Settings.Default.IconSizeInPercent / 100f; double factorIconSizeInPercent = Properties.Settings.Default.IconSizeInPercent / 100f;
// IcoWidth 100% = 21px, 175% is 33, +3+2 is padding from ColumnIcon // IcoWidth 100% = 21px, 175% is 33, +3+2 is padding from ColumnIcon
double icoWidth = (16 * Scaling.FactorByDpi) + 5; double icoWidth = (16 * Scaling.FactorByDpi) + 5;
Resources["ColumnIconWidth"] = (double)(int)((icoWidth * factorIconSizeInPercent * Scaling.Factor) + 0.5); Resources["ColumnIconWidth"] = (double)(int)((icoWidth * factorIconSizeInPercent * Scaling.Factor) + 0.5);

View file

@ -12,15 +12,12 @@ namespace SystemTrayMenu.UserInterface
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Threading;
/// <summary> /// <summary>
/// Logic of Taskbar window. /// Logic of Taskbar window.
/// </summary> /// </summary>
public partial class TaskbarLogo : Window public partial class TaskbarLogo : Window
{ {
private DispatcherTimer? moveOutOfScreenTimer = null;
public TaskbarLogo() public TaskbarLogo()
{ {
InitializeComponent(); InitializeComponent();
@ -49,32 +46,28 @@ namespace SystemTrayMenu.UserInterface
Title = myname; Title = myname;
Closed += (_, _) => Application.Current.Shutdown(); Closed += (_, _) => Application.Current.Shutdown();
Deactivated += (_, _) => SetStateNormal(); Deactivated += SetStateNormal;
Activated += (_, _) => Activated += (object? sender, EventArgs e) =>
{ {
SetStateNormal(); SetStateNormal(sender, e);
Activate(); Activate();
UpdateLayout(); UpdateLayout();
Focus(); Focus();
moveOutOfScreenTimer = new DispatcherTimer(
TimeSpan.FromMilliseconds(500),
DispatcherPriority.Loaded,
(s, e) =>
{
// Do this after loading because Top may be invalid at the beginning
// and when initial rendering is out of screen it will never be actually painted.
// This makes sure logo is rendered once and then window is moved.
Top += SystemParameters.VirtualScreenHeight;
((DispatcherTimer)s!).IsEnabled = false; // only once
},
Dispatcher.CurrentDispatcher);
}; };
ContentRendered += MoveOutOfScreen;
}
private void MoveOutOfScreen(object? sender, EventArgs e)
{
// Do this only once
ContentRendered -= MoveOutOfScreen;
Top += SystemParameters.VirtualScreenHeight;
} }
/// <summary> /// <summary>
/// This ensures that next click on taskbaritem works as activate event/click event. /// This ensures that next click on taskbaritem works as activate event/click event.
/// </summary> /// </summary>
private void SetStateNormal() private void SetStateNormal(object? sender, EventArgs e)
{ {
if (IsActive) if (IsActive)
{ {