diff --git a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj
index 2ddd5386b..a52cde147 100644
--- a/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj
+++ b/src/Artemis.UI.Avalonia.Shared/Artemis.UI.Avalonia.Shared.csproj
@@ -21,5 +21,8 @@
..\..\..\..\Users\Robert\.nuget\packages\material.icons.avalonia\1.0.2\lib\netstandard2.0\Material.Icons.Avalonia.dll
+
+ ..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll
+
diff --git a/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizer.cs
index 841069aed..76c83efc8 100644
--- a/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizer.cs
@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.IO;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Artemis.Core;
+using Artemis.UI.Avalonia.Shared.Events;
using Avalonia;
using Avalonia.Controls;
+using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Media;
+using Avalonia.Media.Imaging;
using Avalonia.Threading;
namespace Artemis.UI.Avalonia.Shared.Controls
@@ -18,6 +21,121 @@ namespace Artemis.UI.Avalonia.Shared.Controls
///
public class DeviceVisualizer : Control
{
+ private readonly DispatcherTimer _timer;
+ private readonly List _deviceVisualizerLeds;
+
+ private Bitmap? _deviceImage;
+ private List? _dimmedLeds;
+ private List? _highlightedLeds;
+ private ArtemisDevice? _oldDevice;
+
+ ///
+ public DeviceVisualizer()
+ {
+ // Run an update timer at 25 fps
+ _timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
+ _deviceVisualizerLeds = new List();
+
+ PointerReleased += OnPointerReleased;
+ }
+
+ ///
+ public override void Render(DrawingContext drawingContext)
+ {
+ if (Device == null)
+ return;
+
+ // Determine the scale required to fit the desired size of the control
+ Rect measureSize = MeasureDevice();
+ double scale = Math.Min(Bounds.Width / measureSize.Width, Bounds.Height / measureSize.Height);
+
+ // Scale the visualization in the desired bounding box
+ if (Bounds.Width > 0 && Bounds.Height > 0)
+ drawingContext.PushPostTransform(Matrix.CreateScale(scale, scale));
+
+ // Apply device rotation
+ drawingContext.PushPostTransform(Matrix.CreateTranslation(0 - measureSize.Left, 0 - measureSize.Top));
+ drawingContext.PushPostTransform(Matrix.CreateRotation(Device.Rotation));
+
+ // Apply device scale
+ drawingContext.PushPostTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
+
+ // Render device and LED images
+ if (_deviceImage != null)
+ drawingContext.DrawImage(_deviceImage, new Rect(0, 0, Device.RgbDevice.Size.Width, Device.RgbDevice.Size.Height));
+
+ foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
+ deviceVisualizerLed.RenderImage(drawingContext);
+ }
+
+ ///
+ /// Occurs when a LED of the device has been clicked
+ ///
+ public event EventHandler? LedClicked;
+
+ ///
+ /// Invokes the event
+ ///
+ ///
+ protected virtual void OnLedClicked(LedClickedEventArgs e)
+ {
+ LedClicked?.Invoke(this, e);
+ }
+
+ private void Update()
+ {
+ InvalidateVisual();
+ }
+
+ private void UpdateTransform()
+ {
+ InvalidateVisual();
+ InvalidateMeasure();
+ }
+
+ private Rect MeasureDevice()
+ {
+ if (Device == null)
+ return Rect.Empty;
+
+ Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
+ Geometry geometry = new RectangleGeometry(deviceRect);
+ geometry.Transform = new RotateTransform(Device.Rotation);
+
+ return geometry.Bounds;
+ }
+
+ private void TimerOnTick(object? sender, EventArgs e)
+ {
+ if (ShowColors && IsVisible && Opacity > 0)
+ Update();
+ }
+
+ private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (Device == null)
+ return;
+
+ Point position = e.GetPosition(this);
+ double x = position.X / Bounds.Width;
+ double y = position.Y / Bounds.Height;
+
+ Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
+ DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
+ if (deviceVisualizerLed != null)
+ OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led));
+ }
+
+ private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ Dispatcher.UIThread.Post(SetupForDevice);
+ }
+
+ private void DeviceUpdated(object? sender, EventArgs e)
+ {
+ Dispatcher.UIThread.Post(SetupForDevice);
+ }
+
#region Properties
///
@@ -67,26 +185,6 @@ namespace Artemis.UI.Avalonia.Shared.Controls
#endregion
- private readonly DispatcherTimer _timer;
-
- ///
- public DeviceVisualizer()
- {
- // Run an update timer at 25 fps
- _timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
- }
-
- ///
- public override void Render(DrawingContext context)
- {
- base.Render(context);
- }
-
- private void Update()
- {
- throw new NotImplementedException();
- }
-
#region Lifetime management
///
@@ -105,14 +203,37 @@ namespace Artemis.UI.Avalonia.Shared.Controls
base.OnDetachedFromLogicalTree(e);
}
- #endregion
-
- #region Event handlers
-
- private void TimerOnTick(object? sender, EventArgs e)
+ private void SetupForDevice()
{
- if (ShowColors && IsVisible && Opacity > 0)
- Update();
+ _deviceImage = null;
+ _deviceVisualizerLeds.Clear();
+ _highlightedLeds = new List();
+ _dimmedLeds = new List();
+
+ if (Device == null)
+ return;
+
+ if (_oldDevice != null)
+ {
+ Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
+ Device.DeviceUpdated -= DeviceUpdated;
+ }
+
+ _oldDevice = Device;
+
+ Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
+ Device.DeviceUpdated += DeviceUpdated;
+ UpdateTransform();
+
+ // Load the device main image
+ if (Device.Layout?.Image != null && File.Exists(Device.Layout.Image.LocalPath))
+ _deviceImage = new Bitmap(Device.Layout.Image.AbsolutePath);
+
+ // Create all the LEDs
+ foreach (ArtemisLed artemisLed in Device.Leds)
+ _deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
+
+ InvalidateMeasure();
}
#endregion
diff --git a/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizerLed.cs
new file mode 100644
index 000000000..ae6c6ff5e
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizerLed.cs
@@ -0,0 +1,159 @@
+using System;
+using System.IO;
+using Artemis.Core;
+using Avalonia;
+using Avalonia.Media;
+using Avalonia.Media.Imaging;
+using RGB.NET.Core;
+using Color = Avalonia.Media.Color;
+using Point = Avalonia.Point;
+using SolidColorBrush = Avalonia.Media.SolidColorBrush;
+
+namespace Artemis.UI.Avalonia.Shared.Controls
+{
+ internal class DeviceVisualizerLed
+ {
+ private const byte Dimmed = 100;
+ private const byte NonDimmed = 255;
+
+ public DeviceVisualizerLed(ArtemisLed led)
+ {
+ Led = led;
+ LedRect = new Rect(
+ Led.RgbLed.Location.X,
+ Led.RgbLed.Location.Y,
+ Led.RgbLed.Size.Width,
+ Led.RgbLed.Size.Height
+ );
+
+ if (Led.Layout?.Image != null && File.Exists(Led.Layout.Image.LocalPath))
+ LedImage = new Bitmap(Led.Layout.Image.AbsolutePath);
+
+ CreateLedGeometry();
+ }
+
+ public ArtemisLed Led { get; }
+ public Rect LedRect { get; set; }
+ public Bitmap? LedImage { get; set; }
+ public Geometry? DisplayGeometry { get; private set; }
+
+ public void RenderImage(DrawingContext drawingContext)
+ {
+ if (LedImage == null)
+ return;
+
+ drawingContext.DrawImage(LedImage, LedRect);
+
+ byte r = Led.RgbLed.Color.GetR();
+ byte g = Led.RgbLed.Color.GetG();
+ byte b = Led.RgbLed.Color.GetB();
+ SolidColorBrush fillBrush = new(new Color(100, r, g, b));
+ SolidColorBrush penBrush = new(new Color(255, r, g, b));
+
+ // Create transparent pixels covering the entire LedRect so the image size matched the LedRect size
+ // drawingContext.DrawRectangle(new SolidColorBrush(Colors.Transparent), new Pen(new SolidColorBrush(Colors.Transparent), 1), LedRect);
+ // Translate to the top-left of the LedRect
+ using DrawingContext.PushedState push = drawingContext.PushPostTransform(Matrix.CreateTranslation(LedRect.X, LedRect.Y));
+ // Render the LED geometry
+ drawingContext.DrawGeometry(fillBrush, new Pen(penBrush) {LineJoin = PenLineJoin.Round}, DisplayGeometry);
+ }
+
+ public bool HitTest(Point position)
+ {
+ if (DisplayGeometry == null)
+ return false;
+
+ Geometry translatedGeometry = DisplayGeometry.Clone();
+ translatedGeometry.Transform = new TranslateTransform(Led.RgbLed.Location.X, Led.RgbLed.Location.Y);
+ return translatedGeometry.FillContains(position);
+ }
+
+ private void CreateLedGeometry()
+ {
+ // The minimum required size for geometry to be created
+ if (Led.RgbLed.Size.Width < 2 || Led.RgbLed.Size.Height < 2)
+ return;
+
+ switch (Led.RgbLed.Shape)
+ {
+ case Shape.Custom:
+ if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
+ CreateCustomGeometry(2.0);
+ else
+ CreateCustomGeometry(1.0);
+ break;
+ case Shape.Rectangle:
+ if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
+ CreateKeyCapGeometry();
+ else
+ CreateRectangleGeometry();
+ break;
+ case Shape.Circle:
+ CreateCircleGeometry();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void CreateRectangleGeometry()
+ {
+ DisplayGeometry = new RectangleGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
+ }
+
+ private void CreateCircleGeometry()
+ {
+ DisplayGeometry = new EllipseGeometry(new Rect(0.5, 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
+ }
+
+ private void CreateKeyCapGeometry()
+ {
+ PathGeometry path = PathGeometry.Parse($"M1,1" +
+ $"h{Led.RgbLed.Size.Width - 2} a10," +
+ $"10 0 0 1 10," +
+ $"10 v{Led.RgbLed.Size.Height - 2} a10," +
+ $"10 0 0 1 -10," +
+ $"10 h-{Led.RgbLed.Size.Width - 2} a10," +
+ $"10 0 0 1 -10," +
+ $"-10 v-{Led.RgbLed.Size.Height - 2} a10," +
+ $"10 0 0 1 10,-10 z");
+ DisplayGeometry = path;
+ }
+
+ private void CreateCustomGeometry(double deflateAmount)
+ {
+ try
+ {
+ double width = Led.RgbLed.Size.Width - deflateAmount;
+ double height = Led.RgbLed.Size.Height - deflateAmount;
+
+ Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
+ geometry.Transform = new ScaleTransform(width, height);
+ geometry = geometry.Clone();
+ geometry.Transform = new TranslateTransform(deflateAmount / 2, deflateAmount / 2);
+ DisplayGeometry = geometry.Clone();
+
+ // TODO: Figure out wtf was going on here
+ // if (DisplayGeometry.Bounds.Width > width)
+ // {
+ // DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup
+ // {
+ // Children = new TransformCollection {new ScaleTransform(width / DisplayGeometry.Bounds.Width, 1)}
+ // });
+ // }
+ //
+ // if (DisplayGeometry.Bounds.Height > height)
+ // {
+ // DisplayGeometry = Geometry.Combine(Geometry.Empty, DisplayGeometry, GeometryCombineMode.Union, new TransformGroup
+ // {
+ // Children = new TransformCollection {new ScaleTransform(1, height / DisplayGeometry.Bounds.Height)}
+ // });
+ // }
+ }
+ catch (Exception)
+ {
+ CreateRectangleGeometry();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputDynamicEventArgs.cs b/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputDynamicEventArgs.cs
new file mode 100644
index 000000000..596f7a3c2
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputDynamicEventArgs.cs
@@ -0,0 +1,21 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Avalonia.Shared.Events
+{
+ ///
+ /// Provides data about selection events raised by
+ ///
+ public class DataModelInputDynamicEventArgs : EventArgs
+ {
+ internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath)
+ {
+ DataModelPath = dataModelPath;
+ }
+
+ ///
+ /// Gets the data model path that was selected
+ ///
+ public DataModelPath? DataModelPath { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputStaticEventArgs.cs b/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputStaticEventArgs.cs
new file mode 100644
index 000000000..2224d5021
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Events/DataModelInputStaticEventArgs.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Artemis.UI.Avalonia.Shared.Events
+{
+ ///
+ /// Provides data about submit events raised by
+ ///
+ public class DataModelInputStaticEventArgs : EventArgs
+ {
+ internal DataModelInputStaticEventArgs(object? value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// The value that was submitted
+ ///
+ public object? Value { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia.Shared/Events/LedClickedEventArgs.cs b/src/Artemis.UI.Avalonia.Shared/Events/LedClickedEventArgs.cs
new file mode 100644
index 000000000..e6f763a25
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Events/LedClickedEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Avalonia.Shared.Events
+{
+ ///
+ /// Provides data on LED click events raised by the device visualizer
+ ///
+ public class LedClickedEventArgs : EventArgs
+ {
+ internal LedClickedEventArgs(ArtemisDevice device, ArtemisLed led)
+ {
+ Device = device;
+ Led = led;
+ }
+
+ ///
+ /// The device that was clicked
+ ///
+ public ArtemisDevice Device { get; set; }
+
+ ///
+ /// The LED that was clicked
+ ///
+ public ArtemisLed Led { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia.Shared/Events/ProfileConfigurationEventArgs.cs b/src/Artemis.UI.Avalonia.Shared/Events/ProfileConfigurationEventArgs.cs
new file mode 100644
index 000000000..ce9e24a5d
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Events/ProfileConfigurationEventArgs.cs
@@ -0,0 +1,32 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Avalonia.Shared.Events
+{
+ ///
+ /// Provides data on profile related events raised by the profile editor
+ ///
+ public class ProfileConfigurationEventArgs : EventArgs
+ {
+ internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
+ {
+ ProfileConfiguration = profileConfiguration;
+ }
+
+ internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
+ {
+ ProfileConfiguration = profileConfiguration;
+ PreviousProfileConfiguration = previousProfileConfiguration;
+ }
+
+ ///
+ /// Gets the profile the event was raised for
+ ///
+ public ProfileConfiguration? ProfileConfiguration { get; }
+
+ ///
+ /// If applicable, the previous active profile before the event was raised
+ ///
+ public ProfileConfiguration? PreviousProfileConfiguration { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia.Shared/Events/RenderProfileElementEventArgs.cs b/src/Artemis.UI.Avalonia.Shared/Events/RenderProfileElementEventArgs.cs
new file mode 100644
index 000000000..95455d818
--- /dev/null
+++ b/src/Artemis.UI.Avalonia.Shared/Events/RenderProfileElementEventArgs.cs
@@ -0,0 +1,32 @@
+using System;
+using Artemis.Core;
+
+namespace Artemis.UI.Avalonia.Shared.Events
+{
+ ///
+ /// Provides data on profile element related events raised by the profile editor
+ ///
+ public class RenderProfileElementEventArgs : EventArgs
+ {
+ internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement)
+ {
+ RenderProfileElement = renderProfileElement;
+ }
+
+ internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement)
+ {
+ RenderProfileElement = renderProfileElement;
+ PreviousRenderProfileElement = previousRenderProfileElement;
+ }
+
+ ///
+ /// Gets the profile element the event was raised for
+ ///
+ public RenderProfileElement? RenderProfileElement { get; }
+
+ ///
+ /// If applicable, the previous active profile element before the event was raised
+ ///
+ public RenderProfileElement? PreviousRenderProfileElement { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/App.axaml b/src/Artemis.UI.Avalonia/App.axaml
index bc3f412c4..0daed3338 100644
--- a/src/Artemis.UI.Avalonia/App.axaml
+++ b/src/Artemis.UI.Avalonia/App.axaml
@@ -11,8 +11,13 @@
-
-
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI.Avalonia/App.axaml.cs b/src/Artemis.UI.Avalonia/App.axaml.cs
index de78f841c..345424826 100644
--- a/src/Artemis.UI.Avalonia/App.axaml.cs
+++ b/src/Artemis.UI.Avalonia/App.axaml.cs
@@ -1,10 +1,10 @@
using Artemis.Core.Ninject;
using Artemis.UI.Avalonia.Ninject;
-using Artemis.UI.Avalonia.Screens.Main.Views;
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
+using FluentAvalonia.Styling;
using Ninject;
using Splat.Ninject;
@@ -23,10 +23,13 @@ namespace Artemis.UI.Avalonia
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get()
};
+ AvaloniaLocator.Current.GetService().ForceNativeTitleBarToTheme(desktop.MainWindow, "Dark");
+ }
base.OnFrameworkInitializationCompleted();
}
diff --git a/src/Artemis.UI.Avalonia/MainWindow.axaml b/src/Artemis.UI.Avalonia/MainWindow.axaml
index ee8375848..2b5967a0c 100644
--- a/src/Artemis.UI.Avalonia/MainWindow.axaml
+++ b/src/Artemis.UI.Avalonia/MainWindow.axaml
@@ -4,11 +4,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="Artemis.UI.Avalonia.Screens.Main.Views.MainWindow"
+ x:Class="Artemis.UI.Avalonia.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Artemis.UI.Avalonia"
+ ExtendClientAreaToDecorationsHint="True"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
- ExtendClientAreaToDecorationsHint="True"
Content="{Binding}">
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/MainWindow.axaml.cs b/src/Artemis.UI.Avalonia/MainWindow.axaml.cs
index be13f759e..d57321c31 100644
--- a/src/Artemis.UI.Avalonia/MainWindow.axaml.cs
+++ b/src/Artemis.UI.Avalonia/MainWindow.axaml.cs
@@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
-namespace Artemis.UI.Avalonia.Screens.Main.Views
+namespace Artemis.UI.Avalonia
{
public partial class MainWindow : ReactiveWindow
{
diff --git a/src/Artemis.UI.Avalonia/Ninject/UIModule.cs b/src/Artemis.UI.Avalonia/Ninject/UIModule.cs
index 74f829d80..a934f3b91 100644
--- a/src/Artemis.UI.Avalonia/Ninject/UIModule.cs
+++ b/src/Artemis.UI.Avalonia/Ninject/UIModule.cs
@@ -3,6 +3,7 @@ using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
+using Ninject.Planning.Bindings.Resolvers;
namespace Artemis.UI.Avalonia.Ninject
{
@@ -13,6 +14,7 @@ namespace Artemis.UI.Avalonia.Ninject
if (Kernel == null)
throw new ArgumentNullException("Kernel shouldn't be null here.");
+ Kernel.Components.Add();
Kernel.Bind(x =>
{
diff --git a/src/Artemis.UI.Avalonia/Screens/Home/ViewModels/HomeViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Home/ViewModels/HomeViewModel.cs
index 53783ae7b..c91071b45 100644
--- a/src/Artemis.UI.Avalonia/Screens/Home/ViewModels/HomeViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Home/ViewModels/HomeViewModel.cs
@@ -1,8 +1,10 @@
-namespace Artemis.UI.Avalonia.Screens.Home.ViewModels
+using ReactiveUI;
+
+namespace Artemis.UI.Avalonia.Screens.Home.ViewModels
{
public class HomeViewModel : MainScreenViewModel
{
- public HomeViewModel()
+ public HomeViewModel(IScreen hostScreens) : base(hostScreens, "home")
{
DisplayName = "Home";
}
diff --git a/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs b/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs
index 7ad2e7bcd..466d7b0c0 100644
--- a/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/MainScreenViewModel.cs
@@ -1,7 +1,19 @@
-namespace Artemis.UI.Avalonia.Screens
+using ReactiveUI;
+
+namespace Artemis.UI.Avalonia.Screens
{
- public class MainScreenViewModel : ViewModelBase
+ public abstract class MainScreenViewModel : ViewModelBase, IRoutableViewModel
{
-
+ protected MainScreenViewModel(IScreen hostScreen, string urlPathSegment)
+ {
+ HostScreen = hostScreen;
+ UrlPathSegment = urlPathSegment;
+ }
+
+ ///
+ public string UrlPathSegment { get; }
+
+ ///
+ public IScreen HostScreen { get; }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs
index 2645dc999..376906861 100644
--- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/RootViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reactive;
using Artemis.Core.Services;
using ReactiveUI;
@@ -10,18 +11,20 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
public RootViewModel(ICoreService coreService, SidebarViewModel sidebarViewModel)
{
+ Router = new RoutingState();
SidebarViewModel = sidebarViewModel;
+ SidebarViewModel.Router = Router;
+
_coreService = coreService;
_coreService.Initialize();
- Console.WriteLine("test");
}
public SidebarViewModel SidebarViewModel { get; }
-
+
///
public ViewModelActivator Activator { get; } = new();
///
- public RoutingState Router { get; } = new();
+ public RoutingState Router { get; }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs
index 5d8d7e11a..2b2769587 100644
--- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarScreenViewModel.cs
@@ -1,5 +1,8 @@
-using Material.Icons;
+using System;
+using System.Reactive.Linq;
+using Material.Icons;
using Ninject;
+using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
@@ -27,5 +30,10 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
public string DisplayName { get; }
public abstract MainScreenViewModel CreateInstance(IKernel kernel);
+
+ public bool IsActive(IObservable routerCurrentViewModel)
+ {
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs
index 80a0340f0..a8b63697a 100644
--- a/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Root/ViewModels/SidebarViewModel.cs
@@ -10,6 +10,7 @@ using Artemis.UI.Avalonia.Screens.Workshop.ViewModels;
using Material.Icons;
using Ninject;
using ReactiveUI;
+using RGB.NET.Core;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
@@ -17,13 +18,18 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
private readonly IKernel _kernel;
private readonly IProfileService _profileService;
+ private readonly IRgbService _rgbService;
private readonly ISidebarVmFactory _sidebarVmFactory;
- private SidebarScreenViewModel _selectedSidebarScreen;
+ private ArtemisDevice? _headerDevice;
- public SidebarViewModel(IKernel kernel, IProfileService profileService, ISidebarVmFactory sidebarVmFactory)
+ private SidebarScreenViewModel _selectedSidebarScreen;
+ private RoutingState _router;
+
+ public SidebarViewModel(IKernel kernel, IProfileService profileService, IRgbService rgbService, ISidebarVmFactory sidebarVmFactory)
{
_kernel = kernel;
_profileService = profileService;
+ _rgbService = rgbService;
_sidebarVmFactory = sidebarVmFactory;
SidebarScreens = new ObservableCollection
@@ -33,17 +39,43 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
new SidebarScreenViewModel(MaterialIconKind.Devices, "Surface Editor"),
new SidebarScreenViewModel(MaterialIconKind.Cog, "Settings")
};
- SelectedSidebarScreen = SidebarScreens.First();
+ _selectedSidebarScreen = SidebarScreens.First();
+
UpdateProfileCategories();
+ UpdateHeaderDevice();
}
public ObservableCollection SidebarScreens { get; }
public ObservableCollection SidebarCategories { get; } = new();
-
+
+ public ArtemisDevice? HeaderDevice
+ {
+ get => _headerDevice;
+ set => this.RaiseAndSetIfChanged(ref _headerDevice, value);
+ }
+
public SidebarScreenViewModel SelectedSidebarScreen
{
get => _selectedSidebarScreen;
- set => this.RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
+ set
+ {
+ this.RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
+ // if (!SelectedSidebarScreen.IsActive(Router.CurrentViewModel))
+ // Router.Navigate.Execute(SelectedSidebarScreen.CreateInstance(_kernel)).Subscribe();
+ }
+ }
+
+ public RoutingState Router
+ {
+ get => _router;
+ set => this.RaiseAndSetIfChanged(ref _router, value);
+ }
+
+ public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory)
+ {
+ SidebarCategoryViewModel viewModel = _sidebarVmFactory.SidebarCategoryViewModel(profileCategory);
+ SidebarCategories.Add(viewModel);
+ return viewModel;
}
private void UpdateProfileCategories()
@@ -53,11 +85,9 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
AddProfileCategoryViewModel(profileCategory);
}
- public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory)
+ private void UpdateHeaderDevice()
{
- SidebarCategoryViewModel viewModel = _sidebarVmFactory.SidebarCategoryViewModel(profileCategory);
- SidebarCategories.Add(viewModel);
- return viewModel;
+ HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout is {IsValid: true});
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/Screens/Root/Views/RootView.axaml b/src/Artemis.UI.Avalonia/Screens/Root/Views/RootView.axaml
index 0d4df056b..472555f77 100644
--- a/src/Artemis.UI.Avalonia/Screens/Root/Views/RootView.axaml
+++ b/src/Artemis.UI.Avalonia/Screens/Root/Views/RootView.axaml
@@ -1,10 +1,10 @@
+ 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:reactiveUi="http://reactiveui.net"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="Artemis.UI.Avalonia.Screens.Root.Views.RootView">
@@ -19,11 +19,10 @@
TintOpacity="1"
MaterialOpacity="0.65" />
-
+
-
-
-
+
+
\ No newline at end of file
diff --git a/src/Artemis.UI.Avalonia/Screens/Root/Views/SidebarView.axaml b/src/Artemis.UI.Avalonia/Screens/Root/Views/SidebarView.axaml
index 44e0b35c1..5dc95cbc8 100644
--- a/src/Artemis.UI.Avalonia/Screens/Root/Views/SidebarView.axaml
+++ b/src/Artemis.UI.Avalonia/Screens/Root/Views/SidebarView.axaml
@@ -4,7 +4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
- mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
+ xmlns:shared="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
+ mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.SidebarView">
@@ -16,40 +18,41 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
+
+
+
Artemis 2
diff --git a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SurfaceEditorViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SurfaceEditorViewModel.cs
index 116b88016..30c9fde1a 100644
--- a/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SurfaceEditorViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Settings/ViewModels/SurfaceEditorViewModel.cs
@@ -1,8 +1,10 @@
-namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
+using ReactiveUI;
+
+namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
{
public class SettingsViewModel : MainScreenViewModel
{
- public SettingsViewModel()
+ public SettingsViewModel(IScreen hostScreens) : base(hostScreens, "settings")
{
DisplayName = "Settings";
}
diff --git a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceEditorViewModel.cs b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceEditorViewModel.cs
index dfbf164bd..c97d12ed8 100644
--- a/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceEditorViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/SurfaceEditor/ViewModels/SurfaceEditorViewModel.cs
@@ -1,8 +1,10 @@
-namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
+using ReactiveUI;
+
+namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
{
public class SurfaceEditorViewModel : MainScreenViewModel
{
- public SurfaceEditorViewModel()
+ public SurfaceEditorViewModel(IScreen hostScreens) : base(hostScreens, "surface-editor")
{
DisplayName = "Surface Editor";
}
diff --git a/src/Artemis.UI.Avalonia/Screens/Workshop/ViewModels/WorkshopViewModel.cs b/src/Artemis.UI.Avalonia/Screens/Workshop/ViewModels/WorkshopViewModel.cs
index f645047c9..28eb2caef 100644
--- a/src/Artemis.UI.Avalonia/Screens/Workshop/ViewModels/WorkshopViewModel.cs
+++ b/src/Artemis.UI.Avalonia/Screens/Workshop/ViewModels/WorkshopViewModel.cs
@@ -1,8 +1,10 @@
-namespace Artemis.UI.Avalonia.Screens.Workshop.ViewModels
+using ReactiveUI;
+
+namespace Artemis.UI.Avalonia.Screens.Workshop.ViewModels
{
public class WorkshopViewModel : MainScreenViewModel
{
- public WorkshopViewModel()
+ public WorkshopViewModel(IScreen hostScreens) : base(hostScreens, "workshop")
{
DisplayName = "Workshop";
}