1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Merge branch 'development' into feature/workshop

This commit is contained in:
Robert 2023-06-22 23:09:26 +02:00
commit 3860ef0f3d
48 changed files with 807 additions and 707 deletions

View File

@ -18,13 +18,13 @@ jobs:
with: with:
dotnet-version: "7.0.x" dotnet-version: "7.0.x"
- name: Setup DocFX - name: Setup DocFX
run: choco install docfx -y run: dotnet tool update -g docfx
- name: Build Core - name: Build Core
run: dotnet build src/Artemis.Core/Artemis.Core.csproj run: dotnet build src/Artemis.Core/Artemis.Core.csproj
- name: Build UI.Shared - name: Build UI.Shared
run: dotnet build src/Artemis.UI.Shared/Artemis.UI.Shared.csproj run: dotnet build src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
- name: Build DocFX - name: Build DocFX
run: docfx.exe docfx/docfx_project/docfx.json run: docfx docfx/docfx_project/docfx.json
- name: Upload to FTP - name: Upload to FTP
uses: SamKirkland/FTP-Deploy-Action@4.3.2 uses: SamKirkland/FTP-Deploy-Action@4.3.2
with: with:

View File

@ -5,7 +5,7 @@
{ {
"files": [ "files": [
"Artemis.Core/bin/net7.0/Artemis.Core.dll", "Artemis.Core/bin/net7.0/Artemis.Core.dll",
"Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll", "Artemis.UI.Shared/bin/net7.0/Artemis.UI.Shared.dll"
], ],
"src": "../../src" "src": "../../src"
} }

View File

@ -43,9 +43,9 @@
<PackageReference Include="LiteDB" Version="5.0.16" /> <PackageReference Include="LiteDB" Version="5.0.16" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.83" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.83" />
<PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Presets" Version="2.0.0-prerelease.83" />
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" /> <PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />

View File

@ -16,11 +16,11 @@
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -15,11 +15,11 @@
<None Remove=".gitignore" /> <None Remove=".gitignore" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -10,17 +10,17 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-rc1.1" />
<PackageReference Include="DynamicData" Version="7.13.1" /> <PackageReference Include="DynamicData" Version="7.13.1" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0-preview8" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.0-rc1" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" /> <PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.83" />
<PackageReference Include="SkiaSharp" Version="2.88.3" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,9 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -13,6 +14,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Threading; using Avalonia.Threading;
using RGB.NET.Core; using RGB.NET.Core;
using DryIoc;
using Color = RGB.NET.Core.Color; using Color = RGB.NET.Core.Color;
using Point = Avalonia.Point; using Point = Avalonia.Point;
using Size = Avalonia.Size; using Size = Avalonia.Size;
@ -24,20 +26,19 @@ namespace Artemis.UI.Shared;
/// </summary> /// </summary>
public class DeviceVisualizer : Control public class DeviceVisualizer : Control
{ {
private const double UPDATE_FRAME_RATE = 25.0; private readonly ICoreService _coreService;
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds; private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
private readonly DispatcherTimer _timer;
private Rect _deviceBounds; private Rect _deviceBounds;
private RenderTargetBitmap? _deviceImage; private RenderTargetBitmap? _deviceImage;
private ArtemisDevice? _oldDevice; private ArtemisDevice? _oldDevice;
private bool _loading; private bool _loading;
private Color[] _previousState = Array.Empty<Color>(); private Color[] _previousState = Array.Empty<Color>();
/// <inheritdoc /> /// <inheritdoc />
public DeviceVisualizer() public DeviceVisualizer()
{ {
_timer = new DispatcherTimer(DispatcherPriority.Background) {Interval = TimeSpan.FromMilliseconds(1000.0 / UPDATE_FRAME_RATE)}; _coreService = UI.Locator.Resolve<ICoreService>();
_deviceVisualizerLeds = new List<DeviceVisualizerLed>(); _deviceVisualizerLeds = new List<DeviceVisualizerLed>();
PointerReleased += OnPointerReleased; PointerReleased += OnPointerReleased;
@ -120,23 +121,26 @@ public class DeviceVisualizer : Control
if (Device == null) if (Device == null)
return false; return false;
Color[] state = new Color[Device.RgbDevice.Count()]; bool difference = false;
bool difference = _previousState.Length != state.Length;
int newLedCount = Device.RgbDevice.Count();
if (_previousState.Length != newLedCount)
{
_previousState = new Color[newLedCount];
difference = true;
}
// Check all LEDs for differences and copy the colors to a new state // Check all LEDs for differences and copy the colors to a new state
int index = 0; int index = 0;
foreach (Led led in Device.RgbDevice) foreach (Led led in Device.RgbDevice)
{ {
if (!difference && !led.Color.Equals(_previousState[index])) if (_previousState[index] != led.Color)
difference = true; difference = true;
state[index] = led.Color; _previousState[index] = led.Color;
index++; index++;
} }
// Store the new state for next time
_previousState = state;
return difference; return difference;
} }
@ -156,11 +160,14 @@ public class DeviceVisualizer : Control
return geometry.Bounds; return geometry.Bounds;
} }
private void TimerOnTick(object? sender, EventArgs e) private void OnFrameRendered(object? sender, FrameRenderedEventArgs e)
{ {
if (IsDirty() && ShowColors && IsVisible && Opacity > 0) Dispatcher.UIThread.Post(() =>
Update(); {
if (ShowColors && IsVisible && Opacity > 0 && IsDirty())
Update();
}, DispatcherPriority.Background);
} }
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
@ -250,16 +257,16 @@ public class DeviceVisualizer : Control
/// <inheritdoc /> /// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_timer.Start(); _coreService.FrameRendered += OnFrameRendered;
_timer.Tick += TimerOnTick;
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_timer.Stop(); _coreService.FrameRendered -= OnFrameRendered;
_timer.Tick -= TimerOnTick;
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
} }

View File

@ -6,25 +6,27 @@
xmlns:sharedControls="clr-namespace:Artemis.UI.Shared.Controls" xmlns:sharedControls="clr-namespace:Artemis.UI.Shared.Controls"
xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties" xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.DraggableNumberBox"> x:Class="Artemis.UI.Shared.Controls.DraggableNumberBox"
Focusable="True">
<UserControl.Styles> <UserControl.Styles>
<Styles> <Styles>
<Style Selector="sharedControls|DraggableNumberBox:not(:focus-within)"> <Style Selector="Panel#Container:not(:focus-within)">
<Setter Property="Cursor" Value="{DynamicResource DragHorizontalCursor}" /> <Setter Property="Cursor" Value="{DynamicResource DragHorizontalCursor}" />
</Style> </Style>
<Style Selector="sharedControls|DraggableNumberBox:focus-within Rectangle#DragCollider"> <Style Selector="#Container:focus-within Rectangle#DragCollider">
<Setter Property="IsHitTestVisible" Value="False" /> <Setter Property="IsHitTestVisible" Value="False" />
</Style> </Style>
<Style Selector="sharedControls|DraggableNumberBox:not(:focus-within) controls|NumberBox#NumberBox"> <Style Selector="#Container:not(:focus-within) controls|NumberBox#NumberBox">
<Setter Property="IsHitTestVisible" Value="False" /> <Setter Property="IsHitTestVisible" Value="False" />
</Style> </Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
<Panel> <Panel Name="Container" Focusable="True">
<controls:NumberBox Name="InnerNumberBox" <controls:NumberBox Name="InnerNumberBox"
AcceptsExpression="True" AcceptsExpression="True"
Focusable="True"
LargeChange="{CompiledBinding $parent[sharedControls:DraggableNumberBox].LargeChange}" LargeChange="{CompiledBinding $parent[sharedControls:DraggableNumberBox].LargeChange}"
SmallChange="{CompiledBinding $parent[sharedControls:DraggableNumberBox].SmallChange}" SmallChange="{CompiledBinding $parent[sharedControls:DraggableNumberBox].SmallChange}"
Minimum="{CompiledBinding $parent[sharedControls:DraggableNumberBox].Minimum}" Minimum="{CompiledBinding $parent[sharedControls:DraggableNumberBox].Minimum}"

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
@ -68,7 +69,7 @@ public partial class DraggableNumberBox : UserControl
public DraggableNumberBox() public DraggableNumberBox()
{ {
InitializeComponent(); InitializeComponent();
PointerPressed += OnPointerPressed; PointerPressed += OnPointerPressed;
PointerMoved += OnPointerMoved; PointerMoved += OnPointerMoved;
PointerReleased += OnPointerReleased; PointerReleased += OnPointerReleased;
@ -186,8 +187,8 @@ public partial class DraggableNumberBox : UserControl
private void HandleKeyUp(object? sender, KeyEventArgs e) private void HandleKeyUp(object? sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter || e.Key == Key.Escape) if (e.Key is Key.Enter or Key.Escape)
FocusManager.Instance?.Focus(Parent as IInputElement); Focus();
} }
private void OnPointerPressed(object? sender, PointerPressedEventArgs e) private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
@ -215,9 +216,9 @@ public partial class DraggableNumberBox : UserControl
if (!_moved) if (!_moved)
{ {
// Let our parent take focus, it would make more sense to take focus ourselves but that hides the collider // Let our parent take focus, it would make more sense to take focus ourselves but that hides the collider
FocusManager.Instance?.Focus(Parent as IInputElement); PseudoClasses.Add("dragging");
Focus();
_moved = true; _moved = true;
e.Pointer.Capture(this);
DragStarted?.Invoke(this, EventArgs.Empty); DragStarted?.Invoke(this, EventArgs.Empty);
} }
@ -252,6 +253,7 @@ public partial class DraggableNumberBox : UserControl
else else
{ {
_moved = false; _moved = false;
PseudoClasses.Remove("dragging");
DragFinished?.Invoke(this, EventArgs.Empty); DragFinished?.Invoke(this, EventArgs.Empty);
} }

View File

@ -9,6 +9,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
using DryIoc; using DryIoc;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using Humanizer; using Humanizer;
@ -40,7 +41,7 @@ public partial class HotkeyBox : UserControl
{ {
_inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown; _inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
base.OnGotFocus(e); base.OnGotFocus(e);
} }
@ -49,10 +50,10 @@ public partial class HotkeyBox : UserControl
{ {
_inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown; _inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp; _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
base.OnLostFocus(e); base.OnLostFocus(e);
} }
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (e.Property == HotkeyProperty) if (e.Property == HotkeyProperty)
@ -67,7 +68,7 @@ public partial class HotkeyBox : UserControl
Hotkey ??= new Hotkey(); Hotkey ??= new Hotkey();
Hotkey.Key = e.Key; Hotkey.Key = e.Key;
Hotkey.Modifiers = e.Modifiers; Hotkey.Modifiers = e.Modifiers;
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
UpdateDisplayTextBox(); UpdateDisplayTextBox();
@ -78,7 +79,19 @@ public partial class HotkeyBox : UserControl
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{ {
if (e.Modifiers == KeyboardModifierKey.None) if (e.Modifiers == KeyboardModifierKey.None)
Dispatcher.UIThread.Post(() => FocusManager.Instance?.Focus(null)); Dispatcher.UIThread.Post(ClearFocus);
}
private void ClearFocus()
{
InputElement? element = this.FindAncestorOfType<InputElement>();
if (element == null)
return;
bool wasFocusable = element.Focusable;
element.Focusable = true;
element.Focus();
element.Focusable = wasFocusable;
} }
private void UpdateDisplayTextBox() private void UpdateDisplayTextBox()
@ -96,7 +109,7 @@ public partial class HotkeyBox : UserControl
private void Button_OnClick(object? sender, RoutedEventArgs e) private void Button_OnClick(object? sender, RoutedEventArgs e)
{ {
Hotkey = null; Hotkey = null;
FocusManager.Instance?.Focus(null); ClearFocus();
UpdateDisplayTextBox(); UpdateDisplayTextBox();
} }

View File

@ -48,7 +48,7 @@ public class ReactiveAppWindow<TViewModel> : AppWindow, IViewFor<TViewModel> whe
return; return;
TransparencyBackgroundFallback = Brushes.Transparent; TransparencyBackgroundFallback = Brushes.Transparent;
TransparencyLevelHint = WindowTransparencyLevel.Mica; TransparencyLevelHint = new[] {WindowTransparencyLevel.Mica};
TryEnableMicaEffect(); TryEnableMicaEffect();
} }

View File

@ -10,6 +10,15 @@
<TextBlock>I'm in a panel yo!</TextBlock> <TextBlock>I'm in a panel yo!</TextBlock>
<Border Classes="card-separator" /> <Border Classes="card-separator" />
<TextBlock>I'm in a panel yo!</TextBlock> <TextBlock>I'm in a panel yo!</TextBlock>
<Border Classes="card-separator-slim" />
<TextBlock>I'm in a panel yo!</TextBlock>
</StackPanel>
</Border>
<Border Classes="card" Margin="20">
<StackPanel Orientation="Horizontal">
<TextBlock>I'm in a panel yo!</TextBlock>
<Border Classes="card-separator-vertical" />
<TextBlock>I'm in a panel yo!</TextBlock>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
@ -50,4 +59,23 @@
<Setter Property="Height" Value="1" /> <Setter Property="Height" Value="1" />
</Style> </Style>
<Style Selector="Border.card-separator-slim">
<Setter Property="Background" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="Margin" Value="-12 10" />
<Setter Property="Height" Value="1" />
</Style>
<Style Selector="Border.card-separator-vertical">
<Setter Property="Background" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="Margin" Value="15 -12" />
<Setter Property="Width" Value="1" />
</Style>
<Style Selector="Border.card-separator-slim-vertical">
<Setter Property="Background" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="Margin" Value="10 -12" />
<Setter Property="Width" Value="1" />
</Style>
</Styles> </Styles>

View File

@ -1,7 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
@ -14,12 +12,9 @@ namespace Artemis.UI.Shared;
/// </summary> /// </summary>
public static class UI public static class UI
{ {
private static readonly BehaviorSubject<bool> KeyBindingsEnabledSubject = new(false);
static UI() static UI()
{ {
if (KeyboardDevice.Instance != null) KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true);
KeyboardDevice.Instance.PropertyChanged += InstanceOnPropertyChanged;
} }
/// <summary> /// <summary>
@ -35,15 +30,5 @@ public static class UI
/// <summary> /// <summary>
/// Gets a boolean indicating whether hotkeys are to be disabled. /// Gets a boolean indicating whether hotkeys are to be disabled.
/// </summary> /// </summary>
public static IObservable<bool> KeyBindingsEnabled { get; } = KeyBindingsEnabledSubject.AsObservable(); public static IObservable<bool> KeyBindingsEnabled { get; }
private static void InstanceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (KeyboardDevice.Instance == null || e.PropertyName != nameof(KeyboardDevice.FocusedElement))
return;
bool enabled = KeyboardDevice.Instance.FocusedElement is not TextBox;
if (KeyBindingsEnabledSubject.Value != enabled)
KeyBindingsEnabledSubject.OnNext(enabled);
}
} }

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.17763.0</TargetFramework> <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
@ -21,15 +21,15 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Win32" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Win32" Version="11.0.0-rc1.1" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1" /> <PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" /> <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.3" />
<PackageReference Include="RawInput.Sharp" Version="0.1.1" /> <PackageReference Include="RawInput.Sharp" Version="0.1.1" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.3" /> <PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.3" />

View File

@ -16,25 +16,25 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-rc1.1" />
<PackageReference Include="DryIoc.dll" Version="5.4.0" /> <PackageReference Include="DryIoc.dll" Version="5.4.0" />
<PackageReference Include="DynamicData" Version="7.13.1" /> <PackageReference Include="DynamicData" Version="7.13.1" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0-preview8" /> <PackageReference Include="FluentAvaloniaUI" Version="2.0.0-rc1" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" /> <PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.0-c1" /> <PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.0-d1" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.0-preview3" /> <PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.261" /> <PackageReference Include="Octopus.Octodiff" Version="2.0.261" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" /> <PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
<PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Core" Version="2.0.0-prerelease.83" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.69" /> <PackageReference Include="RGB.NET.Layout" Version="2.0.0-prerelease.83" />
<PackageReference Include="SkiaSharp" Version="2.88.3" /> <PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="Splat.DryIoc" Version="14.6.8" /> <PackageReference Include="Splat.DryIoc" Version="14.6.8" />
</ItemGroup> </ItemGroup>
@ -42,27 +42,4 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\**" /> <AvaloniaResource Include="Assets\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Screens\Settings\Tabs\ReleasesTabView.axaml.cs">
<DependentUpon>UpdatingTabView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Settings\Updating\ReleaseView.axaml.cs">
<DependentUpon>UpdatingTabView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Plugins\Features\PluginFeatureView.axaml.cs">
<DependentUpon>PluginFeatureView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Plugins\Prerequisites\PluginPrerequisiteActionView.axaml.cs">
<DependentUpon>PluginPrerequisiteActionView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Plugins\Prerequisites\PluginPrerequisiteView.axaml.cs">
<DependentUpon>PluginPrerequisiteView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@ -103,8 +103,9 @@ public class SimpleContextDragBehavior : Behavior<Control>
private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e) private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e)
{ {
IFocusManager? focusManager = TopLevel.GetTopLevel(AssociatedObject)?.FocusManager;
PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (!properties.IsLeftButtonPressed || FocusManager.Instance?.Current is TextBox) if (!properties.IsLeftButtonPressed || focusManager?.GetFocusedElement() is TextBox)
return; return;
if (e.Source is not Control control || AssociatedObject?.DataContext != control.DataContext) if (e.Source is not Control control || AssociatedObject?.DataContext != control.DataContext)
return; return;
@ -130,8 +131,9 @@ public class SimpleContextDragBehavior : Behavior<Control>
private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e) private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e)
{ {
IFocusManager? focusManager = TopLevel.GetTopLevel(AssociatedObject)?.FocusManager;
PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties; PointerPointProperties properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (!properties.IsLeftButtonPressed || FocusManager.Instance?.Current is TextBox) if (!properties.IsLeftButtonPressed || focusManager?.GetFocusedElement() is TextBox)
return; return;
if (_triggerEvent is null) if (_triggerEvent is null)

View File

@ -1,5 +1,4 @@
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.DefaultTypes.PropertyInput; namespace Artemis.UI.DefaultTypes.PropertyInput;
@ -16,6 +15,6 @@ public partial class StringPropertyInputView : ReactiveUserControl<FloatProperty
private void OnRoutedKeyUp(object? sender, KeyEventArgs e) private void OnRoutedKeyUp(object? sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter || e.Key == Key.Escape) if (e.Key == Key.Enter || e.Key == Key.Escape)
FocusManager.Instance!.Focus(null); Focus();
} }
} }

View File

@ -25,6 +25,7 @@ using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
using DryIoc; using DryIoc;
using ReactiveUI; using ReactiveUI;
@ -39,10 +40,10 @@ public interface IDeviceVmFactory : IVmFactory
DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device); DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device);
DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel); DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel);
DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device); DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device);
DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device); DeviceLayoutTabViewModel DeviceLayoutTabViewModel(ArtemisDevice device);
DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device);
DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds); DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds); InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device);
} }
public class DeviceFactory : IDeviceVmFactory public class DeviceFactory : IDeviceVmFactory
{ {
@ -68,16 +69,11 @@ public class DeviceFactory : IDeviceVmFactory
return _container.Resolve<DeviceDetectInputViewModel>(new object[] { device }); return _container.Resolve<DeviceDetectInputViewModel>(new object[] { device });
} }
public DevicePropertiesTabViewModel DevicePropertiesTabViewModel(ArtemisDevice device) public DeviceLayoutTabViewModel DeviceLayoutTabViewModel(ArtemisDevice device)
{ {
return _container.Resolve<DevicePropertiesTabViewModel>(new object[] { device }); return _container.Resolve<DeviceLayoutTabViewModel>(new object[] { device });
} }
public DeviceInfoTabViewModel DeviceInfoTabViewModel(ArtemisDevice device)
{
return _container.Resolve<DeviceInfoTabViewModel>(new object[] { device });
}
public DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds) public DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
{ {
return _container.Resolve<DeviceLedsTabViewModel>(new object[] { device, selectedLeds }); return _container.Resolve<DeviceLedsTabViewModel>(new object[] { device, selectedLeds });
@ -87,6 +83,11 @@ public class DeviceFactory : IDeviceVmFactory
{ {
return _container.Resolve<InputMappingsTabViewModel>(new object[] { device, selectedLeds }); return _container.Resolve<InputMappingsTabViewModel>(new object[] { device, selectedLeds });
} }
public DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device)
{
return _container.Resolve<DeviceGeneralTabViewModel>(new object[] { device });
}
} }
public interface ISettingsVmFactory : IVmFactory public interface ISettingsVmFactory : IVmFactory

View File

@ -17,7 +17,7 @@
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10"> <TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
These performance stats are rather basic, for advanced performance profiling check out the wiki. These performance stats are rather basic, for advanced performance profiling check out the wiki.
</TextBlock> </TextBlock>
<controls:HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling"> <controls:HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling?mtm_campaign=artemis&mtm_kwd=debugger">
JetBrains Profiling Guide JetBrains Profiling Guide
</controls:HyperlinkButton> </controls:HyperlinkButton>
</Grid> </Grid>

View File

@ -1,58 +1,59 @@
<windowing:AppWindow xmlns="https://github.com/avaloniaui" <windowing:AppWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device" xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia" xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.DevicePropertiesView" x:Class="Artemis.UI.Screens.Device.DevicePropertiesView"
x:DataType="device:DevicePropertiesViewModel" x:DataType="device:DevicePropertiesViewModel"
Icon="/Assets/Images/Logo/application.ico" Icon="/Assets/Images/Logo/application.ico"
Title="Artemis | Device Properties" Title="Artemis | Device Properties"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
Width="1250" Width="1400"
Height="900"> Height="800">
<windowing:AppWindow.KeyBindings> <windowing:AppWindow.KeyBindings>
<KeyBinding Gesture="Escape" Command="{CompiledBinding ClearSelectedLeds}" /> <KeyBinding Gesture="Escape" Command="{CompiledBinding ClearSelectedLeds}" />
</windowing:AppWindow.KeyBindings> </windowing:AppWindow.KeyBindings>
<Grid ColumnDefinitions="*,0,1.5*"> <Grid ColumnDefinitions="*,0,1.5*">
<Grid.Background> <Border Grid.Column="0" Classes="card" Margin="5">
<VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,25,25"> <Border.Background>
<VisualBrush.Visual> <VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,25,25">
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*"> <VisualBrush.Visual>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" /> <Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
<Rectangle Grid.Row="0" Grid.Column="1" /> <Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
<Rectangle Grid.Row="1" Grid.Column="0" /> <Rectangle Grid.Row="0" Grid.Column="1" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" /> <Rectangle Grid.Row="1" Grid.Column="0" />
</Grid> <Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
</VisualBrush.Visual> </Grid>
</VisualBrush> </VisualBrush.Visual>
</Grid.Background> </VisualBrush>
<Grid Grid.Column="0" Name="DeviceDisplayGrid" PointerReleased="DeviceDisplayGrid_OnPointerReleased"> </Border.Background>
<shared:DeviceVisualizer Device="{CompiledBinding Device}" <Grid Name="DeviceDisplayGrid" PointerReleased="DeviceDisplayGrid_OnPointerReleased">
HorizontalAlignment="Center" <shared:DeviceVisualizer Device="{CompiledBinding Device}"
VerticalAlignment="Center" HorizontalAlignment="Center"
ShowColors="True" VerticalAlignment="Center"
Margin="20" ShowColors="True"
RenderOptions.BitmapInterpolationMode="MediumQuality" Margin="5"
LedClicked="DeviceVisualizer_OnLedClicked" RenderOptions.BitmapInterpolationMode="MediumQuality"
Clicked="DeviceVisualizer_OnClicked" /> LedClicked="DeviceVisualizer_OnLedClicked"
Clicked="DeviceVisualizer_OnClicked" />
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="15" IsVisible="{CompiledBinding Device.Layout.RgbLayout.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
IsVisible="{CompiledBinding Device.Layout.RgbLayout.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"> <TextBlock Classes="h5" Text="Device layout by " />
<TextBlock Classes="h5" Text="Device layout by " /> <TextBlock Classes="h5" FontWeight="Bold" Text="{CompiledBinding Device.Layout.RgbLayout.Author}" />
<TextBlock Classes="h5" FontWeight="Bold" Text="{CompiledBinding Device.Layout.RgbLayout.Author}" /> </StackPanel>
</StackPanel> </Grid>
</Grid> </Border>
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" /> <GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" />
<Border Grid.Column="2" Classes="card-condensed" CornerRadius="10 0 0 0" Margin="0 10 0 0" Background="#ff323232"> <Border Grid.Column="2" Classes="card-condensed" Margin="5">
<Panel> <Panel>
<TabControl ItemsSource="{CompiledBinding Tabs}" IsVisible="{CompiledBinding Tabs.Count}" Padding="12"> <TabControl ItemsSource="{CompiledBinding Tabs}" IsVisible="{CompiledBinding Tabs.Count}" Padding="12">
<TabControl.ItemTemplate> <TabControl.ItemTemplate>
@ -69,6 +70,6 @@
</Border> </Border>
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right" /> <StackPanel Grid.Column="0" Grid.ColumnSpan="3" Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Top" HorizontalAlignment="Right" />
</Grid> </Grid>
</windowing:AppWindow> </windowing:AppWindow>

View File

@ -64,8 +64,8 @@ public class DevicePropertiesViewModel : DialogViewModelBase<object>
private void AddTabs() private void AddTabs()
{ {
Tabs.Add(_deviceVmFactory.DevicePropertiesTabViewModel(Device)); Tabs.Add(_deviceVmFactory.DeviceGeneralTabViewModel(Device));
Tabs.Add(_deviceVmFactory.DeviceInfoTabViewModel(Device)); Tabs.Add(_deviceVmFactory.DeviceLayoutTabViewModel(Device));
if (Device.DeviceType == RGBDeviceType.Keyboard) if (Device.DeviceType == RGBDeviceType.Keyboard)
Tabs.Add(_deviceVmFactory.InputMappingsTabViewModel(Device, SelectedLeds)); Tabs.Add(_deviceVmFactory.InputMappingsTabViewModel(Device, SelectedLeds));
Tabs.Add(_deviceVmFactory.DeviceLedsTabViewModel(Device, SelectedLeds)); Tabs.Add(_deviceVmFactory.DeviceLedsTabViewModel(Device, SelectedLeds));

View File

@ -0,0 +1,210 @@
<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:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
x:Class="Artemis.UI.Screens.Device.DeviceGeneralTabView"
x:DataType="device:DeviceGeneralTabViewModel">
<UserControl.Resources>
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="0" Orientation="Vertical">
<!-- Device information and categories -->
<Grid ColumnDefinitions="*,*" RowDefinitions="*,*">
<TextBlock Grid.Column="0" Grid.Row="0" Classes="card-title" Text="Information" Margin="10,0,0,0" />
<Border Grid.Column="0" Grid.Row="1" Classes="card" Margin="5" x:Name="InformationBorder">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,*,*,*,*,*">
<StackPanel Grid.Row="0" Grid.Column="0">
<TextBlock Text="Model" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.Model}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Manufacturer" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.Manufacturer}" />
</StackPanel>
<StackPanel Grid.Row="2" Grid.Column="0">
<TextBlock Text="Device Type" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceType}" />
</StackPanel>
<StackPanel Grid.Row="3" Grid.Column="0">
<TextBlock Text="Size (1px = 1mm)" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.Size}" />
</StackPanel>
<StackPanel Grid.Row="4" Grid.Column="0" IsVisible="{CompiledBinding IsKeyboard}">
<TextBlock Text="Physical Layout" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.PhysicalLayout}" />
</StackPanel>
<StackPanel Grid.Row="5" Grid.Column="0" IsVisible="{CompiledBinding IsKeyboard}" >
<TextBlock Text="Logical Layout" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.LogicalLayout}" />
</StackPanel>
</Grid>
</Border>
<TextBlock Grid.Column="1" Grid.Row="0" Classes="card-title" Text="Categories" Margin="10,0,0,0" />
<Border Grid.Row="1" Grid.Column="1" Classes="card" Margin="5" x:Name="CategoryBorder">
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="Artemis uses categories to determine where the layers of imported profiles are applied to." />
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,*,*,*,*" Margin="0,10,0,0">
<StackPanel Grid.Row="0" Grid.Column="0" Margin="0,0,0,10">
<TextBlock Text="Peripheral" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="A peripheral such as a mouse or keyboard" />
</StackPanel>
<CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{CompiledBinding HasPeripheralsCategory}" />
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0,0,0,10">
<TextBlock Text="Desk" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="A device acting as desk ornamentation such as a LED strip" />
</StackPanel>
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{CompiledBinding HasDeskCategory}" />
<StackPanel Grid.Row="2" Grid.Column="0" Margin="0,0,0,10">
<TextBlock Text="Monitor" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="A device attached to the monitor such as ambilight LEDs" />
</StackPanel>
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{CompiledBinding HasMonitorCategory}" />
<StackPanel Grid.Row="3" Grid.Column="0" Margin="0,0,0,10">
<TextBlock Text="Case" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="A device inside your computer case" />
</StackPanel>
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{CompiledBinding HasCaseCategory}" />
<StackPanel Grid.Row="4" Grid.Column="0" Margin="0,0,0,10">
<TextBlock Text="Room" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="A device elsewhere in the room" />
</StackPanel>
<CheckBox Grid.Row="4" Grid.Column="1" IsChecked="{CompiledBinding HasRoomCategory}" />
</Grid>
</StackPanel>
</Border>
</Grid>
<!-- Surface and Calibration -->
<Grid ColumnDefinitions="*,*" RowDefinitions="*,*">
<TextBlock Grid.Column="0" Grid.Row="0" Classes="card-title" Text="Surface" Margin="10,0,0,0" />
<Border Grid.Column="0" Grid.Row="1" Classes="card" Margin="5" x:Name="SurfaceBorder">
<StackPanel Orientation="Vertical">
<TextBlock TextWrapping="Wrap" Text="The device can be rotated and scaled on the surface with the values below." />
<Grid ColumnDefinitions="*,Auto,Auto" RowDefinitions="*,*,*,*">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center">X-coordinate</TextBlock>
<NumericUpDown Grid.Row="0"
Grid.Column="1"
Margin="10 5"
VerticalAlignment="Center"
Value="{CompiledBinding X}" />
<TextBlock Grid.Row="0" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">Y-coordinate</TextBlock>
<NumericUpDown Grid.Row="1"
Grid.Column="1"
Margin="10 5"
VerticalAlignment="Center"
Value="{CompiledBinding Y}" />
<TextBlock Grid.Row="1" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Scale</TextBlock>
<NumericUpDown Grid.Row="2"
Grid.Column="1"
Margin="10 5"
VerticalAlignment="Center"
Increment="0.1"
FormatString="F1"
Value="{CompiledBinding Scale}" />
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">times</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Rotation</TextBlock>
<NumericUpDown Grid.Row="3"
Grid.Column="1"
Margin="10 5"
VerticalAlignment="Center"
Value="{CompiledBinding Rotation}" />
<TextBlock Grid.Row="3" Grid.Column="2" VerticalAlignment="Center">deg</TextBlock>
</Grid>
</StackPanel>
</Border>
<TextBlock Grid.Column="1" Grid.Row="0" Classes="card-title" Text="Calibration" Margin="10,0,0,0" />
<Border Grid.Row="1" Grid.Column="1" Classes="card" Margin="5" x:Name="CalibrationBorder">
<StackPanel Orientation="Vertical">
<TextBlock TextWrapping="Wrap" Text="Use the sliders below to adjust the colors of your device so that it matches your other devices." />
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*,*,*">
<Label Grid.Row="0" Grid.Column="0" Content="R" VerticalAlignment="Center" />
<Slider Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding RedScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="0"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding RedScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<Label Grid.Row="1" Grid.Column="0" Content="G" VerticalAlignment="Center" />
<Slider Grid.Row="1" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding GreenScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding GreenScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<Label Grid.Row="2" Grid.Column="0" Content="B" VerticalAlignment="Center" />
<Slider Grid.Row="2" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding BlueScale}" Margin="10 0" Ticks="100" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding BlueScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto">
<Button
Grid.Column="0"
Content="Reset"
ToolTip.Tip="Reset the color scaling to what it had previously"
Command="{CompiledBinding ResetScaling}"
HorizontalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{CompiledBinding DisplayOnDevices}" Content="Preview specific color" VerticalAlignment="Center" HorizontalAlignment="Right" />
<controls:ColorPickerButton Grid.Column="2"
VerticalAlignment="Center"
Color="{CompiledBinding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
ShowAcceptDismissButtons="False" />
</Grid>
</StackPanel>
</Border>
</Grid>
</StackPanel>
<Button Grid.Row="1" Margin="5"
IsVisible="{CompiledBinding RequiresManualSetup}"
Command="{CompiledBinding RestartSetup}"
ToolTip.Tip="Restart device setup, allowing you to select a new physical and logical layout">
Restart setup
</Button>
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.Controls;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DeviceGeneralTabView : ReactiveUserControl<DeviceGeneralTabViewModel>
{
public DeviceGeneralTabView()
{
InitializeComponent();
}
}

View File

@ -1,53 +1,57 @@
using System; using Artemis.UI.Shared;
using Artemis.UI.Shared;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device;
public class DevicePropertiesTabViewModel : ActivatableViewModelBase public class DeviceGeneralTabViewModel : ActivatableViewModelBase
{ {
private readonly List<DeviceCategory> _categories;
private readonly ICoreService _coreService; private readonly ICoreService _coreService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private readonly List<DeviceCategory> _categories;
private readonly float _initialBlueScale; private readonly float _initialBlueScale;
private readonly float _initialGreenScale; private readonly float _initialGreenScale;
private readonly float _initialRedScale; private readonly float _initialRedScale;
private readonly INotificationService _notificationService;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
private float _greenScale;
private float _redScale;
private int _rotation; private int _rotation;
private float _scale; private float _scale;
private int _x; private int _x;
private int _y; private int _y;
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService) private float _redScale;
private float _greenScale;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
public DeviceGeneralTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService)
{ {
_coreService = coreService; _coreService = coreService;
_rgbService = rgbService; _rgbService = rgbService;
_windowService = windowService; _windowService = windowService;
_notificationService = notificationService;
_categories = new List<DeviceCategory>(device.Categories); _categories = new List<DeviceCategory>(device.Categories);
Device = device; Device = device;
DisplayName = "Properties"; DisplayName = "General";
X = (int)Device.X;
X = (int) Device.X; Y = (int)Device.Y;
Y = (int) Device.Y;
Scale = Device.Scale; Scale = Device.Scale;
Rotation = (int) Device.Rotation; Rotation = (int)Device.Rotation;
RedScale = Device.RedScale * 100f; RedScale = Device.RedScale * 100f;
GreenScale = Device.GreenScale * 100f; GreenScale = Device.GreenScale * 100f;
BlueScale = Device.BlueScale * 100f; BlueScale = Device.BlueScale * 100f;
@ -69,10 +73,13 @@ public class DevicePropertiesTabViewModel : ActivatableViewModelBase
{ {
_coreService.FrameRendering -= OnFrameRendering; _coreService.FrameRendering -= OnFrameRendering;
Device.PropertyChanged -= DeviceOnPropertyChanged; Device.PropertyChanged -= DeviceOnPropertyChanged;
Apply();
}).DisposeWith(d); }).DisposeWith(d);
}); });
} }
public bool RequiresManualSetup => !Device.DeviceProvider.CanDetectPhysicalLayout || !Device.DeviceProvider.CanDetectLogicalLayout;
public ArtemisDevice Device { get; } public ArtemisDevice Device { get; }
public int X public int X
@ -99,6 +106,38 @@ public class DevicePropertiesTabViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _rotation, value); set => RaiseAndSetIfChanged(ref _rotation, value);
} }
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public bool HasDeskCategory
{
get => GetCategory(DeviceCategory.Desk);
set => SetCategory(DeviceCategory.Desk, value);
}
public bool HasMonitorCategory
{
get => GetCategory(DeviceCategory.Monitor);
set => SetCategory(DeviceCategory.Monitor, value);
}
public bool HasCaseCategory
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public float RedScale public float RedScale
{ {
get => _redScale; get => _redScale;
@ -129,72 +168,19 @@ public class DevicePropertiesTabViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _displayOnDevices, value); set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
} }
// This solution won't scale well but I don't expect there to be many more categories. private bool GetCategory(DeviceCategory category)
// If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory
{ {
get => GetCategory(DeviceCategory.Desk); return _categories.Contains(category);
set => SetCategory(DeviceCategory.Desk, value);
} }
public bool HasMonitorCategory private void SetCategory(DeviceCategory category, bool value)
{ {
get => GetCategory(DeviceCategory.Monitor); if (value && !_categories.Contains(category))
set => SetCategory(DeviceCategory.Monitor, value); _categories.Add(category);
} else if (!value)
_categories.Remove(category);
public bool HasCaseCategory this.RaisePropertyChanged($"Has{category}Category");
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public bool RequiresManualSetup => !Device.DeviceProvider.CanDetectPhysicalLayout || !Device.DeviceProvider.CanDetectLogicalLayout;
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.FlushLeds = true;
}
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
} }
public async Task RestartSetup() public async Task RestartSetup()
@ -211,12 +197,12 @@ public class DevicePropertiesTabViewModel : ActivatableViewModelBase
_rgbService.ApplyBestDeviceLayout(Device); _rgbService.ApplyBestDeviceLayout(Device);
} }
public async Task Apply() private void Apply()
{ {
// TODO: Validation // TODO: Validation
_coreService.ProfileRenderingDisabled = true; _coreService.ProfileRenderingDisabled = true;
await Task.Delay(100); Thread.Sleep(100);
Device.X = X; Device.X = X;
Device.Y = Y; Device.Y = Y;
@ -234,40 +220,28 @@ public class DevicePropertiesTabViewModel : ActivatableViewModelBase
_coreService.ProfileRenderingDisabled = false; _coreService.ProfileRenderingDisabled = false;
} }
public void Reset() public void ApplyScaling()
{ {
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk); Device.RedScale = RedScale / 100f;
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor); Device.GreenScale = GreenScale / 100f;
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case); Device.BlueScale = BlueScale / 100f;
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
_rgbService.FlushLeds = true;
}
public void ResetScaling()
{
RedScale = _initialRedScale * 100; RedScale = _initialRedScale * 100;
GreenScale = _initialGreenScale * 100; GreenScale = _initialGreenScale * 100;
BlueScale = _initialBlueScale * 100; BlueScale = _initialBlueScale * 100;
} }
private bool GetCategory(DeviceCategory category)
{
return _categories.Contains(category);
}
private void SetCategory(DeviceCategory category, bool value)
{
if (value && !_categories.Contains(category))
_categories.Add(category);
else if (!value)
_categories.Remove(category);
this.RaisePropertyChanged($"Has{category}Category");
}
private void OnFrameRendering(object? sender, FrameRenderingEventArgs e) private void OnFrameRendering(object? sender, FrameRenderingEventArgs e)
{ {
if (!_displayOnDevices) if (!DisplayOnDevices)
return; return;
using SKPaint overlayPaint = new() {Color = CurrentColor}; using SKPaint overlayPaint = new() { Color = CurrentColor };
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint); e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
} }

View File

@ -1,88 +0,0 @@
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DeviceInfoTabView"
x:DataType="device:DeviceInfoTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,*" Margin="-5">
<!-- First row -->
<Border Classes="card" Grid.Column="0" Grid.Row="0" Margin="5">
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<TextBlock FontWeight="Bold">Device name</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName}" />
<Border Classes="card-separator" />
<TextBlock FontWeight="Bold">Manufacturer</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.Manufacturer}" />
<Border Classes="card-separator" />
<TextBlock FontWeight="Bold">Device type</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.DeviceType}" />
<Border Classes="card-separator" IsVisible="{CompiledBinding IsKeyboard}" />
<StackPanel IsVisible="{CompiledBinding IsKeyboard}">
<TextBlock FontWeight="Bold">Physical layout</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.PhysicalLayout}" />
</StackPanel>
</StackPanel>
</Border>
<Border Classes="card" Grid.Row="0" Grid.Column="1" Margin="5">
<StackPanel VerticalAlignment="Top">
<TextBlock FontWeight="Bold">Size (1px = 1mm)</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.Size}" />
<Border Classes="card-separator" />
<TextBlock FontWeight="Bold">Location (1px = 1mm)</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.Location}" />
<Border Classes="card-separator" />
<TextBlock FontWeight="Bold">Rotation (degrees)</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.Rotation.Degrees}" />
<Border Classes="card-separator" IsVisible="{CompiledBinding IsKeyboard}" />
<StackPanel IsVisible="{CompiledBinding IsKeyboard}">
<TextBlock FontWeight="Bold">Logical layout</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.LogicalLayout}" />
</StackPanel>
</StackPanel>
</Border>
<!-- Second row -->
<Border Classes="card" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="5">
<StackPanel>
<Grid ColumnDefinitions="*,Auto">
<TextBlock FontWeight="Bold">Default layout file path</TextBlock>
<Button Grid.Column="1"
Classes="icon-button"
ToolTip.Tip="Copy path to clipboard"
Click="LayoutPathButton_OnClick">
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock
TextWrapping="Wrap"
Text="{CompiledBinding DefaultLayoutPath}" />
<Border Classes="card-separator" />
<Grid ColumnDefinitions="*,Auto">
<TextBlock FontWeight="Bold">Image file path</TextBlock>
<Button Grid.Column="1"
Classes="icon-button"
ToolTip.Tip="Copy path to clipboard"
Click="ImagePathButton_OnClick">
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.Layout.Image.LocalPath}" />
</StackPanel>
</Border>
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -1,29 +0,0 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Avalonia;
using Avalonia.Controls;
using RGB.NET.Core;
namespace Artemis.UI.Screens.Device;
public class DeviceInfoTabViewModel : ActivatableViewModelBase
{
private readonly INotificationService _notificationService;
public DeviceInfoTabViewModel(ArtemisDevice device, INotificationService notificationService)
{
_notificationService = notificationService;
Device = device;
DisplayName = "Info";
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
}
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public ArtemisDevice Device { get; }
public string DefaultLayoutPath { get; }
}

View File

@ -0,0 +1,89 @@
<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:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.DeviceLayoutTabView"
x:DataType="device:DeviceLayoutTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Border Classes="card" Margin="5">
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="0">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="0" Grid.Column="0">
<TextBlock Text="Default layout file path" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding DefaultLayoutPath}" />
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button
Classes="icon-button"
HorizontalAlignment="Right"
ToolTip.Tip="Copy layout file path to clipboard"
Click="LayoutPathButton_OnClick">
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Image file path" />
<TextBlock Classes="subtitle" FontSize="12" TextWrapping="Wrap" Text="{CompiledBinding Device.Layout.Image.LocalPath}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">
<Button
Classes="icon-button"
HorizontalAlignment="Right"
ToolTip.Tip="Copy image file path to clipboard"
Click="ImagePathButton_OnClick">
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Disable default layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="With this checked, Artemis will not load a layout for this device unless you specifically provide one." />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">
<CheckBox HorizontalAlignment="Right" Margin="0,0,-10,0" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Custom layout path" />
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding CustomLayoutPath}" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding HasCustomLayout}" />
<!-- 5 pixels of margin between the buttons -->
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" />
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Export current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="If there is a layout used, export that. Otherwise, export the LEDs present." />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button HorizontalAlignment="Right" Content="Export" Command="{CompiledBinding ExportLayout}" />
</StackPanel>
</Grid>
</StackPanel>
<controls:HyperlinkButton
Grid.Row="1"
Content="Learn more about layouts on the wiki"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts?mtm_campaign=artemis&mtm_kwd=device-properties"
Margin="0 20"
HorizontalAlignment="Right"
VerticalAlignment="Bottom" />
</Grid>
</Border>
</ScrollViewer>
</UserControl>

View File

@ -1,13 +1,15 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device; namespace Artemis.UI.Screens.Device;
public partial class DeviceInfoTabView : ReactiveUserControl<DeviceInfoTabViewModel> public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabViewModel>
{ {
public DeviceInfoTabView() public DeviceLayoutTabView()
{ {
InitializeComponent(); InitializeComponent();
} }
@ -15,10 +17,12 @@ public partial class DeviceInfoTabView : ReactiveUserControl<DeviceInfoTabViewMo
private void LayoutPathButton_OnClick(object? sender, RoutedEventArgs e) private void LayoutPathButton_OnClick(object? sender, RoutedEventArgs e)
{ {
TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.DefaultLayoutPath); TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.DefaultLayoutPath);
ViewModel.ShowCopiedNotification();
} }
private void ImagePathButton_OnClick(object? sender, RoutedEventArgs e) private void ImagePathButton_OnClick(object? sender, RoutedEventArgs e)
{ {
TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.Device.Layout.Image.LocalPath); TopLevel.GetTopLevel(this).Clipboard.SetTextAsync(ViewModel.Device.Layout.Image.LocalPath);
ViewModel.ShowCopiedNotification();
} }
} }

View File

@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls;
using ReactiveUI;
using RGB.NET.Layout;
using SkiaSharp;
namespace Artemis.UI.Screens.Device;
public class DeviceLayoutTabViewModel : ActivatableViewModelBase
{
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
public DeviceLayoutTabViewModel(
IWindowService windowService,
INotificationService notificationService,
ArtemisDevice device)
{
_windowService = windowService;
_notificationService = notificationService;
Device = device;
DisplayName = "Layout";
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
}
public ArtemisDevice Device { get; }
public string DefaultLayoutPath { get; }
public string CustomLayoutPath => Device.CustomLayoutPath ?? "None";
public bool HasCustomLayout => Device.CustomLayoutPath != null;
private void RaiseCustomLayoutChanged()
{
this.RaisePropertyChanged(nameof(CustomLayoutPath));
this.RaisePropertyChanged(nameof(HasCustomLayout));
}
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
RaiseCustomLayoutChanged();
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
RaiseCustomLayoutChanged();
}
public async Task ExportLayout()
{
string fileName = Device.DeviceProvider.GetDeviceLayoutName(Device);
string layoutDir = Constants.LayoutsFolder;
string filePath = Path.Combine(
layoutDir,
Device.RgbDevice.DeviceInfo.Manufacturer,
Device.DeviceType.ToString(),
fileName
);
if (!Directory.Exists(filePath))
Directory.CreateDirectory(filePath);
string? result = await _windowService.CreateSaveFileDialog()
.HavingFilter(f => f.WithExtension("xml").WithName("Artemis layout"))
.WithDirectory(filePath)
.WithInitialFileName(fileName)
.ShowAsync();
if (result == null)
return;
ArtemisLayout? layout = Device.Layout;
if (layout?.IsValid == true)
{
string path = layout.FilePath;
File.Copy(path, result, true);
}
else
{
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout()
{
Id = x.RgbLed.Id.ToString(),
DescriptiveX = x.Rectangle.Left.ToString(),
DescriptiveY = x.Rectangle.Top.ToString(),
DescriptiveWidth = $"{x.Rectangle.Width}mm",
DescriptiveHeight = $"{x.Rectangle.Height}mm",
}).ToList();
DeviceLayout emptyLayout = new()
{
Author = "Artemis",
Type = Device.DeviceType,
Vendor = Device.RgbDevice.DeviceInfo.Manufacturer,
Model = Device.RgbDevice.DeviceInfo.Model,
Width = Device.Rectangle.Width,
Height = Device.Rectangle.Height,
InternalLeds = ledLayouts,
};
XmlSerializer serializer = new(typeof(DeviceLayout));
await using StreamWriter writer = new(result);
serializer.Serialize(writer, emptyLayout);
}
_notificationService.CreateNotification()
.WithMessage("Layout exported")
.WithTimeout(TimeSpan.FromSeconds(5))
.WithSeverity(NotificationSeverity.Success)
.Show();
}
public void ShowCopiedNotification()
{
_notificationService.CreateNotification()
.WithTitle("Copied!")
.WithSeverity(NotificationSeverity.Informational)
.Show();
}
}

View File

@ -1,204 +0,0 @@
<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:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1200"
x:Class="Artemis.UI.Screens.Device.DevicePropertiesTabView"
x:DataType="device:DevicePropertiesTabViewModel">
<UserControl.Resources>
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<!-- Body -->
<Grid RowDefinitions="*,Auto">
<StackPanel Grid.Row="0">
<!-- Layout -->
<TextBlock Classes="h4">
Categories
</TextBlock>
<TextBlock TextWrapping="Wrap">
Artemis uses categories to determine where the layers of imported profiles are applied to.
</TextBlock>
<TextBlock TextWrapping="Wrap">
You can hover over a category for a more detailed description.
</TextBlock>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding HasDeskCategory}"
ToolTip.Tip="A device acting as desk ornamentation such as a LED strip"
Content="Desk" />
<CheckBox IsChecked="{CompiledBinding HasMonitorCategory}"
ToolTip.Tip="A device attached to the monitor such as ambilight LEDs"
Content="Monitor" />
<CheckBox ToolTip.Tip="A device inside your computer case"
IsChecked="{CompiledBinding HasCaseCategory}"
Content="Case" />
<CheckBox IsChecked="{CompiledBinding HasRoomCategory}"
ToolTip.Tip="A device elsewhere in the room"
Content="Room" />
<CheckBox IsChecked="{CompiledBinding HasPeripheralsCategory}"
ToolTip.Tip="A peripheral such as a mouse or keyboard"
Content="Peripheral" />
</StackPanel>
<Grid Margin="0 25" ColumnDefinitions="*,40,*">
<Grid Grid.Column="0" ColumnDefinitions="*,Auto,Auto" RowDefinitions="*,*,*,*,*">
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Classes="h4">
Surface properties
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">X-coordinate</TextBlock>
<NumericUpDown Grid.Row="1"
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{CompiledBinding X}" />
<TextBlock Grid.Row="1" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Y-coordinate</TextBlock>
<NumericUpDown Grid.Row="2"
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{CompiledBinding Y}" />
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Scale</TextBlock>
<NumericUpDown Grid.Row="3"
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Increment="0.1"
FormatString="F1"
Value="{CompiledBinding Scale}" />
<TextBlock Grid.Row="3" Grid.Column="2" VerticalAlignment="Center">times</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Rotation</TextBlock>
<NumericUpDown Grid.Row="4"
Grid.Column="1"
Margin="10 0"
VerticalAlignment="Center"
Value="{CompiledBinding Rotation}" />
<TextBlock Grid.Row="4" Grid.Column="2" VerticalAlignment="Center">deg</TextBlock>
</Grid>
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" Width="1" Fill="{DynamicResource ButtonBorderBrush}" Margin="0 0 0 5" />
<StackPanel Grid.Column="2">
<TextBlock Classes="h4">
Color calibration
</TextBlock>
<TextBlock TextWrapping="Wrap">
Use the sliders below to adjust the colors of your device so that it matches your other devices.
</TextBlock>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*,*,*,*">
<Label Grid.Row="0" Grid.Column="0" Content="R" VerticalAlignment="Center" />
<Slider Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding RedScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="0"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding RedScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<Label Grid.Row="1" Grid.Column="0" Content="G" VerticalAlignment="Center" />
<Slider Grid.Row="1" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding GreenScale}" Margin="10 0" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding GreenScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<Label Grid.Row="2" Grid.Column="0" Content="B" VerticalAlignment="Center" />
<Slider Grid.Row="2" Grid.Column="1" Minimum="0" Maximum="200" Value="{CompiledBinding BlueScale}" Margin="10 0" Ticks="100" VerticalAlignment="Center" />
<NumericUpDown Grid.Row="2"
Grid.Column="2"
VerticalAlignment="Center"
Width="65"
Value="{CompiledBinding BlueScale}"
ShowButtonSpinner="False"
FormatString="{}{0:0.0}"
Minimum="0"
Maximum="200"
ClipValueToMinMax="True" />
<CheckBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{CompiledBinding DisplayOnDevices}" Content="Show preview" VerticalAlignment="Center" />
<controls:ColorPickerButton Grid.Row="3"
Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Color="{CompiledBinding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
ShowAcceptDismissButtons="False" />
</Grid>
</StackPanel>
</Grid>
<!-- Layout -->
<TextBlock Classes="h4">
Layout
</TextBlock>
<TextBlock TextWrapping="Wrap">
The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window.
</TextBlock>
<CheckBox Margin="0 0 0 5" IsChecked="{CompiledBinding Device.DisableDefaultLayout}">
<CheckBox.Content>
<StackPanel Orientation="Horizontal">
<TextBlock>Don't load default layout</TextBlock>
<avalonia:MaterialIcon Kind="HelpCircle"
Margin="5 0"
ToolTip.Tip="With this enabled Artemis will not load a layout for this device unless you specifically provide one." />
</StackPanel>
</CheckBox.Content>
</CheckBox>
<Grid>
<TextBox Text="{CompiledBinding Device.CustomLayoutPath}"
Watermark="Custom layout path"
UseFloatingWatermark="True"
IsReadOnly="True"
PointerReleased="InputElement_OnPointerReleased"
Padding="10 5 36 6" />
<Button Classes="AppBarButton" HorizontalAlignment="Right" Command="{CompiledBinding ClearCustomLayout}" Padding="6" Margin="5 0">
<avalonia:MaterialIcon Kind="CloseCircle" />
</Button>
</Grid>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts" Margin="0 20" HorizontalAlignment="Right">
Learn more about layouts on the wiki
</controls:HyperlinkButton>
</StackPanel>
<!-- Buttons -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<Button Grid.Column="0"
IsVisible="{CompiledBinding RequiresManualSetup}"
Command="{CompiledBinding RestartSetup}"
ToolTip.Tip="Restart device setup, allowing you to select a new physical and logical layout">
Restart setup
</Button>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button IsCancel="True" Command="{CompiledBinding Reset}" Margin="0 0 5 0">Reset</Button>
<Button Classes="accent" IsDefault="True" Command="{CompiledBinding Apply}">Apply</Button>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -1,19 +0,0 @@
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device;
public partial class DevicePropertiesTabView : ReactiveUserControl<DevicePropertiesTabViewModel>
{
public DevicePropertiesTabView()
{
InitializeComponent();
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
ViewModel?.BrowseCustomLayout();
}
}

View File

@ -43,7 +43,7 @@
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. 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> </TextBlock>
</StackPanel> </StackPanel>
<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 Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins?mtm_campaign=artemis&mtm_kwd=home" HorizontalAlignment="Right">
<controls:HyperlinkButton.ContextMenu> <controls:HyperlinkButton.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="Test"></MenuItem> <MenuItem Header="Test"></MenuItem>
@ -75,7 +75,7 @@
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
</StackPanel> </StackPanel>
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com"> <controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=home">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
@ -110,7 +110,7 @@
<controls:HyperlinkButton Grid.Row="1" <controls:HyperlinkButton Grid.Row="1"
Grid.Column="0" Grid.Column="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/donating"> NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&mtm_kwd=home">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Gift" /> <avalonia:MaterialIcon Kind="Gift" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock> <TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>

View File

@ -8,7 +8,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView" x:Class="Artemis.UI.Screens.ProfileEditor.MenuBar.MenuBarView"
x:DataType="menuBar:MenuBarViewModel"> x:DataType="menuBar:MenuBarViewModel">
<Menu VerticalAlignment="Top" MenuClosed="MenuBase_OnMenuClosed"> <Menu VerticalAlignment="Top" Closed="MenuBase_OnMenuClosed">
<MenuItem Header="_File"> <MenuItem Header="_File">
<MenuItem Header="New"> <MenuItem Header="New">
<MenuItem.Icon> <MenuItem.Icon>
@ -164,44 +164,44 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_Help"> <MenuItem Header="_Help">
<MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/"> <MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" /> <avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles"> <MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Edit" /> <avalonia:MaterialIcon Kind="Edit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers"> <MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Layers" /> <avalonia:MaterialIcon Kind="Layers" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions"> <MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="NotEqual" /> <avalonia:MaterialIcon Kind="NotEqual" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline"> <MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Stopwatch" /> <avalonia:MaterialIcon Kind="Stopwatch" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings"> <MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="VectorLink" /> <avalonia:MaterialIcon Kind="VectorLink" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting"> <MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="CodeJson" /> <avalonia:MaterialIcon Kind="CodeJson" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Report a Bug" Command="{CompiledBinding OpenUri}" CommandParameter="https://github.com/Artemis-RGB/Artemis/issues"> <MenuItem Header="Report a Bug" Command="{CompiledBinding OpenUri}" CommandParameter="https://github.com/Artemis-RGB/Artemis/issues?mtm_campaign=artemis&mtm_kwd=profile-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</MenuItem.Icon> </MenuItem.Icon>

View File

@ -25,7 +25,7 @@
<controls:HyperlinkButton Grid.Row="0" <controls:HyperlinkButton Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Top" VerticalAlignment="Top"
NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints"> NavigateUri="https://wiki.artemis-rgb.com/guides/user/profiles/layers/adaption-hints?mtm_campaign=artemis&mtm_kwd=profile-editor">
Learn more about adaption hints Learn more about adaption hints
</controls:HyperlinkButton> </controls:HyperlinkButton>

View File

@ -43,7 +43,7 @@
When you enable data bindings you can no longer use keyframes or normal values for this property. When you enable data bindings you can no longer use keyframes or normal values for this property.
</TextBlock> </TextBlock>
<controls:HyperlinkButton HorizontalAlignment="Center" <controls:HyperlinkButton HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings?mtm_campaign=artemis&mtm_kwd=profile-editor"
Margin="0 10"> Margin="0 10">
Learn more Learn more
</controls:HyperlinkButton> </controls:HyperlinkButton>

View File

@ -63,7 +63,12 @@ public partial class VisualEditorView : ReactiveUserControl<VisualEditorViewMode
private void UpdateZoomBorderBackground() private void UpdateZoomBorderBackground()
{ {
if (ZoomBorder.Background is VisualBrush visualBrush) if (ZoomBorder.Background is VisualBrush visualBrush)
{
visualBrush.DestinationRect = new RelativeRect(ZoomBorder.OffsetX * -1, ZoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute); visualBrush.DestinationRect = new RelativeRect(ZoomBorder.OffsetX * -1, ZoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
// Workaround
// This fixes an issue where the container is not invalidated, which leaves behind a 'smear' since Avalonia 11 rc1, check if still required later
ZoomBorder.InvalidateVisual();
}
} }

View File

@ -24,13 +24,13 @@
</TextBlock> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=about">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=about">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
</StackPanel> </StackPanel>

View File

@ -17,7 +17,7 @@
<TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0" /> <TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0" />
<StackPanel Spacing="5" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel Spacing="5" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal">
<controls:HyperlinkButton VerticalAlignment="Top" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins"> <controls:HyperlinkButton VerticalAlignment="Top" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins?mtm_campaign=artemis&mtm_kwd=plugins">
Get more plugins Get more plugins
</controls:HyperlinkButton> </controls:HyperlinkButton>
<Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button> <Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button>

View File

@ -28,7 +28,7 @@
TextWrapping="Wrap" TextWrapping="Wrap"
Text="{CompiledBinding Channel, StringFormat='Found no releases for the \'{0}\' channel.'}"> Text="{CompiledBinding Channel, StringFormat='Found no releases for the \'{0}\' channel.'}">
</TextBlock> </TextBlock>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels" <controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/channels?mtm_campaign=artemis&mtm_kwd=releases"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
Learn more about channels on the wiki Learn more about channels on the wiki
</controls:HyperlinkButton> </controls:HyperlinkButton>

View File

@ -47,7 +47,7 @@
ToolTip.Tip="View website" ToolTip.Tip="View website"
ToolTip.Placement="Top" ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com"> NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" <controls:HyperlinkButton Classes="icon-button"
@ -65,7 +65,7 @@
ToolTip.Tip="View Wiki" ToolTip.Tip="View Wiki"
ToolTip.Placement="Top" ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com"> NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" <controls:HyperlinkButton Classes="icon-button"
@ -83,7 +83,7 @@
ToolTip.Tip="View donation options" ToolTip.Tip="View donation options"
ToolTip.Placement="Top" ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5" ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating"> NavigateUri="https://wiki.artemis-rgb.com/en/donating?mtm_campaign=artemis&mtm_kwd=sidebar">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
</WrapPanel> </WrapPanel>

View File

@ -33,10 +33,10 @@
<TextBlock Classes="link-name">Discord</TextBlock> <TextBlock Classes="link-name">Discord</TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1"> <StackPanel Grid.Column="1">
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/"> <controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=wizard">
https://wiki.artemis-rgb.com/ https://wiki.artemis-rgb.com/
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction"> <controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&mtm_kwd=wizard">
https://wiki.artemis-rgb.com/en/guides/user/introduction https://wiki.artemis-rgb.com/en/guides/user/introduction
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis"> <controls:HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">

View File

@ -16,13 +16,13 @@
</TextBlock> </TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" /> <avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com"> <controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
<avalonia:MaterialIcon Kind="BookOpenOutline" /> <avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
</StackPanel> </StackPanel>

View File

@ -110,14 +110,14 @@
</shared:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</shared:SelectionRectangle> </shared:SelectionRectangle>
<Border Name="SurfaceBounds" <Rectangle Name="SurfaceBounds"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Width="{Binding MaxTextureSize}" Width="{Binding MaxTextureSize}"
Height="{Binding MaxTextureSize}" Height="{Binding MaxTextureSize}"
BorderThickness="2" StrokeThickness="2"
BorderBrush="{DynamicResource SystemAccentColorLight1}" Stroke="{DynamicResource SystemAccentColorLight1}"
BorderDashArray="6,2" StrokeDashArray="6,2"
Opacity="0.5" /> Opacity="0.5" />
</Grid> </Grid>
</paz:ZoomBorder> </paz:ZoomBorder>

View File

@ -5,7 +5,6 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom; using Avalonia.Controls.PanAndZoom;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -30,7 +29,7 @@ public partial class SurfaceEditorView : ReactiveUserControl<SurfaceEditorViewMo
private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e) private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e)
{ {
UpdateZoomBorderBackground(); UpdateZoomBorderBackground();
SurfaceBounds.BorderThickness = new Thickness(2 / ContainerZoomBorder.ZoomX); SurfaceBounds.StrokeThickness = 2 / ContainerZoomBorder.ZoomX;
} }
private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e) private void SelectionRectangle_OnSelectionUpdated(object? sender, SelectionRectangleEventArgs e)
@ -49,6 +48,11 @@ public partial class SurfaceEditorView : ReactiveUserControl<SurfaceEditorViewMo
private void UpdateZoomBorderBackground() private void UpdateZoomBorderBackground()
{ {
if (ContainerZoomBorder.Background is VisualBrush visualBrush) if (ContainerZoomBorder.Background is VisualBrush visualBrush)
{
visualBrush.DestinationRect = new RelativeRect(ContainerZoomBorder.OffsetX * -1, ContainerZoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute); visualBrush.DestinationRect = new RelativeRect(ContainerZoomBorder.OffsetX * -1, ContainerZoomBorder.OffsetY * -1, 20, 20, RelativeUnit.Absolute);
// Workaround
// This fixes an issue where the container is not invalidated, which leaves behind a 'smear' since Avalonia 11 rc1, check if still required later
ContainerZoomBorder.InvalidateVisual();
}
} }
} }

View File

@ -116,38 +116,38 @@
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_Help"> <MenuItem Header="_Help">
<MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/"> <MenuItem Header="Artemis Wiki" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" /> <avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles"> <MenuItem Header="Editor" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/profiles?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Edit" /> <avalonia:MaterialIcon Kind="Edit" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers"> <MenuItem Header="Layers" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/layers?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Layers" /> <avalonia:MaterialIcon Kind="Layers" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions"> <MenuItem Header="Display Conditions" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/conditions?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="NotEqual" /> <avalonia:MaterialIcon Kind="NotEqual" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline"> <MenuItem Header="Timeline" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/timeline?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Stopwatch" /> <avalonia:MaterialIcon Kind="Stopwatch" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings"> <MenuItem Header="Data Bindings" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/data-bindings?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="VectorLink" /> <avalonia:MaterialIcon Kind="VectorLink" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting"> <MenuItem Header="Scripting" Command="{CompiledBinding OpenUri}" CommandParameter="https://wiki.artemis-rgb.com/guides/user/profiles/scripting?mtm_campaign=artemis&mtm_kwd=script-editor">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="CodeJson" /> <avalonia:MaterialIcon Kind="CodeJson" />
</MenuItem.Icon> </MenuItem.Icon>
@ -173,7 +173,7 @@
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Right" HorizontalAlignment="Right"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes"> NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes?mtm_campaign=artemis&mtm_kwd=script-editor">
Learn more about visual scripts Learn more about visual scripts
</controls:HyperlinkButton> </controls:HyperlinkButton>

View File

@ -3,32 +3,30 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Rendering; using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Artemis.UI.Screens.VisualScripting.Pins; namespace Artemis.UI.Screens.VisualScripting.Pins;
public class PinView : ReactiveUserControl<PinViewModel> public class PinView : ReactiveUserControl<PinViewModel>
{ {
private Canvas? _container; private readonly DispatcherTimer _updateTimer;
private bool _dragging; private bool _dragging;
private Canvas? _container;
private Border? _pinPoint; private Border? _pinPoint;
private PinViewRenderLoopTaks _renderLoopTask;
public PinView()
{
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(16), DispatcherPriority.Render, UpdatePosition);
}
protected void InitializePin(Border pinPoint) protected void InitializePin(Border pinPoint)
{ {
_pinPoint = pinPoint; _pinPoint = pinPoint;
_pinPoint.PointerMoved += PinPointOnPointerMoved; _pinPoint.PointerMoved += PinPointOnPointerMoved;
_pinPoint.PointerReleased += PinPointOnPointerReleased; _pinPoint.PointerReleased += PinPointOnPointerReleased;
_pinPoint.PropertyChanged += PinPointOnPropertyChanged;
_renderLoopTask = new PinViewRenderLoopTaks(this);
} }
private void PinPointOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
Console.WriteLine(e);
}
private void PinPointOnPointerMoved(object? sender, PointerEventArgs e) private void PinPointOnPointerMoved(object? sender, PointerEventArgs e)
{ {
if (ViewModel == null || _container == null || _pinPoint == null) if (ViewModel == null || _container == null || _pinPoint == null)
@ -74,19 +72,17 @@ public class PinView : ReactiveUserControl<PinViewModel>
/// <inheritdoc /> /// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e);
_container = this.FindAncestorOfType<Canvas>(); _container = this.FindAncestorOfType<Canvas>();
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Add(_renderLoopTask); _updateTimer.Start();
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); _updateTimer.Stop();
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>().Remove(_renderLoopTask);
} }
public void UpdatePosition() public void UpdatePosition(object? sender, EventArgs eventArgs)
{ {
if (_container == null || _pinPoint == null || ViewModel == null) if (_container == null || _pinPoint == null || ViewModel == null)
return; return;
@ -97,25 +93,4 @@ public class PinView : ReactiveUserControl<PinViewModel>
} }
#endregion #endregion
}
public class PinViewRenderLoopTaks : IRenderLoopTask
{
private readonly PinView _pinView;
public PinViewRenderLoopTaks(PinView pinView)
{
_pinView = pinView;
}
public void Update(TimeSpan time)
{
_pinView.UpdatePosition();
}
public void Render()
{
}
public bool NeedsUpdate => true;
} }

View File

@ -8,9 +8,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-preview8" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.0-rc1.1" />
<PackageReference Include="DryIoc.dll" Version="5.4.0" /> <PackageReference Include="DryIoc.dll" Version="5.4.0" />
<PackageReference Include="NoStringEvaluating" Version="2.5.2" /> <PackageReference Include="NoStringEvaluating" Version="2.5.2" />
<PackageReference Include="ReactiveUI" Version="18.4.26" /> <PackageReference Include="ReactiveUI" Version="18.4.26" />

View File

@ -5,7 +5,7 @@ using NoStringEvaluating.Models.FormulaChecker;
namespace Artemis.VisualScripting.Nodes.Mathematics; namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Math Expression", "Outputs the result of a math expression.", "Mathematics", "https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes/mathematics/math-expression", InputType = typeof(Numeric), OutputType = typeof(Numeric))] [Node("Math Expression", "Outputs the result of a math expression.", "Mathematics", "https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes/mathematics/math-expression?mtm_campaign=artemis&mtm_kwd=node-help", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel> public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel>
{ {
private readonly IFormulaChecker _checker; private readonly IFormulaChecker _checker;