mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Restructured the VM/Vs folder layout while it's still feasible
This commit is contained in:
1 parent
7443c0eda7
commit
368b73ce9c
48 files changed
+397
-469
No files matched your search
@@ -0,0 +1,24 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Dialogs.ConfirmDialogView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Dialogs"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="163.274" d:DesignWidth="254.425"
|
||||
d:DataContext="{d:DesignInstance dialogs:ConfirmDialogViewModel}">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Style="{StaticResource MaterialDesignTitleTextBlock}" Text="{Binding Header}" TextWrapping="Wrap" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" Margin="0 20 0 20" Text="{Binding Text}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0"
|
||||
Command="{s:Action Cancel}" Content="{Binding CancelText}" />
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0"
|
||||
Command="{s:Action Confirm}" Content="{Binding ConfirmText}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using Artemis.UI.ViewModels.Dialogs;
|
||||
|
||||
namespace Artemis.UI.Screens.Dialogs
|
||||
{
|
||||
public class ConfirmDialogViewModel : DialogViewModelBase
|
||||
{
|
||||
public ConfirmDialogViewModel(string header, string text, string confirmText, string cancelText)
|
||||
{
|
||||
Header = header;
|
||||
Text = text;
|
||||
ConfirmText = confirmText;
|
||||
CancelText = cancelText;
|
||||
}
|
||||
|
||||
public string Header { get; }
|
||||
public string Text { get; }
|
||||
public string ConfirmText { get; }
|
||||
public string CancelText { get; }
|
||||
|
||||
public void Confirm()
|
||||
{
|
||||
Session.Close(true);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
Session.Close(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Home.HomeView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="574.026"
|
||||
d:DesignWidth="1029.87"
|
||||
d:DataContext="{d:DesignInstance home:HomeViewModel, IsDesignTimeCreatable=True}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
<!-- throw in some extra colour for our floating action button -->
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.Green.Named.Primary.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Source="/Resources/logo-512.png" Stretch="Uniform" Width="100" Height="100" />
|
||||
<StackPanel Grid.Column="1" Margin="24 0 0 0" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignDisplay1TextBlock}" TextWrapping="Wrap">Welcome to Artemis, RGB on steroids.</TextBlock>
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Command="{x:Static materialDesign:DrawerHost.OpenDrawerCommand}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Binoculars" />
|
||||
<TextBlock Margin="8 0 0 0">EXPLORE</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0 0 0 32">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:Card Width="420" Margin="8 2 4 16" Height="Auto">
|
||||
<Grid VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="155" />
|
||||
<RowDefinition Height="95" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Kind="Discord" Width="140" Height="140"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}" Margin="16 16 16 8">Have a chat</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
VerticalAlignment="Top">
|
||||
<Run
|
||||
Text="If you need help, have some feedback or have any other questions feel free to contact us through any of the following channels" />
|
||||
<Run Text=". " />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Button Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Left"
|
||||
x:Name="GitHubButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://github.com/SpoinkyNL/Artemis">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="GithubCircle" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Right"
|
||||
x:Name="TwitterButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://twitter.com/spoinkynl">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Twitter" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Twitter</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Left"
|
||||
x:Name="DiscordButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://discordapp.com/invite/S3MVaC9">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Discord" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Right"
|
||||
x:Name="MailButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="mailto:spoinky.nl@gmail.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Email" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
<materialDesign:Card Width="420" Margin="8 2 4 16" Height="Auto">
|
||||
<Grid VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="155" />
|
||||
<RowDefinition Height="95" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Kind="GithubCircle" Width="160" Height="160"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}" Margin="16 16 16 8">Open Source</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
VerticalAlignment="Top">
|
||||
<Run
|
||||
Text="This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button, tweet or post about it" />
|
||||
<Run Text=". You can also make your own modules!" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
<Button Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
DockPanel.Dock="Right"
|
||||
x:Name="DonateButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap" Margin="16" VerticalAlignment="Center">
|
||||
<Run
|
||||
Text="Feel like you want to make a donation? It would be gratefully received. Click the button to donate via " />
|
||||
<Run Text="Paypal" /><Run Text="." />
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
<!-- PopupBox could be nice in the future when we actually have some stuff to send ppl to -->
|
||||
<!--<materialDesign:PopupBox Style="{StaticResource MaterialDesignMultiFloatingActionPopupBox}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
Margin="32" Grid.Row="1">
|
||||
--><!-- add the visibility binding https://github.com/ButchersBoy/MaterialDesignInXamlToolkit/issues/723 --><!--
|
||||
<StackPanel Visibility="{Binding Path=IsPopupOpen, ElementName=MyPopupBox, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Button ToolTip="GitHub" Command="{s:Action OpenUrl}" CommandParameter="https://github.com/SpoinkyNL/Artemis">
|
||||
<materialDesign:PackIcon Kind="GithubCircle" Height="20" Width="20" />
|
||||
</Button>
|
||||
<Button ToolTip="Twitter" Command="{s:Action OpenUrl}" CommandParameter="https://github.com/SpoinkyNL/Artemis"
|
||||
Background="{DynamicResource PrimaryHueMidBrush}"
|
||||
Foreground="{DynamicResource PrimaryHueMidForegroundBrush}">
|
||||
<materialDesign:PackIcon Kind="Twitter" />
|
||||
</Button>
|
||||
<Button ToolTip="Chat" Command="{s:Action OpenUrl}" CommandParameter="https://github.com/SpoinkyNL/Artemis">
|
||||
--><!-- mix up the colours by brinking in a named palette (see merged dictionaries at top) --><!--
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{StaticResource GreenPrimary500}" />
|
||||
</Button.Background>
|
||||
<Button.Foreground>
|
||||
<SolidColorBrush Color="{StaticResource GreenPrimary500Foreground}" />
|
||||
</Button.Foreground>
|
||||
<materialDesign:PackIcon Kind="Message" />
|
||||
</Button>
|
||||
<Button ToolTip="Email" Command="{s:Action OpenUrl}" CommandParameter="https://github.com/SpoinkyNL/Artemis"
|
||||
Background="{DynamicResource SecondaryAccentBrush}"
|
||||
Foreground="{DynamicResource SecondaryAccentForegroundBrush}"
|
||||
>
|
||||
<materialDesign:PackIcon Kind="Email" />
|
||||
</Button>
|
||||
<Button ToolTip="Feel like you want to make a donation? It would be gratefully received. Click the button to donate via Pledgie."
|
||||
Command="{s:Action OpenUrl}" CommandParameter="https://github.com/SpoinkyNL/Artemis">
|
||||
<Button.Background>
|
||||
<SolidColorBrush Color="{StaticResource GreenPrimary200}" />
|
||||
</Button.Background>
|
||||
<Button.Foreground>
|
||||
<SolidColorBrush Color="{StaticResource GreenPrimary200Foreground}" />
|
||||
</Button.Foreground>
|
||||
<materialDesign:PackIcon Kind="Gift" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</materialDesign:PopupBox>-->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Home
|
||||
{
|
||||
public class HomeViewModel : Screen, IScreenViewModel
|
||||
{
|
||||
public string Title => "Home";
|
||||
|
||||
public void OpenUrl(string url)
|
||||
{
|
||||
// Don't open anything but valid URIs
|
||||
if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
|
||||
Process.Start(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens
|
||||
{
|
||||
public interface IScreenViewModel : IScreen
|
||||
{
|
||||
string Title { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ModuleRootView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dragablz="http://dragablz.net/winfx/xaml/dragablz"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:module="clr-namespace:Artemis.UI.Screens.Module"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance module:ModuleRootViewModel}">
|
||||
<dragablz:TabablzControl Margin="0 -1 0 0" ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem}" FixedHeaderCount="{Binding FixedHeaderCount}">
|
||||
<dragablz:TabablzControl.HeaderItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</dragablz:TabablzControl.HeaderItemTemplate>
|
||||
<dragablz:TabablzControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" IsTabStop="False" />
|
||||
</DataTemplate>
|
||||
</dragablz:TabablzControl.ContentTemplate>
|
||||
</dragablz:TabablzControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module
|
||||
{
|
||||
public class ModuleRootViewModel : Conductor<ModuleViewModel>.Collection.OneActive
|
||||
{
|
||||
public ModuleRootViewModel(Core.Plugins.Abstract.Module module, IProfileEditorViewModelFactory profileEditorViewModelFactory)
|
||||
{
|
||||
Module = module;
|
||||
|
||||
// Add the profile editor and module VMs
|
||||
var profileEditor = profileEditorViewModelFactory.CreateModuleViewModel(Module);
|
||||
Items.Add(profileEditor);
|
||||
Items.AddRange(Module.GetViewModels());
|
||||
|
||||
// Activate the profile editor
|
||||
ActiveItem = profileEditor;
|
||||
}
|
||||
|
||||
public string Title => Module?.DisplayName;
|
||||
public Core.Plugins.Abstract.Module Module { get; }
|
||||
public int FixedHeaderCount => Items.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.ProfileEditorView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
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}}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="300" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="200" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<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 10" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Design area -->
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch">
|
||||
<Grid ClipToBounds="True"
|
||||
KeyUp="{s:Action EditorGridKeyUp}"
|
||||
KeyDown="{s:Action EditorGridKeyDown}"
|
||||
MouseWheel="{s:Action EditorGridMouseWheel}"
|
||||
MouseUp="{s:Action EditorGridMouseClick}"
|
||||
MouseDown="{s:Action EditorGridMouseClick}"
|
||||
MouseMove="{s:Action EditorGridMouseMove}">
|
||||
<Grid.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform"
|
||||
Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="20" Height="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="LightGray" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="LightGray" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<Grid Name="EditorDisplayGrid">
|
||||
<Grid.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
|
||||
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
|
||||
</TransformGroup>
|
||||
</Grid.RenderTransform>
|
||||
<ItemsControl ItemsSource="{Binding Devices}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Width="{Binding Device.RgbDevice.Size.Width}"
|
||||
Height="{Binding Device.RgbDevice.Size.Height}"
|
||||
s:View.Model="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
Margin="0, 0, 15, 15">
|
||||
<Slider Margin="0,0,14,0"
|
||||
Orientation="Vertical"
|
||||
Minimum="10"
|
||||
Maximum="400"
|
||||
Height="100"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
Value="{Binding PanZoomViewModel.ZoomPercentage}"
|
||||
Style="{StaticResource MaterialDesignDiscreteSlider}" />
|
||||
<Button Command="{s:Action ResetZoomAndPan}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
HorizontalAlignment="Right"
|
||||
ToolTip="Reset zoom & position">
|
||||
<materialDesign:PackIcon Kind="ImageFilterCenterFocus" Height="24" Width="24" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Background="{StaticResource MaterialDesignPaper}" Visibility="{Binding IsInitializing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
Initializing LED visualization...
|
||||
</TextBlock>
|
||||
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeWE" Margin="5 0" />
|
||||
|
||||
<!-- Right panels -->
|
||||
<Grid Grid.Row="1" Grid.Column="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<materialDesign:Card Grid.Row="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Right top</TextBlock>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeNS" Margin="0 5" />
|
||||
|
||||
<materialDesign:Card Grid.Row="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Right bottom</TextBlock>
|
||||
</materialDesign:Card>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Height="5" HorizontalAlignment="Stretch" ShowsPreview="True" 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">
|
||||
<TextBlock>Bottom left</TextBlock>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="5" Margin="5 0" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeWE" />
|
||||
|
||||
<materialDesign:Card Grid.Column="2" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch">
|
||||
<TextBlock>Bottom right</TextBlock>
|
||||
</materialDesign:Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,214 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Storage;
|
||||
using Artemis.UI.Screens.Module.ProfileEditor.Visualization;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
using RGB.NET.Core;
|
||||
using Stylet;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor
|
||||
{
|
||||
public class ProfileEditorViewModel : ModuleViewModel
|
||||
{
|
||||
private readonly TimerUpdateTrigger _updateTrigger;
|
||||
|
||||
public ProfileEditorViewModel(Core.Plugins.Abstract.Module module, ISurfaceService surfaceService, ISettingsService settingsService) : base(module, "Profile Editor")
|
||||
{
|
||||
surfaceService.ActiveSurfaceConfigurationChanged += OnActiveSurfaceConfigurationChanged;
|
||||
Devices = new ObservableCollection<ProfileDeviceViewModel>();
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
});
|
||||
|
||||
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 targetFps = Math.Min(targetFpsSetting.Value, editorTargetFpsSetting.Value);
|
||||
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFps};
|
||||
_updateTrigger.Update += UpdateLeds;
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileDeviceViewModel> Devices { get; set; }
|
||||
public bool IsInitializing { get; private set; }
|
||||
public RectangleGeometry SelectionRectangle { get; set; }
|
||||
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||
|
||||
private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e)
|
||||
{
|
||||
ApplySurfaceConfiguration(e.Surface);
|
||||
}
|
||||
|
||||
private void UpdateLeds(object sender, CustomUpdateData customUpdateData)
|
||||
{
|
||||
if (IsInitializing)
|
||||
IsInitializing = Devices.Any(d => !d.AddedLeds);
|
||||
|
||||
foreach (var profileDeviceViewModel in Devices)
|
||||
profileDeviceViewModel.Update();
|
||||
}
|
||||
|
||||
private void ApplySurfaceConfiguration(Surface surface)
|
||||
{
|
||||
// Make sure all devices have an up-to-date VM
|
||||
foreach (var surfaceDeviceConfiguration in surface.Devices)
|
||||
{
|
||||
// Create VMs for missing devices
|
||||
var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
||||
if (viewModel == null)
|
||||
{
|
||||
// Create outside the UI thread to avoid slowdowns as much as possible
|
||||
var profileDeviceViewModel = new ProfileDeviceViewModel(surfaceDeviceConfiguration);
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
// Gotta call IsInitializing on the UI thread or its never gets picked up
|
||||
IsInitializing = true;
|
||||
Devices.Add(profileDeviceViewModel);
|
||||
});
|
||||
}
|
||||
// Update existing devices
|
||||
else
|
||||
viewModel.Device = surfaceDeviceConfiguration;
|
||||
}
|
||||
|
||||
// Sort the devices by ZIndex
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList())
|
||||
Devices.Move(Devices.IndexOf(device), device.ZIndex - 1);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
_updateTrigger.Start();
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
_updateTrigger.Stop();
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
#region Selection
|
||||
|
||||
private MouseDragStatus _mouseDragStatus;
|
||||
private Point _mouseDragStartPoint;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
StartMouseDrag(position, relative);
|
||||
else
|
||||
StopMouseDrag(position);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
// If holding down Ctrl, pan instead of move/select
|
||||
if (IsPanKeyDown())
|
||||
{
|
||||
Pan(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
if (_mouseDragStatus == MouseDragStatus.Selecting)
|
||||
UpdateSelection(position);
|
||||
}
|
||||
|
||||
private void StartMouseDrag(Point position, Point relative)
|
||||
{
|
||||
_mouseDragStatus = MouseDragStatus.Selecting;
|
||||
_mouseDragStartPoint = position;
|
||||
|
||||
// Any time dragging starts, start with a new rect
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
}
|
||||
|
||||
private void StopMouseDrag(Point position)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
// TODO: Select LEDs
|
||||
|
||||
Mouse.OverrideCursor = null;
|
||||
_mouseDragStatus = MouseDragStatus.None;
|
||||
}
|
||||
|
||||
private void UpdateSelection(Point position)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
lock (Devices)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
|
||||
// TODO: Highlight LEDs
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Panning and zooming
|
||||
|
||||
public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseScroll(sender, e);
|
||||
}
|
||||
|
||||
public void EditorGridKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown)
|
||||
Mouse.OverrideCursor = Cursors.ScrollAll;
|
||||
}
|
||||
|
||||
public void EditorGridKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp)
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
public void Pan(object sender, MouseEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseMove(sender, e);
|
||||
|
||||
// Empty the selection rect since it's shown while mouse is down
|
||||
SelectionRectangle.Rect = Rect.Empty;
|
||||
}
|
||||
|
||||
public void ResetZoomAndPan()
|
||||
{
|
||||
PanZoomViewModel.Reset();
|
||||
}
|
||||
|
||||
private bool IsPanKeyDown()
|
||||
{
|
||||
return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Module.ProfileEditor.Visualization.ProfileDeviceView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:visualization="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileDeviceViewModel}}"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- Device image with fallback -->
|
||||
<Image Source="{Binding Device.RgbDevice.DeviceInfo.Image, Converter={StaticResource NullToImageConverter}, Mode=OneWay}" />
|
||||
|
||||
<Rectangle Fill="{DynamicResource ControlBackgroundBrush}"
|
||||
Stroke="{DynamicResource ControlBorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<!-- LEDs -->
|
||||
<ItemsControl ItemsSource="{Binding Leds}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X, Mode=OneWay}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y, Mode=OneWay}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Width="{Binding Width, Mode=OneWay}" Height="{Binding Height, Mode=OneWay}" s:View.Model="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
{
|
||||
public class ProfileDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
public ProfileDeviceViewModel(Device device)
|
||||
{
|
||||
Device = device;
|
||||
Leds = new ObservableCollection<ProfileLedViewModel>();
|
||||
|
||||
if (Device.RgbDevice != null)
|
||||
Task.Run(AddLedsAsync);
|
||||
}
|
||||
|
||||
public ObservableCollection<ProfileLedViewModel> Leds { get; set; }
|
||||
public Device Device { get; set; }
|
||||
public bool AddedLeds { get; private set; }
|
||||
|
||||
public double X
|
||||
{
|
||||
get => Device.X;
|
||||
set => Device.X = value;
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get => Device.Y;
|
||||
set => Device.Y = value;
|
||||
}
|
||||
|
||||
public int ZIndex
|
||||
{
|
||||
get => Device.ZIndex;
|
||||
set => Device.ZIndex = value;
|
||||
}
|
||||
|
||||
|
||||
public Rect DeviceRectangle => Device.RgbDevice == null
|
||||
? new Rect()
|
||||
: new Rect(X, Y, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height);
|
||||
|
||||
/// <summary>
|
||||
/// Adds LEDs in batches of 5 to avoid UI freezes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task AddLedsAsync()
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var led in Device.RgbDevice.ToList())
|
||||
{
|
||||
Execute.OnUIThreadSync(() => Leds.Add(new ProfileLedViewModel(led)));
|
||||
if (index % 5 == 0)
|
||||
await Task.Delay(1);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
AddedLeds = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the color of all LEDs if finished adding
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
if (!AddedLeds)
|
||||
return;
|
||||
|
||||
foreach (var ledViewModel in Leds)
|
||||
ledViewModel.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<UserControl
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:visualization="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization"
|
||||
x:Class="Artemis.UI.Screens.Module.ProfileEditor.Visualization.ProfileLedView"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type visualization:ProfileLedViewModel}}"
|
||||
d:DesignHeight="25" d:DesignWidth="25">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
</UserControl.Resources>
|
||||
<Canvas Width="{Binding Width, Mode=OneWay}" Height="{Binding Height, Mode=OneWay}">
|
||||
<Canvas.Background>
|
||||
<ImageBrush AlignmentX="Center" AlignmentY="Center" Stretch="Fill"
|
||||
ImageSource="{Binding Led.Image, Converter={StaticResource NullToImageConverter}, Mode=OneWay}" />
|
||||
</Canvas.Background>
|
||||
|
||||
<Path Data="{Binding DisplayGeometry, Mode=OneWay}" ClipToBounds="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" Opacity="0.333" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
<Path Data="{Binding StrokeGeometry, Mode=OneWay}" ClipToBounds="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{Binding DisplayColor, Mode=OneWay}" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Artemis.UI.Extensions;
|
||||
using RGB.NET.Core;
|
||||
using Stylet;
|
||||
using Color = System.Windows.Media.Color;
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
|
||||
{
|
||||
public class ProfileLedViewModel : PropertyChangedBase
|
||||
{
|
||||
public ProfileLedViewModel(Led led)
|
||||
{
|
||||
Led = led;
|
||||
X = Led.LedRectangle.X;
|
||||
Y = Led.LedRectangle.Y;
|
||||
Width = Led.LedRectangle.Width;
|
||||
Height = Led.LedRectangle.Height;
|
||||
|
||||
Execute.OnUIThread(CreateLedGeometry);
|
||||
}
|
||||
|
||||
public Led Led { get; }
|
||||
|
||||
public double X { get; }
|
||||
public double Y { get; }
|
||||
public double Width { get; }
|
||||
public double Height { get; }
|
||||
|
||||
public Geometry DisplayGeometry { get; private set; }
|
||||
public Geometry StrokeGeometry { get; private set; }
|
||||
public Color DisplayColor { get; private set; }
|
||||
|
||||
private void CreateLedGeometry()
|
||||
{
|
||||
switch (Led.Shape)
|
||||
{
|
||||
case Shape.Custom:
|
||||
if (Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateCustomGeometry(2.0);
|
||||
else
|
||||
CreateCustomGeometry(1.0);
|
||||
break;
|
||||
case Shape.Rectangle:
|
||||
if (Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateKeyCapGeometry();
|
||||
else
|
||||
CreateRectangleGeometry();
|
||||
break;
|
||||
case Shape.Circle:
|
||||
CreateCircleGeometry();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// Stroke geometry is the display geometry excluding the inner geometry
|
||||
StrokeGeometry = DisplayGeometry.GetWidenedPathGeometry(new Pen(null, 1.0), 0.1, ToleranceType.Absolute);
|
||||
DisplayGeometry.Freeze();
|
||||
StrokeGeometry.Freeze();
|
||||
}
|
||||
|
||||
private void CreateRectangleGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Led.LedRectangle.Width - 1, Led.LedRectangle.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateCircleGeometry()
|
||||
{
|
||||
DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Led.LedRectangle.Width - 1, Led.LedRectangle.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateKeyCapGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(1, 1, Led.LedRectangle.Width - 2, Led.LedRectangle.Height - 2), 1.6, 1.6);
|
||||
}
|
||||
|
||||
private void CreateCustomGeometry(double deflateAmount)
|
||||
{
|
||||
try
|
||||
{
|
||||
DisplayGeometry = Geometry.Combine(
|
||||
Geometry.Empty,
|
||||
Geometry.Parse(Led.ShapeData),
|
||||
GeometryCombineMode.Union,
|
||||
new TransformGroup
|
||||
{
|
||||
Children = new TransformCollection
|
||||
{
|
||||
new ScaleTransform(Led.LedRectangle.Width - deflateAmount, Led.LedRectangle.Height - deflateAmount),
|
||||
new TranslateTransform(deflateAmount / 2, deflateAmount / 2)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CreateRectangleGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var newColor = Led.Color.ToMediaColor();
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
if (!DisplayColor.Equals(newColor))
|
||||
DisplayColor = newColor;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.News.NewsView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.News"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<TextBlock>Today's news: There is no news (yet)!</TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.News
|
||||
{
|
||||
public class NewsViewModel : Screen, IScreenViewModel
|
||||
{
|
||||
public string Title => "News";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<metro:MetroWindow x:Class="Artemis.UI.Screens.RootView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:abstract="clr-namespace:Artemis.Core.Plugins.Abstract;assembly=Artemis.Core"
|
||||
mc:Ignorable="d"
|
||||
GlowBrush="{DynamicResource AccentColorBrush}"
|
||||
FontFamily="{StaticResource DefaultFont}"
|
||||
Title="Artemis"
|
||||
d:DesignHeight="640"
|
||||
d:DesignWidth="1200"
|
||||
d:DataContext="{d:DesignInstance screens:RootViewModel}"
|
||||
SaveWindowPosition="True"
|
||||
Icon="/Artemis.UI;component/Resources/logo-512.png">
|
||||
<metro:MetroWindow.LeftWindowCommands>
|
||||
<metro:WindowCommands>
|
||||
<!-- Make the window title dissapear by providing an empty overwrite of WindowCommands -->
|
||||
</metro:WindowCommands>
|
||||
</metro:MetroWindow.LeftWindowCommands>
|
||||
<metro:MetroWindow.IconTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Width="{TemplateBinding Width}"
|
||||
Height="{TemplateBinding Height}"
|
||||
Background="Transparent"
|
||||
RenderOptions.BitmapScalingMode="HighQuality"
|
||||
Margin="0,0,-10,0">
|
||||
<Image Source="{StaticResource BowIcon}" Stretch="Uniform" Margin="6" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</metro:MetroWindow.IconTemplate>
|
||||
<materialDesign:DialogHost Identifier="RootDialog">
|
||||
<materialDesign:DrawerHost IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
|
||||
<materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<DockPanel MinWidth="212">
|
||||
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}"
|
||||
DockPanel.Dock="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="16"
|
||||
IsChecked="{Binding ElementName=MenuToggleButton, Path=IsChecked, Mode=TwoWay}" />
|
||||
|
||||
<!-- Built-in pages -->
|
||||
<ListBox Margin="0 16 0 16"
|
||||
SelectedIndex="0"
|
||||
DockPanel.Dock="Top"
|
||||
SelectedItem="{Binding SelectedPage}">
|
||||
<ListBoxItem x:Name="Home">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Home"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock>Home</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem x:Name="News">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Newspaper"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock>News</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem x:Name="Workshop">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="TestTube"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock>Workshop</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem x:Name="SurfaceEditor">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Edit"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock>Surface Editor</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
<ListBoxItem x:Name="Settings">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Settings"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock>Settings</TextBlock>
|
||||
</StackPanel>
|
||||
</ListBoxItem>
|
||||
</ListBox>
|
||||
|
||||
<!-- Modules -->
|
||||
<TextBlock Margin="8,8,0,8"
|
||||
DockPanel.Dock="Top">
|
||||
Modules
|
||||
</TextBlock>
|
||||
<Border BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<ListBox Margin="0 0 0 16"
|
||||
SelectedIndex="-1"
|
||||
ItemsSource="{Binding Modules}"
|
||||
SelectedItem="{Binding SelectedModule}"
|
||||
DockPanel.Dock="Top">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="abstract:Module">
|
||||
<DockPanel HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="0">
|
||||
<materialDesign:PackIcon Kind="Home"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</DockPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<DockPanel>
|
||||
<materialDesign:ColorZone Padding="10"
|
||||
materialDesign:ShadowAssist.ShadowDepth="Depth2"
|
||||
Mode="PrimaryMid"
|
||||
DockPanel.Dock="Top">
|
||||
<DockPanel>
|
||||
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}"
|
||||
IsChecked="{Binding MenuOpen}"
|
||||
x:Name="MenuToggleButton" />
|
||||
<materialDesign:PopupBox DockPanel.Dock="Right"
|
||||
PlacementMode="BottomAndAlignRightEdges"
|
||||
StaysOpen="False">
|
||||
<StackPanel>
|
||||
<Button Content="Hello World" />
|
||||
<Button Content="Nice Popup" />
|
||||
<Button Content="Can't Touch This"
|
||||
IsEnabled="False" />
|
||||
<Separator />
|
||||
<Button Content="Goodbye" />
|
||||
</StackPanel>
|
||||
</materialDesign:PopupBox>
|
||||
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="22"
|
||||
Text="{Binding ActiveItem.Title}" />
|
||||
</DockPanel>
|
||||
</materialDesign:ColorZone>
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}" />
|
||||
</DockPanel>
|
||||
</materialDesign:DrawerHost>
|
||||
</materialDesign:DialogHost>
|
||||
</metro:MetroWindow>
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Home;
|
||||
using Artemis.UI.Screens.News;
|
||||
using Artemis.UI.Screens.Settings;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
using Artemis.UI.Screens.Workshop;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens
|
||||
{
|
||||
public class RootViewModel : Conductor<IScreen>.Collection.OneActive
|
||||
{
|
||||
private readonly ICollection<IScreenViewModel> _artemisViewModels;
|
||||
private readonly IModuleViewModelFactory _moduleViewModelFactory;
|
||||
private readonly IPluginService _pluginService;
|
||||
|
||||
public RootViewModel(ICollection<IScreenViewModel> artemisViewModels, IPluginService pluginService, IModuleViewModelFactory moduleViewModelFactory)
|
||||
{
|
||||
_artemisViewModels = artemisViewModels;
|
||||
_pluginService = pluginService;
|
||||
_moduleViewModelFactory = moduleViewModelFactory;
|
||||
|
||||
// Add the built-in items
|
||||
Items.AddRange(artemisViewModels);
|
||||
// Activate the home item
|
||||
ActiveItem = _artemisViewModels.First(v => v.GetType() == typeof(HomeViewModel));
|
||||
|
||||
// Sync up with the plugin service
|
||||
Modules = new BindableCollection<Core.Plugins.Abstract.Module>();
|
||||
Modules.AddRange(_pluginService.GetPluginsOfType<Core.Plugins.Abstract.Module>());
|
||||
|
||||
_pluginService.PluginEnabled += PluginServiceOnPluginEnabled;
|
||||
_pluginService.PluginDisabled += PluginServiceOnPluginDisabled;
|
||||
PropertyChanged += OnSelectedModuleChanged;
|
||||
PropertyChanged += OnSelectedPageChanged;
|
||||
}
|
||||
|
||||
public IObservableCollection<Core.Plugins.Abstract.Module> Modules { get; set; }
|
||||
public bool MenuOpen { get; set; }
|
||||
public ListBoxItem SelectedPage { get; set; }
|
||||
public Core.Plugins.Abstract.Module SelectedModule { get; set; }
|
||||
|
||||
public async Task NavigateToSelectedModule()
|
||||
{
|
||||
if (SelectedModule == null)
|
||||
return;
|
||||
|
||||
// Create a view model for the given plugin info (which will be a module)
|
||||
var viewModel = await Task.Run(() => _moduleViewModelFactory.CreateModuleViewModel(SelectedModule));
|
||||
ActivateItem(viewModel);
|
||||
|
||||
SelectedPage = null;
|
||||
MenuOpen = false;
|
||||
}
|
||||
|
||||
private void PluginServiceOnPluginEnabled(object sender, PluginEventArgs e)
|
||||
{
|
||||
var existing = Modules.FirstOrDefault(m => _pluginService.GetPluginInfo(m)?.Guid == e.PluginInfo.Guid);
|
||||
if (existing != null)
|
||||
{
|
||||
if (SelectedModule == existing && SelectedModule != null)
|
||||
SelectedModule = null;
|
||||
Modules.Remove(existing);
|
||||
}
|
||||
|
||||
if (e.PluginInfo.Instance is Core.Plugins.Abstract.Module module)
|
||||
Modules.Add(module);
|
||||
}
|
||||
|
||||
private void PluginServiceOnPluginDisabled(object sender, PluginEventArgs e)
|
||||
{
|
||||
var existing = Modules.FirstOrDefault(m => _pluginService.GetPluginInfo(m)?.Guid == e.PluginInfo.Guid);
|
||||
if (existing != null)
|
||||
{
|
||||
if (SelectedModule == existing && SelectedModule != null)
|
||||
SelectedModule = null;
|
||||
Modules.Remove(existing);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSelectedModuleChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(SelectedModule))
|
||||
await NavigateToSelectedModule();
|
||||
}
|
||||
|
||||
private void OnSelectedPageChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "SelectedPage" || SelectedPage == null)
|
||||
return;
|
||||
|
||||
switch (SelectedPage.Name)
|
||||
{
|
||||
case "Home":
|
||||
ActivateItem(_artemisViewModels.First(v => v is HomeViewModel));
|
||||
break;
|
||||
case "News":
|
||||
ActivateItem(_artemisViewModels.First(v => v is NewsViewModel));
|
||||
break;
|
||||
case "Workshop":
|
||||
ActivateItem(_artemisViewModels.First(v => v is WorkshopViewModel));
|
||||
break;
|
||||
case "SurfaceEditor":
|
||||
ActivateItem(_artemisViewModels.First(v => v is SurfaceEditorViewModel));
|
||||
break;
|
||||
case "Settings":
|
||||
ActivateItem(_artemisViewModels.First(v => v is SettingsViewModel));
|
||||
break;
|
||||
}
|
||||
|
||||
SelectedModule = null;
|
||||
MenuOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Settings.SettingsView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:xaml="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance screens:SettingsViewModel}"
|
||||
d:DesignHeight="600" d:DesignWidth="600">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">General</TextBlock>
|
||||
<StackPanel>
|
||||
<TextBlock>General settings like start up with Windows etc.</TextBlock>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock VerticalAlignment="Bottom" Grid.Row="0" Grid.Column="0">Render scale</TextBlock>
|
||||
<Slider Minimum="0.10"
|
||||
Maximum="1"
|
||||
TickFrequency="0.05"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignDiscreteSlider}"
|
||||
ToolTip="MaterialDesignDiscreteSlider"
|
||||
Value="{Binding RenderScale}" />
|
||||
|
||||
<TextBlock VerticalAlignment="Bottom" Grid.Row="0" Grid.Column="1">Target framerate</TextBlock>
|
||||
<Slider Minimum="5"
|
||||
Maximum="60"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignDiscreteSlider}"
|
||||
ToolTip="MaterialDesignDiscreteSlider"
|
||||
Value="{Binding TargetFrameRate}" />
|
||||
</Grid>
|
||||
|
||||
<Button Command="{s:Action ShowDebugger}" Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
HorizontalAlignment="Left" Margin="0, 10, 0, 0">
|
||||
Show debugger
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">Devices</TextBlock>
|
||||
|
||||
<TextBlock>A list of devices and options to disable them</TextBlock>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
|
||||
<ItemsControl ItemsSource="{Binding DeviceSettingsViewModels}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl xaml:View.Model="{Binding}" Margin="5" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadlineTextBlock}">Plugins</TextBlock>
|
||||
<Grid>
|
||||
<TextBlock>A list of plugins and options to disable them</TextBlock>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,68 @@
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Core.Services.Storage;
|
||||
using Artemis.UI.Screens.Settings.Debug;
|
||||
using Artemis.UI.Screens.Settings.Tabs.Devices;
|
||||
using Ninject;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings
|
||||
{
|
||||
public class SettingsViewModel : Screen, IScreenViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ISurfaceService _surfaceService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
|
||||
public SettingsViewModel(IKernel kernel, ICoreService coreService, ISurfaceService surfaceService, IWindowManager windowManager, ISettingsService settingsService)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_coreService = coreService;
|
||||
_surfaceService = surfaceService;
|
||||
_windowManager = windowManager;
|
||||
_settingsService = settingsService;
|
||||
|
||||
DeviceSettingsViewModels = new BindableCollection<DeviceSettingsViewModel>();
|
||||
}
|
||||
|
||||
public BindableCollection<DeviceSettingsViewModel> DeviceSettingsViewModels { get; set; }
|
||||
|
||||
public double RenderScale
|
||||
{
|
||||
get => _settingsService.GetSetting("RenderScale", 1.0).Value;
|
||||
set
|
||||
{
|
||||
_settingsService.GetSetting("RenderScale", 1.0).Value = value;
|
||||
_settingsService.GetSetting("RenderScale", 1.0).Save();
|
||||
}
|
||||
}
|
||||
|
||||
public int TargetFrameRate
|
||||
{
|
||||
get => _settingsService.GetSetting("TargetFrameRate", 25).Value;
|
||||
set
|
||||
{
|
||||
_settingsService.GetSetting("TargetFrameRate", 25).Value = value;
|
||||
_settingsService.GetSetting("TargetFrameRate", 25).Save();
|
||||
}
|
||||
}
|
||||
|
||||
public string Title => "Settings";
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
DeviceSettingsViewModels.Clear();
|
||||
foreach (var device in _surfaceService.ActiveSurface.Devices)
|
||||
DeviceSettingsViewModels.Add(new DeviceSettingsViewModel(device, _coreService));
|
||||
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
public void ShowDebugger()
|
||||
{
|
||||
_windowManager.ShowWindow(_kernel.Get<DebugViewModel>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Devices.DeviceSettingsView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
d:DataContext="{d:DesignInstance settings:DeviceSettingsViewModel}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<materialDesign:Card Width="200">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="140" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle Grid.Row="0" Height="140" Width="196" Fill="{DynamicResource MaterialDesignPaper}" />
|
||||
<Image Grid.Row="0" Source="{Binding Device.RgbDevice.DeviceInfo.Image}" Height="130" Width="190"
|
||||
RenderOptions.BitmapScalingMode="HighQuality" />
|
||||
<Button Grid.Row="0"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0 0 16 -20"
|
||||
ToolTip="Identify"
|
||||
Command="{s:Action Identify}">
|
||||
<materialDesign:PackIcon Kind="AlarmLight" />
|
||||
</Button>
|
||||
<StackPanel Grid.Row="1" Margin="8 24 8 0">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Center">
|
||||
<Run Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer, Mode=OneWay}" />
|
||||
-
|
||||
<Run Text="{Binding Device.RgbDevice.DeviceInfo.DeviceType, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" Grid.Row="2" Orientation="Horizontal" Margin="8">
|
||||
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsDeviceEnabled}">
|
||||
Device enabled
|
||||
</CheckBox>
|
||||
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}" Padding="2 0 2 0">
|
||||
<StackPanel>
|
||||
<Button Content="Show device debugger" Command="{s:Action ShowDeviceDebugger}" />
|
||||
<Button Content="Open plugin directory" Command="{s:Action OpenPluginDirectory}" />
|
||||
</StackPanel>
|
||||
</materialDesign:PopupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Tabs.Devices
|
||||
{
|
||||
public class DeviceSettingsViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
|
||||
public DeviceSettingsViewModel(Device device, ICoreService coreService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
|
||||
Device = device;
|
||||
|
||||
Type = Device.RgbDevice.DeviceInfo.DeviceType.ToString().Humanize();
|
||||
Name = Device.RgbDevice.DeviceInfo.Model;
|
||||
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
|
||||
IsDeviceEnabled = true;
|
||||
}
|
||||
|
||||
public Device Device { get; }
|
||||
|
||||
public string Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Manufacturer { get; set; }
|
||||
public bool IsDeviceEnabled { get; set; }
|
||||
|
||||
public void Identify()
|
||||
{
|
||||
BlinkDevice(0);
|
||||
}
|
||||
|
||||
private void BlinkDevice(int blinkCount)
|
||||
{
|
||||
// Draw a white overlay over the device
|
||||
void DrawOverlay(object sender, FrameRenderingEventArgs args)
|
||||
{
|
||||
using (var g = Graphics.FromImage(args.Bitmap))
|
||||
{
|
||||
g.FillPath(new SolidBrush(Color.White), Device.RenderPath);
|
||||
}
|
||||
}
|
||||
|
||||
_coreService.FrameRendering += DrawOverlay;
|
||||
|
||||
// After 200ms, stop drawing the overlay
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(200);
|
||||
_coreService.FrameRendering -= DrawOverlay;
|
||||
|
||||
if (blinkCount < 5)
|
||||
{
|
||||
// After another 200ms, draw the overlay again, repeat six times
|
||||
await Task.Delay(200);
|
||||
BlinkDevice(blinkCount + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ShowDeviceDebugger()
|
||||
{
|
||||
}
|
||||
|
||||
public void OpenPluginDirectory()
|
||||
{
|
||||
Process.Start(Device.Plugin.PluginInfo.Directory.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Shared
|
||||
{
|
||||
public class PanZoomViewModel : PropertyChangedBase
|
||||
{
|
||||
private Point? _lastPanPosition;
|
||||
public double Zoom { get; set; } = 1;
|
||||
|
||||
public double ZoomPercentage
|
||||
{
|
||||
get => Zoom * 100;
|
||||
set => SetZoomFromPercentage(value);
|
||||
}
|
||||
|
||||
public double PanX { get; set; }
|
||||
public double PanY { get; set; }
|
||||
|
||||
public Rect BackgroundViewport => new Rect(PanX, PanY, 20, 20);
|
||||
|
||||
public void ProcessMouseScroll(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
var relative = GetRelativeMousePosition(sender, e);
|
||||
var absoluteX = relative.X * Zoom + PanX;
|
||||
var absoluteY = relative.Y * Zoom + PanY;
|
||||
|
||||
if (e.Delta > 0)
|
||||
Zoom *= 1.1;
|
||||
else
|
||||
Zoom *= 0.9;
|
||||
|
||||
// Limit to a min of 0.1 and a max of 4 (10% - 400% in the view)
|
||||
Zoom = Math.Max(0.1, Math.Min(4, Zoom));
|
||||
|
||||
// Update the PanX/Y to enable zooming relative to cursor
|
||||
PanX = Math.Min(0, absoluteX - relative.X * Zoom);
|
||||
PanY = Math.Min(0, absoluteY - relative.Y * Zoom);
|
||||
}
|
||||
|
||||
public void ProcessMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
{
|
||||
Mouse.OverrideCursor = Cursors.Arrow;
|
||||
_lastPanPosition = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastPanPosition == null)
|
||||
_lastPanPosition = e.GetPosition((IInputElement) sender);
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var delta = _lastPanPosition - position;
|
||||
|
||||
PanX = Math.Min(0, PanX - delta.Value.X);
|
||||
PanY = Math.Min(0, PanY - delta.Value.Y);
|
||||
|
||||
_lastPanPosition = position;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Zoom = 1;
|
||||
PanX = 0;
|
||||
PanY = 0;
|
||||
}
|
||||
|
||||
public Rect TransformContainingRect(Rect rect)
|
||||
{
|
||||
// Create the same transform group the view is using
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(Zoom, Zoom));
|
||||
transformGroup.Children.Add(new TranslateTransform(PanX, PanY));
|
||||
|
||||
// Apply it to the device rect
|
||||
return transformGroup.TransformBounds(rect);
|
||||
}
|
||||
|
||||
public Point GetRelativeMousePosition(object container, MouseEventArgs e)
|
||||
{
|
||||
// Get the mouse position relative to the panned / zoomed panel, not very MVVM but I can't find a better way
|
||||
return e.GetPosition(((Panel) container).Children[0]);
|
||||
}
|
||||
|
||||
private void SetZoomFromPercentage(double value)
|
||||
{
|
||||
var newZoom = value / 100;
|
||||
// Focus towards the center of the zoomed area
|
||||
PanX += newZoom - Zoom;
|
||||
PanY += newZoom - Zoom;
|
||||
Zoom = value / 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<mah:MetroWindow x:Class="Artemis.UI.Screens.Splash.SplashView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:splash="clr-namespace:Artemis.UI.Screens.Splash"
|
||||
mc:Ignorable="d"
|
||||
Title="Artemis"
|
||||
Height="450"
|
||||
Width="450"
|
||||
ShowTitleBar="False"
|
||||
ShowMaxRestoreButton="False"
|
||||
ShowCloseButton="False"
|
||||
ShowMinButton="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
GlowBrush="{DynamicResource AccentColorBrush}"
|
||||
FontFamily="{StaticResource DefaultFont}"
|
||||
d:DataContext="{d:DesignInstance splash:SplashViewModel}">
|
||||
<Grid Background="{DynamicResource PrimaryHueMidBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="250" />
|
||||
<RowDefinition Height="50" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="{StaticResource BowIcon}" Stretch="Uniform" Margin="6,50,6,6" />
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Foreground="White" FontSize="16">
|
||||
Artemis is initializing...
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="2" HorizontalAlignment="Center" Foreground="#FFDDDDDD" Text="{Binding Status}" />
|
||||
<ProgressBar Grid.Row="3" IsIndeterminate="True" Maximum="1" Minimum="1" Margin="16 0" />
|
||||
</Grid>
|
||||
</mah:MetroWindow>
|
||||
@@ -0,0 +1,27 @@
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Ninject;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Splash
|
||||
{
|
||||
public class SplashViewModel : Screen
|
||||
{
|
||||
private readonly IKernel _kernel;
|
||||
|
||||
public SplashViewModel(IKernel kernel)
|
||||
{
|
||||
_kernel = kernel;
|
||||
Status = "Initializing Core";
|
||||
}
|
||||
|
||||
public string Status { get; set; }
|
||||
|
||||
public void ListenToEvents()
|
||||
{
|
||||
var pluginService = _kernel.Get<IPluginService>();
|
||||
pluginService.CopyingBuildInPlugins += (sender, args) => Status = "Updating built-in plugins";
|
||||
pluginService.PluginLoading += (sender, args) => Status = "Loading plugin: " + args.PluginInfo.Name;
|
||||
pluginService.PluginLoaded += (sender, args) => Status = "Initializing UI";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Dialogs.SurfaceCreateView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="213.053" d:DesignWidth="254.425">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock>
|
||||
Add a new surface layout
|
||||
</TextBlock>
|
||||
|
||||
<TextBox materialDesign:HintAssist.Hint="Layout name" Margin="0 8 0 16"
|
||||
Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding SurfaceName}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0"
|
||||
Command="{s:Action Cancel}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
False
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
CANCEL
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 8 0"
|
||||
Command="{s:Action Accept}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
True
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
ACCEPT
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.ViewModels.Dialogs;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
{
|
||||
public class SurfaceCreateViewModel : DialogViewModelBase
|
||||
{
|
||||
public SurfaceCreateViewModel(IModelValidator<SurfaceCreateViewModel> validator) : base(validator)
|
||||
{
|
||||
}
|
||||
|
||||
public string SurfaceName { get; set; }
|
||||
|
||||
public async Task Accept()
|
||||
{
|
||||
await ValidateAsync();
|
||||
|
||||
if (HasErrors)
|
||||
return;
|
||||
|
||||
Session.Close(SurfaceName);
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
Session.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Dialogs
|
||||
{
|
||||
public class SurfaceCreateViewModelValidator : AbstractValidator<SurfaceCreateViewModel>
|
||||
{
|
||||
public SurfaceCreateViewModelValidator()
|
||||
{
|
||||
RuleFor(m => m.SurfaceName).NotEmpty().WithMessage("Layout name may not be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:models="clr-namespace:Artemis.Core.Models.Surface;assembly=Artemis.Core"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="600">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="100" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="{Binding SurfaceListWidth.Value, Mode=TwoWay}" MinWidth="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
|
||||
<TextBlock>
|
||||
<materialDesign:PackIcon Kind="AboutOutline" Margin="0 0 0 -3" /> The surface is a digital representation of your LED setup. Set this up accurately and effects will seamlessly move from one device to the other.
|
||||
</TextBlock>
|
||||
<Border BorderBrush="{DynamicResource MaterialDesignDivider}" BorderThickness="0 0 0 1" Margin="0 10" />
|
||||
</StackPanel>
|
||||
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="0"
|
||||
VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<Grid ClipToBounds="True"
|
||||
KeyUp="{s:Action EditorGridKeyUp}"
|
||||
KeyDown="{s:Action EditorGridKeyDown}"
|
||||
MouseWheel="{s:Action EditorGridMouseWheel}"
|
||||
MouseUp="{s:Action EditorGridMouseClick}"
|
||||
MouseDown="{s:Action EditorGridMouseClick}"
|
||||
MouseMove="{s:Action EditorGridMouseMove}">
|
||||
<Grid.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform"
|
||||
Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="20" Height="20">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="LightGray" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="LightGray" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<Grid.Triggers>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonDown">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="0" To="1" Duration="0:0:0.1" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="Grid.MouseLeftButtonUp">
|
||||
<BeginStoryboard>
|
||||
<Storyboard TargetName="MultiSelectionPath" TargetProperty="Opacity">
|
||||
<DoubleAnimation From="1" To="0" Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</Grid.Triggers>
|
||||
|
||||
<Grid Name="EditorDisplayGrid">
|
||||
<Grid.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}"
|
||||
ScaleY="{Binding PanZoomViewModel.Zoom}" />
|
||||
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
|
||||
</TransformGroup>
|
||||
</Grid.RenderTransform>
|
||||
<ItemsControl ItemsSource="{Binding Devices}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Width="{Binding Device.RgbDevice.Size.Width}"
|
||||
Height="{Binding Device.RgbDevice.Size.Height}"
|
||||
s:View.Model="{Binding}">
|
||||
<ContentControl.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Bring to Front" Command="{s:Action BringToFront}"
|
||||
CommandParameter="{Binding}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringToFront" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Bring Forward" Command="{s:Action BringForward}"
|
||||
CommandParameter="{Binding}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeBringForward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send to Back" Command="{s:Action SendToBack}"
|
||||
CommandParameter="{Binding}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendToBack" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send Backward" Command="{s:Action SendBackward}"
|
||||
CommandParameter="{Binding}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrangeSendBackward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</ContentControl.ContextMenu>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Multi-selection rectangle -->
|
||||
<Path Data="{Binding SelectionRectangle}" Opacity="0"
|
||||
Stroke="{DynamicResource PrimaryHueLightBrush}"
|
||||
StrokeThickness="1"
|
||||
Name="MultiSelectionPath"
|
||||
IsHitTestVisible="False">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{DynamicResource Primary400}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
<StackPanel Orientation="Vertical" VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
||||
Margin="0, 0, 15, 15">
|
||||
<Slider Margin="0,0,14,0"
|
||||
Orientation="Vertical"
|
||||
Minimum="10"
|
||||
Maximum="400"
|
||||
Height="100"
|
||||
FocusVisualStyle="{x:Null}"
|
||||
Value="{Binding PanZoomViewModel.ZoomPercentage}"
|
||||
Style="{StaticResource MaterialDesignDiscreteSlider}" />
|
||||
<Button Command="{s:Action ResetZoomAndPan}"
|
||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||
HorizontalAlignment="Right"
|
||||
ToolTip="Reset zoom & position">
|
||||
<materialDesign:PackIcon Kind="ImageFilterCenterFocus" Height="24" Width="24" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<GridSplitter Grid.Column="1" Grid.Row="1" Width="5" HorizontalAlignment="Stretch" ShowsPreview="True" Cursor="SizeWE" />
|
||||
|
||||
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" Grid.Row="1" Grid.Column="2"
|
||||
VerticalAlignment="Stretch" Margin="5,0,0,0">
|
||||
<materialDesign:DialogHost Identifier="SurfaceListDialogHost" CloseOnClickAway="True"
|
||||
UseLayoutRounding="True">
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0" HorizontalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding SurfaceConfigurations}" SelectedItem="{Binding SelectedSurface}">
|
||||
<ListBox.Resources>
|
||||
<DataTemplate DataType="{x:Type models:Surface}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="40" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding Name}" ToolTip="{Binding Name}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<Button Grid.Column="1"
|
||||
Command="{s:Action DeleteSurfaceConfiguration}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Delete layout configuration"
|
||||
HorizontalAlignment="Right">
|
||||
<materialDesign:PackIcon Kind="Trashcan" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
</DataTemplate>
|
||||
</ListBox.Resources>
|
||||
</ListBox>
|
||||
<Button Grid.Row="0"
|
||||
Style="{StaticResource MaterialDesignFloatingActionButton}"
|
||||
Command="{s:Action AddSurfaceConfiguration}"
|
||||
Margin="0 0 10 10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
ToolTip="Add a new surface layout">
|
||||
<materialDesign:PackIcon Kind="Add" Height="24" Width="24" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</materialDesign:Card>
|
||||
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Style="{StaticResource MaterialDesignCaptionTextBlock}" Margin="0,5,0,0">
|
||||
<materialDesign:PackIcon Kind="Keyboard" Margin="0 0 0 -3" />
|
||||
<Run Text="Hold" />
|
||||
<Run FontWeight="Bold" Text="shift" />
|
||||
<Run Text="or click and drag to select multiple devices at once. To move around the surface hold down" />
|
||||
<Run FontWeight="Bold" Text="ctrl" />
|
||||
<Run Text="and drag." />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Storage;
|
||||
using Artemis.UI.Screens.Shared;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Dialogs;
|
||||
using Artemis.UI.Screens.SurfaceEditor.Visualization;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor
|
||||
{
|
||||
public class SurfaceEditorViewModel : Screen, IScreenViewModel
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ISurfaceService _surfaceService;
|
||||
|
||||
public SurfaceEditorViewModel(ISurfaceService surfaceService, IDialogService dialogService, ISettingsService settingsService)
|
||||
{
|
||||
Devices = new ObservableCollection<SurfaceDeviceViewModel>();
|
||||
SurfaceConfigurations = new ObservableCollection<Surface>();
|
||||
SelectionRectangle = new RectangleGeometry();
|
||||
PanZoomViewModel = new PanZoomViewModel();
|
||||
|
||||
_surfaceService = surfaceService;
|
||||
_dialogService = dialogService;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
public ObservableCollection<SurfaceDeviceViewModel> Devices { get; set; }
|
||||
public ObservableCollection<Surface> SurfaceConfigurations { get; set; }
|
||||
public RectangleGeometry SelectionRectangle { get; set; }
|
||||
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||
public PluginSetting<GridLength> SurfaceListWidth { get; set; }
|
||||
|
||||
public Surface SelectedSurface
|
||||
{
|
||||
get => _selectedSurface;
|
||||
set
|
||||
{
|
||||
_selectedSurface = value;
|
||||
ApplySelectedSurfaceConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public string Title => "Surface Editor";
|
||||
|
||||
public Surface CreateSurfaceConfiguration(string name)
|
||||
{
|
||||
var config = _surfaceService.CreateSurfaceConfiguration(name);
|
||||
Execute.OnUIThread(() => SurfaceConfigurations.Add(config));
|
||||
return config;
|
||||
}
|
||||
|
||||
private void LoadWorkspaceSettings()
|
||||
{
|
||||
SurfaceListWidth = _settingsService.GetSetting("SurfaceEditor.SurfaceListWidth", new GridLength(300.0));
|
||||
}
|
||||
|
||||
private void SaveWorkspaceSettings()
|
||||
{
|
||||
SurfaceListWidth.Save();
|
||||
}
|
||||
|
||||
private void LoadSurfaceConfigurations()
|
||||
{
|
||||
// Get surface configs
|
||||
var configs = _surfaceService.SurfaceConfigurations;
|
||||
|
||||
// Get the active config, if empty, create a default config
|
||||
var activeConfig = _surfaceService.ActiveSurface;
|
||||
if (activeConfig == null)
|
||||
{
|
||||
activeConfig = CreateSurfaceConfiguration("Default");
|
||||
_surfaceService.SetActiveSurfaceConfiguration(activeConfig);
|
||||
}
|
||||
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
// Populate the UI collection
|
||||
SurfaceConfigurations.Clear();
|
||||
foreach (var surfaceConfiguration in configs)
|
||||
SurfaceConfigurations.Add(surfaceConfiguration);
|
||||
|
||||
// Set the active config
|
||||
SelectedSurface = activeConfig;
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplySelectedSurfaceConfiguration()
|
||||
{
|
||||
if (SelectedSurface == null)
|
||||
{
|
||||
Execute.OnUIThread(Devices.Clear);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure all devices have an up-to-date VM
|
||||
foreach (var surfaceDeviceConfiguration in SelectedSurface.Devices)
|
||||
{
|
||||
// Create VMs for missing devices
|
||||
var viewModel = Devices.FirstOrDefault(vm => vm.Device.RgbDevice == surfaceDeviceConfiguration.RgbDevice);
|
||||
if (viewModel == null)
|
||||
Execute.OnUIThread(() => Devices.Add(new SurfaceDeviceViewModel(surfaceDeviceConfiguration)));
|
||||
// Update existing devices
|
||||
else
|
||||
viewModel.Device = surfaceDeviceConfiguration;
|
||||
}
|
||||
|
||||
// Sort the devices by ZIndex
|
||||
Execute.OnUIThread(() =>
|
||||
{
|
||||
foreach (var device in Devices.OrderBy(d => d.ZIndex).ToList())
|
||||
Devices.Move(Devices.IndexOf(device), device.ZIndex - 1);
|
||||
});
|
||||
|
||||
_surfaceService.SetActiveSurfaceConfiguration(SelectedSurface);
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
LoadSurfaceConfigurations();
|
||||
LoadWorkspaceSettings();
|
||||
base.OnActivate();
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
SaveWorkspaceSettings();
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration management
|
||||
|
||||
public async Task DeleteSurfaceConfiguration(Surface surface)
|
||||
{
|
||||
var result = await _dialogService.ShowConfirmDialogAt(
|
||||
"SurfaceListDialogHost",
|
||||
"Delete surface configuration",
|
||||
"Are you sure you want to delete\nthis surface configuration?"
|
||||
);
|
||||
if (result)
|
||||
{
|
||||
SurfaceConfigurations.Remove(surface);
|
||||
_surfaceService.DeleteSurfaceConfiguration(surface);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddSurfaceConfiguration()
|
||||
{
|
||||
var result = await _dialogService.ShowDialogAt<SurfaceCreateViewModel>("SurfaceListDialogHost");
|
||||
if (result is string name)
|
||||
CreateSurfaceConfiguration(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
public void BringToFront(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
{
|
||||
Devices.Move(Devices.IndexOf(surfaceDeviceViewModel), Devices.Count - 1);
|
||||
for (var i = 0; i < Devices.Count; i++)
|
||||
{
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.ZIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void BringForward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
{
|
||||
var currentIndex = Devices.IndexOf(surfaceDeviceViewModel);
|
||||
var newIndex = Math.Min(currentIndex + 1, Devices.Count - 1);
|
||||
Devices.Move(currentIndex, newIndex);
|
||||
|
||||
for (var i = 0; i < Devices.Count; i++)
|
||||
{
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.ZIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendToBack(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
{
|
||||
Devices.Move(Devices.IndexOf(surfaceDeviceViewModel), 0);
|
||||
for (var i = 0; i < Devices.Count; i++)
|
||||
{
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.ZIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendBackward(SurfaceDeviceViewModel surfaceDeviceViewModel)
|
||||
{
|
||||
var currentIndex = Devices.IndexOf(surfaceDeviceViewModel);
|
||||
var newIndex = Math.Max(currentIndex - 1, 0);
|
||||
Devices.Move(currentIndex, newIndex);
|
||||
for (var i = 0; i < Devices.Count; i++)
|
||||
{
|
||||
var deviceViewModel = Devices[i];
|
||||
deviceViewModel.ZIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Selection
|
||||
|
||||
private MouseDragStatus _mouseDragStatus;
|
||||
private Point _mouseDragStartPoint;
|
||||
private Surface _selectedSurface;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
|
||||
return;
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
StartMouseDrag(position, relative);
|
||||
else
|
||||
StopMouseDrag(position);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void EditorGridMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
// If holding down Ctrl, pan instead of move/select
|
||||
if (IsPanKeyDown())
|
||||
{
|
||||
Pan(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
var position = e.GetPosition((IInputElement) sender);
|
||||
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||
if (_mouseDragStatus == MouseDragStatus.Dragging)
|
||||
MoveSelected(relative);
|
||||
else if (_mouseDragStatus == MouseDragStatus.Selecting)
|
||||
UpdateSelection(position);
|
||||
}
|
||||
|
||||
private void StartMouseDrag(Point position, Point relative)
|
||||
{
|
||||
// If drag started on top of a device, initialise dragging
|
||||
var device = Devices.LastOrDefault(d => PanZoomViewModel.TransformContainingRect(d.DeviceRectangle).Contains(position));
|
||||
if (device != null)
|
||||
{
|
||||
_mouseDragStatus = MouseDragStatus.Dragging;
|
||||
// If the device is not selected, deselect others and select only this one (if shift not held)
|
||||
if (device.SelectionStatus != SelectionStatus.Selected)
|
||||
{
|
||||
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
foreach (var others in Devices)
|
||||
others.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
}
|
||||
|
||||
foreach (var selectedDevice in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||
selectedDevice.StartMouseDrag(relative);
|
||||
}
|
||||
// Start multi-selection
|
||||
else
|
||||
{
|
||||
_mouseDragStatus = MouseDragStatus.Selecting;
|
||||
_mouseDragStartPoint = position;
|
||||
}
|
||||
|
||||
// While dragging always show a hand to avoid cursor flicker
|
||||
if (_mouseDragStatus == MouseDragStatus.Dragging)
|
||||
Mouse.OverrideCursor = Cursors.Hand;
|
||||
else
|
||||
Mouse.OverrideCursor = Cursors.Arrow;
|
||||
|
||||
// Any time dragging starts, start with a new rect
|
||||
SelectionRectangle.Rect = new Rect();
|
||||
}
|
||||
|
||||
private void StopMouseDrag(Point position)
|
||||
{
|
||||
if (_mouseDragStatus != MouseDragStatus.Dragging)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
foreach (var device in Devices)
|
||||
{
|
||||
if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
|
||||
|
||||
Mouse.OverrideCursor = null;
|
||||
_mouseDragStatus = MouseDragStatus.None;
|
||||
}
|
||||
|
||||
private void UpdateSelection(Point position)
|
||||
{
|
||||
if (IsPanKeyDown())
|
||||
return;
|
||||
|
||||
lock (Devices)
|
||||
{
|
||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||
SelectionRectangle.Rect = selectedRect;
|
||||
|
||||
foreach (var device in Devices)
|
||||
{
|
||||
if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveSelected(Point position)
|
||||
{
|
||||
foreach (var device in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||
device.UpdateMouseDrag(position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Panning and zooming
|
||||
|
||||
public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseScroll(sender, e);
|
||||
}
|
||||
|
||||
public void EditorGridKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown)
|
||||
Mouse.OverrideCursor = Cursors.ScrollAll;
|
||||
}
|
||||
|
||||
public void EditorGridKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp)
|
||||
Mouse.OverrideCursor = null;
|
||||
}
|
||||
|
||||
public void Pan(object sender, MouseEventArgs e)
|
||||
{
|
||||
PanZoomViewModel.ProcessMouseMove(sender, e);
|
||||
|
||||
// Empty the selection rect since it's shown while mouse is down
|
||||
SelectionRectangle.Rect = Rect.Empty;
|
||||
}
|
||||
|
||||
public void ResetZoomAndPan()
|
||||
{
|
||||
PanZoomViewModel.Reset();
|
||||
}
|
||||
|
||||
private bool IsPanKeyDown()
|
||||
{
|
||||
return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal enum MouseDragStatus
|
||||
{
|
||||
None,
|
||||
Selecting,
|
||||
Dragging
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.SurfaceEditor.Visualization.SurfaceDeviceView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceDeviceViewModel}}"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Cursor="{Binding Cursor}"
|
||||
MouseEnter="{s:Action MouseEnter}"
|
||||
MouseLeave="{s:Action MouseLeave}"
|
||||
ToolTip="{Binding Device.RgbDevice.DeviceInfo.DeviceName}">
|
||||
<UserControl.Resources>
|
||||
<converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
<converters:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- Device image with fallback -->
|
||||
<Image Source="{Binding Device.RgbDevice.DeviceInfo.Image, Converter={StaticResource NullToImageConverter}}" />
|
||||
|
||||
<Rectangle Fill="{DynamicResource ControlBackgroundBrush}"
|
||||
Stroke="{DynamicResource ControlBorderBrush}"
|
||||
StrokeThickness="1"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}"
|
||||
Visibility="{Binding Device.RgbDevice.DeviceInfo.Image, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Center" />
|
||||
|
||||
<!-- LEDs -->
|
||||
<ItemsControl ItemsSource="{Binding Leds}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Width="{Binding Width}" Height="{Binding Height}" s:View.Model="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Selection rectangle -->
|
||||
<Rectangle Width="Auto" Height="Auto" StrokeThickness="2">
|
||||
<Rectangle.Stroke>
|
||||
<SolidColorBrush Color="{StaticResource IdealForegroundColor}" />
|
||||
</Rectangle.Stroke>
|
||||
<Rectangle.Fill>
|
||||
<SolidColorBrush Color="{StaticResource IdealForegroundColor}" Opacity="0.25" />
|
||||
</Rectangle.Fill>
|
||||
<Rectangle.Style>
|
||||
<Style TargetType="{x:Type Rectangle}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}"
|
||||
Value="{x:Static surfaceEditor:SelectionStatus.Hover}">
|
||||
<DataTrigger.EnterActions>
|
||||
<StopStoryboard BeginStoryboardName="ToSelected" />
|
||||
<BeginStoryboard x:Name="ToHover">
|
||||
<Storyboard>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
|
||||
To="{StaticResource IdealForegroundColor}" Duration="0:0:0.25" />
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)"
|
||||
To="{StaticResource IdealForegroundColor}" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}"
|
||||
Value="{x:Static surfaceEditor:SelectionStatus.Selected}">
|
||||
<DataTrigger.EnterActions>
|
||||
<StopStoryboard BeginStoryboardName="ToHover" />
|
||||
<BeginStoryboard x:Name="ToSelected">
|
||||
<Storyboard>
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
|
||||
To="{StaticResource Primary400}" Duration="0:0:0.25" />
|
||||
<ColorAnimation
|
||||
Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)"
|
||||
To="{StaticResource Primary400}" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding SelectionStatus}"
|
||||
Value="{x:Static surfaceEditor:SelectionStatus.None}">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Rectangle.Style>
|
||||
</Rectangle>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Visualization
|
||||
{
|
||||
public class SurfaceDeviceViewModel : PropertyChangedBase
|
||||
{
|
||||
private readonly List<SurfaceLedViewModel> _leds;
|
||||
private double _dragOffsetX;
|
||||
private double _dragOffsetY;
|
||||
|
||||
public SurfaceDeviceViewModel(Device device)
|
||||
{
|
||||
Device = device;
|
||||
_leds = new List<SurfaceLedViewModel>();
|
||||
|
||||
if (Device.RgbDevice != null)
|
||||
{
|
||||
foreach (var led in Device.RgbDevice)
|
||||
_leds.Add(new SurfaceLedViewModel(led));
|
||||
}
|
||||
}
|
||||
|
||||
public Device Device { get; set; }
|
||||
public SelectionStatus SelectionStatus { get; set; }
|
||||
public Cursor Cursor { get; set; }
|
||||
|
||||
public double X
|
||||
{
|
||||
get => Device.X;
|
||||
set => Device.X = value;
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get => Device.Y;
|
||||
set => Device.Y = value;
|
||||
}
|
||||
|
||||
public int ZIndex
|
||||
{
|
||||
get => Device.ZIndex;
|
||||
set => Device.ZIndex = value;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<SurfaceLedViewModel> Leds => _leds.AsReadOnly();
|
||||
|
||||
public Rect DeviceRectangle => Device.RgbDevice == null
|
||||
? new Rect()
|
||||
: new Rect(X, Y, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height);
|
||||
|
||||
public void StartMouseDrag(Point mouseStartPosition)
|
||||
{
|
||||
_dragOffsetX = X - mouseStartPosition.X;
|
||||
_dragOffsetY = Y - mouseStartPosition.Y;
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition)
|
||||
{
|
||||
var roundedX = Math.Round((mousePosition.X + _dragOffsetX) / 10, 0, MidpointRounding.AwayFromZero) * 10;
|
||||
var roundedY = Math.Round((mousePosition.Y + _dragOffsetY) / 10, 0, MidpointRounding.AwayFromZero) * 10;
|
||||
X = Math.Max(0, roundedX);
|
||||
Y = Math.Max(0, roundedY);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void MouseEnter()
|
||||
{
|
||||
if (SelectionStatus == SelectionStatus.None)
|
||||
{
|
||||
SelectionStatus = SelectionStatus.Hover;
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global - Called from view
|
||||
public void MouseLeave()
|
||||
{
|
||||
if (SelectionStatus == SelectionStatus.Hover)
|
||||
{
|
||||
SelectionStatus = SelectionStatus.None;
|
||||
Cursor = Cursors.Arrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum SelectionStatus
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Selected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<UserControl
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:Converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:surfaceEditor="clr-namespace:Artemis.UI.Screens.SurfaceEditor.Visualization"
|
||||
x:Class="Artemis.UI.Screens.SurfaceEditor.Visualization.SurfaceLedView"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance {x:Type surfaceEditor:SurfaceLedViewModel}}"
|
||||
d:DesignHeight="25" d:DesignWidth="25">
|
||||
<UserControl.Resources>
|
||||
<Converters:NullToImageConverter x:Key="NullToImageConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid Width="{Binding Width}" Height="{Binding Height}">
|
||||
<Grid.Background>
|
||||
<ImageBrush AlignmentX="Center" AlignmentY="Center" Stretch="Fill"
|
||||
ImageSource="{Binding Led.Image, Converter={StaticResource NullToImageConverter}}" />
|
||||
</Grid.Background>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,29 @@
|
||||
using RGB.NET.Core;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.SurfaceEditor.Visualization
|
||||
{
|
||||
public class SurfaceLedViewModel : PropertyChangedBase
|
||||
{
|
||||
public SurfaceLedViewModel(Led led)
|
||||
{
|
||||
Led = led;
|
||||
ApplyLedToViewModel();
|
||||
}
|
||||
|
||||
public Led Led { get; set; }
|
||||
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
|
||||
public void ApplyLedToViewModel()
|
||||
{
|
||||
X = Led.LedRectangle.X;
|
||||
Y = Led.LedRectangle.Y;
|
||||
Width = Led.LedRectangle.Width;
|
||||
Height = Led.LedRectangle.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
|
||||
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"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Workshop"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<TextBlock>Work work!</TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop
|
||||
{
|
||||
public class WorkshopViewModel : Screen, IScreenViewModel
|
||||
{
|
||||
public string Title => "Workshop";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user