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

Added a Material-styled scrollbar

Cleaned up reorder code
Reorganised profile editor layout and added panel titles
This commit is contained in:
Robert 2019-11-26 23:31:41 +01:00
parent b8fbb3fa24
commit 06f014a294
31 changed files with 897 additions and 369 deletions

View File

@ -15,7 +15,7 @@ namespace Artemis.Core.Models.Profile
{
public sealed class Layer : ProfileElement
{
internal Layer(Profile profile, ProfileElement parent, string name)
public Layer(Profile profile, ProfileElement parent, string name)
{
LayerEntity = new LayerEntity();
EntityId = Guid.NewGuid();

View File

@ -26,8 +26,8 @@ namespace Artemis.Core.Services
internal RgbService(ILogger logger, ISettingsService settingsService)
{
_logger = logger;
_renderScaleSetting = settingsService.GetSetting("RenderScale", 1.0);
_targetFrameRateSetting = settingsService.GetSetting("TargetFrameRate", 25);
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 1.0);
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
Surface = RGBSurface.Instance;

View File

@ -31,7 +31,7 @@ namespace Artemis.Core.Services.Storage
_rgbService = rgbService;
_pluginService = pluginService;
_surfaceConfigurations = new List<Surface>();
_renderScaleSetting = settingsService.GetSetting("RenderScale", 1.0);
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 1.0);
LoadFromRepository();

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Artemis.Storage.Entities;
namespace Artemis.Storage.Repositories.Interfaces

View File

@ -30,6 +30,8 @@
<!-- Include the Dragablz Material Design style -->
<ResourceDictionary Source="pack://application:,,,/Dragablz;component/Themes/materialdesign.xaml" />
<ResourceDictionary Source="ResourceDictionaries/Scrollbar.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- MahApps Brushes -->

View File

@ -153,6 +153,8 @@
<Compile Include="Converters\InverseBooleanConverter.cs" />
<Compile Include="Converters\NullToImageConverter.cs" />
<Compile Include="Converters\NullToVisibilityConverter.cs" />
<Compile Include="DebugTriggers\TriggerTracing.cs" />
<Compile Include="Exceptions\ArtemisCoreException.cs" />
<Compile Include="Extensions\RgbColorExtensions.cs" />
<Compile Include="Extensions\RgbRectangleExtensions.cs" />
<Compile Include="Ninject\Factories\IArtemisUIFactory.cs" />
@ -176,14 +178,11 @@
<Compile Include="Screens\Module\ProfileEditor\ElementProperties\ElementPropertyViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\LayerElements\LayerElementsViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\LayerElements\LayerElementViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\FolderView.xaml.cs">
<DependentUpon>FolderView.xaml</DependentUpon>
</Compile>
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\FolderViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\LayerViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElement\FolderViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElement\LayerViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElementsViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileEditorPanelViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\Abstract\ProfileElementViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElement\ProfileElementViewModel.cs" />
<Compile Include="Screens\Module\ProfileEditor\Visualization\ProfileViewModel.cs" />
<Compile Include="Screens\News\NewsViewModel.cs" />
<Compile Include="Screens\SurfaceEditor\Dialogs\SurfaceDeviceConfigViewModelValidator.cs" />
@ -219,6 +218,10 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="Screens\Settings\SettingsViewModel.cs" />
<Page Include="ResourceDictionaries\Scrollbar.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Module\ProfileEditor\Dialogs\ProfileCreateView.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
@ -251,11 +254,11 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Module\ProfileEditor\ProfileElements\FolderView.xaml">
<Page Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElement\FolderView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Module\ProfileEditor\ProfileElements\LayerView.xaml">
<Page Include="Screens\Module\ProfileEditor\ProfileElements\ProfileElement\LayerView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>

View File

@ -1,5 +1,7 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Artemis.Core.Ninject;
using Artemis.Core.Services.Interfaces;
using Artemis.UI.Ninject;
@ -7,6 +9,7 @@ using Artemis.UI.Screens;
using Artemis.UI.Screens.Splash;
using Artemis.UI.Stylet;
using Ninject;
using Serilog;
using Stylet;
namespace Artemis.UI
@ -31,12 +34,33 @@ namespace Artemis.UI
Task.Run(() =>
{
// Start the Artemis core
_core = Kernel.Get<ICoreService>();
// When the core is done, hide the splash and show the main window
_core.Initialized += (sender, args) => ShowMainWindow(windowManager, splashViewModel);
// While the core is instantiated, start listening for events on the splash
splashViewModel.ListenToEvents();
try
{
// Start the Artemis core
_core = Kernel.Get<ICoreService>();
// When the core is done, hide the splash and show the main window
_core.Initialized += (sender, args) => ShowMainWindow(windowManager, splashViewModel);
// While the core is instantiated, start listening for events on the splash
splashViewModel.ListenToEvents();
}
catch (Exception e)
{
var logger = Kernel.Get<ILogger>();
logger.Fatal(e, "Fatal exception during initialization, shutting down.");
// TODO: A proper exception viewer
Execute.OnUIThread(() =>
{
windowManager.ShowMessageBox(e.Message + "\n\n Please refer the log file for more details.",
"Fatal exception during initialization",
MessageBoxButton.OK,
MessageBoxImage.Error
);
Environment.Exit(1);
});
throw;
}
});
}
@ -59,5 +83,13 @@ namespace Artemis.UI
kernel.Load<CoreModule>();
base.ConfigureIoC(kernel);
}
protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{
var logger = Kernel.Get<ILogger>();
logger.Fatal(e.Exception, "Fatal exception, shutting down.");
base.OnUnhandledException(e);
}
}
}

View File

@ -0,0 +1,208 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media.Animation;
// Code from http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html
// No license specified - this code is trimmed out from Release build anyway so it should be ok using it this way
// HOWTO: add the following attached property to any trigger and you will see when it is activated/deactivated in the output window
// TriggerTracing.TriggerName="your debug name"
// TriggerTracing.TraceEnabled="True"
// Example:
// <Trigger my:TriggerTracing.TriggerName="BoldWhenMouseIsOver"
// my:TriggerTracing.TraceEnabled="True"
// Property="IsMouseOver"
// Value="True">
// <Setter Property = "FontWeight" Value="Bold"/>
// </Trigger>
//
// As this works on anything that inherits from TriggerBase, it will also work on <MultiTrigger>.
namespace Artemis.UI.DebugTriggers
{
#if DEBUG
/// <summary>
/// Contains attached properties to activate Trigger Tracing on the specified Triggers.
/// This file alone should be dropped into your app.
/// </summary>
public static class TriggerTracing
{
static TriggerTracing()
{
// Initialise WPF Animation tracing and add a TriggerTraceListener
PresentationTraceSources.Refresh();
PresentationTraceSources.AnimationSource.Listeners.Clear();
PresentationTraceSources.AnimationSource.Listeners.Add(new TriggerTraceListener());
PresentationTraceSources.AnimationSource.Switch.Level = SourceLevels.All;
}
private enum TriggerTraceStoryboardType
{
Enter,
Exit
}
/// <summary>
/// A dummy storyboard for tracing purposes
/// </summary>
private class TriggerTraceStoryboard : Storyboard
{
public TriggerTraceStoryboard(TriggerBase triggerBase, TriggerTraceStoryboardType storyboardType)
{
TriggerBase = triggerBase;
StoryboardType = storyboardType;
}
public TriggerTraceStoryboardType StoryboardType { get; }
public TriggerBase TriggerBase { get; }
}
/// <summary>
/// A custom tracelistener.
/// </summary>
private class TriggerTraceListener : TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
base.TraceEvent(eventCache, source, eventType, id, format, args);
if (format.StartsWith("Storyboard has begun;"))
{
var storyboard = args[1] as TriggerTraceStoryboard;
if (storyboard != null)
{
// add a breakpoint here to see when your trigger has been
// entered or exited
// the element being acted upon
var targetElement = args[5];
// the namescope of the element being acted upon
var namescope = (INameScope) args[7];
var triggerBase = storyboard.TriggerBase;
var triggerName = GetTriggerName(storyboard.TriggerBase);
Debug.WriteLine("Element: {0}, {1}: {2}: {3}", targetElement, triggerBase.GetType().Name, triggerName, storyboard.StoryboardType);
}
}
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
#region TriggerName attached property
/// <summary>
/// Gets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static string GetTriggerName(TriggerBase trigger)
{
return (string) trigger.GetValue(TriggerNameProperty);
}
/// <summary>
/// Sets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static void SetTriggerName(TriggerBase trigger, string value)
{
trigger.SetValue(TriggerNameProperty, value);
}
public static readonly DependencyProperty TriggerNameProperty =
DependencyProperty.RegisterAttached(
"TriggerName",
typeof(string),
typeof(TriggerTracing),
new UIPropertyMetadata(string.Empty));
#endregion
#region TraceEnabled attached property
/// <summary>
/// Gets a value indication whether trace is enabled for the specified trigger.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static bool GetTraceEnabled(TriggerBase trigger)
{
return (bool) trigger.GetValue(TraceEnabledProperty);
}
/// <summary>
/// Sets a value specifying whether trace is enabled for the specified trigger
/// </summary>
/// <param name="trigger"></param>
/// <param name="value"></param>
public static void SetTraceEnabled(TriggerBase trigger, bool value)
{
trigger.SetValue(TraceEnabledProperty, value);
}
public static readonly DependencyProperty TraceEnabledProperty =
DependencyProperty.RegisterAttached(
"TraceEnabled",
typeof(bool),
typeof(TriggerTracing),
new UIPropertyMetadata(false, OnTraceEnabledChanged));
private static void OnTraceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var triggerBase = d as TriggerBase;
if (triggerBase == null)
return;
if (!(e.NewValue is bool))
return;
if ((bool) e.NewValue)
{
// insert dummy story-boards which can later be traced using WPF animation tracing
var storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Enter);
triggerBase.EnterActions.Insert(0, new BeginStoryboard {Storyboard = storyboard});
storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Exit);
triggerBase.ExitActions.Insert(0, new BeginStoryboard {Storyboard = storyboard});
}
else
{
// remove the dummy storyboards
foreach (var actionCollection in new[] {triggerBase.EnterActions, triggerBase.ExitActions})
{
foreach (var triggerAction in actionCollection)
{
var bsb = triggerAction as BeginStoryboard;
if (bsb != null && bsb.Storyboard != null && bsb.Storyboard is TriggerTraceStoryboard)
{
actionCollection.Remove(bsb);
break;
}
}
}
}
}
#endregion
}
#endif
}

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.UI.Exceptions
{
// ReSharper disable once InconsistentNaming
public class ArtemisUIException : Exception
{
public ArtemisUIException()
{
}
public ArtemisUIException(string message) : base(message)
{
}
public ArtemisUIException(string message, Exception inner) : base(message, inner)
{
}
}
}

View File

@ -0,0 +1,142 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="StandardBorderBrush" Color="#888" />
<SolidColorBrush x:Key="StandardBackgroundBrush" Color="#FFF" />
<SolidColorBrush x:Key="HoverBorderBrush" Color="#DDD" />
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="Gray" />
<SolidColorBrush x:Key="SelectedForegroundBrush" Color="White" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<SolidColorBrush x:Key="NormalBrush" Color="#888" />
<SolidColorBrush x:Key="NormalBorderBrush" Color="#888" />
<SolidColorBrush x:Key="HorizontalNormalBrush" Color="#888" />
<SolidColorBrush x:Key="HorizontalNormalBorderBrush" Color="#888" />
<LinearGradientBrush x:Key="ListBoxBackgroundBrush" StartPoint="0,0" EndPoint="1,0.001">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="White" Offset="0.0" />
<GradientStop Color="White" Offset="0.6" />
<GradientStop Color="#DDDDDD" Offset="1.2" />
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="StandardBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0" />
<GradientStop Color="#CCC" Offset="1.0" />
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />
<LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#BBB" Offset="0.0" />
<GradientStop Color="#EEE" Offset="0.1" />
<GradientStop Color="#EEE" Offset="0.9" />
<GradientStop Color="#FFF" Offset="1.0" />
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<Style x:Key="ScrollBarPageButton" TargetType="{x:Type RepeatButton}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Focusable" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RepeatButton}">
<Border Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ScrollBarThumb" TargetType="{x:Type Thumb}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="IsTabStop" Value="false" />
<Setter Property="Focusable" Value="false" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border CornerRadius="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="VerticalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Track Name="PART_Track" IsDirectionReversed="true">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageUpCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}" Margin="1" Background="{StaticResource HorizontalNormalBrush}" BorderBrush="{StaticResource HorizontalNormalBorderBrush}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageDownCommand" />
</Track.IncreaseRepeatButton>
</Track>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Track Name="PART_Track" Grid.Column="1" IsDirectionReversed="False">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageLeftCommand" />
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumb}" Margin="1" Background="{StaticResource NormalBrush}" BorderBrush="{StaticResource NormalBorderBrush}" />
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource ScrollBarPageButton}" Command="ScrollBar.PageRightCommand" />
</Track.IncreaseRepeatButton>
</Track>
</Grid>
</ControlTemplate>
<Style x:Key="{x:Type ScrollBar}" TargetType="{x:Type ScrollBar}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Style.Triggers>
<Trigger Property="Orientation" Value="Horizontal">
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="10" />
<Setter Property="Template" Value="{StaticResource HorizontalScrollBar}" />
</Trigger>
<Trigger Property="Orientation" Value="Vertical">
<Setter Property="Width" Value="10" />
<Setter Property="Height" Value="Auto" />
<Setter Property="Template" Value="{StaticResource VerticalScrollBar}" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type ScrollViewer}" TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.ColumnSpan="2" Grid.RowSpan="2" />
<ScrollBar Grid.Column="1" Name="PART_VerticalScrollBar" Value="{TemplateBinding VerticalOffset}" Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<ScrollBar Grid.Row="1" Name="PART_HorizontalScrollBar" Orientation="Horizontal" Value="{TemplateBinding HorizontalOffset}" Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@ -6,5 +6,12 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid />
<Grid>
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignSubheadingTextBlock}" Margin="10 5 0 -4">
Display conditions
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -6,5 +6,12 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ElementProperties"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid />
<Grid>
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignSubheadingTextBlock}" Margin="10 5 0 -4">
Layer element properties
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -6,5 +6,12 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.LayerElements"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid />
<Grid>
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignSubheadingTextBlock}" Margin="10 5 0 -4">
Layer elements
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -8,7 +8,7 @@
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type profileEditor:ProfileEditorViewModel}}">
d:DataContext="{x:Type profileEditor:ProfileEditorViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@ -22,64 +22,113 @@
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="{Binding ProfileElementsWidth.Value, Mode=TwoWay}" MinWidth="100" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="100" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="200" />
<RowDefinition Height="{Binding LayerElementsHeight.Value, Mode=TwoWay}" MinHeight="100" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<TextBlock>
<materialDesign:PackIcon Kind="AboutOutline" Margin="0 0 0 -3" /> The profile defines what colors the LEDs will have. Multiple groups of LEDs are defined into layers. On these layers you can apply effects.
</TextBlock>
<Border BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1" Margin="0 9 0 8" />
</StackPanel>
<Grid Grid.Row="0" Grid.Column="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox Name="LocaleCombo"
Height="26"
VerticalAlignment="Top"
ItemsSource="{Binding Profiles}"
SelectedItem="{Binding SelectedProfile}"
DisplayMemberPath="Name">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
<Button Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
Margin="10 0"
Grid.Column="1"
Width="26"
Height="26"
VerticalAlignment="Top"
Command="{s:Action AddProfile}">
<materialDesign:PackIcon Kind="Add" Height="14" Width="14" />
</Button>
<Button Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
Grid.Column="2"
Width="26"
Height="26"
VerticalAlignment="Top"
Command="{s:Action DeleteActiveProfile}">
<materialDesign:PackIcon Kind="TrashCanOutline" Height="14" Width="14" />
</Button>
<!-- Left side -->
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<!-- Introduction -->
<RowDefinition Height="Auto"/>
<!-- Design area -->
<RowDefinition Height="*" MinHeight="200"/>
<!-- Bottom panels resize -->
<RowDefinition Height="Auto"/>
<!-- Bottom panels -->
<RowDefinition Height="{Binding BottomPanelsHeight.Value, Mode=TwoWay}" MinHeight="100"/>
</Grid.RowDefinitions>
<!-- Introduction -->
<StackPanel Grid.Row="0" Margin="0 0 15 0">
<TextBlock>
<materialDesign:PackIcon Kind="AboutOutline" Margin="0 0 0 -4" /> The profile defines what colors the LEDs will have. Multiple groups of LEDs are defined into layers. On these layers you can apply effects.
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" />
</StackPanel>
<!-- Design area -->
<materialDesign:Card Grid.Row="1" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding ProfileViewModel}" />
</materialDesign:Card>
<!-- Bottom panels resize -->
<GridSplitter Grid.Row="2" Grid.Column="0" Height="5" HorizontalAlignment="Stretch" Cursor="SizeNS" Margin="0 5" />
<!-- Bottom panels -->
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{Binding ElementPropertiesWidth.Value, Mode=TwoWay}" MinWidth="100" />
</Grid.ColumnDefinitions>
<!-- Layer elements -->
<materialDesign:Card Grid.Column="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding LayerElementsViewModel}" />
</materialDesign:Card>
<!-- Element properties resize -->
<GridSplitter Grid.Column="1" Width="5" Margin="5 0" HorizontalAlignment="Stretch" Cursor="SizeWE" />
<!-- Element properties -->
<materialDesign:Card Grid.Column="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding ElementPropertiesViewModel}" />
</materialDesign:Card>
</Grid>
</Grid>
<!-- Right panels resize -->
<GridSplitter Grid.Row="0" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Cursor="SizeWE" Margin="5 0" />
<!-- Right side -->
<Grid Grid.Row="0" Grid.Column="2">
<!-- Profile selection -->
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox Name="LocaleCombo"
Height="26"
VerticalAlignment="Top"
ItemsSource="{Binding Profiles}"
SelectedItem="{Binding SelectedProfile}"
DisplayMemberPath="Name">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
<Button Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
Margin="10 0"
Grid.Column="1"
Width="26"
Height="26"
VerticalAlignment="Top"
Command="{s:Action AddProfile}">
<materialDesign:PackIcon Kind="Add" Height="14" Width="14" />
</Button>
<Button Style="{StaticResource MaterialDesignFloatingActionMiniDarkButton}"
Grid.Column="2"
Width="26"
Height="26"
VerticalAlignment="Top"
Command="{s:Action DeleteActiveProfile}">
<materialDesign:PackIcon Kind="TrashCanOutline" Height="14" Width="14" />
</Button>
</Grid>
</Grid>
<!-- Design area -->
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding ProfileViewModel}" />
</materialDesign:Card>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Cursor="SizeWE" Margin="5 0" />
@ -88,7 +137,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="{Binding DisplayConditionsHeight.Value, Mode=TwoWay}" MinHeight="100" />
</Grid.RowDefinitions>
<materialDesign:Card Grid.Row="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
@ -102,24 +151,8 @@
</materialDesign:Card>
</Grid>
<GridSplitter Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Height="5" HorizontalAlignment="Stretch" Cursor="SizeNS" Margin="0 5" />
<GridSplitter Grid.Row="2" Grid.Column="0" Height="5" HorizontalAlignment="Stretch" Cursor="SizeNS" Margin="0 5" />
<!-- Bottom panels -->
<Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<materialDesign:Card Grid.Column="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding LayerElementsViewModel}" />
</materialDesign:Card>
<GridSplitter Grid.Column="1" Width="5" Margin="5 0" HorizontalAlignment="Stretch" Cursor="SizeWE" />
<materialDesign:Card Grid.Column="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
<ContentControl s:View.Model="{Binding ElementPropertiesViewModel}" />
</materialDesign:Card>
</Grid>
</Grid>
</UserControl>

View File

@ -2,8 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Artemis.Core.Models.Profile;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services;
using Artemis.Core.Services.Storage.Interfaces;
using Artemis.UI.Screens.Module.ProfileEditor.Dialogs;
using Artemis.UI.Screens.Module.ProfileEditor.DisplayConditions;
@ -19,10 +22,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor
public class ProfileEditorViewModel : Conductor<ProfileEditorPanelViewModel>.Collection.AllActive
{
private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
public ProfileEditorViewModel(ProfileModule module, ICollection<ProfileEditorPanelViewModel> viewModels, IProfileService profileService, IDialogService dialogService)
public ProfileEditorViewModel(ProfileModule module, ICollection<ProfileEditorPanelViewModel> viewModels, IProfileService profileService,
IDialogService dialogService, ISettingsService settingsService)
{
_profileService = profileService;
_settingsService = settingsService;
DisplayName = "Profile editor";
Module = module;
@ -47,9 +53,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor
public LayerElementsViewModel LayerElementsViewModel { get; }
public ProfileElementsViewModel ProfileElementsViewModel { get; }
public ProfileViewModel ProfileViewModel { get; }
public BindableCollection<Profile> Profiles { get; set; }
public PluginSetting<GridLength> ProfileElementsWidth { get; set; }
public PluginSetting<GridLength> DisplayConditionsHeight { get; set; }
public PluginSetting<GridLength> LayerElementsHeight { get; set; }
public PluginSetting<GridLength> ElementPropertiesWidth { get; set; }
public Profile SelectedProfile
{
get => Module.ActiveProfile;
@ -126,6 +136,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor
protected override void OnActivate()
{
LoadWorkspaceSettings();
Task.Run(() =>
{
LoadProfiles();
@ -138,6 +149,28 @@ namespace Artemis.UI.Screens.Module.ProfileEditor
base.OnActivate();
}
protected override void OnDeactivate()
{
SaveWorkspaceSettings();
base.OnDeactivate();
}
private void LoadWorkspaceSettings()
{
ProfileElementsWidth = _settingsService.GetSetting("ProfileEditor.ProfileElementsWidth", new GridLength(550));
DisplayConditionsHeight = _settingsService.GetSetting("ProfileEditor.DisplayConditionsHeight", new GridLength(320));
LayerElementsHeight = _settingsService.GetSetting("ProfileEditor.LayerElementsHeight", new GridLength(350));
ElementPropertiesWidth = _settingsService.GetSetting("ProfileEditor.ElementPropertiesWidth", new GridLength(920));
}
private void SaveWorkspaceSettings()
{
ProfileElementsWidth.Save();
DisplayConditionsHeight.Save();
LayerElementsHeight.Save();
ElementPropertiesWidth.Save();
}
private void LoadProfiles()
{
// Get all profiles from the database

View File

@ -1,95 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Models.Profile.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.Dialogs;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.Abstract
{
public abstract class ProfileElementViewModel : PropertyChangedBase
{
protected ProfileElementViewModel(FolderViewModel parent, ProfileElement profileElement, ProfileEditorViewModel profileEditorViewModel)
{
Parent = parent;
ProfileElement = profileElement;
ProfileEditorViewModel = profileEditorViewModel;
Children = new BindableCollection<ProfileElementViewModel>();
}
public FolderViewModel Parent { get; set; }
public ProfileEditorViewModel ProfileEditorViewModel { get; set; }
public ProfileElement ProfileElement { get; set; }
public BindableCollection<ProfileElementViewModel> Children { get; set; }
public virtual void UpdateProfileElements()
{
// Order the children
var vmsList = Children.OrderBy(v => v.ProfileElement.Order).ToList();
for (var index = 0; index < vmsList.Count; index++)
{
var profileElementViewModel = vmsList[index];
Children.Move(Children.IndexOf(profileElementViewModel), index);
}
}
public void SetElementInFront(ProfileElementViewModel source)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.Folder.RemoveChild(source.ProfileElement);
Parent.Folder.AddChild(source.ProfileElement, ProfileElement.Order);
Parent.UpdateProfileElements();
}
public void SetElementBehind(ProfileElementViewModel source)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.Folder.RemoveChild(source.ProfileElement);
Parent.Folder.AddChild(source.ProfileElement, ProfileElement.Order + 1);
Parent.UpdateProfileElements();
}
public async Task RenameElement()
{
var result = await ProfileEditorViewModel.DialogService.ShowDialog<ProfileElementRenameViewModel>(
new Dictionary<string, object> {{"profileElement", ProfileElement}}
);
if (result is string newName)
{
ProfileElement.Name = newName;
ProfileEditorViewModel.OnProfileUpdated();
}
}
public async Task DeleteElement()
{
var result = await ProfileEditorViewModel.DialogService.ShowConfirmDialog(
"Delete profile element",
"Are you sure you want to delete this element? This cannot be undone."
);
if (!result)
return;
// Farewell, cruel world
var parent = Parent;
ProfileElement.Parent.RemoveChild(ProfileElement);
parent.RemoveExistingElement(this);
parent.UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
}
}

View File

@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
{
/// <summary>
/// Interaction logic for FolderView.xaml
/// </summary>
public partial class FolderView : UserControl
{
public FolderView()
{
InitializeComponent();
}
}
}

View File

@ -1,75 +0,0 @@
using System.Linq;
using Artemis.Core.Models.Profile;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.Abstract;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
{
public class FolderViewModel : ProfileElementViewModel
{
public FolderViewModel(FolderViewModel parent, Folder folder, ProfileEditorViewModel profileEditorViewModel) : base(parent, folder, profileEditorViewModel)
{
Folder = folder;
UpdateProfileElements();
}
public Folder Folder { get; }
public void AddFolder()
{
Folder.AddFolder("New folder");
UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
public void AddLayer()
{
Folder.AddLayer("New layer");
UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
public void RemoveExistingElement(ProfileElementViewModel element)
{
Folder.RemoveChild(element.ProfileElement);
Children.Remove(element);
element.Parent = null;
}
public void AddExistingElement(ProfileElementViewModel element)
{
Folder.AddChild(element.ProfileElement);
Children.Add(element);
element.Parent = this;
}
public sealed override void UpdateProfileElements()
{
// Ensure every child element has an up-to-date VM
if (Folder.Children != null)
{
foreach (var profileElement in Folder.Children.OrderBy(c => c.Order))
{
ProfileElementViewModel existing = null;
if (profileElement is Folder folder)
{
existing = Children.FirstOrDefault(p => p is FolderViewModel vm && vm.Folder == folder);
if (existing == null)
Children.Add(new FolderViewModel(this, folder, ProfileEditorViewModel));
}
else if (profileElement is Layer layer)
{
existing = Children.FirstOrDefault(p => p is LayerViewModel vm && vm.Layer == layer);
if (existing == null)
Children.Add(new LayerViewModel(this, layer, ProfileEditorViewModel));
}
existing?.UpdateProfileElements();
}
}
base.UpdateProfileElements();
}
}
}

View File

@ -1,15 +0,0 @@
using Artemis.Core.Models.Profile;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.Abstract;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
{
public class LayerViewModel : ProfileElementViewModel
{
public LayerViewModel(FolderViewModel parent, Layer layer, ProfileEditorViewModel profileEditorViewModel) : base(parent, layer, profileEditorViewModel)
{
Layer = layer;
}
public Layer Layer { get; }
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.FolderView"
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement.FolderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,10 +6,11 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:profileElement="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:FolderViewModel}}">
<!-- Margin is a bit hacky but the tree view puts a margin on 10 on us which when clicked outside of doesn't trigger the context menu -->
d:DataContext="{d:DesignInstance {x:Type profileElement:FolderViewModel}}">
<!-- Capture clicks on full tree view item -->
<StackPanel Margin="-10" Background="Transparent">
<StackPanel.ContextMenu>
<ContextMenu>
@ -37,8 +38,10 @@
</ContextMenu>
</StackPanel.ContextMenu>
<StackPanel Orientation="Horizontal" Margin="10">
<materialDesign:PackIcon Kind="Folder" />
<TextBlock Text="{Binding Folder.Name}" Margin="10 0 0 0" />
<materialDesign:PackIcon Kind="Folder" Visibility="{Binding IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}"/>
<materialDesign:PackIcon Kind="FolderOpen" Visibility="{Binding IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}"/>
<TextBlock Text="{Binding ProfileElement.Name}" Margin="10 0 0 0" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,12 @@
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement
{
public class FolderViewModel : ProfileElementViewModel
{
public FolderViewModel(ProfileElementViewModel parent, Core.Models.Profile.Abstract.ProfileElement folder, ProfileEditorViewModel profileEditorViewModel)
: base(parent, folder, profileEditorViewModel)
{
}
public override bool SupportsChildren => true;
}
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.LayerView"
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement.LayerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -6,9 +6,11 @@
xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:profileElement="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:LayerViewModel}}">
d:DataContext="{d:DesignInstance {x:Type profileElement:LayerViewModel}}">
<!-- Capture clicks on full tree view item -->
<StackPanel Margin="-10" Background="Transparent">
<StackPanel.ContextMenu>
<ContextMenu>
@ -26,7 +28,7 @@
</StackPanel.ContextMenu>
<StackPanel Orientation="Horizontal" Margin="10">
<materialDesign:PackIcon Kind="Layers" />
<TextBlock Text="{Binding Layer.Name}" Margin="10 0 0 0" />
<TextBlock Text="{Binding ProfileElement.Name}" Margin="10 0 0 0" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,12 @@
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement
{
public class LayerViewModel : ProfileElementViewModel
{
public LayerViewModel(ProfileElementViewModel parent, Core.Models.Profile.Abstract.ProfileElement layer, ProfileEditorViewModel profileEditorViewModel)
: base(parent, layer, profileEditorViewModel)
{
}
public override bool SupportsChildren => false;
}
}

View File

@ -0,0 +1,163 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Models.Profile;
using Artemis.UI.Exceptions;
using Artemis.UI.Screens.Module.ProfileEditor.Dialogs;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement
{
public abstract class ProfileElementViewModel : PropertyChangedBase
{
protected ProfileElementViewModel(ProfileElementViewModel parent, Core.Models.Profile.Abstract.ProfileElement profileElement, ProfileEditorViewModel profileEditorViewModel)
{
Parent = parent;
ProfileElement = profileElement;
ProfileEditorViewModel = profileEditorViewModel;
Children = new BindableCollection<ProfileElementViewModel>();
UpdateProfileElements();
}
public abstract bool SupportsChildren { get; }
public ProfileElementViewModel Parent { get; set; }
public ProfileEditorViewModel ProfileEditorViewModel { get; set; }
public Core.Models.Profile.Abstract.ProfileElement ProfileElement { get; set; }
public BindableCollection<ProfileElementViewModel> Children { get; set; }
public void SetElementInFront(ProfileElementViewModel source)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.ProfileElement.RemoveChild(source.ProfileElement);
Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order);
Parent.UpdateProfileElements();
}
public void SetElementBehind(ProfileElementViewModel source)
{
if (source.Parent != Parent)
{
source.Parent.RemoveExistingElement(source);
Parent.AddExistingElement(source);
}
Parent.ProfileElement.RemoveChild(source.ProfileElement);
Parent.ProfileElement.AddChild(source.ProfileElement, ProfileElement.Order + 1);
Parent.UpdateProfileElements();
}
public void RemoveExistingElement(ProfileElementViewModel element)
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot remove a child from a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.RemoveChild(element.ProfileElement);
Children.Remove(element);
element.Parent = null;
}
public void AddExistingElement(ProfileElementViewModel element)
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a child to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(element.ProfileElement);
Children.Add(element);
element.Parent = this;
}
public void AddFolder()
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a folder to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(new Folder(ProfileElement.Profile, ProfileElement, "New folder"));
UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
public void AddLayer()
{
if (!SupportsChildren)
throw new ArtemisUIException("Cannot add a layer to a profile element of type " + ProfileElement.GetType().Name);
ProfileElement.AddChild(new Layer(ProfileElement.Profile, ProfileElement, "New layer"));
UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
// ReSharper disable once UnusedMember.Global - Called from view
public async Task RenameElement()
{
var result = await ProfileEditorViewModel.DialogService.ShowDialog<ProfileElementRenameViewModel>(
new Dictionary<string, object> {{"profileElement", ProfileElement}}
);
if (result is string newName)
{
ProfileElement.Name = newName;
ProfileEditorViewModel.OnProfileUpdated();
}
}
// ReSharper disable once UnusedMember.Global - Called from view
public async Task DeleteElement()
{
var result = await ProfileEditorViewModel.DialogService.ShowConfirmDialog(
"Delete profile element",
"Are you sure you want to delete this element? This cannot be undone."
);
if (!result)
return;
// Farewell, cruel world
var parent = Parent;
ProfileElement.Parent.RemoveChild(ProfileElement);
parent.RemoveExistingElement(this);
parent.UpdateProfileElements();
ProfileEditorViewModel.OnProfileUpdated();
}
private void UpdateProfileElements()
{
// Order the children
var vmsList = Children.OrderBy(v => v.ProfileElement.Order).ToList();
for (var index = 0; index < vmsList.Count; index++)
{
var profileElementViewModel = vmsList[index];
Children.Move(Children.IndexOf(profileElementViewModel), index);
}
// Ensure every child element has an up-to-date VM
if (ProfileElement.Children != null)
{
foreach (var profileElement in ProfileElement.Children.OrderBy(c => c.Order))
{
ProfileElementViewModel existing = null;
if (profileElement is Folder folder)
{
existing = Children.FirstOrDefault(p => p is FolderViewModel vm && vm.ProfileElement == folder);
if (existing == null)
Children.Add(new FolderViewModel(this, folder, ProfileEditorViewModel));
}
else if (profileElement is Layer layer)
{
existing = Children.FirstOrDefault(p => p is LayerViewModel vm && vm.ProfileElement == layer);
if (existing == null)
Children.Add(new LayerViewModel(this, layer, ProfileEditorViewModel));
}
existing?.UpdateProfileElements();
}
}
}
}
}

View File

@ -9,36 +9,45 @@
xmlns:s="https://github.com/canton7/Stylet"
xmlns:dd="urn:gong-wpf-dragdrop"
xmlns:profileElements="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements"
xmlns:profileElement="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type profileElements:ProfileElementsViewModel}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TreeView Grid.Row="0"
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignSubheadingTextBlock}" Margin="10 5 0 -4">
Profile elements
</TextBlock>
<Separator Style="{StaticResource MaterialDesignDarkSeparator}" Margin="8 0" />
</StackPanel>
<TreeView Grid.Row="1"
ItemsSource="{Binding RootFolder.Children}"
HorizontalContentAlignment="Stretch"
dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.DropHandler="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type profileEditor:FolderViewModel}" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate DataType="{x:Type profileElement:FolderViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type profileEditor:LayerViewModel}" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate DataType="{x:Type profileElement:LayerViewModel}" ItemsSource="{Binding Children}">
<ContentControl s:View.Model="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<StackPanel HorizontalAlignment="Right" Grid.Row="1" Orientation="Horizontal" Margin="8">
<StackPanel HorizontalAlignment="Right" Grid.Row="2" Orientation="Horizontal" Margin="8">
<Button Style="{StaticResource MaterialDesignToolButton}"
Width="30"
Padding="2 0 2 0"
materialDesign:RippleAssist.IsCentered="True"
ToolTip="Add new folder"
ToolTip="Add new folder to root"
Command="{s:Action AddFolder}">
<materialDesign:PackIcon Kind="CreateNewFolder" />
</Button>
@ -46,7 +55,7 @@
Width="30"
Padding="2 0 2 0"
materialDesign:RippleAssist.IsCentered="True"
ToolTip="Add new layer"
ToolTip="Add new layer to root"
Command="{s:Action AddLayer}">
<materialDesign:PackIcon Kind="LayersPlus" />
</Button>

View File

@ -1,7 +1,7 @@
using System.Linq;
using System.Windows;
using Artemis.Core.Models.Profile;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.Abstract;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileElements.ProfileElement;
using GongSolutions.Wpf.DragDrop;
namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
@ -21,14 +21,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
switch (dragDropType)
{
case DragDropType.FolderAdd:
case DragDropType.Add:
dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight;
dropInfo.Effects = DragDropEffects.Move;
break;
case DragDropType.FolderInsertBefore:
case DragDropType.FolderInsertAfter:
case DragDropType.LayerInsertBefore:
case DragDropType.LayerInsertAfter:
case DragDropType.InsertBefore:
case DragDropType.InsertAfter:
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
break;
@ -39,20 +37,18 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
{
var source = (ProfileElementViewModel) dropInfo.Data;
var target = (ProfileElementViewModel) dropInfo.TargetItem;
var dragDropType = GetDragDropType(dropInfo);
switch (dragDropType)
{
case DragDropType.FolderAdd:
case DragDropType.Add:
source.Parent.RemoveExistingElement(source);
((FolderViewModel) target).AddExistingElement(source);
target.AddExistingElement(source);
break;
case DragDropType.FolderInsertBefore:
case DragDropType.LayerInsertBefore:
case DragDropType.InsertBefore:
target.SetElementInFront(source);
break;
case DragDropType.FolderInsertAfter:
case DragDropType.LayerInsertAfter:
case DragDropType.InsertAfter:
target.SetElementBehind(source);
break;
}
@ -60,11 +56,13 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
ProfileEditorViewModel.OnProfileUpdated();
}
// ReSharper disable once UnusedMember.Global - Called from view
public void AddFolder()
{
RootFolder?.AddFolder();
}
// ReSharper disable once UnusedMember.Global - Called from view
public void AddLayer()
{
RootFolder?.AddLayer();
@ -89,8 +87,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
private static DragDropType GetDragDropType(IDropInfo dropInfo)
{
var source = dropInfo.Data as ProfileElementViewModel;
var target = dropInfo.TargetItem as ProfileElementViewModel;
var source = (ProfileElementViewModel) dropInfo.Data;
var target = (ProfileElementViewModel) dropInfo.TargetItem;
if (source == target)
return DragDropType.None;
@ -102,35 +100,26 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.ProfileElements
parent = parent.Parent;
}
if (target is FolderViewModel)
switch (dropInfo.InsertPosition)
{
if (dropInfo.InsertPosition >= RelativeInsertPosition.TargetItemCenter)
return DragDropType.FolderAdd;
if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem)
return DragDropType.FolderInsertBefore;
return DragDropType.FolderInsertAfter;
case RelativeInsertPosition.AfterTargetItem | RelativeInsertPosition.TargetItemCenter:
case RelativeInsertPosition.BeforeTargetItem | RelativeInsertPosition.TargetItemCenter:
return target.SupportsChildren ? DragDropType.Add : DragDropType.None;
case RelativeInsertPosition.BeforeTargetItem:
return DragDropType.InsertBefore;
case RelativeInsertPosition.AfterTargetItem:
return DragDropType.InsertAfter;
default:
return DragDropType.None;
}
if (target is LayerViewModel)
{
if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem)
return DragDropType.LayerInsertBefore;
if (dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem)
return DragDropType.LayerInsertAfter;
return DragDropType.None;
}
return DragDropType.None;
}
}
public enum DragDropType
{
None,
FolderAdd,
FolderInsertBefore,
FolderInsertAfter,
LayerInsertBefore,
LayerInsertAfter
Add,
InsertBefore,
InsertAfter
}
}

View File

@ -68,14 +68,22 @@
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" >
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
@ -84,10 +92,10 @@
</Style>
</Path.Style>
<Path.Fill>
<SolidColorBrush Color="{StaticResource Accent400}" Opacity="0.65" />
<SolidColorBrush Opacity="0.65" />
</Path.Fill>
<Path.Stroke>
<SolidColorBrush Color="{StaticResource Accent400}"></SolidColorBrush>
<SolidColorBrush />
</Path.Stroke>
</Path>
</Canvas>

View File

@ -43,6 +43,7 @@
MouseDown="{s:Action EditorGridMouseClick}"
MouseMove="{s:Action EditorGridMouseMove}"
Cursor="{Binding Cursor}">
<Grid.Background>
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
<VisualBrush.Visual>
@ -81,6 +82,37 @@
</EventTrigger>
</Grid.Triggers>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Create new layer for selection" Command="{s:Action CreateLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="LayersPlus" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Apply selection to layer" Command="{s:Action ApplyToLayer}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="Selection" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Select all" Command="{s:Action SelectAll}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="SelectAll" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Inverse selection" Command="{s:Action InverseSelection}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="SelectInverse" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Clear selection" Command="{s:Action ClearSelection}" CommandParameter="{Binding}">
<MenuItem.Icon>
<materialDesign:PackIcon Kind="SelectOff" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Grid.ContextMenu>
<Grid Name="EditorDisplayGrid">
<Grid.RenderTransform>
<TransformGroup>
@ -119,8 +151,21 @@
</Path.Fill>
</Path>
<StackPanel ZIndex="1" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10">
<materialDesign:Card Padding="8">
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignCheckBox}">
Highlight selected layer
</CheckBox>
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" Margin="10 0 0 0">
Pause visualization on focus loss
</CheckBox>
</StackPanel>
</materialDesign:Card>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
Margin="0, 0, 15, 15">
Margin="10" ZIndex="1">
<Slider Margin="0,0,14,0"
Orientation="Vertical"
Minimum="10"

View File

@ -35,8 +35,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
ApplySurfaceConfiguration(surfaceService.ActiveSurface);
// Borrow RGB.NET's update trigger but limit the FPS
var targetFpsSetting = settingsService.GetSetting("TargetFrameRate", 25);
var editorTargetFpsSetting = settingsService.GetSetting("EditorTargetFrameRate", 15);
var targetFpsSetting = settingsService.GetSetting("Core.TargetFrameRate", 25);
var editorTargetFpsSetting = settingsService.GetSetting("ProfileEditor.TargetFrameRate", 15);
var targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value);
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps};
_updateTrigger.Update += UpdateLeds;
@ -122,9 +122,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
private Point _mouseDragStartPoint;
// ReSharper disable once UnusedMember.Global - Called from view
public void EditorGridMouseClick(object sender, MouseEventArgs e)
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
{
if (IsPanKeyDown())
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
return;
var position = e.GetPosition((IInputElement) sender);

View File

@ -35,21 +35,21 @@ namespace Artemis.UI.Screens.Settings
public double RenderScale
{
get => _settingsService.GetSetting("RenderScale", 1.0).Value;
get => _settingsService.GetSetting("Core.RenderScale", 1.0).Value;
set
{
_settingsService.GetSetting("RenderScale", 1.0).Value = value;
_settingsService.GetSetting("RenderScale", 1.0).Save();
_settingsService.GetSetting("Core.RenderScale", 1.0).Value = value;
_settingsService.GetSetting("Core.RenderScale", 1.0).Save();
}
}
public int TargetFrameRate
{
get => _settingsService.GetSetting("TargetFrameRate", 25).Value;
get => _settingsService.GetSetting("Core.TargetFrameRate", 25).Value;
set
{
_settingsService.GetSetting("TargetFrameRate", 25).Value = value;
_settingsService.GetSetting("TargetFrameRate", 25).Save();
_settingsService.GetSetting("Core.TargetFrameRate", 25).Value = value;
_settingsService.GetSetting("Core.TargetFrameRate", 25).Save();
}
}

View File

@ -67,13 +67,5 @@ namespace Artemis.UI.Stylet
if (Kernel != null)
Kernel.Dispose();
}
protected override void OnUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{
var logger = Kernel.Get<ILogger>();
logger.Fatal(e.Exception, "Fatal exception, shutting down.");
base.OnUnhandledException(e);
}
}
}