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;
}
internal static void OpenFolder(string? path = null)
{
if (string.IsNullOrEmpty(path))
{
path = Config.Path;
}
Log.ProcessStart(path);
}
internal void SwitchOpenCloseByTaskbarItem()
{
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)
{
string path;
@ -1000,21 +1000,24 @@ namespace SystemTrayMenu.Business
if (menuPrevious != null)
{
// Clean up menu status IsMenuOpen for previous one
ListView dgvPrevious = menuPrevious.GetDataGridView();
foreach (ListViewItemData item in dgvPrevious.Items)
ListView? dgvPrevious = menuPrevious.GetDataGridView();
if (dgvPrevious != null)
{
RowData rowDataToClear = item.data;
if (rowDataToClear == (RowData)menuToShow.Tag)
foreach (ListViewItemData item in dgvPrevious.Items)
{
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
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.Triggers>
<EventTrigger RoutedEvent="Loaded">
<EventTrigger RoutedEvent="local:Menu.FadeIn">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1" />
<!-- Completed="fadeCompleted" -->
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:0.5"
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>
</BeginStoryboard>
</EventTrigger>

View file

@ -26,13 +26,18 @@ namespace SystemTrayMenu.UserInterface
/// </summary>
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
public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'";
#endif
private const int CornerRadius = 10;
private readonly Fading fading = new();
private bool isShowing;
private bool isFading;
private bool directionToRight;
private bool mouseDown;
private Point lastLocation;
@ -48,56 +53,10 @@ namespace SystemTrayMenu.UserInterface
timerUpdateIcons.Tick += TimerUpdateIcons_Tick;
Closed += (_, _) =>
{
fading.Fade(Fading.FadingState.Idle);
timerUpdateIcons.Stop();
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();
Level = level;
@ -220,6 +179,8 @@ namespace SystemTrayMenu.UserInterface
Loaded += (sender, e) =>
{
NativeMethods.HideFromAltTab(this);
RaiseEvent(new(routedEvent: FadeInEvent));
};
Closed += (sender, e) =>
@ -263,6 +224,18 @@ namespace SystemTrayMenu.UserInterface
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
{
Predecessor,
@ -283,13 +256,14 @@ namespace SystemTrayMenu.UserInterface
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
internal bool ScrollbarVisible { get; private set; }
private ListView tableLayoutPanelDgvAndScrollbar => dgv; // TODO WPF Remove and replace with dgv
#endif
internal void ResetSearchText()
{
textBoxSearch.Text = string.Empty;
@ -450,21 +424,58 @@ namespace SystemTrayMenu.UserInterface
}
}
internal void ShowWithFade()
{
fading.Fade(Fading.FadingState.Show);
}
internal void ShowWithFade() => Fading_Show(false);
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()
{
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:
RowData trigger = (RowData)Tag;
ListView dgv = menuPredecessor!.GetDataGridView()!;
ListView dgv = menuPredecessor!.GetDataGridView() !;
// Set position on same height as the selected row from predecessor
y = menuPredecessor.Location.Y;
@ -758,7 +769,7 @@ namespace SystemTrayMenu.UserInterface
UpdateLayout();
SizeToContent = SizeToContent.Manual;
#if TODO // SEARCH
dgvHeightSet = false;
dgvHeightSet = false;
#endif
}
}
@ -770,9 +781,20 @@ namespace SystemTrayMenu.UserInterface
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)
{
searchPanel.Visibility= Visibility.Visible;
searchPanel.Visibility = Visibility.Visible;
ModifierKeys modifiers = Keyboard.Modifiers;
switch (e.Key)
@ -881,6 +903,7 @@ namespace SystemTrayMenu.UserInterface
}
double factorIconSizeInPercent = Properties.Settings.Default.IconSizeInPercent / 100f;
// IcoWidth 100% = 21px, 175% is 33, +3+2 is padding from ColumnIcon
double icoWidth = (16 * Scaling.FactorByDpi) + 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.Interop;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
/// <summary>
/// Logic of Taskbar window.
/// </summary>
public partial class TaskbarLogo : Window
{
private DispatcherTimer? moveOutOfScreenTimer = null;
public TaskbarLogo()
{
InitializeComponent();
@ -49,32 +46,28 @@ namespace SystemTrayMenu.UserInterface
Title = myname;
Closed += (_, _) => Application.Current.Shutdown();
Deactivated += (_, _) => SetStateNormal();
Activated += (_, _) =>
Deactivated += SetStateNormal;
Activated += (object? sender, EventArgs e) =>
{
SetStateNormal();
SetStateNormal(sender, e);
Activate();
UpdateLayout();
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>
/// This ensures that next click on taskbaritem works as activate event/click event.
/// </summary>
private void SetStateNormal()
private void SetStateNormal(object? sender, EventArgs e)
{
if (IsActive)
{