diff --git a/src/RepoM.App/Controls/AcrylicMenuItem.cs b/src/RepoM.App/Controls/AcrylicMenuItem.cs index 3ecc7649..e143b472 100644 --- a/src/RepoM.App/Controls/AcrylicMenuItem.cs +++ b/src/RepoM.App/Controls/AcrylicMenuItem.cs @@ -8,7 +8,8 @@ namespace RepoM.App.Controls; public class AcrylicMenuItem : MenuItem { - private RoutedEventHandler? _evt; + private RoutedEventHandler? _clickEvtHandler; + private RoutedEventHandler? _subMenuOpenedEventHandler; private static readonly Brush _solidColorBrush = new SolidColorBrush(Color.FromArgb(80, 0, 0, 0)); protected override void OnSubmenuOpened(RoutedEventArgs e) @@ -53,21 +54,55 @@ private void BlurSubMenu() AcrylicHelper.EnableBlur(container); } + public void SoftReset() + { + ClearClick(); + ClearSubMenuOpened(); + } + public void SetClick(RoutedEventHandler routedEventHandler) { ClearClick(); Click += routedEventHandler; - _evt = routedEventHandler; + _clickEvtHandler = routedEventHandler; } public void ClearClick() { - if (_evt == null) + if (_clickEvtHandler == null) + { + return; + } + + Click -= _clickEvtHandler; + _clickEvtHandler = null; + } + + public void SetSubMenuOpened(RoutedEventHandler routedEventHandler) + { + ClearSubMenuOpened(); + SubmenuOpened += routedEventHandler; + _subMenuOpenedEventHandler = routedEventHandler; + } + + public void ClearSubMenuOpened() + { + if (_subMenuOpenedEventHandler == null) + { + return; + } + + SubmenuOpened -= _subMenuOpenedEventHandler; + _subMenuOpenedEventHandler = null; + } + + public void ClearItems() + { + if (Items.Count == 0) { return; } - Click -= _evt; - _evt = null; + Items.Clear(); } } \ No newline at end of file diff --git a/src/RepoM.App/MainWindow.xaml.cs b/src/RepoM.App/MainWindow.xaml.cs index 0ca95a7e..9049fcbc 100644 --- a/src/RepoM.App/MainWindow.xaml.cs +++ b/src/RepoM.App/MainWindow.xaml.cs @@ -1,7 +1,6 @@ namespace RepoM.App; using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO.Abstractions; @@ -50,18 +49,7 @@ public partial class MainWindow private readonly ILogger _logger; private readonly IUserMenuActionMenuFactory _userMenuActionFactory; private readonly IAppDataPathProvider _appDataPathProvider; - - private readonly List _separators = new(); - private readonly List _menuItems = new(); - private int _separatorIndex = 0; - private int _menuItemIndex = 0; - - private readonly AcrylicMenuItem _loadingMenuItem = new() - { - Header = "Loading ..", - IsEnabled = true, - }; - + public MainWindow( IRepositoryInformationAggregator aggregator, IRepositoryMonitor repositoryMonitor, @@ -259,57 +247,128 @@ private async Task LstRepositoriesContextMenuOpeningWrapperAsync(ContextMe private async Task LstRepositoriesContextMenuOpeningAsync(ContextMenu ctxMenu) { - ResetIndex(); if (lstRepositories.SelectedItem is not RepositoryViewModel vm) { return false; } - var items = new List(); - // ItemCollection items = ctxMenu.Items; - // ItemCollection items = new ItemCollection(); - //items.Clear(); - - // foreach (var item in ctxMenu.Items) - // { - // if (item is Control c) - // { - // c.IsEnabled = false; - // } - // } - - ctxMenu.Items.Clear(); - ctxMenu.Items.Add(_loadingMenuItem); + if (ctxMenu.Items.Count == 0) + { + for (int i = 0; i < 50; i++) + { + ctxMenu.Items.Add(new AcrylicMenuItem + { + Header = string.Empty, + Visibility = Visibility.Collapsed, + }); + ctxMenu.Items.Add(new Separator + { + Visibility = Visibility.Collapsed, + }); + } + } + + int j = -1; + bool lastVisibleSeparator = false; await foreach (UserInterfaceRepositoryActionBase action in _userMenuActionFactory.CreateMenuAsync(vm.Repository).ConfigureAwait(true)) { + j++; if (action is UserInterfaceSeparatorRepositoryAction) { - if (items.Count > 0 && items[^1] is not Separator) + while (ctxMenu.Items[j] is AcrylicMenuItem ami) { - items.Add(GetSeparator()); + ami.Visibility = Visibility.Collapsed; + ami.SoftReset(); + j++; } + + if (ctxMenu.Items[j] is Separator s) + { + s.Visibility = lastVisibleSeparator + ? Visibility.Collapsed + : Visibility.Visible; + lastVisibleSeparator = true; + continue; + } + + /* should never happen */ } - else if (action is DeferredSubActionsUserInterfaceRepositoryAction or UserInterfaceRepositoryAction) + + + if (action is DeferredSubActionsUserInterfaceRepositoryAction or UserInterfaceRepositoryAction) { - Control? controlItem = CreateMenuItemNewStyleAsync(action, vm); - if (controlItem != null) + while (ctxMenu.Items[j] is /*not AcrylicMenuItem*/ Separator s) { - items.Add(controlItem); + s.Visibility = Visibility.Collapsed; + j++; } + + var acrylicMenuItem = (AcrylicMenuItem)ctxMenu.Items[j]; + lastVisibleSeparator = false; + + if (action is UserInterfaceRepositoryAction repositoryAction) + { + acrylicMenuItem.Header = repositoryAction.Name; + acrylicMenuItem.IsEnabled = repositoryAction.CanExecute; + acrylicMenuItem.ClearItems(); + SetClick(acrylicMenuItem, repositoryAction, vm); + SetSubMenu(acrylicMenuItem, repositoryAction); + acrylicMenuItem.Visibility = Visibility.Visible; + continue; + } + + /* should never happen */ } } - ctxMenu.Items.Clear(); - foreach (Control item in items) + if (!lastVisibleSeparator) { - ctxMenu.Items.Add(item); + j++; + } + + var len = ctxMenu.Items.Count; + while (j < len) + { + if (ctxMenu.Items[j] is AcrylicMenuItem ami) + { + ami.SoftReset(); + } + + ((Control)ctxMenu.Items[j]).Visibility = Visibility.Collapsed; + j++; } return true; } - + private void SetClick(AcrylicMenuItem acrylicMenuItem, UserInterfaceRepositoryAction action, RepositoryViewModel? affectedViews) + { + void ClickAction(object clickSender, object clickArgs) + { + // run actions in the UI async to not block it + if (action.ExecutionCausesSynchronizing) + { + Task.Run(() => SetVmSynchronizing(affectedViews, true)) + .ContinueWith(t => _executor.Execute(action.Repository, action.RepositoryCommand)) + .ContinueWith(t => SetVmSynchronizing(affectedViews, false)); + } + else + { + Task.Run(() => _executor.Execute(action.Repository, action.RepositoryCommand)); + } + } + + if (action.RepositoryCommand is null or NullRepositoryCommand) + { + acrylicMenuItem.ClearClick(); + } + else + { + acrylicMenuItem.SetClick(new RoutedEventHandler((Action)ClickAction)); + } + } + private async void LstRepositories_KeyDown(object? sender, KeyEventArgs e) { if (e.Key is Key.Return or Key.Enter) @@ -504,7 +563,7 @@ private void ShowUpdateIfAvailable() { if (action is RepositorySeparatorAction) { - return GetSeparator(); + return new Separator(); } if (action is not RepositoryAction repositoryAction) @@ -533,9 +592,11 @@ private void ShowUpdateIfAvailable() } }; - AcrylicMenuItem item = GetMenuItem(); - item.Header = repositoryAction.Name; - item.IsEnabled = repositoryAction.CanExecute; + var item = new AcrylicMenuItem + { + Header = repositoryAction.Name, + IsEnabled = repositoryAction.CanExecute, + }; item.SetClick(new RoutedEventHandler(clickAction)); // this is a deferred submenu. We want to make sure that the context menu can pop up @@ -579,47 +640,33 @@ void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) return item; } - private Control? /*MenuItem*/ CreateMenuItemNewStyleAsync(UserInterfaceRepositoryActionBase action, RepositoryViewModel? affectedViews = null) + private Control? CreateMenuItemNewStyleAsync(UserInterfaceRepositoryActionBase action, RepositoryViewModel? affectedViews = null) { if (action is UserInterfaceSeparatorRepositoryAction) { - return GetSeparator(); + return new Separator(); } - // UserInterfaceRepositoryAction - // DeferredSubActionsUserInterfaceRepositoryAction - if (action is not UserInterfaceRepositoryAction repositoryAction) { // throw?? return null; } - Action clickAction = (object clickSender, object clickArgs) => + var item = new AcrylicMenuItem { - if (repositoryAction.RepositoryCommand is null or NullRepositoryCommand) - { - return; - } - - // run actions in the UI async to not block it - if (repositoryAction.ExecutionCausesSynchronizing) - { - Task.Run(() => SetVmSynchronizing(affectedViews, true)) - .ContinueWith(t => _executor.Execute(action.Repository, action.RepositoryCommand)) - .ContinueWith(t => SetVmSynchronizing(affectedViews, false)); - } - else - { - Task.Run(() => _executor.Execute(action.Repository, action.RepositoryCommand)); - } + Header = repositoryAction.Name, + IsEnabled = repositoryAction.CanExecute, }; + SetClick(item, repositoryAction, affectedViews); + SetSubMenu(item, repositoryAction); + return item; + } - AcrylicMenuItem item = GetMenuItem(); - item.Header = repositoryAction.Name; - item.IsEnabled = repositoryAction.CanExecute; - item.SetClick(new RoutedEventHandler(clickAction)); - + private void SetSubMenu(AcrylicMenuItem item, UserInterfaceRepositoryAction repositoryAction) + { + item.ClearItems(); + // this is a deferred submenu. We want to make sure that the context menu can pop up // fast, while submenus are not evaluated yet. We don't want to make the context menu // itself slow because the creation of the submenu items takes some time. @@ -631,9 +678,9 @@ void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) async void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) { - item.SubmenuOpened -= SelfDetachingEventHandler; - item.Items.Clear(); - + item.ClearSubMenuOpened(); + item.ClearItems(); + foreach (UserInterfaceRepositoryActionBase subAction in await deferredRepositoryAction.GetAsync().ConfigureAwait(true)) { Control? controlItem = CreateMenuItemNewStyleAsync(subAction); @@ -661,18 +708,18 @@ async void SelfDetachingEventHandler(object _, RoutedEventArgs evtArgs) } } - item.SubmenuOpened += SelfDetachingEventHandler; + item.SetSubMenuOpened(SelfDetachingEventHandler); } else if (repositoryAction.SubActions != null) { // this is a template submenu item to enable submenus under the current // menu item. this item gets removed when the real subitems are created item.Items.Add("Loading.."); - + async void SelfDetachingEventHandler1(object _, RoutedEventArgs evtArgs) { - item.SubmenuOpened -= SelfDetachingEventHandler1; - item.Items.Clear(); + item.ClearSubMenuOpened(); + item.ClearItems(); foreach (UserInterfaceRepositoryActionBase subAction in repositoryAction.SubActions) { @@ -701,10 +748,8 @@ async void SelfDetachingEventHandler1(object _, RoutedEventArgs evtArgs) } } - item.SubmenuOpened += SelfDetachingEventHandler1; + item.SetSubMenuOpened(SelfDetachingEventHandler1); } - - return item; } private static void SetVmSynchronizing(RepositoryViewModel? affectedVm, bool synchronizing) @@ -858,34 +903,5 @@ private void TxtFilter_Finish(object sender, EventArgs e) item?.Focus(); } - private void ResetIndex() - { - _separatorIndex = 0; - _menuItemIndex = 0; - } - - private Separator GetSeparator() - { - while (_separatorIndex >= _separators.Count) - { - _separators.Add(new Separator()); - } - - return _separators[_separatorIndex++]; - } - - private AcrylicMenuItem GetMenuItem() - { - while (_menuItemIndex >= _menuItems.Count) - { - _menuItems.Add(new AcrylicMenuItem()); - } - - AcrylicMenuItem result = _menuItems[_menuItemIndex++]; - result.Items.Clear(); - result.ClearClick(); - return result; - } - public bool IsShown => Visibility == Visibility.Visible && IsActive; } \ No newline at end of file