mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
UI - Further sidebar improvements and fixed titlebar
This commit is contained in:
parent
1784d2b8b5
commit
5d8a541624
@ -21,5 +21,8 @@
|
||||
<Reference Include="Material.Icons.Avalonia">
|
||||
<HintPath>..\..\..\..\Users\Robert\.nuget\packages\material.icons.avalonia\1.0.2\lib\netstandard2.0\Material.Icons.Avalonia.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="RGB.NET.Core">
|
||||
<HintPath>..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -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
|
||||
/// </summary>
|
||||
public class DeviceVisualizer : Control
|
||||
{
|
||||
private readonly DispatcherTimer _timer;
|
||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||
|
||||
private Bitmap? _deviceImage;
|
||||
private List<DeviceVisualizerLed>? _dimmedLeds;
|
||||
private List<DeviceVisualizerLed>? _highlightedLeds;
|
||||
private ArtemisDevice? _oldDevice;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceVisualizer()
|
||||
{
|
||||
// Run an update timer at 25 fps
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
|
||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
PointerReleased += OnPointerReleased;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a LED of the device has been clicked
|
||||
/// </summary>
|
||||
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="LedClicked" /> event
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
@ -67,26 +185,6 @@ namespace Artemis.UI.Avalonia.Shared.Controls
|
||||
|
||||
#endregion
|
||||
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceVisualizer()
|
||||
{
|
||||
// Run an update timer at 25 fps
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#region Lifetime management
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -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<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
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
|
||||
|
||||
159
src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizerLed.cs
Normal file
159
src/Artemis.UI.Avalonia.Shared/Controls/DeviceVisualizerLed.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about selection events raised by <see cref="DataModelDynamicViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelInputDynamicEventArgs : EventArgs
|
||||
{
|
||||
internal DataModelInputDynamicEventArgs(DataModelPath? dataModelPath)
|
||||
{
|
||||
DataModelPath = dataModelPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model path that was selected
|
||||
/// </summary>
|
||||
public DataModelPath? DataModelPath { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about submit events raised by <see cref="DataModelStaticViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelInputStaticEventArgs : EventArgs
|
||||
{
|
||||
internal DataModelInputStaticEventArgs(object? value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value that was submitted
|
||||
/// </summary>
|
||||
public object? Value { get; }
|
||||
}
|
||||
}
|
||||
27
src/Artemis.UI.Avalonia.Shared/Events/LedClickedEventArgs.cs
Normal file
27
src/Artemis.UI.Avalonia.Shared/Events/LedClickedEventArgs.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on LED click events raised by the device visualizer
|
||||
/// </summary>
|
||||
public class LedClickedEventArgs : EventArgs
|
||||
{
|
||||
internal LedClickedEventArgs(ArtemisDevice device, ArtemisLed led)
|
||||
{
|
||||
Device = device;
|
||||
Led = led;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The device that was clicked
|
||||
/// </summary>
|
||||
public ArtemisDevice Device { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The LED that was clicked
|
||||
/// </summary>
|
||||
public ArtemisLed Led { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
{
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
PreviousProfileConfiguration = previousProfileConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile the event was raised for
|
||||
/// </summary>
|
||||
public ProfileConfiguration? ProfileConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile before the event was raised
|
||||
/// </summary>
|
||||
public ProfileConfiguration? PreviousProfileConfiguration { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile element related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class RenderProfileElementEventArgs : EventArgs
|
||||
{
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement)
|
||||
{
|
||||
RenderProfileElement = renderProfileElement;
|
||||
}
|
||||
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement)
|
||||
{
|
||||
RenderProfileElement = renderProfileElement;
|
||||
PreviousRenderProfileElement = previousRenderProfileElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element the event was raised for
|
||||
/// </summary>
|
||||
public RenderProfileElement? RenderProfileElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile element before the event was raised
|
||||
/// </summary>
|
||||
public RenderProfileElement? PreviousRenderProfileElement { get; }
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,13 @@
|
||||
|
||||
<Application.Styles>
|
||||
<!-- Third party styles -->
|
||||
<sty:FluentAvaloniaTheme CustomAccentColor="#4db6ac"/>
|
||||
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml"></StyleInclude>
|
||||
<sty:FluentAvaloniaTheme RequestedTheme="Dark" CustomAccentColor="#4db6ac"/>
|
||||
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml"/>
|
||||
|
||||
<!-- Grab the window styling from Avalonia -->
|
||||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/Window.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/TitleBar.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml" />
|
||||
|
||||
<!-- Global styles -->
|
||||
<StyleInclude Source="/Styles/Button.axaml"></StyleInclude>
|
||||
|
||||
@ -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<RootViewModel>()
|
||||
};
|
||||
AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>().ForceNativeTitleBarToTheme(desktop.MainWindow, "Dark");
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
@ -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}">
|
||||
</Window>
|
||||
@ -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<RootViewModel>
|
||||
{
|
||||
|
||||
@ -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<IMissingBindingResolver, SelfBindingResolver>();
|
||||
|
||||
Kernel.Bind(x =>
|
||||
{
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlPathSegment { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public ViewModelActivator Activator { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public RoutingState Router { get; } = new();
|
||||
public RoutingState Router { get; }
|
||||
}
|
||||
}
|
||||
@ -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<IRoutableViewModel?> routerCurrentViewModel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<SidebarScreenViewModel>
|
||||
@ -33,17 +39,43 @@ namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
|
||||
new SidebarScreenViewModel<SurfaceEditorViewModel>(MaterialIconKind.Devices, "Surface Editor"),
|
||||
new SidebarScreenViewModel<SettingsViewModel>(MaterialIconKind.Cog, "Settings")
|
||||
};
|
||||
SelectedSidebarScreen = SidebarScreens.First();
|
||||
_selectedSidebarScreen = SidebarScreens.First();
|
||||
|
||||
UpdateProfileCategories();
|
||||
UpdateHeaderDevice();
|
||||
}
|
||||
|
||||
public ObservableCollection<SidebarScreenViewModel> SidebarScreens { get; }
|
||||
public ObservableCollection<SidebarCategoryViewModel> 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
<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:reactiveUi="http://reactiveui.net"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.RootView">
|
||||
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">
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@ -19,11 +19,10 @@
|
||||
TintOpacity="1"
|
||||
MaterialOpacity="0.65" />
|
||||
</ExperimentalAcrylicBorder.Material>
|
||||
<ContentControl Content="{Binding SidebarViewModel}"/>
|
||||
<ContentControl Content="{Binding SidebarViewModel}" />
|
||||
</ExperimentalAcrylicBorder>
|
||||
<Border Grid.Column="1" Background="Black">
|
||||
<reactiveUi:RoutedViewHost Router="{Binding Router}" Margin="0 20 0 0" Padding="15" />
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Background="#101010" IsHitTestVisible="False" />
|
||||
<reactiveUi:RoutedViewHost Grid.Column="1" Router="{Binding Router}" Margin="0 20 0 0" Padding="15" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -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">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@ -16,40 +18,41 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" ClipToBounds="True">
|
||||
<Grid Grid.Row="0" IsHitTestVisible="False">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- <shared:DeviceVisualizer Grid.Column="0" -->
|
||||
<!-- Grid.ColumnSpan="2" -->
|
||||
<!-- Device="{Binding HeaderDevice}" -->
|
||||
<!-- ShowColors="True" -->
|
||||
<!-- RenderTransformOrigin="0.5 0.5" -->
|
||||
<!-- VerticalAlignment="Center" -->
|
||||
<!-- HorizontalAlignment="Center" -->
|
||||
<!-- RenderOptions.BitmapScalingMode="HighQuality"> -->
|
||||
<!-- <shared:DeviceVisualizer.RenderTransform> -->
|
||||
<!-- <TransformGroup> -->
|
||||
<!-- <RotateTransform Angle="20" /> -->
|
||||
<!-- <ScaleTransform ScaleX="2" ScaleY="2" /> -->
|
||||
<!-- </TransformGroup> -->
|
||||
<!-- </shared:DeviceVisualizer.RenderTransform> -->
|
||||
<!-- </shared:DeviceVisualizer> -->
|
||||
<Rectangle Grid.Column="0" Grid.ColumnSpan="2" Height="60" VerticalAlignment="Bottom">
|
||||
<Rectangle.Fill>
|
||||
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
|
||||
<GradientStop Color="Black" Offset="0" />
|
||||
<GradientStop Color="#00000000" Offset="1" />
|
||||
<Border Grid.Column="0"
|
||||
Grid.ColumnSpan="2">
|
||||
<shared:DeviceVisualizer Device="{Binding HeaderDevice}"
|
||||
ShowColors="True"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<shared:DeviceVisualizer.RenderTransform>
|
||||
<TransformGroup>
|
||||
<RotateTransform Angle="20" />
|
||||
<ScaleTransform ScaleX="2" ScaleY="2" />
|
||||
</TransformGroup>
|
||||
</shared:DeviceVisualizer.RenderTransform>
|
||||
</shared:DeviceVisualizer>
|
||||
<Border.OpacityMask>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,60">
|
||||
<GradientStop Color="White" Offset="0"></GradientStop>
|
||||
<GradientStop Color="Transparent" Offset="1"></GradientStop>
|
||||
</LinearGradientBrush>
|
||||
</Rectangle.Fill>
|
||||
</Rectangle>
|
||||
</Border.OpacityMask>
|
||||
</Border>
|
||||
|
||||
<Image Grid.Column="0" Source="{SvgImage /Assets/Images/Logo/bow.svg}" Height="35" Width="35" Margin="10" />
|
||||
<Image Grid.Column="0" >
|
||||
<Image.Source>
|
||||
<svg:SvgImage Source="/Assets/Images/Logo/bow.svg" />
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<TextBlock Grid.Column="1"
|
||||
FontSize="24"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
Foreground="{DynamicResource MaterialDesignDarkForeground}">
|
||||
Artemis 2
|
||||
</TextBlock>
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user