1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

UI - Added Avalonia projects

This commit is contained in:
Robert 2021-10-09 23:51:42 +02:00
parent c45f1d9130
commit 1784d2b8b5
80 changed files with 4901 additions and 31 deletions

13
src/.idea/.idea.Artemis/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/.idea.Artemis.iml
/projectSettingsUpdater.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
src/.idea/.idea.Artemis/.idea/.name generated Normal file
View File

@ -0,0 +1 @@
Artemis

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="Artemis.UI.Avalonia/App.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Main/Sidebar/Views/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Main/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarCategoryView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
</map>
</option>
</component>
</project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
src/.idea/.idea.Artemis/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -16,7 +16,7 @@ namespace Artemis.Core
/// <returns>The RGB.NET color</returns>
public static Color ToRgbColor(this SKColor color)
{
return new(color.Alpha, color.Red, color.Green, color.Blue);
return new Color(color.Alpha, color.Red, color.Green, color.Blue);
}
/// <summary>
@ -49,7 +49,7 @@ namespace Artemis.Core
/// <returns>The sum of the two colors</returns>
public static SKColor Sum(this SKColor a, SKColor b)
{
return new(
return new SKColor(
ClampToByte(a.Red + b.Red),
ClampToByte(a.Green + b.Green),
ClampToByte(a.Blue + b.Blue),

View File

@ -15,7 +15,7 @@ namespace Artemis.Core
protected DataModelConditionPart()
{
Children = new(_children);
Children = new ReadOnlyCollection<DataModelConditionPart>(_children);
}
/// <summary>

View File

@ -18,7 +18,7 @@ namespace Artemis.Core
{
DataBinding = dataBinding;
Entity = entity;
Conditions = new(_conditions);
Conditions = new ReadOnlyCollection<DataBindingCondition<TLayerProperty, TProperty>>(_conditions);
Load();
}

View File

@ -17,7 +17,7 @@ namespace Artemis.Core
{
DataBinding = dataBinding;
Entity = entity;
Modifiers = new(_modifiers);
Modifiers = new ReadOnlyCollection<DataBindingModifier<TLayerProperty, TProperty>>(_modifiers);
Load();
}

View File

@ -44,7 +44,7 @@ namespace Artemis.Core
_baseValue = default!;
_keyframes = new List<LayerPropertyKeyframe<T>>();
Keyframes = new(_keyframes);
Keyframes = new ReadOnlyCollection<LayerPropertyKeyframe<T>>(_keyframes);
}
/// <summary>

View File

@ -66,7 +66,7 @@ namespace Artemis.Core
/// <inheritdoc />
public KeyframeEntity GetKeyframeEntity()
{
return new()
return new KeyframeEntity
{
Value = CoreJson.SerializeObject(Value),
Position = Position,

View File

@ -38,8 +38,8 @@ namespace Artemis.Core
_layerProperties = new List<ILayerProperty>();
_layerPropertyGroups = new List<LayerPropertyGroup>();
LayerProperties = new(_layerProperties);
LayerPropertyGroups = new(_layerPropertyGroups);
LayerProperties = new ReadOnlyCollection<ILayerProperty>(_layerProperties);
LayerPropertyGroups = new ReadOnlyCollection<LayerPropertyGroup>(_layerPropertyGroups);
}
/// <summary>

View File

@ -24,14 +24,14 @@ namespace Artemis.Core
{
_name = name;
Entity = new ProfileCategoryEntity();
ProfileConfigurations = new(_profileConfigurations);
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
}
internal ProfileCategory(ProfileCategoryEntity entity)
{
_name = null!;
Entity = entity;
ProfileConfigurations = new(_profileConfigurations);
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
Load();
}

View File

@ -24,7 +24,7 @@ namespace Artemis.Core
{
_profile = profile;
ChildrenList = new List<ProfileElement>();
Children = new(ChildrenList);
Children = new ReadOnlyCollection<ProfileElement>(ChildrenList);
}
/// <summary>

View File

@ -24,7 +24,7 @@ namespace Artemis.Core
Timeline = new Timeline();
ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>();
LayerEffects = new(LayerEffectsList);
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(LayerEffectsList);
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;

View File

@ -24,7 +24,7 @@ namespace Artemis.Core
MainSegmentLength = TimeSpan.FromSeconds(5);
_extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
Save();
}
@ -33,7 +33,7 @@ namespace Artemis.Core
{
Entity = entity;
_extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
Load();
}
@ -47,7 +47,7 @@ namespace Artemis.Core
EndSegmentLength = Parent.EndSegmentLength;
_extraTimelines = new List<Timeline>();
ExtraTimelines = new(_extraTimelines);
ExtraTimelines = new ReadOnlyCollection<Timeline>(_extraTimelines);
}
/// <inheritdoc />

View File

@ -17,7 +17,7 @@ namespace Artemis.Core.LayerBrushes
protected LayerBrushProvider()
{
_layerBrushDescriptors = new List<LayerBrushDescriptor>();
LayerBrushDescriptors = new(_layerBrushDescriptors);
LayerBrushDescriptors = new ReadOnlyCollection<LayerBrushDescriptor>(_layerBrushDescriptors);
Disabled += OnDisabled;
}

View File

@ -18,7 +18,7 @@ namespace Artemis.Core.LayerEffects
protected LayerEffectProvider()
{
_layerEffectDescriptors = new List<LayerEffectDescriptor>();
LayerEffectDescriptors = new(_layerEffectDescriptors);
LayerEffectDescriptors = new ReadOnlyCollection<LayerEffectDescriptor>(_layerEffectDescriptors);
Disabled += OnDisabled;
}

View File

@ -27,8 +27,8 @@ namespace Artemis.Core.Modules
Module = null!;
DataModelDescription = null!;
ActivePaths = new(_activePaths);
DynamicChildren = new(_dynamicChildren);
ActivePaths = new ReadOnlyCollection<DataModelPath>(_activePaths);
DynamicChildren = new ReadOnlyDictionary<string, DynamicChild>(_dynamicChildren);
}
/// <summary>

View File

@ -117,7 +117,7 @@ namespace Artemis.Core.Modules
protected Module()
{
DefaultProfilePaths = new ReadOnlyCollection<(DefaultCategoryName, string)>(_defaultProfilePaths);
HiddenProperties = new(HiddenPropertiesList);
HiddenProperties = new ReadOnlyCollection<PropertyInfo>(HiddenPropertiesList);
}
/// <summary>
@ -237,7 +237,7 @@ namespace Artemis.Core.Modules
/// <returns></returns>
public virtual DataModelPropertyAttribute GetDataModelDescription()
{
return new() {Name = Info.Name, Description = Info.Description};
return new DataModelPropertyAttribute {Name = Info.Name, Description = Info.Description};
}
/// <summary>

View File

@ -31,8 +31,8 @@ namespace Artemis.Core
_features = new List<PluginFeatureInfo>();
_profilers = new List<Profiler>();
Features = new(_features);
Profilers = new(_profilers);
Features = new ReadOnlyCollection<PluginFeatureInfo>(_features);
Profilers = new ReadOnlyCollection<Profiler>(_profilers);
}
/// <summary>

View File

@ -70,7 +70,7 @@ namespace Artemis.Core
/// </summary>
public TimeSpan GetLast()
{
return new(_last);
return new TimeSpan(_last);
}
/// <summary>

View File

@ -46,7 +46,7 @@ namespace Artemis.Core.ScriptingProviders
{
protected ScriptingProvider()
{
Scripts = new(InternalScripts);
Scripts = new ReadOnlyCollection<Script>(InternalScripts);
}
/// <summary>

View File

@ -66,7 +66,7 @@ namespace Artemis.Core.Services
float bestDarkMutedScore = 0;
//ugly but at least we only loop through the enumerable once ¯\_(ツ)_/¯
foreach (var color in colors)
foreach (SKColor color in colors)
{
static void SetIfBetterScore(ref float bestScore, ref SKColor bestColor, SKColor newColor, ColorType type, bool ignoreLimits)
{
@ -86,7 +86,7 @@ namespace Artemis.Core.Services
SetIfBetterScore(ref bestDarkMutedScore, ref bestDarkMutedColor, color, ColorType.DarkMuted, ignoreLimits);
}
return new()
return new ColorSwatch
{
Vibrant = bestVibrantColor,
LightVibrant = bestLightVibrantColor,

View File

@ -435,7 +435,7 @@ namespace Artemis.Core.Services
}
catch (Exception e)
{
_logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature", plugin);
_logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature from plugin {plugin}", plugin);
featureInfo.LoadException = e;
}
}

View File

@ -24,7 +24,7 @@ namespace Artemis.Core.Services
_profileService = profileService;
InternalGlobalScripts = new List<GlobalScript>();
GlobalScripts = new(InternalGlobalScripts);
GlobalScripts = new ReadOnlyCollection<GlobalScript>(InternalGlobalScripts);
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.7" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.7.2" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.7.1" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.7.1" />
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="0.10.7.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Material.Icons.Avalonia">
<HintPath>..\..\..\..\Users\Robert\.nuget\packages\material.icons.avalonia\1.0.2\lib\netstandard2.0\Material.Icons.Avalonia.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Threading;
namespace Artemis.UI.Avalonia.Shared.Controls
{
/// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
/// </summary>
public class DeviceVisualizer : Control
{
#region Properties
/// <summary>
/// Gets or sets the <see cref="ArtemisDevice" /> to display
/// </summary>
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
AvaloniaProperty.Register<ProfileConfigurationIcon, ArtemisDevice?>(nameof(Device));
/// <summary>
/// Gets or sets the <see cref="ArtemisDevice" /> to display
/// </summary>
public ArtemisDevice? Device
{
get => GetValue(DeviceProperty);
set => SetValue(DeviceProperty, value);
}
/// <summary>
/// Gets or sets boolean indicating whether or not to show per-LED colors
/// </summary>
public static readonly StyledProperty<bool> ShowColorsProperty =
AvaloniaProperty.Register<ProfileConfigurationIcon, bool>(nameof(ShowColors));
/// <summary>
/// Gets or sets a boolean indicating whether or not to show per-LED colors
/// </summary>
public bool ShowColors
{
get => GetValue(ShowColorsProperty);
set => SetValue(ShowColorsProperty, value);
}
/// <summary>
/// Gets or sets a list of LEDs to highlight
/// </summary>
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
AvaloniaProperty.Register<ProfileConfigurationIcon, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
/// <summary>
/// Gets or sets a list of LEDs to highlight
/// </summary>
public ObservableCollection<ArtemisLed>? HighlightedLeds
{
get => GetValue(HighlightedLedsProperty);
set => SetValue(HighlightedLedsProperty, value);
}
#endregion
private readonly DispatcherTimer _timer;
/// <inheritdoc />
public DeviceVisualizer()
{
// Run an update timer at 25 fps
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(40)};
}
/// <inheritdoc />
public override void Render(DrawingContext context)
{
base.Render(context);
}
private void Update()
{
throw new NotImplementedException();
}
#region Lifetime management
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_timer.Start();
_timer.Tick += TimerOnTick;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_timer.Stop();
_timer.Tick -= TimerOnTick;
base.OnDetachedFromLogicalTree(e);
}
#endregion
#region Event handlers
private void TimerOnTick(object? sender, EventArgs e)
{
if (ShowColors && IsVisible && Opacity > 0)
Update();
}
#endregion
}
}

View File

@ -0,0 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Shared.Controls.ProfileConfigurationIcon">
</UserControl>

View File

@ -0,0 +1,100 @@
using System;
using System.ComponentModel;
using Artemis.Core;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Svg.Skia;
using Material.Icons;
using Material.Icons.Avalonia;
namespace Artemis.UI.Avalonia.Shared.Controls
{
public class ProfileConfigurationIcon : UserControl
{
#region Properties
/// <summary>
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
/// </summary>
public static readonly StyledProperty<Core.ProfileConfigurationIcon?> ConfigurationIconProperty =
AvaloniaProperty.Register<ProfileConfigurationIcon, Core.ProfileConfigurationIcon?>(nameof(ConfigurationIcon));
/// <summary>
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
/// </summary>
public Core.ProfileConfigurationIcon? ConfigurationIcon
{
get => GetValue(ConfigurationIconProperty);
set => SetValue(ConfigurationIconProperty, value);
}
#endregion
public ProfileConfigurationIcon()
{
InitializeComponent();
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
PropertyChanged += OnPropertyChanged;
}
private void Update()
{
if (ConfigurationIcon == null)
return;
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.SvgImage && ConfigurationIcon.FileIcon != null)
{
SvgSource source = new();
source.Load(ConfigurationIcon.FileIcon);
Content = new SvgImage {Source = source};
}
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon && ConfigurationIcon.MaterialIcon != null)
{
Content = Enum.TryParse(typeof(MaterialIconKind), ConfigurationIcon.MaterialIcon, true, out object? parsedIcon)
? new MaterialIcon {Kind = (MaterialIconKind) parsedIcon!}
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
else if (ConfigurationIcon.IconType == ProfileConfigurationIconType.BitmapImage && ConfigurationIcon.FileIcon != null)
Content = new Image {Source = new Bitmap(ConfigurationIcon.FileIcon)};
else
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
#region Event handlers
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
{
if (ConfigurationIcon != null)
ConfigurationIcon.PropertyChanged -= IconOnPropertyChanged;
}
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == ConfigurationIconProperty)
{
if (e.OldValue is Core.ProfileConfigurationIcon oldIcon)
oldIcon.PropertyChanged -= IconOnPropertyChanged;
if (e.NewValue is Core.ProfileConfigurationIcon newIcon)
newIcon.PropertyChanged += IconOnPropertyChanged;
Update();
}
}
private void IconOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Update();
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
To use the Avalonia CI feed to get unstable packages, move this file to the root of your solution.
-->
<configuration>
<packageSources>
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" />
</packageSources>
</configuration>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Artemis.UI.Avalonia"
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:uip="using:FluentAvalonia.UI.Controls.Primitives"
x:Class="Artemis.UI.Avalonia.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<!-- Third party styles -->
<sty:FluentAvaloniaTheme CustomAccentColor="#4db6ac"/>
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml"></StyleInclude>
<!-- Global styles -->
<StyleInclude Source="/Styles/Button.axaml"></StyleInclude>
<StyleInclude Source="/Styles/Sidebar.axaml"></StyleInclude>
</Application.Styles>
</Application>

View File

@ -0,0 +1,43 @@
using Artemis.Core.Ninject;
using Artemis.UI.Avalonia.Ninject;
using Artemis.UI.Avalonia.Screens.Main.Views;
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Ninject;
using Splat.Ninject;
namespace Artemis.UI.Avalonia
{
public class App : Application
{
private StandardKernel _kernel = null!;
public override void Initialize()
{
InitializeNinject();
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow
{
DataContext = _kernel.Get<RootViewModel>()
};
base.OnFrameworkInitializationCompleted();
}
private void InitializeNinject()
{
_kernel = new StandardKernel();
_kernel.Load<CoreModule>();
_kernel.Load<UIModule>();
_kernel.UseNinjectDependencyResolver();
}
}
}

View File

@ -0,0 +1,76 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Controls\" />
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
<Folder Include="Screens\Home\Views" />
<Folder Include="Screens\Settings\Views" />
<Folder Include="Screens\SurfaceEditor\Views" />
<Folder Include="Screens\Workshop\Views" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.7" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.7" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.7" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.7" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.7.2" />
<PackageReference Include="FluentAvaloniaUI" Version="1.1.3" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="Splat.Ninject" Version="13.1.22" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="MainWindow.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Root\Views\SidebarCategoryView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Root\Views\SidebarProfileConfigurationView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Root\Views\SidebarScreenView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Root\Views\SidebarView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Sidebar\Views\SidebarView.axaml.cs">
<DependentUpon>SidebarView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Root\Views\RootView.axaml.cs">
<DependentUpon>RootView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Views\MainWindow.axaml" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\Images\Logo\bow-black.ico" />
<Content Include="Assets\Images\Logo\bow-white.ico" />
<Content Include="Assets\Images\Logo\bow.ico" />
</ItemGroup>
<ItemGroup>
<Reference Include="RGB.NET.Core">
<HintPath>..\..\..\RGB.NET\bin\net5.0\RGB.NET.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\Images\Logo\bow-black.ico" />
<Resource Include="Assets\Images\Logo\bow-white.ico" />
<Resource Include="Assets\Images\Logo\bow-white.svg" />
<Resource Include="Assets\Images\Logo\bow.ico" />
<Resource Include="Assets\Images\Logo\bow.svg" />
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>.cls-1,.cls-2{fill:#fff;}.cls-2{fill-rule:evenodd;}</style>
</defs>
<g id="Layer_10" data-name="Layer 10">
<path class="cls-1" d="M52.32,17.68l-2.83,5a1.16,1.16,0,0,1-2,0l-.57-1-.54.32c-.22-.44-.46-.88-.71-1.31s-.66-1.1-1-1.63l.53-.3-.63-1.06a1.16,1.16,0,0,1,1-1.74l5.75,0A1.15,1.15,0,0,1,52.32,17.68Z"/>
<path class="cls-1" d="M24.71,25.63l-4.84,2.58-.53.28a38.15,38.15,0,0,1-5.54-2.18c-4.51-1.49-1.48-7,1.93-4.31.34.22.69.44,1,.64.59.35,1.21.67,1.82,1A25.88,25.88,0,0,0,24.71,25.63Z"/>
<path class="cls-1" d="M30.83,36.22c-.15.43-.28.86-.41,1.29a25.74,25.74,0,0,0-.81,4.09,26.72,26.72,0,0,0-.17,3.1c0,.37,0,.75,0,1.12A2.45,2.45,0,0,1,25,47.72c-.56-1-.22-2-.18-3.08s.21-2,.39-3c.12-.74.27-1.47.43-2.2l.53-.33,3.63-2.26Z"/>
<path class="cls-1" d="M35,28.71l-.91.57L31.3,31,24,35.59l-4.45,2.78-2.22,1.37a2.6,2.6,0,0,1-1.26.34,2.45,2.45,0,0,1-.8-4.72l.58-.31,1.3-.69,4.67-2.5,7.6-4.05,2.94-1.57.94-.5a17.91,17.91,0,0,1,1,1.55C34.57,27.75,34.82,28.23,35,28.71Z"/>
<path class="cls-2" d="M39.29,53.89a2.56,2.56,0,0,1-1.09.74l-.34.08a.13.13,0,0,1-.09,0,1.84,1.84,0,0,1-.33,0,2.41,2.41,0,0,1-1.84-4,22.32,22.32,0,0,0,5-18.09c0-.24-.08-.48-.13-.72s-.1-.48-.16-.73-.11-.48-.18-.72-.12-.45-.2-.68a20.49,20.49,0,0,0-.7-1.94c-.06-.18-.14-.34-.21-.51a21.83,21.83,0,0,0-1.09-2.16c-.14-.22-.27-.45-.4-.66-.25-.4-.51-.78-.77-1.16s-.63-.85-1-1.26l-.48-.56c-.35-.4-.72-.78-1.09-1.14a6.51,6.51,0,0,0-.54-.51l-.45-.4a22.08,22.08,0,0,0-3-2.2c-.17-.11-.36-.21-.54-.31s-.42-.24-.63-.35l-.46-.23a19.7,19.7,0,0,0-2.31-1l-.44-.15-.1,0c-.53-.18-1.07-.34-1.63-.48l-.25-.06a19.61,19.61,0,0,0-2-.39c-.35-.06-.7-.1-1-.13s-.8-.07-1.2-.08-.65,0-1,0h0a22.18,22.18,0,0,0-4,.36,3.28,3.28,0,0,1-.43,0,2.42,2.42,0,0,1-.42-4.8A26,26,0,0,1,18,9.26h.62c.43,0,.86,0,1.28,0l1,.07,1.07.11c.52.07,1,.14,1.53.24.23,0,.46.08.7.13l.14,0c.35.08.69.15,1,.25a20.61,20.61,0,0,1,2.16.65c.48.16.94.33,1.4.52h0c.33.14.67.28,1,.44s.58.27.86.42l.27.13c.28.14.56.29.82.45s.64.36,1,.55c.49.31,1,.62,1.45,1l.15.11c.31.22.62.46.93.7l.11.08c.36.28.71.58,1.06.89l0,0c.33.28.64.57,1,.88s.64.62.94.95c.1.1.19.21.29.32.26.29.52.58.77.88,0,0,0,.05.06.09.28.34.55.68.81,1s.55.75.82,1.15l.28.44c.21.33.42.67.62,1A27.14,27.14,0,0,1,39.29,53.89Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512">
<defs>
<style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:url(#linear-gradient-2);}.cls-3{fill:url(#linear-gradient-3);}.cls-4{fill:url(#linear-gradient-4);}.cls-5{fill:url(#linear-gradient-5);}.cls-6{fill:url(#linear-gradient-6);}.cls-7{fill:url(#linear-gradient-7);}.cls-8{fill-rule:evenodd;fill:url(#linear-gradient-8);}</style>
<linearGradient id="linear-gradient" x1="151.25" y1="94.74" x2="810.05" y2="369.54" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#f19d25"/>
<stop offset="0.12" stop-color="#f63d3d"/>
<stop offset="0.28" stop-color="#c93cec"/>
<stop offset="0.44" stop-color="#2667f4"/>
<stop offset="0.56" stop-color="#1cb6e7"/>
<stop offset="0.7" stop-color="#2df4b5"/>
<stop offset="0.87" stop-color="#70ea37"/>
<stop offset="1" stop-color="#cfe726"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="132.17" y1="204.4" x2="234.7" y2="230.88" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ef3d3d"/>
<stop offset="1" stop-color="#b72222"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="217.07" y1="354.42" x2="253.03" y2="258.52" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2b75f6"/>
<stop offset="1" stop-color="#1452aa"/>
</linearGradient>
<linearGradient id="linear-gradient-4" x1="105.18" y1="320.77" x2="311.18" y2="203.81" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#22f15e"/>
<stop offset="1" stop-color="#29af4d"/>
</linearGradient>
<linearGradient id="linear-gradient-5" x1="112.68" y1="187.21" x2="771.48" y2="462.01" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-6" x1="88.75" y1="244.59" x2="747.54" y2="519.39" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-7" x1="102.97" y1="210.49" x2="761.77" y2="485.29" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-8" x1="141.26" y1="116.54" x2="347.22" y2="356.89" gradientTransform="translate(11.74 6.94)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#009385"/>
<stop offset="1" stop-color="#045e53"/>
</linearGradient>
</defs>
<g id="Teal_Bow" data-name="Teal Bow">
<g id="TealRainbow">
<path class="cls-1" d="M375.54,156.66,340,157a7.14,7.14,0,0,0-6.1,10.76l3.85,6.55-16.8,9.84c2.18,3.26,4.26,6.61,6.24,10,1.54,2.67,3,5.37,4.39,8.09l16.82-9.85,3.53,6a7.14,7.14,0,0,0,12.37-.1l17.53-30.94A7.15,7.15,0,0,0,375.54,156.66Z"/>
<path class="cls-5" d="M235.3,221.27l-34.52,18.41c-6.08-1-12.11-2.36-18.05-4a230.91,230.91,0,0,1-39.4-14.9c-27.91-9.26-9.21-43.22,11.93-26.76,2.11,1.41,4.28,2.76,6.48,4,3.65,2.15,7.42,4.09,11.24,5.91a156.68,156.68,0,0,0,42.24,13.31A130.42,130.42,0,0,0,239.63,219Z"/>
<path class="cls-6" d="M257,258.59l4.09-2.55a135.41,135.41,0,0,0-14.8,34.07,160.44,160.44,0,0,0-5,25.32,158,158,0,0,0-1.06,19.21c0,2.32.11,4.63.26,6.94,4.06,16.9-18.44,27-27.64,11.76-3.51-6-1.35-12.66-1.14-19.08.58-6.21,1.35-12.39,2.39-18.55a200.68,200.68,0,0,1,9.63-36.38l10.53-6.56Z"/>
<path class="cls-7" d="M284,219.06c-1.92-3.33-4-6.55-6.21-9.68h0l-9.46,5.54a17.12,17.12,0,0,0-2.69,1.52l-7.34,3.92L240,230.1q-23.52,12.53-47,25.08L164,270.62l-8,4.27c-1.21.65-2.43,1.29-3.64,1.95C132.81,285.25,146.31,314,165.2,304l13.72-8.55,27.53-17.15,45.22-28.18,17.55-10.94,7-4.39a15.73,15.73,0,0,0,3.58-2.29h0l8.56-5Q286.34,223.21,284,219.06Z"/>
<path class="cls-8" d="M169.2,115.38A169,169,0,0,0,143.28,118a15,15,0,0,0,5.26,29.44A137.81,137.81,0,0,1,278.31,372.15a14.95,14.95,0,0,0,22.82,19.31A167.69,167.69,0,0,0,169.2,115.38Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -0,0 +1,31 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using RGBColor = RGB.NET.Core.Color;
namespace Artemis.UI.Avalonia.Converters
{
/// <summary>
/// Converts <see cref="T:RGB.NET.Core.Color" /> into <see cref="T:Avalonia.Media.Color" />.
/// </summary>
public class ColorToSolidColorBrushConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new SolidColorBrush(!(value is RGBColor color)
? new Color(0, 0, 0, 0)
: new Color((byte) color.A, (byte) color.R, (byte) color.G, (byte) color.B));
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(value is SolidColorBrush brush)
? RGBColor.Transparent
: new RGBColor(brush.Color.A, brush.Color.R, brush.Color.G, brush.Color.B);
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Converters
{
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
private static string Description(Enum value)
{
object[] attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute)?.Description;
// If no description is found, the least we can do is replace underscores with spaces
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
private static IEnumerable<Tuple<object, object>> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select(e => new Tuple<object, object>(e, Description(e))).ToList();
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using RGB.NET.Core;
namespace Artemis.UI.Avalonia.Converters
{
public class LedIdToStringConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.ToString();
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (Enum.TryParse(typeof(LedId), value?.ToString(), true, out object parsedLedId))
return parsedLedId;
return LedId.Unknown1;
}
#endregion
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Artemis.UI.Avalonia.Converters
{
public class NormalizedPercentageConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double number)
return number * 100.0;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double number)
return number / 100.0;
return value;
}
#endregion
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Globalization;
using System.IO;
using Avalonia.Data.Converters;
namespace Artemis.UI.Avalonia.Converters
{
public class UriToFileNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Uri uri && uri.IsFile)
return Path.GetFileName(uri.LocalPath);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Data.Converters;
namespace Artemis.UI.Avalonia.Converters
{
public class ValuesAdditionConverter : IMultiValueConverter
{
/// <inheritdoc />
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return values.Where(v => v is double).Cast<double>().Sum();
}
}
}

View File

@ -0,0 +1,14 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Main.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Artemis.UI.Avalonia"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"
Content="{Binding}">
</Window>

View File

@ -0,0 +1,23 @@
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Main.Views
{
public partial class MainWindow : ReactiveWindow<RootViewModel>
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,15 @@
using Artemis.Core;
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
namespace Artemis.UI.Avalonia.Ninject.Factories
{
public interface IVmFactory
{
}
public interface ISidebarVmFactory : IVmFactory
{
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
}
}

View File

@ -0,0 +1,43 @@
using System;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
namespace Artemis.UI.Avalonia.Ninject
{
public class UIModule : NinjectModule
{
public override void Load()
{
if (Kernel == null)
throw new ArgumentNullException("Kernel shouldn't be null here.");
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<ViewModelBase>()
.BindToSelf();
});
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllClasses()
.InheritedFrom<MainScreenViewModel>()
.BindAllBaseClasses();
});
// Bind UI factories
Kernel.Bind(x =>
{
x.FromThisAssembly()
.SelectAllInterfaces()
.InheritedFrom<IVmFactory>()
.BindToFactory();
});
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using Ninject;
using ReactiveUI;
using Splat;
using Splat.Ninject;
namespace Artemis.UI.Avalonia
{
class Program
{
private static StandardKernel _kernel;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
}
}

View File

@ -0,0 +1,15 @@
namespace Artemis.UI.Avalonia.Screens.Home.ViewModels
{
public class HomeViewModel : MainScreenViewModel
{
public HomeViewModel()
{
DisplayName = "Home";
}
public void OpenUrl(string url)
{
Core.Utilities.OpenUrl(url);
}
}
}

View File

@ -0,0 +1,7 @@
namespace Artemis.UI.Avalonia.Screens
{
public class MainScreenViewModel : ViewModelBase
{
}
}

View File

@ -0,0 +1,27 @@
using System;
using Artemis.Core.Services;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
public class RootViewModel : ViewModelBase, IScreen, IActivatableViewModel
{
private readonly ICoreService _coreService;
public RootViewModel(ICoreService coreService, SidebarViewModel sidebarViewModel)
{
SidebarViewModel = sidebarViewModel;
_coreService = coreService;
_coreService.Initialize();
Console.WriteLine("test");
}
public SidebarViewModel SidebarViewModel { get; }
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
/// <inheritdoc />
public RoutingState Router { get; } = new();
}
}

View File

@ -0,0 +1,72 @@
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
public class SidebarCategoryViewModel : ViewModelBase
{
private readonly IProfileService _profileService;
private readonly ISidebarVmFactory _vmFactory;
private SidebarProfileConfigurationViewModel _selectedProfileConfiguration;
public ProfileCategory ProfileCategory { get; }
public SidebarCategoryViewModel(ProfileCategory profileCategory, IProfileService profileService, ISidebarVmFactory vmFactory)
{
_profileService = profileService;
_vmFactory = vmFactory;
ProfileCategory = profileCategory;
if (ShowItems)
CreateProfileViewModels();
}
public ObservableCollection<SidebarProfileConfigurationViewModel> ProfileConfigurations { get; } = new();
public SidebarProfileConfigurationViewModel SelectedProfileConfiguration
{
get => _selectedProfileConfiguration;
set => this.RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value);
}
public bool ShowItems
{
get => !ProfileCategory.IsCollapsed;
set
{
ProfileCategory.IsCollapsed = !value;
if (ProfileCategory.IsCollapsed)
ProfileConfigurations.Clear();
else
CreateProfileViewModels();
_profileService.SaveProfileCategory(ProfileCategory);
this.RaisePropertyChanged(nameof(ShowItems));
}
}
public bool IsSuspended
{
get => ProfileCategory.IsSuspended;
set
{
ProfileCategory.IsSuspended = value;
this.RaisePropertyChanged(nameof(IsSuspended));
_profileService.SaveProfileCategory(ProfileCategory);
}
}
private void CreateProfileViewModels()
{
ProfileConfigurations.Clear();
foreach (ProfileConfiguration profileConfiguration in ProfileCategory.ProfileConfigurations.OrderBy(p => p.Order))
ProfileConfigurations.Add(_vmFactory.SidebarProfileConfigurationViewModel(profileConfiguration));
SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(i => i.ProfileConfiguration.IsBeingEdited);
}
}
}

View File

@ -0,0 +1,31 @@
using Artemis.Core;
using Artemis.Core.Services;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
public class SidebarProfileConfigurationViewModel : ViewModelBase
{
private readonly IProfileService _profileService;
public ProfileConfiguration ProfileConfiguration { get; }
public SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration, IProfileService profileService)
{
_profileService = profileService;
ProfileConfiguration = profileConfiguration;
_profileService.LoadProfileConfigurationIcon(ProfileConfiguration);
}
public bool IsProfileActive => ProfileConfiguration.Profile != null;
public bool IsSuspended
{
get => ProfileConfiguration.IsSuspended;
set
{
ProfileConfiguration.IsSuspended = value;
_profileService.SaveProfileCategory(ProfileConfiguration.Category);
}
}
}
}

View File

@ -0,0 +1,31 @@
using Material.Icons;
using Ninject;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
public class SidebarScreenViewModel<T> : SidebarScreenViewModel where T : MainScreenViewModel
{
public SidebarScreenViewModel(MaterialIconKind icon, string displayName) : base(icon, displayName)
{
}
public override MainScreenViewModel CreateInstance(IKernel kernel)
{
return kernel.Get<T>();
}
}
public abstract class SidebarScreenViewModel : ViewModelBase
{
protected SidebarScreenViewModel(MaterialIconKind icon, string displayName)
{
Icon = icon;
DisplayName = displayName;
}
public MaterialIconKind Icon { get; }
public string DisplayName { get; }
public abstract MainScreenViewModel CreateInstance(IKernel kernel);
}
}

View File

@ -0,0 +1,63 @@
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Avalonia.Ninject.Factories;
using Artemis.UI.Avalonia.Screens.Home.ViewModels;
using Artemis.UI.Avalonia.Screens.Settings.ViewModels;
using Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels;
using Artemis.UI.Avalonia.Screens.Workshop.ViewModels;
using Material.Icons;
using Ninject;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.ViewModels
{
public class SidebarViewModel : ViewModelBase
{
private readonly IKernel _kernel;
private readonly IProfileService _profileService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private SidebarScreenViewModel _selectedSidebarScreen;
public SidebarViewModel(IKernel kernel, IProfileService profileService, ISidebarVmFactory sidebarVmFactory)
{
_kernel = kernel;
_profileService = profileService;
_sidebarVmFactory = sidebarVmFactory;
SidebarScreens = new ObservableCollection<SidebarScreenViewModel>
{
new SidebarScreenViewModel<HomeViewModel>(MaterialIconKind.Home, "Home"),
new SidebarScreenViewModel<WorkshopViewModel>(MaterialIconKind.TestTube, "Workshop"),
new SidebarScreenViewModel<SurfaceEditorViewModel>(MaterialIconKind.Devices, "Surface Editor"),
new SidebarScreenViewModel<SettingsViewModel>(MaterialIconKind.Cog, "Settings")
};
SelectedSidebarScreen = SidebarScreens.First();
UpdateProfileCategories();
}
public ObservableCollection<SidebarScreenViewModel> SidebarScreens { get; }
public ObservableCollection<SidebarCategoryViewModel> SidebarCategories { get; } = new();
public SidebarScreenViewModel SelectedSidebarScreen
{
get => _selectedSidebarScreen;
set => this.RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
}
private void UpdateProfileCategories()
{
SidebarCategories.Clear();
foreach (ProfileCategory profileCategory in _profileService.ProfileCategories.OrderBy(p => p.Order))
AddProfileCategoryViewModel(profileCategory);
}
public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory)
{
SidebarCategoryViewModel viewModel = _sidebarVmFactory.SidebarCategoryViewModel(profileCategory);
SidebarCategories.Add(viewModel);
return viewModel;
}
}
}

View File

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.RootView">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240" MinWidth="175" MaxWidth="400" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ExperimentalAcrylicBorder Grid.Column="0">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
BackgroundSource="Digger"
TintColor="Black"
TintOpacity="1"
MaterialOpacity="0.65" />
</ExperimentalAcrylicBorder.Material>
<ContentControl Content="{Binding SidebarViewModel}"/>
</ExperimentalAcrylicBorder>
<Border Grid.Column="1" Background="Black">
<reactiveUi:RoutedViewHost Router="{Binding Router}" Margin="0 20 0 0" Padding="15" />
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,19 @@
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.Views
{
public class RootView : ReactiveUserControl<RootViewModel>
{
public RootView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,136 @@
<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:local="clr-namespace:Artemis.UI.Avalonia.Screens.Root.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.SidebarCategoryView">
<UserControl.Styles>
<Style Selector=":is(Button).category-button">
<Setter Property="IsVisible" Value="False"></Setter>
</Style>
<Style Selector="Grid#ContainerGrid:pointerover :is(Button).category-button">
<Setter Property="IsVisible" Value="True"></Setter>
</Style>
<Style Selector="avalonia|MaterialIcon.chevron-collapsed">
<Setter Property="RenderTransform" Value="rotate(180deg)" />
</Style>
<Style Selector="TextBlock.fadable.suspended">
<Setter Property="Opacity" Value="0.33" />
</Style>
<Style Selector="Border.fadable">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="Border.fadable.suspended">
<Setter Property="Opacity" Value="1" />
</Style>
</UserControl.Styles>
<Grid x:Name="ContainerGrid" Margin="0 8 0 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<avalonia:MaterialIcon Classes.chevron-collapsed="{Binding ShowItems}"
Kind="ChevronUp"
Grid.Column="0"
Margin="5 0"
PointerPressed="Title_OnPointerPressed"
Background="Transparent">
<avalonia:MaterialIcon.Transitions>
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" />
</Transitions>
</avalonia:MaterialIcon.Transitions>
</avalonia:MaterialIcon>
<TextBlock Classes="fadable"
Classes.suspended="{Binding IsSuspended}"
Grid.Column="1"
Padding="0 5"
FontWeight="SemiBold"
FontSize="13"
VerticalAlignment="Center"
Text="{Binding ProfileCategory.Name, FallbackValue='Profile name'}"
PointerPressed="Title_OnPointerPressed"
Background="Transparent">
<TextBlock.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
</Transitions>
</TextBlock.Transitions>
</TextBlock>
<Border Classes="fadable"
Classes.suspended="{Binding IsSuspended}"
Grid.Column="1"
BorderBrush="White"
BorderThickness="0.5"
Height="1">
<Border.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.2" />
</Transitions>
</Border.Transitions>
</Border>
<Button Classes="category-button icon-button icon-button-small"
Grid.Column="2"
ToolTip.Tip="Edit category"
HorizontalAlignment="Right">
<avalonia:MaterialIcon Kind="Cog" />
</Button>
<ToggleButton Classes="category-button icon-button icon-button-small"
Grid.Column="3"
ToolTip.Tip="Suspend profile"
Margin="2 0 0 0"
IsChecked="{Binding IsSuspended}">
<avalonia:MaterialIcon Kind="Pause" />
</ToggleButton>
</Grid>
<Border Grid.Row="1">
<Border.Resources>
<DataTemplate x:Key="ProfileDragTemplate" DataType="{x:Type local:SidebarProfileConfigurationViewModel}">
<Border Background="{DynamicResource MaterialDesignDivider}" Padding="10" CornerRadius="4">
<StackPanel Orientation="Horizontal">
<!-- <shared:ProfileConfigurationIcon ConfigurationIcon="{Binding ProfileConfiguration.Icon}" -->
<!-- Width="20" -->
<!-- Height="20" -->
<!-- Margin="0 0 10 0" -->
<!-- Foreground="{DynamicResource MaterialDesignBody}" /> -->
<TextBlock Text="{Binding ProfileConfiguration.Name}" VerticalAlignment="Center" Foreground="{DynamicResource MaterialDesignBody}" />
</StackPanel>
</Border>
</DataTemplate>
</Border.Resources>
<ListBox Classes="sidebar-listbox"
Items="{Binding ProfileConfigurations}"
SelectedItem="{Binding SelectedProfileConfiguration}"
MinHeight="10"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Paste">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentPaste" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</Border>
</Grid>
</UserControl>

View File

@ -0,0 +1,29 @@
using Artemis.UI.Avalonia.Screens.Root.ViewModels;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Screens.Root.Views
{
public class SidebarCategoryView : ReactiveUserControl<SidebarCategoryViewModel>
{
public SidebarCategoryView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void Title_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (ViewModel != null)
ViewModel.ShowItems = !ViewModel.ShowItems;
}
}
}

View File

@ -0,0 +1,124 @@
<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:converters="clr-namespace:Artemis.UI.Avalonia.Converters"
xmlns:controls="clr-namespace:Artemis.UI.Avalonia.Shared.Controls;assembly=Artemis.UI.Avalonia.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.SidebarProfileConfigurationView">
<UserControl.Resources>
<converters:ValuesAdditionConverter x:Key="ValuesAddition" />
</UserControl.Resources>
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem Header="View properties">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Cog" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Suspend">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Pause" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Suspend all">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Pause" />
</MenuItem.Icon>
<MenuItem Header="Above" CommandParameter="suspend" />
<MenuItem Header="Below" CommandParameter="suspend" />
</MenuItem>
<MenuItem Header="Resume all">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Play" />
</MenuItem.Icon>
<MenuItem Header="Above" CommandParameter="resume" />
<MenuItem Header="Below" CommandParameter="resume" />
</MenuItem>
<Separator />
<MenuItem Header="Export">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Export" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Duplicate">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentDuplicate" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentCopy" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Paste">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="ContentPaste" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Delete">
<MenuItem.Icon>
<avalonia:MaterialIcon Kind="Trash" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</UserControl.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<controls:ProfileConfigurationIcon Grid.Column="0"
x:Name="ProfileIcon"
VerticalAlignment="Center"
ConfigurationIcon="{Binding ProfileConfiguration.Icon}"
Width="20"
Height="20">
</controls:ProfileConfigurationIcon>
<TextBlock Grid.Column="1"
x:Name="ProfileName"
FontSize="12"
Margin="10 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding ProfileConfiguration.Name}"
TextTrimming="CharacterEllipsis" />
<Border Grid.Column="0"
Grid.ColumnSpan="2"
BorderThickness="1"
BorderBrush="{DynamicResource MaterialDesignBody}"
Height="1"
Opacity="0"
HorizontalAlignment="Left">
<!-- Ensure the line covers the profile and the text but not the full two columns -->
<Border.Width>
<MultiBinding Converter="{StaticResource ValuesAddition}">
<Binding Path="ActualWidth" ElementName="ProfileIcon" />
<Binding Path="Width" ElementName="ProfileName" />
<Binding Path="Margin.Left" ElementName="ProfileName" />
</MultiBinding>
</Border.Width>
</Border>
<Button Classes="icon-button icon-button-small"
Grid.Column="2"
ToolTip.Tip="View properties"
HorizontalAlignment="Right">
<avalonia:MaterialIcon Kind="Cog"/>
</Button>
<ToggleButton Classes="icon-button icon-button-small"
Grid.Column="3"
ToolTip.Tip="Suspend profile"
Margin="2 0 0 0"
IsChecked="{Binding IsSuspended}">
<avalonia:MaterialIcon Kind="Pause" />
</ToggleButton>
</Grid>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Root.Views
{
public class SidebarProfileConfigurationView : UserControl
{
public SidebarProfileConfigurationView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,12 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.SidebarScreenView">
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{Binding Icon}" Width="16" Height="16" />
<TextBlock FontSize="12" Margin="10 0" VerticalAlignment="Center" Text="{Binding DisplayName}" />
</StackPanel>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Root.Views
{
public partial class SidebarScreenView : UserControl
{
public SidebarScreenView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,114 @@
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Avalonia.Screens.Root.Views.SidebarView">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- <shared:DeviceVisualizer Grid.Column="0" -->
<!-- Grid.ColumnSpan="2" -->
<!-- Device="{Binding HeaderDevice}" -->
<!-- ShowColors="True" -->
<!-- RenderTransformOrigin="0.5 0.5" -->
<!-- VerticalAlignment="Center" -->
<!-- HorizontalAlignment="Center" -->
<!-- RenderOptions.BitmapScalingMode="HighQuality"> -->
<!-- <shared:DeviceVisualizer.RenderTransform> -->
<!-- <TransformGroup> -->
<!-- <RotateTransform Angle="20" /> -->
<!-- <ScaleTransform ScaleX="2" ScaleY="2" /> -->
<!-- </TransformGroup> -->
<!-- </shared:DeviceVisualizer.RenderTransform> -->
<!-- </shared:DeviceVisualizer> -->
<Rectangle Grid.Column="0" Grid.ColumnSpan="2" Height="60" VerticalAlignment="Bottom">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,1" EndPoint="0,0">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="#00000000" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Image Grid.Column="0" Source="{SvgImage /Assets/Images/Logo/bow.svg}" Height="35" Width="35" Margin="10" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
Foreground="{DynamicResource MaterialDesignDarkForeground}">
Artemis 2
</TextBlock>
</Grid>
<!-- Built-in screens -->
<ListBox Classes="sidebar-listbox"
Grid.Row="1"
Margin="10 2"
Items="{Binding SidebarScreens}"
SelectedItem="{Binding SelectedSidebarScreen}" />
<Separator Grid.Row="2" Margin="8" Height="1" Background="White" />
<!-- Categories -->
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
<StackPanel>
<ItemsControl Margin="10 2" Items="{Binding SidebarCategories}" />
<Button Content="ADD NEW CATEGORY" Margin="10 10 10 0" />
</StackPanel>
</ScrollViewer>
<!-- Bottom buttons -->
<Separator Grid.Row="4" Margin="8" />
<WrapPanel Grid.Row="5" Orientation="Horizontal" HorizontalAlignment="Left" Margin="5 0 5 5">
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View website"
NavigateUri="https://artemis-rgb.com">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View GitHub repository"
NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View Wiki"
NavigateUri="https://wiki.artemis-rgb.com">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="Join our Discord"
NavigateUri="https://discord.gg/S3MVaC9">
<avalonia:MaterialIcon Kind="Discord" Width="20" Height="20" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button"
Width="44"
Height="44"
ToolTip.Tip="View donation options"
NavigateUri="https://wiki.artemis-rgb.com/en/donating">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</controls:HyperlinkButton>
</WrapPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Avalonia.Screens.Root.Views
{
public class SidebarView : UserControl
{
public SidebarView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,10 @@
namespace Artemis.UI.Avalonia.Screens.Settings.ViewModels
{
public class SettingsViewModel : MainScreenViewModel
{
public SettingsViewModel()
{
DisplayName = "Settings";
}
}
}

View File

@ -0,0 +1,10 @@
namespace Artemis.UI.Avalonia.Screens.SurfaceEditor.ViewModels
{
public class SurfaceEditorViewModel : MainScreenViewModel
{
public SurfaceEditorViewModel()
{
DisplayName = "Surface Editor";
}
}
}

View File

@ -0,0 +1,10 @@
namespace Artemis.UI.Avalonia.Screens.Workshop.ViewModels
{
public class WorkshopViewModel : MainScreenViewModel
{
public WorkshopViewModel()
{
DisplayName = "Workshop";
}
}
}

View File

@ -0,0 +1,55 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<!-- Preview -->
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<TextBlock Margin="0 5 0 0">Button.icon-button</TextBlock>
<Button Classes="icon-button">
<avalonia:MaterialIcon Kind="Cog"></avalonia:MaterialIcon>
</Button>
<TextBlock Margin="0 5 0 0">Button.icon-button icon-button-small</TextBlock>
<Button Classes="icon-button icon-button-small">
<avalonia:MaterialIcon Kind="Cog"></avalonia:MaterialIcon>
</Button>
<TextBlock Margin="0 5 0 0">ToggleButton.icon-button</TextBlock>
<ToggleButton Classes="icon-button">
<avalonia:MaterialIcon Kind="Cog"></avalonia:MaterialIcon>
</ToggleButton>
<TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button</TextBlock>
<controls:HyperlinkButton Classes="icon-button">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<TextBlock Margin="0 5 0 0">HyperlinkButton.icon-button icon-button-small</TextBlock>
<controls:HyperlinkButton Classes="icon-button icon-button-small">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
</StackPanel>
</Border>
</Design.PreviewWith>
<!-- Styles -->
<Style Selector=":is(Button).icon-button">
<Setter Property="BorderBrush" Value="Transparent"></Setter>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="5.5"/>
</Style>
<Style Selector=":is(Button).icon-button-small">
<Setter Property="Padding" Value="2"/>
</Style>
<Style Selector=":is(Button).icon-button-small avalonia|MaterialIcon">
<Setter Property="Width" Value="14"></Setter>
<Setter Property="Height" Value="14"></Setter>
</Style>
<Style Selector="controls|HyperlinkButton.icon-button">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
</Styles>

View File

@ -0,0 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Artemis.UI.Avalonia.Screens.Root.Views">
<Design.PreviewWith>
<Border Padding="20">
<ListBox Classes="sidebar-listbox">
<ListBoxItem>Test</ListBoxItem>
<ListBoxItem>Test</ListBoxItem>
<ListBoxItem>Test</ListBoxItem>
<ListBoxItem>Test</ListBoxItem>
</ListBox>
</Border>
</Design.PreviewWith>
<!-- Add Styles Here -->
<Style Selector="ListBox.sidebar-listbox ListBoxItem">
<Setter Property="Margin" Value="0 2.5"></Setter>
<Setter Property="MinHeight" Value="35"></Setter>
</Style>
</Styles>

View File

@ -0,0 +1,26 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Artemis.UI.Avalonia
{
public class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
string name = data.GetType().FullName!.Split('`')[0].Replace("ViewModel", "View");
Type? type = Type.GetType(name);
if (type != null)
return (Control) Activator.CreateInstance(type)!;
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
}

View File

@ -0,0 +1,15 @@
using ReactiveUI;
namespace Artemis.UI.Avalonia
{
public class ViewModelBase : ReactiveObject
{
private string? _displayName;
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"version": 1,
"dependencies": {
".NETCoreApp,Version=v5.0": {
"net5.0-windows7.0": {
"Humanizer.Core": {
"type": "Direct",
"requested": "[2.11.10, )",

View File

@ -13,32 +13,64 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Shared", "Artemi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Console", "Artemis.ConsoleUI\Artemis.UI.Console.csproj", "{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Avalonia", "Artemis.UI.Avalonia\Artemis.UI.Avalonia.csproj", "{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.UI.Avalonia.Shared", "Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.csproj", "{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Debug|Any CPU.ActiveCfg = Debug|x64
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Debug|x64.ActiveCfg = Debug|x64
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Debug|x64.Build.0 = Debug|x64
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Release|Any CPU.ActiveCfg = Release|x64
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Release|x64.ActiveCfg = Release|x64
{46B74153-77CF-4489-BDF9-D53FDB1F7ACB}.Release|x64.Build.0 = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Debug|Any CPU.ActiveCfg = Debug|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Debug|x64.ActiveCfg = Debug|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Debug|x64.Build.0 = Debug|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|Any CPU.ActiveCfg = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.ActiveCfg = Release|x64
{E489E5E3-1A65-4AF5-A1EA-F9805FD19A65}.Release|x64.Build.0 = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|Any CPU.ActiveCfg = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.ActiveCfg = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Debug|x64.Build.0 = Debug|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|Any CPU.ActiveCfg = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.ActiveCfg = Release|x64
{9B811F9B-86B9-4771-87AF-72BAE7078A36}.Release|x64.Build.0 = Release|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Debug|Any CPU.ActiveCfg = Debug|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Debug|x64.ActiveCfg = Debug|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Debug|x64.Build.0 = Debug|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Release|Any CPU.ActiveCfg = Release|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Release|x64.ActiveCfg = Release|x64
{ADB357E6-151D-4D0D-87CB-68FD0BC29812}.Release|x64.Build.0 = Release|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Debug|Any CPU.ActiveCfg = Debug|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Debug|x64.ActiveCfg = Debug|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Debug|x64.Build.0 = Debug|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Release|Any CPU.ActiveCfg = Release|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Release|x64.ActiveCfg = Release|x64
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Release|x64.Build.0 = Release|x64
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Debug|x64.Build.0 = Debug|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|Any CPU.Build.0 = Release|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|x64.ActiveCfg = Release|Any CPU
{035CBB38-7B9E-4375-A39C-E9A5B01F23A5}.Release|x64.Build.0 = Release|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.ActiveCfg = Debug|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Debug|x64.Build.0 = Debug|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|Any CPU.Build.0 = Release|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|x64.ActiveCfg = Release|Any CPU
{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE