diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index c891fa422..cd2d9a19e 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -69,19 +69,16 @@ namespace Artemis.Core.Modules /// /// The modules display name that's shown in the menu /// - public string DisplayName { get; protected set; } + public string? DisplayName { get; protected set; } /// - /// The modules display icon that's shown in the menu see for available - /// icons + /// The modules display icon that's shown in the UI accepts: + /// + /// Either set to the name of a Material Icon see ( for available + /// icons) or set to a path relative to the plugin folder pointing to a .svg file + /// /// - public string DisplayIcon { get; set; } - - /// - /// A path to an image to use as the modules display icon that's shown in the menu. - /// If set, takes precedence over - /// - public string DisplayIconPath { get; set; } + public string? DisplayIcon { get; set; } /// /// Gets whether this module is activated. A module can only be active while its @@ -97,7 +94,8 @@ namespace Artemis.Core.Modules /// /// Gets whether this module should update while is . When - /// set to and any timed updates will not get called during an activation override. + /// set to and any timed updates will not get called during an + /// activation override. /// Defaults to /// public bool UpdateDuringActivationOverride { get; protected set; } @@ -129,21 +127,21 @@ namespace Artemis.Core.Modules /// public int Priority { get; internal set; } - internal DataModel InternalDataModel { get; set; } - - internal bool InternalExpandsMainDataModel { get; set; } - internal ModuleSettingsEntity Entity { get; set; } - /// /// A list of custom module tabs that show in the UI /// - public IEnumerable ModuleTabs { get; protected set; } + public IEnumerable? ModuleTabs { get; protected set; } /// /// Gets whether updating this module is currently allowed /// public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride); + internal DataModel? InternalDataModel { get; set; } + + internal bool InternalExpandsMainDataModel { get; set; } + internal ModuleSettingsEntity? Entity { get; set; } + /// /// Called each frame when the module should update /// diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 414b33d44..1c071d7cd 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -176,5 +176,10 @@ namespace Artemis.Core { return File.Exists(Path.Combine(Directory.FullName, "artemis.lock")); } + + public string? ResolveRelativePath(string path) + { + return path == null ? null : Path.Combine(Directory.FullName, path); + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs b/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs new file mode 100644 index 000000000..3afd7cc80 --- /dev/null +++ b/src/Artemis.UI.Shared/Converters/TypeToStringConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using Artemis.Core; + +namespace Artemis.UI.Shared +{ + /// + /// Converts into . + /// + public class TypeToStringConverter : IValueConverter + { + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize); + if (value is Type type) + return type.GetDisplayName(humanizeProvided && humanize); + + return value?.ToString(); + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index 4190752ea..c4ff3d4eb 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -12,6 +12,7 @@ namespace Artemis.UI.Shared private string _countDisplay; private IEnumerable _list; private int _listCount; + private Type _displayValueType; internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { @@ -30,6 +31,12 @@ namespace Artemis.UI.Shared set => SetAndNotify(ref _listCount, value); } + public Type DisplayValueType + { + get => _displayValueType; + set => SetAndNotify(ref _displayValueType, value); + } + public string CountDisplay { get => _countDisplay; @@ -68,6 +75,7 @@ namespace Artemis.UI.Shared return; List = GetCurrentValue() as IEnumerable; + DisplayValueType = List?.GetType(); if (List == null) return; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs index 6da643ac7..57623d7b4 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs @@ -1,4 +1,5 @@ -using Artemis.Core; +using System; +using Artemis.Core; using Artemis.Core.DataModelExpansions; using Artemis.UI.Shared.Services; @@ -6,12 +7,21 @@ namespace Artemis.UI.Shared { public class DataModelPropertiesViewModel : DataModelVisualizationViewModel { + private Type _displayValueType; + internal DataModelPropertiesViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { } + + public Type DisplayValueType + { + get => _displayValueType; + set => SetAndNotify(ref _displayValueType, value); + } public override void Update(IDataModelUIService dataModelUIService) { + DisplayValueType = DataModelPath?.GetPropertyType(); // Always populate properties PopulateProperties(dataModelUIService); @@ -28,15 +38,15 @@ namespace Artemis.UI.Shared return Parent.IsRootViewModel ? DataModel : base.GetCurrentValue(); } - internal override int GetChildDepth() - { - return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1; - } - /// public override string ToString() { return DisplayPath ?? Path; } + + internal override int GetChildDepth() + { + return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1; + } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 4fb72523f..59764842e 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -81,7 +81,9 @@ namespace Artemis.UI.Shared } } - public virtual string DisplayPath => string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier)); + public virtual string DisplayPath => DataModelPath != null + ? string.Join(" › ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier)) + : null; /// /// Updates the datamodel and if in an parent, any children @@ -133,7 +135,7 @@ namespace Artemis.UI.Shared } if (looseMatch) - IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) || + IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) || t == typeof(Enum) && type.IsEnum || t == typeof(IEnumerable<>) && type.IsGenericEnumerable()); else @@ -267,7 +269,7 @@ namespace Artemis.UI.Shared // For other value types create a child view model if (propertyType.IsClass || propertyType.IsStruct()) return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth}; - + return null; } diff --git a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs index fef262aa4..07d51249d 100644 --- a/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs +++ b/src/Artemis.UI.Shared/DefaultTypes/DataModel/Display/DefaultDataModelDisplayViewModel.cs @@ -5,6 +5,11 @@ private bool _showNull; private bool _showToString; + public DefaultDataModelDisplayViewModel() + { + ShowNull = true; + } + public bool ShowToString { get => _showToString; diff --git a/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs b/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs new file mode 100644 index 000000000..140b104bd --- /dev/null +++ b/src/Artemis.UI.Shared/Utilities/PluginUtilities.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using Artemis.Core; +using Artemis.UI.Shared.Controls; +using MaterialDesignThemes.Wpf; + +namespace Artemis.UI.Shared +{ + /// + /// Provides utilities for UI-related plugin tasks + /// + public static class PluginUtilities + { + /// + /// Transforms the provided icon so that it is usable by the control + /// + /// The info of the plugin the icon belongs to + /// + /// The icon, may be a string representation of a or a relative path + /// pointing to a .svg file + /// + /// + public static object GetPluginIcon(PluginInfo pluginInfo, string icon) + { + if (icon == null) + return PackIconKind.QuestionMarkCircle; + + // Icon is provided as a path + if (icon.EndsWith(".svg")) + { + string iconPath = pluginInfo.ResolveRelativePath(icon); + if (!File.Exists(iconPath)) + return PackIconKind.QuestionMarkCircle; + return iconPath; + } + + // Icon is provided as string to avoid having to reference MaterialDesignThemes + bool parsedIcon = Enum.TryParse(icon, true, out PackIconKind iconEnum); + if (parsedIcon == false) + iconEnum = PackIconKind.QuestionMarkCircle; + return iconEnum; + + return icon; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/App.xaml b/src/Artemis.UI/App.xaml index ae724e222..ea1d2f328 100644 --- a/src/Artemis.UI/App.xaml +++ b/src/Artemis.UI/App.xaml @@ -24,9 +24,11 @@ - + + + diff --git a/src/Artemis.UI/ResourceDictionaries/SideNavigation.xaml b/src/Artemis.UI/ResourceDictionaries/SideNavigation.xaml new file mode 100644 index 000000000..199df5fb4 --- /dev/null +++ b/src/Artemis.UI/ResourceDictionaries/SideNavigation.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml index e0f56aa7b..429cf4e16 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugView.xaml @@ -10,6 +10,9 @@ xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}"> + + + @@ -60,7 +63,17 @@ - + + + + + + + [] + + + + @@ -69,7 +82,9 @@ - + + [] + - [] + [] @@ -105,7 +120,7 @@ - [] + [] List item [] diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index cccc40208..9b4577cb8 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -15,6 +15,7 @@ using Artemis.UI.Screens.News; using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.Workshop; +using Artemis.UI.Shared; using MaterialDesignExtensions.Controls; using MaterialDesignExtensions.Model; using MaterialDesignThemes.Wpf; @@ -143,19 +144,11 @@ namespace Artemis.UI.Screens.Sidebar if (SidebarModules.Any(io => io.Value == module)) return; - object icon; - if (module.DisplayIconPath != null && File.Exists(Path.Combine(module.PluginInfo.Directory.FullName, module.DisplayIconPath))) - icon = new BitmapImage(new Uri(Path.Combine(module.PluginInfo.Directory.FullName, module.DisplayIconPath))); - else + FirstLevelNavigationItem sidebarItem = new FirstLevelNavigationItem { - // Icon is provided as string to avoid having to reference MaterialDesignThemes - bool parsedIcon = Enum.TryParse(module.DisplayIcon, true, out PackIconKind iconEnum); - if (parsedIcon == false) - iconEnum = PackIconKind.QuestionMarkCircle; - icon = iconEnum; - } - - FirstLevelNavigationItem sidebarItem = new FirstLevelNavigationItem {Icon = icon, Label = module.DisplayName}; + Icon = PluginUtilities.GetPluginIcon(module.PluginInfo, module.DisplayIcon), + Label = module.DisplayName + }; SidebarItems.Add(sidebarItem); SidebarModules.Add(sidebarItem, module); } diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj b/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj index 5e84fc74f..8638b8d53 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj +++ b/src/Plugins/Artemis.Plugins.Modules.General/Artemis.Plugins.Modules.General.csproj @@ -32,6 +32,11 @@ false + + + PreserveNewest + + diff --git a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs index 1099f977d..9fc1b14f2 100644 --- a/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs +++ b/src/Plugins/Artemis.Plugins.Modules.General/GeneralModule.cs @@ -16,7 +16,7 @@ namespace Artemis.Plugins.Modules.General public override void EnablePlugin() { DisplayName = "General"; - DisplayIcon = "AllInclusive"; + DisplayIcon = "Images/bow.svg"; ExpandsDataModel = true; ModuleTabs = new List {new ModuleTab("General")}; diff --git a/src/Plugins/Artemis.Plugins.Modules.General/Images/bow.svg b/src/Plugins/Artemis.Plugins.Modules.General/Images/bow.svg new file mode 100644 index 000000000..66817473d --- /dev/null +++ b/src/Plugins/Artemis.Plugins.Modules.General/Images/bow.svg @@ -0,0 +1,44 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + +