mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Added notifications and dialogs
This commit is contained in:
parent
f98e398bc5
commit
4c836fb505
@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Ninject;
|
||||||
|
using Ninject.Parameters;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||||
|
{
|
||||||
|
public class ContentDialogBuilder
|
||||||
|
{
|
||||||
|
private readonly ContentDialog _contentDialog;
|
||||||
|
private readonly IKernel _kernel;
|
||||||
|
private readonly Window _parent;
|
||||||
|
|
||||||
|
internal ContentDialogBuilder(IKernel kernel, Window parent)
|
||||||
|
{
|
||||||
|
_kernel = kernel;
|
||||||
|
_parent = parent;
|
||||||
|
_contentDialog = new ContentDialog
|
||||||
|
{
|
||||||
|
CloseButtonText = "CLose"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder WithTitle(string? title)
|
||||||
|
{
|
||||||
|
_contentDialog.Title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder WithContent(string? content)
|
||||||
|
{
|
||||||
|
_contentDialog.Content = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton)
|
||||||
|
{
|
||||||
|
_contentDialog.DefaultButton = defaultButton;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder HavingPrimaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||||
|
{
|
||||||
|
ContentDialogButtonBuilder builder = new();
|
||||||
|
configure(builder);
|
||||||
|
|
||||||
|
_contentDialog.IsPrimaryButtonEnabled = true;
|
||||||
|
_contentDialog.PrimaryButtonText = builder.Text;
|
||||||
|
_contentDialog.PrimaryButtonCommand = builder.Command;
|
||||||
|
_contentDialog.PrimaryButtonCommandParameter = builder.CommandParameter;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder HavingSecondaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||||
|
{
|
||||||
|
ContentDialogButtonBuilder builder = new();
|
||||||
|
configure(builder);
|
||||||
|
|
||||||
|
_contentDialog.IsSecondaryButtonEnabled = true;
|
||||||
|
_contentDialog.SecondaryButtonText = builder.Text;
|
||||||
|
_contentDialog.SecondaryButtonCommand = builder.Command;
|
||||||
|
_contentDialog.SecondaryButtonCommandParameter = builder.CommandParameter;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder WithCloseButtonText(string? text)
|
||||||
|
{
|
||||||
|
_contentDialog.CloseButtonText = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogBuilder WithViewModel<T>(params (string name, object value)[] parameters)
|
||||||
|
{
|
||||||
|
if (parameters.Length != 0)
|
||||||
|
{
|
||||||
|
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
||||||
|
_contentDialog.Content = _kernel.Get<T>(paramsArray);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_contentDialog.Content = _kernel.Get<T>();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ContentDialogResult> ShowAsync()
|
||||||
|
{
|
||||||
|
if (_parent.Content is not Panel panel)
|
||||||
|
return ContentDialogResult.None;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
panel.Children.Add(_contentDialog);
|
||||||
|
return await _contentDialog.ShowAsync();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
panel.Children.Remove(_contentDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ContentDialogButtonBuilder
|
||||||
|
{
|
||||||
|
internal ContentDialogButtonBuilder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string? Text { get; set; }
|
||||||
|
internal ICommand? Command { get; set; }
|
||||||
|
internal object? CommandParameter { get; set; }
|
||||||
|
|
||||||
|
public ContentDialogButtonBuilder WithText(string? text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogButtonBuilder WithCommand(ICommand? command)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter)
|
||||||
|
{
|
||||||
|
CommandParameter = commandParameter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a builder that can create a <see cref="FileDialogFilter" />.
|
||||||
|
/// </summary>
|
||||||
|
public class FileDialogFilterBuilder
|
||||||
|
{
|
||||||
|
private readonly FileDialogFilter _filter;
|
||||||
|
|
||||||
|
internal FileDialogFilterBuilder()
|
||||||
|
{
|
||||||
|
_filter = new FileDialogFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the name of the filter
|
||||||
|
/// </summary>
|
||||||
|
public FileDialogFilterBuilder WithName(string name)
|
||||||
|
{
|
||||||
|
_filter.Name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the provided extension to the filter
|
||||||
|
/// </summary>
|
||||||
|
public FileDialogFilterBuilder WithExtension(string extension)
|
||||||
|
{
|
||||||
|
_filter.Extensions.Add(extension);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FileDialogFilter Build()
|
||||||
|
{
|
||||||
|
return _filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||||
|
{
|
||||||
|
public class NotificationBuilder
|
||||||
|
{
|
||||||
|
private readonly InfoBar _infoBar;
|
||||||
|
private readonly Window _parent;
|
||||||
|
private TimeSpan _timeout = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
|
public NotificationBuilder(Window parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
_infoBar = new InfoBar {Classes = Classes.Parse("notification-info-bar")};
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationBuilder WithTitle(string? title)
|
||||||
|
{
|
||||||
|
_infoBar.Title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationBuilder WithMessage(string? content)
|
||||||
|
{
|
||||||
|
_infoBar.Message = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationBuilder WithTimeout(TimeSpan timeout)
|
||||||
|
{
|
||||||
|
_timeout = timeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationBuilder WithSeverity(NotificationSeverity severity)
|
||||||
|
{
|
||||||
|
_infoBar.Severity = (InfoBarSeverity) severity;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
if (_parent.Content is not Panel panel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
panel.Children.Add(_infoBar);
|
||||||
|
_infoBar.Closed += InfoBarOnClosed;
|
||||||
|
_infoBar.IsOpen = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(_timeout);
|
||||||
|
Dispatcher.UIThread.Post(() => _infoBar.IsOpen = false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InfoBarOnClosed(InfoBar sender, InfoBarClosedEventArgs args)
|
||||||
|
{
|
||||||
|
_infoBar.Closed -= InfoBarOnClosed;
|
||||||
|
if (_parent.Content is not Panel panel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
panel.Children.Remove(_infoBar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NotificationSeverity
|
||||||
|
{
|
||||||
|
Informational,
|
||||||
|
Success,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a builder that can create a <see cref="OpenFileDialog" />.
|
||||||
|
/// </summary>
|
||||||
|
public class OpenFileDialogBuilder
|
||||||
|
{
|
||||||
|
private readonly OpenFileDialog _openFileDialog;
|
||||||
|
private readonly Window _parent;
|
||||||
|
|
||||||
|
public OpenFileDialogBuilder(Window parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
_openFileDialog = new OpenFileDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate that the user can select multiple files.
|
||||||
|
/// </summary>
|
||||||
|
public OpenFileDialogBuilder WithAllowMultiple()
|
||||||
|
{
|
||||||
|
_openFileDialog.AllowMultiple = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the title of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public OpenFileDialogBuilder WithTitle(string? title)
|
||||||
|
{
|
||||||
|
_openFileDialog.Title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial directory of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public OpenFileDialogBuilder WithDirectory(string? directory)
|
||||||
|
{
|
||||||
|
_openFileDialog.Directory = directory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial file name of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public OpenFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||||
|
{
|
||||||
|
_openFileDialog.InitialFileName = initialFileName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a filter to the dialog
|
||||||
|
/// </summary>
|
||||||
|
public OpenFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||||
|
{
|
||||||
|
FileDialogFilterBuilder builder = new();
|
||||||
|
configure(builder);
|
||||||
|
_openFileDialog.Filters.Add(builder.Build());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the file dialog
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A task that on completion returns an array containing the full path to the selected
|
||||||
|
/// files, or null if the dialog was canceled.
|
||||||
|
/// </returns>
|
||||||
|
public async Task<string[]?> ShowAsync()
|
||||||
|
{
|
||||||
|
return await _openFileDialog.ShowAsync(_parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a builder that can create a <see cref="SaveFileDialog" />.
|
||||||
|
/// </summary>
|
||||||
|
public class SaveFileDialogBuilder
|
||||||
|
{
|
||||||
|
private readonly Window _parent;
|
||||||
|
private readonly SaveFileDialog _saveFileDialog;
|
||||||
|
|
||||||
|
public SaveFileDialogBuilder(Window parent)
|
||||||
|
{
|
||||||
|
_parent = parent;
|
||||||
|
_saveFileDialog = new SaveFileDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the title of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public SaveFileDialogBuilder WithTitle(string? title)
|
||||||
|
{
|
||||||
|
_saveFileDialog.Title = title;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial directory of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public SaveFileDialogBuilder WithDirectory(string? directory)
|
||||||
|
{
|
||||||
|
_saveFileDialog.Directory = directory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial file name of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public SaveFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||||
|
{
|
||||||
|
_saveFileDialog.InitialFileName = initialFileName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the default extension of the dialog
|
||||||
|
/// </summary>
|
||||||
|
public SaveFileDialogBuilder WithDefaultExtension(string? defaultExtension)
|
||||||
|
{
|
||||||
|
_saveFileDialog.DefaultExtension = defaultExtension;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a filter to the dialog
|
||||||
|
/// </summary>
|
||||||
|
public SaveFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||||
|
{
|
||||||
|
FileDialogFilterBuilder builder = new();
|
||||||
|
configure(builder);
|
||||||
|
_saveFileDialog.Filters.Add(builder.Build());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the save file dialog.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A task that on completion contains the full path of the save location, or null if the
|
||||||
|
/// dialog was canceled.
|
||||||
|
/// </returns>
|
||||||
|
public async Task<string?> ShowAsync()
|
||||||
|
{
|
||||||
|
return await _saveFileDialog.ShowAsync(_parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
||||||
|
{
|
||||||
|
public interface INotificationService : IArtemisSharedUIService
|
||||||
|
{
|
||||||
|
NotificationBuilder CreateNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,16 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
||||||
{
|
{
|
||||||
public interface IWindowService : IArtemisSharedUIService
|
public interface IWindowService : IArtemisSharedUIService
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a view model instance of type <typeparamref name="T" /> and shows its corresponding View as a window
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of view model to create</typeparam>
|
||||||
|
/// <returns>The created view model</returns>
|
||||||
T ShowWindow<T>();
|
T ShowWindow<T>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -13,11 +20,27 @@ namespace Artemis.UI.Avalonia.Shared.Services.Interfaces
|
|||||||
void ShowWindow(object viewModel);
|
void ShowWindow(object viewModel);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a ViewModel, show its corresponding View as a Dialog
|
/// Given a ViewModel, show its corresponding View as a Dialog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The return type</typeparam>
|
/// <typeparam name="T">The return type</typeparam>
|
||||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||||
/// <returns>A task containing the return value of type <typeparamref name="T"/></returns>
|
/// <returns>A task containing the return value of type <typeparamref name="T" /></returns>
|
||||||
Task<T> ShowDialog<T>(object viewModel);
|
Task<T> ShowDialogAsync<T>(object viewModel);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an open file dialog, use the fluent API to configure it
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||||
|
OpenFileDialogBuilder CreateOpenFileDialog();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a save file dialog, use the fluent API to configure it
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||||
|
SaveFileDialogBuilder CreateSaveFileDialog();
|
||||||
|
|
||||||
|
ContentDialogBuilder CreateContentDialog();
|
||||||
|
|
||||||
|
Window GetCurrentWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||||
|
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Shared.Services
|
||||||
|
{
|
||||||
|
public class NotificationService : INotificationService
|
||||||
|
{
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
|
||||||
|
public NotificationService(IWindowService windowService)
|
||||||
|
{
|
||||||
|
_windowService = windowService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotificationBuilder CreateNotification()
|
||||||
|
{
|
||||||
|
return new NotificationBuilder(_windowService.GetCurrentWindow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Avalonia.Shared.Exceptions;
|
using Artemis.UI.Avalonia.Shared.Exceptions;
|
||||||
|
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||||
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@ -19,9 +20,6 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
|||||||
_kernel = kernel;
|
_kernel = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Implementation of IWindowService
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public T ShowWindow<T>()
|
public T ShowWindow<T>()
|
||||||
{
|
{
|
||||||
T viewModel = _kernel.Get<T>()!;
|
T viewModel = _kernel.Get<T>()!;
|
||||||
@ -29,7 +27,6 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
|||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ShowWindow(object viewModel)
|
public void ShowWindow(object viewModel)
|
||||||
{
|
{
|
||||||
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
string name = viewModel.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
|
||||||
@ -45,8 +42,7 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
|||||||
window.Show();
|
window.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public async Task<T> ShowDialogAsync<T>(object viewModel)
|
||||||
public async Task<T> ShowDialog<T>(object viewModel)
|
|
||||||
{
|
{
|
||||||
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
|
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
|
||||||
throw new ArtemisSharedUIException($"Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
|
throw new ArtemisSharedUIException($"Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
|
||||||
@ -65,6 +61,28 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
|||||||
return await window.ShowDialog<T>(parent);
|
return await window.ShowDialog<T>(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
public ContentDialogBuilder CreateContentDialog()
|
||||||
|
{
|
||||||
|
return new ContentDialogBuilder(_kernel, GetCurrentWindow());
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenFileDialogBuilder CreateOpenFileDialog()
|
||||||
|
{
|
||||||
|
return new OpenFileDialogBuilder(GetCurrentWindow());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveFileDialogBuilder CreateSaveFileDialog()
|
||||||
|
{
|
||||||
|
return new SaveFileDialogBuilder(GetCurrentWindow());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Window GetCurrentWindow()
|
||||||
|
{
|
||||||
|
if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime classic)
|
||||||
|
throw new ArtemisSharedUIException("Can't show a dialog when application lifetime is not IClassicDesktopStyleApplicationLifetime.");
|
||||||
|
|
||||||
|
Window parent = classic.Windows.FirstOrDefault(w => w.IsActive) ?? classic.MainWindow;
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
src/Artemis.UI.Avalonia.Shared/Styles/InfoBar.axaml
Normal file
19
src/Artemis.UI.Avalonia.Shared/Styles/InfoBar.axaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<Border Padding="20">
|
||||||
|
<!-- Add Controls for Previewer Here -->
|
||||||
|
<controls:InfoBar Classes="notification-info-bar" Title="Test title" Message="Test message" IsOpen="True"/>
|
||||||
|
</Border>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
|
||||||
|
<!-- Add Styles Here -->
|
||||||
|
<Style Selector="controls|InfoBar.notification-info-bar">
|
||||||
|
<Setter Property="MaxWidth" Value="600" />
|
||||||
|
<Setter Property="Margin" Value="15"/>
|
||||||
|
<Setter Property="Background" Value="#75000000"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
@ -22,5 +22,9 @@
|
|||||||
<StyleInclude Source="/Styles/Button.axaml" />
|
<StyleInclude Source="/Styles/Button.axaml" />
|
||||||
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
||||||
<StyleInclude Source="/Styles/Sidebar.axaml" />
|
<StyleInclude Source="/Styles/Sidebar.axaml" />
|
||||||
|
|
||||||
|
<!-- Shared styles -->
|
||||||
|
<!-- TODO: Make a single file-->
|
||||||
|
<StyleInclude Source="avares://Artemis.UI.Avalonia.Shared/Styles/InfoBar.axaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Exceptions
|
||||||
|
{
|
||||||
|
public class ArtemisGraphicsContextException : Exception
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ArtemisGraphicsContextException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ArtemisGraphicsContextException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ArtemisGraphicsContextException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/Artemis.UI.Avalonia/Exceptions/ArtemisUIException.cs
Normal file
19
src/Artemis.UI.Avalonia/Exceptions/ArtemisUIException.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Exceptions
|
||||||
|
{
|
||||||
|
public class ArtemisUIException : Exception
|
||||||
|
{
|
||||||
|
public ArtemisUIException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisUIException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisUIException(string message, Exception inner) : base(message, inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Avalonia;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||||
|
{
|
||||||
|
public class DeviceInfoTabViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
public DeviceInfoTabViewModel(ArtemisDevice device)
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public async Task CopyToClipboard(string content)
|
||||||
|
{
|
||||||
|
await Application.Current.Clipboard.SetTextAsync(content);
|
||||||
|
// ((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Copied path to clipboard.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using Artemis.Core;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||||
|
{
|
||||||
|
public class DeviceLedsTabViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly DevicePropertiesViewModel _devicePropertiesViewModel;
|
||||||
|
|
||||||
|
public DeviceLedsTabViewModel(ArtemisDevice device, DevicePropertiesViewModel devicePropertiesViewModel)
|
||||||
|
{
|
||||||
|
_devicePropertiesViewModel = devicePropertiesViewModel;
|
||||||
|
|
||||||
|
Device = device;
|
||||||
|
DisplayName = "LEDS";
|
||||||
|
LedViewModels = new ObservableCollection<DeviceLedsTabLedViewModel>(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, _devicePropertiesViewModel.SelectedLeds)));
|
||||||
|
|
||||||
|
this.WhenActivated(disposables => _devicePropertiesViewModel.SelectedLeds.ToObservableChangeSet().Subscribe(_ => UpdateSelectedLeds()).DisposeWith(disposables));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisDevice Device { get; }
|
||||||
|
public ObservableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
|
||||||
|
|
||||||
|
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateSelectedLeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectedLeds()
|
||||||
|
{
|
||||||
|
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
|
||||||
|
deviceLedsTabLedViewModel.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeviceLedsTabLedViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
|
||||||
|
private bool _isSelected;
|
||||||
|
|
||||||
|
public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, ObservableCollection<ArtemisLed> selectedLeds)
|
||||||
|
{
|
||||||
|
_selectedLeds = selectedLeds;
|
||||||
|
ArtemisLed = artemisLed;
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisLed ArtemisLed { get; }
|
||||||
|
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!this.RaiseAndSetIfChanged(ref _isSelected, value))
|
||||||
|
return;
|
||||||
|
Apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
IsSelected = _selectedLeds.Contains(ArtemisLed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply()
|
||||||
|
{
|
||||||
|
if (IsSelected && !_selectedLeds.Contains(ArtemisLed))
|
||||||
|
_selectedLeds.Add(ArtemisLed);
|
||||||
|
else if (!IsSelected && _selectedLeds.Contains(ArtemisLed))
|
||||||
|
_selectedLeds.Remove(ArtemisLed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,270 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||||
|
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using ReactiveUI;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||||
|
{
|
||||||
|
public class DevicePropertiesTabViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly List<DeviceCategory> _categories;
|
||||||
|
private readonly ICoreService _coreService;
|
||||||
|
private readonly float _initialBlueScale;
|
||||||
|
private readonly float _initialGreenScale;
|
||||||
|
private readonly float _initialRedScale;
|
||||||
|
private readonly IRgbService _rgbService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
private float _blueScale;
|
||||||
|
private SKColor _currentColor;
|
||||||
|
private bool _displayOnDevices;
|
||||||
|
private float _greenScale;
|
||||||
|
private float _redScale;
|
||||||
|
private int _rotation;
|
||||||
|
private float _scale;
|
||||||
|
private int _x;
|
||||||
|
private int _y;
|
||||||
|
|
||||||
|
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService)
|
||||||
|
{
|
||||||
|
_coreService = coreService;
|
||||||
|
_rgbService = rgbService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
_categories = new List<DeviceCategory>(device.Categories);
|
||||||
|
|
||||||
|
Device = device;
|
||||||
|
DisplayName = "PROPERTIES";
|
||||||
|
|
||||||
|
X = (int) Device.X;
|
||||||
|
Y = (int) Device.Y;
|
||||||
|
Scale = Device.Scale;
|
||||||
|
Rotation = (int) Device.Rotation;
|
||||||
|
RedScale = Device.RedScale * 100f;
|
||||||
|
GreenScale = Device.GreenScale * 100f;
|
||||||
|
BlueScale = Device.BlueScale * 100f;
|
||||||
|
CurrentColor = SKColors.White;
|
||||||
|
|
||||||
|
// We need to store the initial values to be able to restore them when the user clicks "Cancel"
|
||||||
|
_initialRedScale = Device.RedScale;
|
||||||
|
_initialGreenScale = Device.GreenScale;
|
||||||
|
_initialBlueScale = Device.BlueScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisDevice Device { get; }
|
||||||
|
|
||||||
|
public int X
|
||||||
|
{
|
||||||
|
get => _x;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _x, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Y
|
||||||
|
{
|
||||||
|
get => _y;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _y, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Scale
|
||||||
|
{
|
||||||
|
get => _scale;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _scale, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Rotation
|
||||||
|
{
|
||||||
|
get => _rotation;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _rotation, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float RedScale
|
||||||
|
{
|
||||||
|
get => _redScale;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _redScale, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GreenScale
|
||||||
|
{
|
||||||
|
get => _greenScale;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _greenScale, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float BlueScale
|
||||||
|
{
|
||||||
|
get => _blueScale;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _blueScale, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SKColor CurrentColor
|
||||||
|
{
|
||||||
|
get => _currentColor;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _currentColor, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DisplayOnDevices
|
||||||
|
{
|
||||||
|
get => _displayOnDevices;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _displayOnDevices, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This solution won't scale well but I don't expect there to be many more categories.
|
||||||
|
// 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);
|
||||||
|
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 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 SelectPhysicalLayout()
|
||||||
|
{
|
||||||
|
// await _windowService.CreateContentDialog()
|
||||||
|
// .WithTitle("Select layout")
|
||||||
|
// .WithViewModel<DeviceLayoutDialogViewModel>(("device", Device))
|
||||||
|
// .ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Apply()
|
||||||
|
{
|
||||||
|
// TODO: Validation
|
||||||
|
|
||||||
|
_coreService.ProfileRenderingDisabled = true;
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
Device.X = X;
|
||||||
|
Device.Y = Y;
|
||||||
|
Device.Scale = Scale;
|
||||||
|
Device.Rotation = Rotation;
|
||||||
|
Device.RedScale = RedScale / 100f;
|
||||||
|
Device.GreenScale = GreenScale / 100f;
|
||||||
|
Device.BlueScale = BlueScale / 100f;
|
||||||
|
Device.Categories.Clear();
|
||||||
|
foreach (DeviceCategory deviceCategory in _categories)
|
||||||
|
Device.Categories.Add(deviceCategory);
|
||||||
|
|
||||||
|
_rgbService.SaveDevice(Device);
|
||||||
|
|
||||||
|
_coreService.ProfileRenderingDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk);
|
||||||
|
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor);
|
||||||
|
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case);
|
||||||
|
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
|
||||||
|
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
|
||||||
|
|
||||||
|
Device.RedScale = _initialRedScale;
|
||||||
|
Device.GreenScale = _initialGreenScale;
|
||||||
|
Device.BlueScale = _initialBlueScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnActivate()
|
||||||
|
{
|
||||||
|
_coreService.FrameRendering += OnFrameRendering;
|
||||||
|
Device.PropertyChanged += DeviceOnPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnDeactivate()
|
||||||
|
{
|
||||||
|
_coreService.FrameRendering -= OnFrameRendering;
|
||||||
|
Device.PropertyChanged -= DeviceOnPropertyChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
_categories.Remove(category);
|
||||||
|
|
||||||
|
this.RaisePropertyChanged($"Has{category}Category");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout)) Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_displayOnDevices)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using SKPaint overlayPaint = new()
|
||||||
|
{
|
||||||
|
Color = CurrentColor
|
||||||
|
};
|
||||||
|
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using Artemis.Core;
|
using System.Collections.ObjectModel;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||||
{
|
{
|
||||||
@ -10,5 +11,6 @@ namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ArtemisDevice Device { get; }
|
public ArtemisDevice Device { get; }
|
||||||
|
public ObservableCollection<ArtemisLed> SelectedLeds { get; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Avalonia.Exceptions;
|
||||||
|
using ReactiveUI;
|
||||||
|
using RGB.NET.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Avalonia.Screens.Device.ViewModels
|
||||||
|
{
|
||||||
|
public class InputMappingsTabViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IRgbService _rgbService;
|
||||||
|
private readonly IInputService _inputService;
|
||||||
|
private ArtemisLed _selectedLed;
|
||||||
|
|
||||||
|
public InputMappingsTabViewModel(ArtemisDevice device, IRgbService rgbService, IInputService inputService)
|
||||||
|
{
|
||||||
|
if (device.DeviceType != RGBDeviceType.Keyboard)
|
||||||
|
throw new ArtemisUIException("The input mappings tab only supports keyboards");
|
||||||
|
_rgbService = rgbService;
|
||||||
|
_inputService = inputService;
|
||||||
|
|
||||||
|
Device = device;
|
||||||
|
DisplayName = "INPUT MAPPINGS";
|
||||||
|
InputMappings = new ObservableCollection<(ArtemisLed, ArtemisLed)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtemisDevice Device { get; }
|
||||||
|
|
||||||
|
public ArtemisLed SelectedLed
|
||||||
|
{
|
||||||
|
get => _selectedLed;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _selectedLed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<(ArtemisLed, ArtemisLed)> InputMappings { get; }
|
||||||
|
|
||||||
|
public void DeleteMapping(Tuple<ArtemisLed, ArtemisLed> inputMapping)
|
||||||
|
{
|
||||||
|
Device.InputMappings.Remove(inputMapping.Item1);
|
||||||
|
UpdateInputMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputServiceOnKeyboardKeyUp(object sender, ArtemisKeyboardKeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedLed == null || e.Led == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Locate the original LED the same way the InputService did it, but supply false to Device.GetLed
|
||||||
|
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
|
||||||
|
if (!foundLedId)
|
||||||
|
return;
|
||||||
|
ArtemisLed artemisLed = Device.GetLed(ledId, false);
|
||||||
|
if (artemisLed == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Apply the new LED mapping
|
||||||
|
Device.InputMappings[SelectedLed] = artemisLed;
|
||||||
|
_rgbService.SaveDevice(Device);
|
||||||
|
// ((DeviceDialogViewModel) Parent).SelectedLeds.Clear();
|
||||||
|
|
||||||
|
UpdateInputMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInputMappings()
|
||||||
|
{
|
||||||
|
if (InputMappings.Any())
|
||||||
|
InputMappings.Clear();
|
||||||
|
|
||||||
|
// InputMappings.AddRange(Device.InputMappings.Select(m => new Tuple<ArtemisLed, ArtemisLed>(m.Key, m.Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// SelectedLed = ((DeviceDialogViewModel) Parent).SelectedLeds.FirstOrDefault();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// #region Overrides of Screen
|
||||||
|
//
|
||||||
|
// /// <inheritdoc />
|
||||||
|
// protected override void OnActivate()
|
||||||
|
// {
|
||||||
|
// UpdateInputMappings();
|
||||||
|
// _inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
|
||||||
|
// ((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
|
||||||
|
//
|
||||||
|
// base.OnActivate();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// <inheritdoc />
|
||||||
|
// protected override void OnDeactivate()
|
||||||
|
// {
|
||||||
|
// InputMappings.Clear();
|
||||||
|
// _inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
|
||||||
|
// ((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
|
||||||
|
// base.OnDeactivate();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -105,7 +105,7 @@ namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
|
|||||||
|
|
||||||
private async Task ExecuteViewProperties(ArtemisDevice device)
|
private async Task ExecuteViewProperties(ArtemisDevice device)
|
||||||
{
|
{
|
||||||
await _windowService.ShowDialog<bool>(_deviceVmFactory.DevicePropertiesViewModel(device));
|
await _windowService.ShowDialogAsync<bool>(_deviceVmFactory.DevicePropertiesViewModel(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Fits(float x, float y)
|
private bool Fits(float x, float y)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user