diff --git a/MapUiTools/Avalonia/MapMenuItem.Avalonia.cs b/MapUiTools/Avalonia/MapMenuItem.Avalonia.cs new file mode 100644 index 00000000..36956211 --- /dev/null +++ b/MapUiTools/Avalonia/MapMenuItem.Avalonia.cs @@ -0,0 +1,42 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MapControl.UiTools +{ + public class MapMenuItem : MenuItem + { + public MapMenuItem() + { + Icon = new TextBlock + { + FontFamily = new("Segoe MDL2 Assets"), + FontWeight = FontWeight.Black, + VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, + }; + } + + public string Text + { + get => Header as string; + set => Header = value; + } + + protected IEnumerable ParentMenuItems => (Parent as ItemsControl)?.Items.OfType(); + + protected override Type StyleKeyOverride => typeof(MenuItem); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + + if (args.Property == IsCheckedProperty) + { + ((TextBlock)Icon).Text = (bool)args.NewValue ? "\uE73E" : ""; // CheckMark + } + } + } +} diff --git a/MapUiTools/Avalonia/MenuButton.Avalonia.cs b/MapUiTools/Avalonia/MenuButton.Avalonia.cs index c1fcda88..1b50cc19 100644 --- a/MapUiTools/Avalonia/MenuButton.Avalonia.cs +++ b/MapUiTools/Avalonia/MenuButton.Avalonia.cs @@ -1,81 +1,37 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Styling; using System; -using System.Collections.Generic; -using System.Linq; -namespace MapControl +namespace MapControl.UiTools { - public class ToggleMenuFlyoutItem : MenuItem + public partial class MenuButton { - internal static readonly FontFamily SymbolFont = new("Segoe MDL2 Assets"); - - private readonly TextBlock icon = new() - { - FontFamily = SymbolFont, - FontWeight = FontWeight.Black, - VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, - }; - - public ToggleMenuFlyoutItem(string text, object item, EventHandler click) - { - Icon = icon; - Header = text; - Tag = item; - Click += click; - } - - protected override Type StyleKeyOverride => typeof(MenuItem); - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) - { - base.OnPropertyChanged(args); - - if (args.Property == IsCheckedProperty) - { - icon.Text = (bool)args.NewValue ? "\uE73E" : ""; // CheckMark - } - } - } - - public class MenuButton : Button - { - protected MenuButton(string icon) + public MenuButton() { var style = new Style(); - style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, ToggleMenuFlyoutItem.SymbolFont)); + style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, new FontFamily("Segoe MDL2 Assets"))); style.Setters.Add(new Setter(TextBlock.FontSizeProperty, 20d)); style.Setters.Add(new Setter(PaddingProperty, new Thickness(8))); Styles.Add(style); - Content = icon; + Flyout = new MenuFlyout(); + Loaded += async (s, e) => await Initialize(); } + public string Icon + { + get => Content as string; + set => Content = value; + } + + public MenuFlyout Menu => (MenuFlyout)Flyout; + + [Content] + public ItemCollection Items => Menu.Items; + protected override Type StyleKeyOverride => typeof(Button); - - protected MenuFlyout CreateMenu() - { - var menu = new MenuFlyout(); - Flyout = menu; - return menu; - } - - protected IEnumerable GetMenuItems() - { - return ((MenuFlyout)Flyout).Items.OfType(); - } - - protected static MenuItem CreateMenuItem(string text, object item, EventHandler click) - { - return new ToggleMenuFlyoutItem(text, item, click); - } - - protected static Separator CreateSeparator() - { - return new Separator(); - } } } diff --git a/MapUiTools/Shared/MapLayerMenuItem.cs b/MapUiTools/Shared/MapLayerMenuItem.cs new file mode 100644 index 00000000..7e042a63 --- /dev/null +++ b/MapUiTools/Shared/MapLayerMenuItem.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +#if WPF +using System.Windows; +using System.Windows.Markup; +#elif UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; +#elif WINUI +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Markup; +#else +using Avalonia.Metadata; +using FrameworkElement = Avalonia.Controls.Control; +#endif + +namespace MapControl.UiTools +{ +#if WPF + [ContentProperty(nameof(MapLayer))] +#elif UWP || WINUI + [ContentProperty(Name = nameof(MapLayer))] +#endif + public class MapLayerMenuItem : MapMenuItem + { +#if AVALONIA + [Content] +#endif + public FrameworkElement MapLayer { get; set; } + + public Func> MapLayerFactory { get; set; } + + public MapLayerMenuItem() + { + Click += async (s, e) => + { + if (DataContext is MapBase map) + { + await Execute(map); + + foreach (var item in ParentMenuItems.OfType()) + { + item.IsChecked = map.Children.Contains(item.MapLayer); + } + } + }; + } + + public virtual async Task Execute(MapBase map) + { + map.MapLayer = MapLayer ?? (MapLayer = await MapLayerFactory.Invoke()); + IsChecked = true; + } + } + + public class MapOverlayMenuItem : MapLayerMenuItem + { + public override async Task Execute(MapBase map) + { + var layer = MapLayer ?? (MapLayer = await MapLayerFactory.Invoke()); + + if (map.Children.Contains(layer)) + { + map.Children.Remove(layer); + } + else + { + var index = 1; + + foreach (var itemLayer in ParentMenuItems? + .OfType() + .Select(item => item.MapLayer) + .Where(itemLayer => itemLayer != null)) + { + if (itemLayer == layer) + { + map.Children.Insert(index, itemLayer); + break; + } + + if (map.Children.Contains(itemLayer)) + { + index++; + } + } + } + } + } +} diff --git a/MapUiTools/Shared/MapLayersMenuButton.cs b/MapUiTools/Shared/MapLayersMenuButton.cs deleted file mode 100644 index dcfdbee6..00000000 --- a/MapUiTools/Shared/MapLayersMenuButton.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Linq; -using System.Threading.Tasks; -#if WPF -using System.Windows; -using System.Windows.Markup; -#elif UWP -using Windows.UI.Xaml; -using Windows.UI.Xaml.Markup; -#elif WINUI -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Markup; -#elif AVALONIA -using Avalonia.Interactivity; -using Avalonia.Metadata; -using DependencyProperty = Avalonia.AvaloniaProperty; -using FrameworkElement = Avalonia.Controls.Control; -#endif - -namespace MapControl.UiTools -{ -#if WPF - [ContentProperty(nameof(Layer))] -#elif UWP || WINUI - [ContentProperty(Name = nameof(Layer))] -#endif - public class MapLayerItem - { -#if AVALONIA - [Content] -#endif - public FrameworkElement Layer { get; set; } - - public string Text { get; set; } - - public Func> LayerFactory { get; set; } - - public async Task GetLayer() => Layer ?? (Layer = await LayerFactory?.Invoke()); - } - -#if WPF - [ContentProperty(nameof(MapLayers))] -#elif UWP || WINUI - [ContentProperty(Name = nameof(MapLayers))] -#endif - public class MapLayersMenuButton : MenuButton - { - private FrameworkElement selectedLayer; - - public MapLayersMenuButton() - : base("\uE81E") - { - ((INotifyCollectionChanged)MapLayers).CollectionChanged += async (s, e) => await InitializeMenu(); - ((INotifyCollectionChanged)MapOverlays).CollectionChanged += async (s, e) => await InitializeMenu(); - } - - public static readonly DependencyProperty MapProperty = - DependencyPropertyHelper.Register(nameof(Map), null, - async (button, oldValue, newValue) => await button.InitializeMenu()); - - public MapBase Map - { - get => (MapBase)GetValue(MapProperty); - set => SetValue(MapProperty, value); - } - -#if AVALONIA - [Content] -#endif - public Collection MapLayers { get; } = new ObservableCollection(); - - public Collection MapOverlays { get; } = new ObservableCollection(); - - private async Task InitializeMenu() - { - if (Map != null) - { - var menu = CreateMenu(); - - foreach (var item in MapLayers) - { - menu.Items.Add(CreateMenuItem(item.Text, item, MapLayerClicked)); - } - - var initialLayer = MapLayers.Select(l => l.GetLayer()).FirstOrDefault(); - - if (MapOverlays.Count > 0) - { - if (initialLayer != null) - { - menu.Items.Add(CreateSeparator()); - } - - foreach (var item in MapOverlays) - { - menu.Items.Add(CreateMenuItem(item.Text, item, MapOverlayClicked)); - } - } - - if (initialLayer != null) - { - SetMapLayer(await initialLayer); - } - } - } - - private async void MapLayerClicked(object sender, RoutedEventArgs e) - { - var item = (FrameworkElement)sender; - var mapLayerItem = (MapLayerItem)item.Tag; - - SetMapLayer(await mapLayerItem.GetLayer()); - } - - private async void MapOverlayClicked(object sender, RoutedEventArgs e) - { - var item = (FrameworkElement)sender; - var mapLayerItem = (MapLayerItem)item.Tag; - - ToggleMapOverlay(await mapLayerItem.GetLayer()); - } - - private void SetMapLayer(FrameworkElement layer) - { - if (selectedLayer != layer) - { - selectedLayer = layer; - Map.MapLayer = selectedLayer; - } - - UpdateCheckedStates(); - } - - private void ToggleMapOverlay(FrameworkElement layer) - { - if (Map.Children.Contains(layer)) - { - Map.Children.Remove(layer); - } - else - { - int index = 1; - - foreach (var overlay in MapOverlays.Select(o => o.Layer).Where(o => o != null)) - { - if (overlay == layer) - { - Map.Children.Insert(index, layer); - break; - } - - if (Map.Children.Contains(overlay)) - { - index++; - } - } - } - - UpdateCheckedStates(); - } - - private void UpdateCheckedStates() - { - foreach (var item in GetMenuItems()) - { - item.IsChecked = Map.Children.Contains(((MapLayerItem)item.Tag).Layer); - } - } - } -} diff --git a/MapUiTools/Shared/MapProjectionMenuItem.cs b/MapUiTools/Shared/MapProjectionMenuItem.cs new file mode 100644 index 00000000..6b94bf1b --- /dev/null +++ b/MapUiTools/Shared/MapProjectionMenuItem.cs @@ -0,0 +1,64 @@ +using System; +using System.Diagnostics; +using System.Linq; +#if WPF +using System.Windows.Markup; +#elif UWP +using Windows.UI.Xaml.Markup; +#elif WINUI +using Microsoft.UI.Xaml.Markup; +#else +using Avalonia.Metadata; +#endif + +namespace MapControl.UiTools +{ +#if WPF + [ContentProperty(nameof(MapProjection))] +#elif UWP || WINUI + [ContentProperty(Name = nameof(MapProjection))] +#endif + public class MapProjectionMenuItem : MapMenuItem + { +#if AVALONIA + [Content] +#endif + public string MapProjection { get; set; } + + public MapProjectionMenuItem() + { + Click += (s, e) => + { + if (DataContext is MapBase map) + { + Execute(map); + + foreach (var item in ParentMenuItems.OfType()) + { + item.IsChecked = map.MapProjection.CrsId == item.MapProjection; + } + } + }; + } + + public void Execute(MapBase map) + { + bool success = true; + + if (map.MapProjection.CrsId != MapProjection) + { + try + { + map.MapProjection = MapProjectionFactory.Instance.GetProjection(MapProjection); + } + catch (Exception ex) + { + Debug.WriteLine($"{nameof(MapProjectionFactory)}: {ex.Message}"); + success = false; + } + } + + IsChecked = success; + } + } +} diff --git a/MapUiTools/Shared/MapProjectionsMenuButton.cs b/MapUiTools/Shared/MapProjectionsMenuButton.cs deleted file mode 100644 index 8e0577f2..00000000 --- a/MapUiTools/Shared/MapProjectionsMenuButton.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -#if WPF -using System.Windows; -using System.Windows.Markup; -#elif UWP -using Windows.UI.Xaml; -using Windows.UI.Xaml.Markup; -#elif WINUI -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Markup; -#elif AVALONIA -using Avalonia.Interactivity; -using Avalonia.Metadata; -using DependencyProperty = Avalonia.AvaloniaProperty; -using FrameworkElement = Avalonia.Controls.Control; -#endif - -namespace MapControl.UiTools -{ -#if WPF - [ContentProperty(nameof(Projection))] -#elif UWP || WINUI - [ContentProperty(Name = nameof(Projection))] -#endif - public class MapProjectionItem - { -#if AVALONIA - [Content] -#endif - public string Projection { get; set; } - - public string Text { get; set; } - } - -#if WPF - [ContentProperty(nameof(MapProjections))] -#elif UWP || WINUI - [ContentProperty(Name = nameof(MapProjections))] -#endif - public class MapProjectionsMenuButton : MenuButton - { - private string selectedProjection; - - public MapProjectionsMenuButton() - : base("\uE809") - { - ((INotifyCollectionChanged)MapProjections).CollectionChanged += (s, e) => InitializeMenu(); - } - - public static readonly DependencyProperty MapProperty = - DependencyPropertyHelper.Register(nameof(Map), null, - (button, oldValue, newValue) => button.InitializeMenu()); - - public MapBase Map - { - get => (MapBase)GetValue(MapProperty); - set => SetValue(MapProperty, value); - } - -#if AVALONIA - [Content] -#endif - public Collection MapProjections { get; } = new ObservableCollection(); - - private void InitializeMenu() - { - if (Map != null) - { - var menu = CreateMenu(); - - foreach (var item in MapProjections) - { - menu.Items.Add(CreateMenuItem(item.Text, item.Projection, MapProjectionClicked)); - } - - var initialProjection = MapProjections.Select(p => p.Projection).FirstOrDefault(); - - if (initialProjection != null) - { - SetMapProjection(initialProjection); - } - } - } - - private void MapProjectionClicked(object sender, RoutedEventArgs e) - { - var item = (FrameworkElement)sender; - var projection = (string)item.Tag; - - SetMapProjection(projection); - } - - private void SetMapProjection(string projection) - { - if (selectedProjection != projection) - { - try - { - Map.MapProjection = MapProjectionFactory.Instance.GetProjection(projection); - selectedProjection = projection; - } - catch (Exception ex) - { - Debug.WriteLine($"{nameof(MapProjectionFactory)}: {ex.Message}"); - } - } - - UpdateCheckedStates(); - } - - private void UpdateCheckedStates() - { - foreach (var item in GetMenuItems()) - { - item.IsChecked = selectedProjection == (string)item.Tag; - } - } - } -} diff --git a/MapUiTools/Shared/MenuButton.cs b/MapUiTools/Shared/MenuButton.cs new file mode 100644 index 00000000..466fa2d5 --- /dev/null +++ b/MapUiTools/Shared/MenuButton.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +#if WPF +using System.Windows; +using System.Windows.Controls; +#elif UWP +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +#elif WINUI +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +#else +using Avalonia.Controls; +using DependencyProperty = Avalonia.AvaloniaProperty; +#endif + +namespace MapControl.UiTools +{ + public partial class MenuButton : Button + { + public static readonly DependencyProperty MapProperty = + DependencyPropertyHelper.Register(nameof(Map), null, + async (button, oldValue, newValue) => await button.Initialize()); + + public MapBase Map + { + get => (MapBase)GetValue(MapProperty); + set => SetValue(MapProperty, value); + } + + private async Task Initialize() + { + if (Map != null) + { + DataContext = Map; + + if (Items.Count > 0) + { + if (Items[0] is MapLayerMenuItem mapLayerItem) + { + await mapLayerItem.Execute(Map); + } + else if (Items[0] is MapProjectionMenuItem mapProjectionItem) + { + mapProjectionItem.Execute(Map); + } + } + } + } + } +} diff --git a/MapUiTools/UWP/MapUiTools.UWP.csproj b/MapUiTools/UWP/MapUiTools.UWP.csproj index 8cf295b6..e9d8f08e 100644 --- a/MapUiTools/UWP/MapUiTools.UWP.csproj +++ b/MapUiTools/UWP/MapUiTools.UWP.csproj @@ -40,11 +40,17 @@ PackageReference - - MapLayersMenuButton.cs + + MapLayerMenuItem.cs - - MapProjectionsMenuButton.cs + + MapProjectionMenuItem.cs + + + MenuButton.cs + + + MapMenuItem.WinUI.cs MenuButton.WinUI.cs diff --git a/MapUiTools/WPF/MapMenuItem.WPF.cs b/MapUiTools/WPF/MapMenuItem.WPF.cs new file mode 100644 index 00000000..9a316d94 --- /dev/null +++ b/MapUiTools/WPF/MapMenuItem.WPF.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Controls; + +namespace MapControl.UiTools +{ + public class MapMenuItem : MenuItem + { + public string Text + { + get => Header as string; + set => Header = value; + } + + protected IEnumerable ParentMenuItems + => (Parent as ItemsControl)?.Items.OfType(); + } +} diff --git a/MapUiTools/WPF/MenuButton.WPF.cs b/MapUiTools/WPF/MenuButton.WPF.cs index b182898e..dede25f2 100644 --- a/MapUiTools/WPF/MenuButton.WPF.cs +++ b/MapUiTools/WPF/MenuButton.WPF.cs @@ -1,46 +1,33 @@ -using System.Collections.Generic; -using System.Linq; -using System.Windows; +using System.Windows; using System.Windows.Controls; +using System.Windows.Markup; namespace MapControl.UiTools { - public class MenuButton : Button + [ContentProperty(nameof(Items))] + public partial class MenuButton { static MenuButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(MenuButton), new FrameworkPropertyMetadata(typeof(MenuButton))); } - protected MenuButton(string icon) + public MenuButton() { - Content = icon; - + ContextMenu = new ContextMenu(); + DataContextChanged += (s, e) => ContextMenu.DataContext = e.NewValue; + Loaded += async (s, e) => await Initialize(); Click += (s, e) => ContextMenu.IsOpen = true; } - protected ContextMenu CreateMenu() + public string Icon { - var menu = new ContextMenu(); - ContextMenu = menu; - return menu; + get => Content as string; + set => Content = value; } - protected IEnumerable GetMenuItems() - { - return ContextMenu.Items.OfType(); - } + public ContextMenu Menu => ContextMenu; - protected static MenuItem CreateMenuItem(string text, object item, RoutedEventHandler click) - { - var menuItem = new MenuItem { Header = text, Tag = item }; - menuItem.Click += click; - return menuItem; - } - - protected static Separator CreateSeparator() - { - return new Separator(); - } + public ItemCollection Items => ContextMenu.Items; } } diff --git a/MapUiTools/WinUI/MapMenuItem.WinUI.cs b/MapUiTools/WinUI/MapMenuItem.WinUI.cs new file mode 100644 index 00000000..3065b00e --- /dev/null +++ b/MapUiTools/WinUI/MapMenuItem.WinUI.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Linq; +#if UWP +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; +#else +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +#endif + +namespace MapControl.UiTools +{ + public class MapMenuItem : ToggleMenuFlyoutItem + { + protected IEnumerable ParentMenuItems + => (VisualTreeHelper.GetParent(this) as Panel)?.Children.OfType(); + } +} diff --git a/MapUiTools/WinUI/MenuButton.WinUI.cs b/MapUiTools/WinUI/MenuButton.WinUI.cs index 2cd45774..6bc705fe 100644 --- a/MapUiTools/WinUI/MenuButton.WinUI.cs +++ b/MapUiTools/WinUI/MenuButton.WinUI.cs @@ -1,44 +1,31 @@ using System.Collections.Generic; -using System.Linq; #if UWP -using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Markup; #elif WINUI -using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; #endif namespace MapControl.UiTools { - public class MenuButton : Button + [ContentProperty(Name = nameof(Items))] + public partial class MenuButton { - protected MenuButton(string icon) + public MenuButton() { - Content = new FontIcon { Glyph = icon }; + Flyout = new MenuFlyout(); + Loaded += async (s, e) => await Initialize(); } - protected MenuFlyout CreateMenu() + public string Icon { - var menu = new MenuFlyout(); - Flyout = menu; - return menu; + get => (Content as FontIcon)?.Glyph; + set => Content = new FontIcon { Glyph = value }; } - protected IEnumerable GetMenuItems() - { - return ((MenuFlyout)Flyout).Items.OfType(); - } + public MenuFlyout Menu => (MenuFlyout)Flyout; - protected static ToggleMenuFlyoutItem CreateMenuItem(string text, object item, RoutedEventHandler click) - { - var menuItem = new ToggleMenuFlyoutItem { Text = text, Tag = item }; - menuItem.Click += click; - return menuItem; - } - - protected static MenuFlyoutSeparator CreateSeparator() - { - return new MenuFlyoutSeparator(); - } + public IList Items => Menu.Items; } }