mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
UI - Implemented most of the surface editor
This commit is contained in:
parent
ca1e3ce365
commit
f98e398bc5
@ -52,9 +52,10 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
// Determine the scale required to fit the desired size of the control
|
||||
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
|
||||
|
||||
// Scale the visualization in the desired bounding box
|
||||
|
||||
DrawingContext.PushedState? boundsPush = null;
|
||||
try
|
||||
{
|
||||
// Scale the visualization in the desired bounding box
|
||||
if (Bounds.Width > 0 && Bounds.Height > 0)
|
||||
boundsPush = drawingContext.PushPostTransform(Matrix.CreateScale(scale, scale));
|
||||
|
||||
@ -71,9 +72,12 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.RenderGeometry(drawingContext, false);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
boundsPush?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a LED of the device has been clicked
|
||||
@ -275,6 +279,16 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
#region Overrides of Layoutable
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
return new Size(Math.Min(availableSize.Width, _deviceBounds.Width), Math.Min(availableSize.Height, _deviceBounds.Height));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -54,6 +54,7 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
public SelectionRectangle()
|
||||
{
|
||||
AffectsRender<TextBlock>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty);
|
||||
IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -108,6 +109,9 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
|
||||
private void ParentOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
e.Pointer.Capture(this);
|
||||
|
||||
_startPosition = e.GetPosition(Parent);
|
||||
@ -131,6 +135,9 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
|
||||
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (!ReferenceEquals(e.Pointer.Captured, this))
|
||||
return;
|
||||
|
||||
e.Pointer.Capture(null);
|
||||
|
||||
if (_displayRect != null)
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
||||
{
|
||||
public interface IWindowService : IArtemisSharedUIService
|
||||
{
|
||||
@ -13,8 +15,9 @@
|
||||
/// <summary>
|
||||
/// Given a ViewModel, show its corresponding View as a Dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The return type</typeparam>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
/// <returns>DialogResult of the View</returns>
|
||||
bool? ShowDialog(object viewModel);
|
||||
/// <returns>A task containing the return value of type <typeparamref name="T"/></returns>
|
||||
Task<T> ShowDialog<T>(object viewModel);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Avalonia.Shared.Exceptions;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Ninject;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Services
|
||||
@ -42,9 +46,23 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool? ShowDialog(object viewModel)
|
||||
public async Task<T> ShowDialog<T>(object viewModel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
|
||||
throw new ArtemisSharedUIException($"Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
|
||||
|
||||
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
||||
Type? type = viewModel.GetType().Assembly.GetType(name);
|
||||
|
||||
if (type == null)
|
||||
throw new ArtemisSharedUIException($"Failed to find a window named {name}.");
|
||||
if (!type.IsAssignableTo(typeof(Window)))
|
||||
throw new ArtemisSharedUIException($"Type {name} is not a window.");
|
||||
|
||||
Window window = (Window) Activator.CreateInstance(type)!;
|
||||
window.DataContext = viewModel;
|
||||
Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
|
||||
return await window.ShowDialog<T>(parent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1,76 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\" />
|
||||
<Folder Include="Models\" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Images\home-banner.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.7" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.7" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.7" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.7" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.7.2" />
|
||||
<PackageReference Include="FluentAvaloniaUI" Version="1.1.3" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
||||
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||
<PackageReference Include="Splat.Ninject" Version="13.1.22" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
<ProjectReference Include="..\Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="MainWindow.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Views\SidebarCategoryView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Views\SidebarProfileConfigurationView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Views\SidebarScreenView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Views\SidebarView.axaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Sidebar\Views\SidebarView.axaml.cs">
|
||||
<DependentUpon>SidebarView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Root\Views\RootView.axaml.cs">
|
||||
<DependentUpon>RootView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Views\MainWindow.axaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\Images\Logo\bow-black.ico" />
|
||||
<Content Include="Assets\Images\Logo\bow-white.ico" />
|
||||
<Content Include="Assets\Images\Logo\bow.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="RGB.NET.Core">
|
||||
<HintPath>..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Assets\Images\Logo\bow-black.ico" />
|
||||
<Resource Include="Assets\Images\Logo\bow-white.ico" />
|
||||
<Resource Include="Assets\Images\Logo\bow-white.svg" />
|
||||
<Resource Include="Assets\Images\Logo\bow.ico" />
|
||||
<Resource Include="Assets\Images\Logo\bow.svg" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -11,6 +11,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Images\home-banner.png" />
|
||||
<None Remove="Screens\SurfaceEditor\Views\ListDeviceView.xaml" />
|
||||
<None Remove="Screens\SurfaceEditor\Views\SurfaceDeviceView.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.10" />
|
||||
@ -62,6 +64,16 @@
|
||||
<Content Include="Assets\Images\Logo\bow-white.ico" />
|
||||
<Content Include="Assets\Images\Logo\bow.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="Screens\SurfaceEditor\Views\ListDeviceView.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Screens\SurfaceEditor\Views\SurfaceDeviceView.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="RGB.NET.Core">
|
||||
<HintPath>..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Extensions
|
||||
{
|
||||
public static class ObservableCollectionExtensions
|
||||
{
|
||||
public static void Sort<T>(this ObservableCollection<T> collection, Func<T, object> order)
|
||||
{
|
||||
List<T> ordered = collection.OrderBy(order).ToList();
|
||||
for (int index = 0; index < ordered.Count; index++)
|
||||
{
|
||||
T dataBindingConditionViewModel = ordered[index];
|
||||
if (collection.IndexOf(dataBindingConditionViewModel) != index)
|
||||
collection.Move(collection.IndexOf(dataBindingConditionViewModel), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Avalonia.Screens.Device.ViewModels;
|
||||
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
|
||||
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Ninject.Factories
|
||||
@ -8,10 +10,21 @@ namespace Artemis.UI.Avalonia.Ninject.Factories
|
||||
{
|
||||
}
|
||||
|
||||
public interface IDeviceVmFactory : IVmFactory
|
||||
{
|
||||
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
|
||||
}
|
||||
|
||||
public interface ISidebarVmFactory : IVmFactory
|
||||
{
|
||||
SidebarViewModel SidebarViewModel(IScreen hostScreen);
|
||||
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
|
||||
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
|
||||
}
|
||||
|
||||
public interface SurfaceVmFactory : IVmFactory
|
||||
{
|
||||
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device);
|
||||
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||
{
|
||||
public class DevicePropertiesViewModel : ActivatableViewModelBase
|
||||
{
|
||||
public DevicePropertiesViewModel(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Avalonia.Screens.Device.Views.DevicePropertiesView"
|
||||
Title="Artemis | Device Properties"
|
||||
Width="1200"
|
||||
Height="800"
|
||||
ExtendClientAreaToDecorationsHint="True">
|
||||
<Grid ColumnDefinitions="*,0,*">
|
||||
|
||||
|
||||
<Grid Grid.Column="0" Name="DeviceDisplayGrid">
|
||||
<Grid.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,25,25">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="1" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="0" />
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
|
||||
</Grid>
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Grid.Background>
|
||||
|
||||
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
|
||||
<controls:DeviceVisualizer Device="{Binding Device}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ShowColors="True"
|
||||
Margin="20" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="15" IsVisible="{Binding Device.Layout.RgbLayout.Author}">
|
||||
<TextBlock Classes="h5" Text="Device layout by " />
|
||||
<TextBlock Classes="h5" FontWeight="Bold" Text="{Binding Device.Layout.RgbLayout.Author}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch"/>
|
||||
|
||||
<Border Grid.Column="2" Classes="card" CornerRadius="10 0 0 0">
|
||||
<TabControl Margin="12" Items="{Binding Tabs}">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl Content="{Binding}" />
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@ -0,0 +1,24 @@
|
||||
using Artemis.UI.Avalonia.Screens.Device.ViewModels;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.Device.Views
|
||||
{
|
||||
public partial class DevicePropertiesView : ReactiveWindow<DevicePropertiesViewModel>
|
||||
{
|
||||
public DevicePropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,54 +28,44 @@
|
||||
Margin="30"
|
||||
Text=" Welcome to Artemis, the unified RGB platform." />
|
||||
|
||||
<Grid Grid.Row="1" MaxWidth="840" Margin="30" VerticalAlignment="Bottom">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1" MaxWidth="840" Margin="30" VerticalAlignment="Bottom" ColumnDefinitions="*,*" RowDefinitions="*,*">
|
||||
<Border Classes="card" Margin="8" Grid.ColumnSpan="2">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="Auto,Auto" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Plug" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<avalonia:MaterialIcon Kind="Plug" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock FontSize="24" Margin="16 16 16 8">Plugins</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8" VerticalAlignment="Top">
|
||||
Artemis is built up using plugins. This means devices, brushes, effects and modules (for supporting games!) can all be added via plugins.
|
||||
<TextBlock Classes="h3">Plugins</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
Artemis is built up using plugins. This means devices, brushes, effects and modules (for supporting games) can all be added via plugins.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8" VerticalAlignment="Top">
|
||||
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8" VerticalAlignment="Top">
|
||||
We're also keeping track of a list of third-party plugins on our wiki.
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Margin="0 15">
|
||||
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. We're also keeping track of a list of third-party plugins on our wiki.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0" BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Test"></MenuItem>
|
||||
</ContextMenu>
|
||||
</controls:HyperlinkButton.ContextMenu>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="OpenInBrowser" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Classes="card" Margin="8" Grid.Row="1" Grid.Column="0">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="175,95" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Discord" Width="100" Height="100"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="150,95" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Discord" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock FontSize="24" Margin="16 16 16 8">Have a chat</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8" VerticalAlignment="Top">
|
||||
<TextBlock Classes="h3">Have a chat</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
If you need help, have some feedback or have any other questions feel free to contact us through any of the following channels.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
|
||||
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0">
|
||||
<Grid Margin="8" RowDefinitions="*,*">
|
||||
<controls:HyperlinkButton Grid.Row="0" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
@ -83,7 +73,7 @@
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="0" NavigateUri="https://artemis-rgb.com" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
|
||||
@ -95,7 +85,7 @@
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="1" NavigateUri="mailto:spoinky.nl@gmail.com" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Email" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
|
||||
@ -103,38 +93,37 @@
|
||||
</controls:HyperlinkButton>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Classes="card" Margin="8" Grid.Row="1" Grid.Column="1">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="175,95" ColumnDefinitions="150,*">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="150,95" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Github" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock FontSize="16" Margin="16 16 16 8">Open Source</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="16 0 16 8" VerticalAlignment="Top">
|
||||
This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button, I like numbers. You could even make plugins, there's a full documentation on the website
|
||||
<TextBlock Classes="h3">Open Source</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
This project is open source. If you like it and want to say thanks you could hit the GitHub Star button, I like numbers.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
<controls:HyperlinkButton NavigateUri="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
NavigateUri="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<TextBlock Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="16"
|
||||
VerticalAlignment="Center">
|
||||
Feel like you want to make a donation? It would be gratefully received. Click the button to donate via PayPal.
|
||||
Feel like making a donation? It would be gratefully received. Click the button to donate via PayPal.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -14,13 +14,13 @@
|
||||
<ContentControl Grid.Column="0" Content="{Binding SidebarViewModel}" />
|
||||
|
||||
<Border Classes="router-container" Grid.Column="1" Margin="0 40 0 0">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" >
|
||||
<!-- <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" > -->
|
||||
<reactiveUi:RoutedViewHost Router="{Binding Router}">
|
||||
<reactiveUi:RoutedViewHost.PageTransition>
|
||||
<CrossFade Duration="0.1" />
|
||||
</reactiveUi:RoutedViewHost.PageTransition>
|
||||
</reactiveUi:RoutedViewHost>
|
||||
</ScrollViewer>
|
||||
<!-- </ScrollViewer> -->
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -12,7 +12,9 @@
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl Content="{Binding}" />
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
using Artemis.Core;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
|
||||
{
|
||||
public class ListDeviceViewModel : ViewModelBase
|
||||
{
|
||||
private SKColor _color;
|
||||
private bool _isSelected;
|
||||
|
||||
public ListDeviceViewModel(ArtemisDevice device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public SKColor Color
|
||||
{
|
||||
get => _color;
|
||||
set => this.RaiseAndSetIfChanged(ref _color, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Avalonia.Ninject.Factories;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
using SkiaSharp;
|
||||
using Point = Avalonia.Point;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
|
||||
{
|
||||
public class SurfaceDeviceViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDeviceVmFactory _deviceVmFactory;
|
||||
private readonly IWindowService _windowService;
|
||||
private Cursor _cursor;
|
||||
private double _dragOffsetX;
|
||||
private double _dragOffsetY;
|
||||
private SelectionStatus _selectionStatus;
|
||||
|
||||
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory, IWindowService windowService)
|
||||
{
|
||||
_rgbService = rgbService;
|
||||
_deviceService = deviceService;
|
||||
_settingsService = settingsService;
|
||||
_deviceVmFactory = deviceVmFactory;
|
||||
_windowService = windowService;
|
||||
|
||||
Device = device;
|
||||
|
||||
IdentifyDevice = ReactiveCommand.Create<ArtemisDevice>(ExecuteIdentifyDevice);
|
||||
ViewProperties = ReactiveCommand.CreateFromTask<ArtemisDevice>(ExecuteViewProperties);
|
||||
}
|
||||
|
||||
public ReactiveCommand<ArtemisDevice, Unit> IdentifyDevice { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> ViewProperties { get; }
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public SelectionStatus SelectionStatus
|
||||
{
|
||||
get => _selectionStatus;
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref _selectionStatus, value);
|
||||
this.RaisePropertyChanged(nameof(Highlighted));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Highlighted => SelectionStatus != SelectionStatus.None;
|
||||
|
||||
public bool CanDetectInput => Device.DeviceType == RGBDeviceType.Keyboard || Device.DeviceType == RGBDeviceType.Mouse;
|
||||
|
||||
public Cursor Cursor
|
||||
{
|
||||
get => _cursor;
|
||||
set => this.RaiseAndSetIfChanged(ref _cursor, value);
|
||||
}
|
||||
|
||||
public void StartMouseDrag(Point mouseStartPosition)
|
||||
{
|
||||
if (SelectionStatus != SelectionStatus.Selected)
|
||||
return;
|
||||
|
||||
_dragOffsetX = Device.X - mouseStartPosition.X;
|
||||
_dragOffsetY = Device.Y - mouseStartPosition.Y;
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition)
|
||||
{
|
||||
if (SelectionStatus != SelectionStatus.Selected)
|
||||
return;
|
||||
|
||||
float roundedX = (float) Math.Round((mousePosition.X + _dragOffsetX) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
float roundedY = (float) Math.Round((mousePosition.Y + _dragOffsetY) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||
|
||||
if (Fits(roundedX, roundedY))
|
||||
{
|
||||
Device.X = roundedX;
|
||||
Device.Y = roundedY;
|
||||
}
|
||||
else if (Fits(roundedX, Device.Y))
|
||||
{
|
||||
Device.X = roundedX;
|
||||
}
|
||||
else if (Fits(Device.X, roundedY))
|
||||
{
|
||||
Device.Y = roundedY;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteIdentifyDevice(ArtemisDevice device)
|
||||
{
|
||||
_deviceService.IdentifyDevice(device);
|
||||
}
|
||||
|
||||
private async Task ExecuteViewProperties(ArtemisDevice device)
|
||||
{
|
||||
await _windowService.ShowDialog<bool>(_deviceVmFactory.DevicePropertiesViewModel(device));
|
||||
}
|
||||
|
||||
private bool Fits(float x, float y)
|
||||
{
|
||||
if (x < 0 || y < 0)
|
||||
return false;
|
||||
|
||||
double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
|
||||
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
|
||||
return false;
|
||||
|
||||
List<SKRect> own = Device.Leds
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height))
|
||||
.ToList();
|
||||
List<SKRect> others = _rgbService.EnabledDevices
|
||||
.Where(d => d != Device && d.IsEnabled)
|
||||
.SelectMany(d => d.Leds)
|
||||
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height))
|
||||
.ToList();
|
||||
|
||||
|
||||
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
|
||||
}
|
||||
}
|
||||
|
||||
public enum SelectionStatus
|
||||
{
|
||||
None,
|
||||
Hover,
|
||||
Selected
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Avalonia.Extensions;
|
||||
using Artemis.UI.Avalonia.Ninject.Factories;
|
||||
using Avalonia;
|
||||
using Avalonia.Skia;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
|
||||
{
|
||||
public class SurfaceEditorViewModel : MainScreenViewModel
|
||||
{
|
||||
public SurfaceEditorViewModel(IScreen hostScreen, IRgbService rgbService) : base(hostScreen, "surface-editor")
|
||||
private readonly IInputService _inputService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private bool _saving;
|
||||
|
||||
public SurfaceEditorViewModel(IScreen hostScreen,
|
||||
IRgbService rgbService,
|
||||
SurfaceVmFactory surfaceVmFactory,
|
||||
IInputService inputService,
|
||||
ISettingsService settingsService) : base(hostScreen, "surface-editor")
|
||||
{
|
||||
_rgbService = rgbService;
|
||||
_inputService = inputService;
|
||||
_settingsService = settingsService;
|
||||
DisplayName = "Surface Editor";
|
||||
Devices = new ObservableCollection<ArtemisDevice>(rgbService.Devices);
|
||||
SurfaceDeviceViewModels = new ObservableCollection<SurfaceDeviceViewModel>(rgbService.Devices.Select(surfaceVmFactory.SurfaceDeviceViewModel));
|
||||
ListDeviceViewModels = new ObservableCollection<ListDeviceViewModel>(rgbService.Devices.Select(surfaceVmFactory.ListDeviceViewModel));
|
||||
|
||||
BringToFront = ReactiveCommand.Create<ArtemisDevice>(ExecuteBringToFront);
|
||||
BringForward = ReactiveCommand.Create<ArtemisDevice>(ExecuteBringForward);
|
||||
SendToBack = ReactiveCommand.Create<ArtemisDevice>(ExecuteSendToBack);
|
||||
SendBackward = ReactiveCommand.Create<ArtemisDevice>(ExecuteSendBackward);
|
||||
|
||||
UpdateSelection = ReactiveCommand.Create<Rect>(ExecuteUpdateSelection);
|
||||
ApplySelection = ReactiveCommand.Create<Rect>(ExecuteApplySelection);
|
||||
}
|
||||
|
||||
public ObservableCollection<ArtemisDevice> Devices { get; }
|
||||
public ObservableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
|
||||
public ObservableCollection<ListDeviceViewModel> ListDeviceViewModels { get; }
|
||||
|
||||
public ReactiveCommand<ArtemisDevice, Unit> BringToFront { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> BringForward { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> SendToBack { get; }
|
||||
public ReactiveCommand<ArtemisDevice, Unit> SendBackward { get; }
|
||||
|
||||
public ReactiveCommand<Rect, Unit> UpdateSelection { get; }
|
||||
public ReactiveCommand<Rect, Unit> ApplySelection { get; }
|
||||
|
||||
public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value;
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
public void StartMouseDrag(Point mousePosition)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.StartMouseDrag(mousePosition);
|
||||
}
|
||||
|
||||
public void UpdateMouseDrag(Point mousePosition)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
|
||||
}
|
||||
|
||||
public void StopMouseDrag(Point mousePosition)
|
||||
{
|
||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
|
||||
|
||||
if (_saving)
|
||||
return;
|
||||
|
||||
// TODO: Figure out why the UI still locks up here
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_saving = true;
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_saving = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ExecuteUpdateSelection(Rect rect)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void ExecuteApplySelection(Rect rect)
|
||||
SKRect hitTestRect = rect.ToSKRect();
|
||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
|
||||
{
|
||||
|
||||
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
|
||||
device.SelectionStatus = SelectionStatus.Selected;
|
||||
else if (!_inputService.IsKeyDown(KeyboardKey.LeftShift) && !_inputService.IsKeyDown(KeyboardKey.RightShift))
|
||||
device.SelectionStatus = SelectionStatus.None;
|
||||
}
|
||||
|
||||
ApplySurfaceSelection();
|
||||
}
|
||||
|
||||
private void ApplySurfaceSelection()
|
||||
{
|
||||
foreach (ListDeviceViewModel viewModel in ListDeviceViewModels)
|
||||
viewModel.IsSelected = SurfaceDeviceViewModels.Any(s => s.Device == viewModel.Device && s.SelectionStatus == SelectionStatus.Selected);
|
||||
}
|
||||
|
||||
#region Context menu commands
|
||||
|
||||
private void ExecuteBringToFront(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), SurfaceDeviceViewModels.Count - 1);
|
||||
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
|
||||
{
|
||||
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
|
||||
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
|
||||
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
private void ExecuteBringForward(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
|
||||
int newIndex = Math.Min(currentIndex + 1, SurfaceDeviceViewModels.Count - 1);
|
||||
SurfaceDeviceViewModels.Move(currentIndex, newIndex);
|
||||
|
||||
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
|
||||
{
|
||||
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
|
||||
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
|
||||
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
private void ExecuteSendToBack(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), 0);
|
||||
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
|
||||
{
|
||||
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
|
||||
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
|
||||
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
private void ExecuteSendBackward(ArtemisDevice device)
|
||||
{
|
||||
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
|
||||
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
|
||||
int newIndex = Math.Max(currentIndex - 1, 0);
|
||||
SurfaceDeviceViewModels.Move(currentIndex, newIndex);
|
||||
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
|
||||
{
|
||||
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
|
||||
deviceViewModel.Device.ZIndex = i + 1;
|
||||
}
|
||||
|
||||
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
|
||||
|
||||
_rgbService.SaveDevices();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Avalonia.Screens.SurfaceEditor.Views.ListDeviceView">
|
||||
Welcome to Avalonia!
|
||||
</UserControl>
|
||||
@ -0,0 +1,19 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.Views
|
||||
{
|
||||
public partial class ListDeviceView : UserControl
|
||||
{
|
||||
public ListDeviceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Avalonia.Screens.SurfaceEditor.Views.SurfaceDeviceView">
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="Border.selection-border">
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Border.selection-border-selected">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<controls:DeviceVisualizer Device="{Binding Device}" ShowColors="True"/>
|
||||
<Border x:Name="SurfaceDeviceBorder"
|
||||
Classes="selection-border"
|
||||
Classes.selection-border-selected="{Binding Highlighted}"
|
||||
BorderThickness="1">
|
||||
<Border.BorderBrush>
|
||||
<SolidColorBrush Color="{DynamicResource SystemAccentColor}"></SolidColorBrush>
|
||||
</Border.BorderBrush>
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{DynamicResource SystemAccentColor}" Opacity="0.2"></SolidColorBrush>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@ -0,0 +1,57 @@
|
||||
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.Views
|
||||
{
|
||||
public class SurfaceDeviceView : ReactiveUserControl<SurfaceDeviceViewModel>
|
||||
{
|
||||
public SurfaceDeviceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
#region Overrides of InputElement
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPointerEnter(PointerEventArgs e)
|
||||
{
|
||||
if (ViewModel?.SelectionStatus == SelectionStatus.None)
|
||||
{
|
||||
ViewModel.SelectionStatus = SelectionStatus.Hover;
|
||||
Cursor = new Cursor(StandardCursorType.Hand);
|
||||
}
|
||||
|
||||
base.OnPointerEnter(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPointerLeave(PointerEventArgs e)
|
||||
{
|
||||
if (ViewModel?.SelectionStatus == SelectionStatus.Hover)
|
||||
{
|
||||
ViewModel.SelectionStatus = SelectionStatus.None;
|
||||
Cursor = new Cursor(StandardCursorType.Arrow);
|
||||
}
|
||||
|
||||
base.OnPointerLeave(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && ViewModel != null)
|
||||
ViewModel.SelectionStatus = SelectionStatus.Selected;
|
||||
|
||||
base.OnPointerPressed(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,8 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
|
||||
xmlns:paz="using:Avalonia.Controls.PanAndZoom"
|
||||
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Avalonia.Screens.SurfaceEditor.Views.SurfaceEditorView">
|
||||
|
||||
@ -14,7 +15,10 @@
|
||||
Focusable="True"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
ZoomChanged="ZoomBorder_OnZoomChanged">
|
||||
ZoomChanged="ZoomBorder_OnZoomChanged"
|
||||
PointerPressed="ZoomBorder_OnPointerPressed"
|
||||
PointerMoved="ZoomBorder_OnPointerMoved"
|
||||
PointerReleased="ZoomBorder_OnPointerReleased">
|
||||
<paz:ZoomBorder.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform" SourceRect="0,0,25,25">
|
||||
<VisualBrush.Visual>
|
||||
@ -27,12 +31,13 @@
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</paz:ZoomBorder.Background>
|
||||
<Grid Background="Transparent">
|
||||
<ItemsControl Items="{Binding Devices}">
|
||||
<Grid Name="ContainerGrid"
|
||||
Background="Transparent">
|
||||
<ItemsControl Items="{Binding SurfaceDeviceViewModels}" ClipToBounds="False">
|
||||
<ItemsControl.Styles>
|
||||
<Style Selector="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}" />
|
||||
<Setter Property="Canvas.Left" Value="{Binding Device.X}" />
|
||||
<Setter Property="Canvas.Top" Value="{Binding Device.Y}" />
|
||||
</Style>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemsPanel>
|
||||
@ -42,17 +47,68 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:DeviceVisualizer Device="{Binding}" ShowColors="True" />
|
||||
<ContentControl Content="{Binding}">
|
||||
<ContentControl.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Identify" Command="{Binding IdentifyDevice}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="AlarmLight" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" />
|
||||
<MenuItem Header="Bring to Front" Command="{Binding $parent[4].DataContext.BringToFront}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeBringToFront" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Bring Forward" Command="{Binding $parent[4].DataContext.BringForward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeBringForward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send to Back" Command="{Binding $parent[4].DataContext.SendToBack}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeSendToBack" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Send Backward" Command="{Binding $parent[4].DataContext.SendBackward}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="ArrangeSendBackward" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="-" />
|
||||
<MenuItem Header="Identify input"
|
||||
Command="{Binding DetectInput}"
|
||||
CommandParameter="{Binding Device}"
|
||||
ToolTip.Tip="Teach Artemis which keypresses and/or button presses belong to this device">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="GestureDoubleTap" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="View properties" Command="{Binding ViewProperties}" CommandParameter="{Binding Device}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Gear" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</ContentControl.ContextFlyout>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<controls:SelectionRectangle Name="SelectionRectangle"
|
||||
InputElement="{Binding #ZoomBorder}"
|
||||
SelectionUpdated="{Binding UpdateSelection}"
|
||||
SelectionFinished="{Binding ApplySelection}"/>
|
||||
SelectionUpdated="{Binding UpdateSelection}" />
|
||||
|
||||
<Border Name="SurfaceBounds"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Width="{Binding MaxTextureSize}"
|
||||
Height="{Binding MaxTextureSize}"
|
||||
BorderThickness="2"
|
||||
BorderBrush="{DynamicResource SystemAccentColorDark3}"
|
||||
CornerRadius="8" />
|
||||
</Grid>
|
||||
|
||||
</paz:ZoomBorder>
|
||||
|
||||
</UserControl>
|
||||
@ -3,23 +3,30 @@ using Artemis.UI.Avalonia.Shared.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.PanAndZoom;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.Views
|
||||
{
|
||||
public class SurfaceEditorView : ReactiveUserControl<SurfaceEditorViewModel>
|
||||
{
|
||||
private readonly SelectionRectangle _selectionRectangle;
|
||||
private readonly Grid _containerGrid;
|
||||
private readonly ZoomBorder _zoomBorder;
|
||||
private readonly Border _surfaceBounds;
|
||||
|
||||
public SurfaceEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_zoomBorder = this.Find<ZoomBorder>("ZoomBorder");
|
||||
_containerGrid = this.Find<Grid>("ContainerGrid");
|
||||
_selectionRectangle = this.Find<SelectionRectangle>("SelectionRectangle");
|
||||
_surfaceBounds = this.Find<Border>("SurfaceBounds");
|
||||
|
||||
|
||||
((VisualBrush) _zoomBorder.Background).DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
|
||||
}
|
||||
@ -33,6 +40,43 @@ namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.Views
|
||||
{
|
||||
((VisualBrush) _zoomBorder.Background).DestinationRect = new RelativeRect(_zoomBorder.OffsetX * -1, _zoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
|
||||
_selectionRectangle.BorderThickness = 1 / _zoomBorder.ZoomX;
|
||||
_surfaceBounds.BorderThickness = new Thickness(2 / _zoomBorder.ZoomX);
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
if (e.Source is Border {Name: "SurfaceDeviceBorder"})
|
||||
{
|
||||
e.Pointer.Capture(_zoomBorder);
|
||||
e.Handled = true;
|
||||
ViewModel?.StartMouseDrag(e.GetPosition(_containerGrid));
|
||||
}
|
||||
else
|
||||
ViewModel?.ClearSelection();
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
if (ReferenceEquals(e.Pointer.Captured, sender))
|
||||
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid));
|
||||
}
|
||||
|
||||
private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (e.InitialPressMouseButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
if (ReferenceEquals(e.Pointer.Captured, sender))
|
||||
{
|
||||
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid));
|
||||
e.Pointer.Capture(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.h5">
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.h6">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia
|
||||
{
|
||||
@ -13,8 +15,35 @@ namespace Artemis.UI.Avalonia
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel
|
||||
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected ActivatableViewModelBase()
|
||||
{
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Disposable.Create(Dispose).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public ViewModelActivator Activator { get; } = new();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user