1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Modules - Added support for SVG icons in sidebar

Modules - Removed separate DisplayIconPath property
Data model debugger - Generics now display their types nicely
Data model debugger - Type keywords are used where applicable
Data model debugger - Nullables display properly
This commit is contained in:
Robert 2020-10-16 22:25:17 +02:00
parent 7b7b75be78
commit 09cd0b4dae
15 changed files with 255 additions and 44 deletions

View File

@ -69,19 +69,16 @@ namespace Artemis.Core.Modules
/// <summary>
/// The modules display name that's shown in the menu
/// </summary>
public string DisplayName { get; protected set; }
public string? DisplayName { get; protected set; }
/// <summary>
/// The modules display icon that's shown in the menu see <see href="https://materialdesignicons.com" /> for available
/// icons
/// The modules display icon that's shown in the UI accepts:
/// <para>
/// Either set to the name of a Material Icon see (<see href="https://materialdesignicons.com" /> for available
/// icons) or set to a path relative to the plugin folder pointing to a .svg file
/// </para>
/// </summary>
public string DisplayIcon { get; set; }
/// <summary>
/// A path to an image to use as the modules display icon that's shown in the menu.
/// <para>If set, takes precedence over <see cref="DisplayIcon" /></para>
/// </summary>
public string DisplayIconPath { get; set; }
public string? DisplayIcon { get; set; }
/// <summary>
/// Gets whether this module is activated. A module can only be active while its <see cref="ActivationRequirements" />
@ -97,7 +94,8 @@ namespace Artemis.Core.Modules
/// <summary>
/// Gets whether this module should update while <see cref="IsActivatedOverride" /> is <see langword="true" />. When
/// set to <see langword="false" /> <see cref="Update" /> and any timed updates will not get called during an activation override.
/// set to <see langword="false" /> <see cref="Update" /> and any timed updates will not get called during an
/// activation override.
/// <para>Defaults to <see langword="false" /></para>
/// </summary>
public bool UpdateDuringActivationOverride { get; protected set; }
@ -129,21 +127,21 @@ namespace Artemis.Core.Modules
/// </summary>
public int Priority { get; internal set; }
internal DataModel InternalDataModel { get; set; }
internal bool InternalExpandsMainDataModel { get; set; }
internal ModuleSettingsEntity Entity { get; set; }
/// <summary>
/// A list of custom module tabs that show in the UI
/// </summary>
public IEnumerable<ModuleTab> ModuleTabs { get; protected set; }
public IEnumerable<ModuleTab>? ModuleTabs { get; protected set; }
/// <summary>
/// Gets whether updating this module is currently allowed
/// </summary>
public bool IsUpdateAllowed => IsActivated && (UpdateDuringActivationOverride || !IsActivatedOverride);
internal DataModel? InternalDataModel { get; set; }
internal bool InternalExpandsMainDataModel { get; set; }
internal ModuleSettingsEntity? Entity { get; set; }
/// <summary>
/// Called each frame when the module should update
/// </summary>

View File

@ -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);
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Globalization;
using System.Windows.Data;
using Artemis.Core;
namespace Artemis.UI.Shared
{
/// <summary>
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
/// </summary>
public class TypeToStringConverter : IValueConverter
{
/// <inheritdoc />
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();
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -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;

View File

@ -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;
}
/// <inheritdoc />
public override string ToString()
{
return DisplayPath ?? Path;
}
internal override int GetChildDepth()
{
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
}
}
}

View File

@ -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;
/// <summary>
/// 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;
}

View File

@ -5,6 +5,11 @@
private bool _showNull;
private bool _showToString;
public DefaultDataModelDisplayViewModel()
{
ShowNull = true;
}
public bool ShowToString
{
get => _showToString;

View File

@ -0,0 +1,46 @@
using System;
using System.IO;
using Artemis.Core;
using Artemis.UI.Shared.Controls;
using MaterialDesignThemes.Wpf;
namespace Artemis.UI.Shared
{
/// <summary>
/// Provides utilities for UI-related plugin tasks
/// </summary>
public static class PluginUtilities
{
/// <summary>
/// Transforms the provided icon so that it is usable by the <see cref="ArtemisIcon" /> control
/// </summary>
/// <param name="pluginInfo">The info of the plugin the icon belongs to</param>
/// <param name="icon">
/// The icon, may be a string representation of a <see cref="PackIconKind" /> or a relative path
/// pointing to a .svg file
/// </param>
/// <returns></returns>
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;
}
}
}

View File

@ -24,9 +24,11 @@
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/MaterialDesignLightTheme.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Scrollbar.xaml" />
<!-- Shared UI -->
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/Resources/ArtemisShared.xaml" />
<ResourceDictionary Source="ResourceDictionaries/SideNavigation.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Some general convertes etc. -->

View File

@ -0,0 +1,49 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:MaterialDesignExtensions.Model;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions">
<DataTemplate DataType="{x:Type model:FirstLevelNavigationItem}">
<Grid Height="48">
<Border Style="{StaticResource navigationItemBackgroundBorderStyle}" />
<Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="16,0,0,0">
<Grid Height="24" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<shared:ArtemisIcon Icon="{Binding Path=Icon}" Height="24" Width="24">
<shared:ArtemisIcon.Style>
<Style TargetType="{x:Type shared:ArtemisIcon}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="{Binding Path=IconForeground, RelativeSource={RelativeSource AncestorType={x:Type controls:SideNavigation}}}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="{Binding Path=SelectionForeground, RelativeSource={RelativeSource AncestorType={x:Type controls:SideNavigation}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</shared:ArtemisIcon.Style>
</shared:ArtemisIcon>
<TextBlock Grid.Column="1" FontSize="14" FontWeight="Bold" Text="{Binding Path=Label}" TextTrimming="CharacterEllipsis"
Margin="32,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Foreground" Value="{Binding Path=LabelForeground, RelativeSource={RelativeSource AncestorType={x:Type controls:SideNavigation}}}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="{Binding Path=SelectionForeground, RelativeSource={RelativeSource AncestorType={x:Type controls:SideNavigation}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</Grid>
</DataTemplate>
</ResourceDictionary>

View File

@ -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}">
<UserControl.Resources>
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -60,7 +63,17 @@
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
<Grid>
@ -69,7 +82,9 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold" Text="[List]"/>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding CountDisplay, Mode=OneWay}"
@ -87,7 +102,7 @@
<!-- Value description -->
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType.Name, Mode=OneWay}" /><Run>]</Run>
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
@ -105,7 +120,7 @@
<!-- Value description -->
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding ListType.Name, Mode=OneWay}" /><Run>]</Run>
<Run>[</Run><Run Text="{Binding ListType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" ToolTip="{Binding PropertyDescription.Description}">
<Run>List item [</Run><Run Text="{Binding Index, Mode=OneWay}" /><Run>]</Run>

View File

@ -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<PackIconKind>(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);
}

View File

@ -32,6 +32,11 @@
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Update="Images\bow.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<Exec Command="echo Copying plugin to Artemis.UI output directory&#xD;&#xA;XCOPY &quot;$(TargetDir.TrimEnd('\'))&quot; &quot;$(SolutionDir)\Artemis.UI\$(OutDir)Plugins\$(ProjectName)&quot; /s /q /i /y" />

View File

@ -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<ModuleTab> {new ModuleTab<GeneralViewModel>("General")};

View File

@ -0,0 +1,44 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="242.000000pt" height="341.000000pt" viewBox="0 0 242.000000 341.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.15, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,341.000000) scale(0.100000,-0.100000)"
fill="#ffffff" stroke="none">
<path d="M46 3388 l-48 -22 5 -47 c15 -121 99 -212 249 -268 l75 -28 1 36 c1
20 4 71 8 114 3 46 2 77 -3 77 -16 0 -33 49 -33 94 0 40 -3 45 -31 55 -54 19
-168 13 -223 -11z"/>
<path d="M378 3281 c-24 -19 -25 -26 -31 -173 -3 -84 -24 -409 -47 -723 -22
-313 -44 -628 -48 -700 -5 -71 -10 -111 -11 -88 l-1 42 -85 -37 c-46 -20 -85
-42 -85 -47 0 -6 38 -19 85 -29 l85 -19 0 -43 c0 -57 14 -92 44 -105 l25 -12
-40 -79 c-22 -44 -39 -81 -37 -83 5 -5 472 215 484 228 6 7 21 51 34 98 l22
86 615 288 c337 158 615 286 617 284 2 -2 -9 -49 -25 -103 -16 -55 -27 -102
-24 -104 7 -8 144 108 235 198 92 91 230 263 230 286 0 27 -334 14 -562 -22
-60 -10 -108 -20 -108 -24 0 -4 45 -27 100 -52 l100 -45 -172 -81 c-684 -321
-1017 -476 -1042 -485 -24 -8 -44 -5 -110 18 l-82 28 -98 -46 c-55 -26 -101
-45 -103 -43 -5 5 87 1349 93 1355 2 2 4 -12 4 -31 0 -29 4 -37 23 -41 12 -4
132 -18 267 -32 333 -35 482 -69 585 -133 62 -38 86 -63 111 -117 33 -69 31
-135 -6 -247 l-30 -89 77 -110 c42 -61 80 -112 84 -115 4 -2 55 18 113 45
l105 50 -49 73 -48 74 41 84 c41 81 42 86 42 184 0 96 -2 104 -34 163 -34 64
-118 154 -184 199 -21 14 -81 46 -135 72 l-97 47 -325 6 c-179 4 -358 12 -397
18 -40 7 -78 9 -84 6 -6 -4 -20 -21 -31 -39 -24 -37 -22 -30 16 57 l29 68 -28
29 c-34 35 -71 39 -107 11z"/>
<path d="M1730 2035 c-52 -25 -99 -46 -103 -48 -5 -1 8 -66 28 -144 l37 -141
45 -12 c115 -28 217 -106 244 -186 47 -138 -16 -306 -236 -635 -74 -110 -147
-221 -162 -246 -26 -45 -26 -46 -7 -60 34 -27 98 -53 129 -52 27 0 37 11 103
112 40 62 105 151 145 198 39 48 110 131 155 185 100 119 116 148 149 273 32
125 34 267 6 357 -47 145 -150 232 -314 266 -34 7 -63 17 -65 23 -2 5 -13 43
-24 83 -12 39 -25 72 -28 72 -4 0 -50 -20 -102 -45z"/>
<path d="M450 1273 l-35 -15 25 -17 c13 -9 123 -86 245 -172 121 -85 360 -254
530 -374 171 -120 352 -251 403 -292 79 -62 98 -72 120 -67 61 15 79 42 64 99
-8 27 -17 33 -113 65 -103 35 -109 39 -641 413 -296 207 -543 377 -550 376 -7
0 -29 -8 -48 -16z"/>
<path d="M1486 380 c-29 -90 -34 -190 -12 -253 17 -46 65 -117 85 -125 20 -8
117 45 147 81 14 16 36 51 49 77 l24 48 -44 45 c-25 24 -45 53 -45 64 0 13
-26 38 -77 75 -43 30 -83 58 -89 61 -7 4 -21 -22 -38 -73z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB