diff --git a/src/Artemis.UI.Linux/App.axaml.cs b/src/Artemis.UI.Linux/App.axaml.cs index 52f729727..88f0f8179 100644 --- a/src/Artemis.UI.Linux/App.axaml.cs +++ b/src/Artemis.UI.Linux/App.axaml.cs @@ -1,3 +1,4 @@ +using System; using Artemis.Core.Services; using Artemis.UI.Linux.DryIoc; using Artemis.UI.Linux.Providers.Input; @@ -26,15 +27,11 @@ public class App : Application public override void OnFrameworkInitializationCompleted() { - if (Design.IsDesignMode) + if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode) return; - - if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) - return; - - ArtemisBootstrapper.Initialize(); - _applicationStateManager = new ApplicationStateManager(_container!, desktop.Args); + _applicationStateManager = new ApplicationStateManager(_container!, desktop.Args ?? Array.Empty()); + ArtemisBootstrapper.Initialize(); RegisterProviders(); } diff --git a/src/Artemis.UI.Shared/ReactiveAppWindow.cs b/src/Artemis.UI.Shared/ReactiveAppWindow.cs index a751f4ee6..c943cbbfe 100644 --- a/src/Artemis.UI.Shared/ReactiveAppWindow.cs +++ b/src/Artemis.UI.Shared/ReactiveAppWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; @@ -26,6 +27,8 @@ public class ReactiveAppWindow : AppWindow, IViewFor whe public static readonly StyledProperty ViewModelProperty = AvaloniaProperty .Register, TViewModel?>(nameof(ViewModel)); + private bool _micaEnabled; + /// /// Initializes a new instance of the class. /// @@ -33,23 +36,33 @@ public class ReactiveAppWindow : AppWindow, IViewFor whe { // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. - this.WhenActivated(disposables => { }); + this.WhenActivated(disposables => UI.MicaEnabled.Subscribe(ToggleMica).DisposeWith(disposables)); this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged); this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } - /// - protected override void OnOpened(EventArgs e) + private void ToggleMica(bool enable) { - // TODO: Move to a style and remove opacity on focus loss - base.OnOpened(e); - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11) + if (enable == _micaEnabled) return; - TransparencyBackgroundFallback = Brushes.Transparent; - TransparencyLevelHint = new[] {WindowTransparencyLevel.Mica}; - TryEnableMicaEffect(); + if (enable) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11) + return; + + // TransparencyBackgroundFallback = Brushes.Transparent; + TransparencyLevelHint = new[] {WindowTransparencyLevel.Mica}; + Background = new SolidColorBrush(new Color(80, 0,0,0)); + + } + else + { + ClearValue(TransparencyLevelHintProperty); + ClearValue(BackgroundProperty); + } + + _micaEnabled = enable; } private void OnDataContextChanged(object? value) @@ -67,32 +80,6 @@ public class ReactiveAppWindow : AppWindow, IViewFor whe else if (DataContext != value) DataContext = value; } - private void TryEnableMicaEffect() - { - // The background colors for the Mica brush are still based around SolidBackgroundFillColorBase resource - // BUT since we can't control the actual Mica brush color, we have to use the window background to create - // the same effect. However, we can't use SolidBackgroundFillColorBase directly since its opaque, and if - // we set the opacity the color become lighter than we want. So we take the normal color, darken it and - // apply the opacity until we get the roughly the correct color - // NOTE that the effect still doesn't look right, but it suffices. Ideally we need access to the Mica - // CompositionBrush to properly change the color but I don't know if we can do that or not - if (ActualThemeVariant == ThemeVariant.Dark) - { - Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Dark, out object? value) ? (Color) value : new Color2(32, 32, 32); - color = color.LightenPercent(-0.5f); - - Background = new ImmutableSolidColorBrush(color, 0.78); - } - else if (ActualThemeVariant == ThemeVariant.Light) - { - // Similar effect here - Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", ThemeVariant.Light, out object? value) ? (Color) value : new Color2(243, 243, 243); - color = color.LightenPercent(0.5f); - - Background = new ImmutableSolidColorBrush(color, 0.9); - } - } - /// /// The ViewModel. /// diff --git a/src/Artemis.UI.Shared/Utilities.cs b/src/Artemis.UI.Shared/Utilities.cs index 344aa4834..3b1c58495 100644 --- a/src/Artemis.UI.Shared/Utilities.cs +++ b/src/Artemis.UI.Shared/Utilities.cs @@ -1,29 +1,29 @@ using System; using System.Reactive.Concurrency; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Platform; -using IContainer = DryIoc.IContainer; +using Avalonia.Threading; +using DryIoc; namespace Artemis.UI.Shared; /// -/// Static UI helpers. +/// Static UI helpers. /// public static class UI { + private static readonly BehaviorSubject MicaEnabledSubject = new(false); + + public static EventLoopScheduler BackgroundScheduler = new(ts => new Thread(ts)); + static UI() { KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true); - } - - public static EventLoopScheduler BackgroundScheduler = new EventLoopScheduler(ts => new Thread(ts)); - - internal static void ClearCache() - { - DeviceVisualizer.BitmapCache.Clear(); + MicaEnabled = MicaEnabledSubject.AsObservable(); } /// @@ -40,4 +40,24 @@ public static class UI /// Gets a boolean indicating whether hotkeys are to be disabled. /// public static IObservable KeyBindingsEnabled { get; } + + /// + /// Gets a boolean indicating whether the Mica effect should be enabled. + /// + public static IObservable MicaEnabled { get; } + + /// + /// Changes whether Mica should be enabled. + /// + /// + public static void SetMicaEnabled(bool enabled) + { + if (MicaEnabledSubject.Value != enabled) + Dispatcher.UIThread.Invoke(() => MicaEnabledSubject.OnNext(enabled)); + } + + internal static void ClearCache() + { + DeviceVisualizer.BitmapCache.Clear(); + } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 617858fb9..9854a1ca5 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -43,8 +43,8 @@ public class App : Application { if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown) return; - - _applicationStateManager = new ApplicationStateManager(_container!, desktop.Args); + + _applicationStateManager = new ApplicationStateManager(_container!, desktop.Args ?? Array.Empty()); ArtemisBootstrapper.Initialize(); RegisterProviders(_container!); } @@ -52,11 +52,14 @@ public class App : Application private void RegisterProviders(IContainer container) { IInputService inputService = container.Resolve(); - inputService.AddInputProvider(container.Resolve(serviceKey: WindowsInputProvider.Id)); + inputService.AddInputProvider(container.Resolve(WindowsInputProvider.Id)); } private bool FocusExistingInstance() { + if (Design.IsDesignMode) + return false; + _artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew); return !createdNew && RemoteFocus(); } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 5da497f13..be43581c5 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -54,6 +54,7 @@ + @@ -61,5 +62,9 @@ ProfileListEntryView.axaml Code + + StartupWizardView.axaml + Code + \ No newline at end of file diff --git a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.axaml b/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.axaml deleted file mode 100644 index ee706785b..000000000 --- a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.axaml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.cs b/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.cs deleted file mode 100644 index 721ce1ebf..000000000 --- a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlur.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Media; - -namespace Artemis.UI.Controls.AcrylicBlur; - -public class AcrylicBlur : ContentControl -{ - private static readonly ImmutableExperimentalAcrylicMaterial DefaultAcrylicMaterial = (ImmutableExperimentalAcrylicMaterial) new ExperimentalAcrylicMaterial() - { - MaterialOpacity = 0.1, - TintColor = new Color(255, 7, 7, 7), - TintOpacity = 1, - PlatformTransparencyCompensationLevel = 0 - }.ToImmutable(); - - public static readonly StyledProperty MaterialProperty = - AvaloniaProperty.Register(nameof(Material)); - - public ExperimentalAcrylicMaterial? Material - { - get => GetValue(MaterialProperty); - set => SetValue(MaterialProperty, value); - } - - public static readonly StyledProperty BlurProperty = AvaloniaProperty.Register(nameof(Blur)); - - public int Blur - { - get => GetValue(BlurProperty); - set => SetValue(BlurProperty, value); - } - - static AcrylicBlur() - { - AffectsRender(MaterialProperty); - AffectsRender(BlurProperty); - } - - public override void Render(DrawingContext context) - { - ImmutableExperimentalAcrylicMaterial mat = Material != null ? (ImmutableExperimentalAcrylicMaterial) Material.ToImmutable() : DefaultAcrylicMaterial; - context.Custom(new AcrylicBlurRenderOperation(this, mat, Blur, new Rect(default, Bounds.Size))); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlurRenderOperation.cs b/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlurRenderOperation.cs deleted file mode 100644 index 326c9aefa..000000000 --- a/src/Artemis.UI/Controls/AcrylicBlur/AcrylicBlurRenderOperation.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.IO; -using Avalonia; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Skia; -using Avalonia.Threading; -using SkiaSharp; - -namespace Artemis.UI.Controls.AcrylicBlur; - -public class AcrylicBlurRenderOperation : ICustomDrawOperation -{ - private static SKShader? _acrylicNoiseShader; - - private readonly AcrylicBlur _acrylicBlur; - private readonly ImmutableExperimentalAcrylicMaterial _material; - private readonly int _blur; - private readonly Rect _bounds; - private SKImage? _backgroundSnapshot; - private bool _disposed; - - public AcrylicBlurRenderOperation(AcrylicBlur acrylicBlur, ImmutableExperimentalAcrylicMaterial material, int blur, Rect bounds) - { - _acrylicBlur = acrylicBlur; - _material = material; - _blur = blur; - _bounds = bounds; - } - - public void Dispose() - { - if (_disposed) - return; - - _backgroundSnapshot?.Dispose(); - _disposed = true; - } - - public bool HitTest(Point p) => _bounds.Contains(p); - - static SKColorFilter CreateAlphaColorFilter(double opacity) - { - if (opacity > 1) - opacity = 1; - byte[] c = new byte[256]; - byte[] a = new byte[256]; - for (int i = 0; i < 256; i++) - { - c[i] = (byte) i; - a[i] = (byte) (i * opacity); - } - - return SKColorFilter.CreateTable(a, c, c, c); - } - - public void Render(ImmediateDrawingContext context) - { - if (_disposed) - throw new ObjectDisposedException(nameof(AcrylicBlurRenderOperation)); - - ISkiaSharpApiLeaseFeature? leaseFeature = context.PlatformImpl.GetFeature(); - if (leaseFeature == null) - return; - using ISkiaSharpApiLease lease = leaseFeature.Lease(); - - if (!lease.SkCanvas.TotalMatrix.TryInvert(out SKMatrix currentInvertedTransform) || lease.SkSurface == null) - return; - - if (lease.SkCanvas.GetLocalClipBounds(out SKRect bounds) && !bounds.Contains(SKRect.Create(bounds.Left, bounds.Top, (float) _acrylicBlur.Bounds.Width, (float) _acrylicBlur.Bounds.Height))) - { - Dispatcher.UIThread.Invoke(() => _acrylicBlur.InvalidateVisual()); - } - else - { - _backgroundSnapshot?.Dispose(); - _backgroundSnapshot = lease.SkSurface.Snapshot(); - } - - _backgroundSnapshot ??= lease.SkSurface.Snapshot(); - using SKShader? backdropShader = SKShader.CreateImage(_backgroundSnapshot, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, currentInvertedTransform); - using SKSurface? blurred = SKSurface.Create( - lease.GrContext, - false, - new SKImageInfo((int) Math.Ceiling(_bounds.Width), (int) Math.Ceiling(_bounds.Height), SKImageInfo.PlatformColorType, SKAlphaType.Premul) - ); - using (SKImageFilter? filter = SKImageFilter.CreateBlur(_blur, _blur, SKShaderTileMode.Clamp)) - using (SKPaint blurPaint = new SKPaint {Shader = backdropShader, ImageFilter = filter}) - { - blurred.Canvas.DrawRect(0, 0, (float) _bounds.Width, (float) _bounds.Height, blurPaint); - - using (SKImage? blurSnap = blurred.Snapshot()) - using (SKShader? blurSnapShader = SKShader.CreateImage(blurSnap)) - using (SKPaint blurSnapPaint = new SKPaint {Shader = blurSnapShader, IsAntialias = true}) - { - // Rendering twice to reduce opacity - lease.SkCanvas.DrawRect(0, 0, (float) _bounds.Width, (float) _bounds.Height, blurSnapPaint); - lease.SkCanvas.DrawRect(0, 0, (float) _bounds.Width, (float) _bounds.Height, blurSnapPaint); - } - - //return; - using SKPaint acrylliPaint = new SKPaint(); - acrylliPaint.IsAntialias = true; - - double opacity = 1; - - const double noiseOpacity = 0.0225; - - Color tintColor = _material.TintColor; - SKColor tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A); - - if (_acrylicNoiseShader == null) - { - using Stream? stream = typeof(SkiaPlatform).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png"); - using SKBitmap? bitmap = SKBitmap.Decode(stream); - _acrylicNoiseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat).WithColorFilter(CreateAlphaColorFilter(noiseOpacity)); - } - - using (SKShader? backdrop = SKShader.CreateColor(new SKColor(_material.MaterialColor.R, _material.MaterialColor.G, _material.MaterialColor.B, _material.MaterialColor.A))) - using (SKShader? tintShader = SKShader.CreateColor(tint)) - using (SKShader? effectiveTint = SKShader.CreateCompose(backdrop, tintShader)) - using (SKShader? compose = SKShader.CreateCompose(effectiveTint, _acrylicNoiseShader)) - { - acrylliPaint.Shader = compose; - acrylliPaint.IsAntialias = true; - lease.SkCanvas.DrawRect(0, 0, (float) _bounds.Width, (float) _bounds.Height, acrylliPaint); - } - } - } - - public Rect Bounds => _bounds.Inflate(4); - - public bool Equals(ICustomDrawOperation? other) - { - return other is AcrylicBlurRenderOperation op && op._bounds == _bounds && op._material.Equals(_material); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/MainWindow.axaml b/src/Artemis.UI/MainWindow.axaml index bff2c07c9..f91d7815a 100644 --- a/src/Artemis.UI/MainWindow.axaml +++ b/src/Artemis.UI/MainWindow.axaml @@ -10,7 +10,7 @@ Icon="/Assets/Images/Logo/application.ico" Title="Artemis 2.0" MinWidth="600" - MinHeight="400" + MinHeight="400" PointerReleased="InputElement_OnPointerReleased"> diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index ebc64a565..828b080ae 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -20,11 +20,11 @@ namespace Artemis.UI.Screens.Root; public class RootViewModel : RoutableScreen, IMainWindowProvider { - private readonly IRouter _router; private readonly ICoreService _coreService; private readonly IDebugService _debugService; private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel; private readonly IClassicDesktopStyleApplicationLifetime _lifeTime; + private readonly IRouter _router; private readonly ISettingsService _settingsService; private readonly IUpdateService _updateService; private readonly IWindowService _windowService; @@ -41,6 +41,7 @@ public class RootViewModel : RoutableScreen, IMainWindowPr SidebarViewModel sidebarViewModel, DefaultTitleBarViewModel defaultTitleBarViewModel) { + Shared.UI.SetMicaEnabled(settingsService.GetSetting("UI.EnableMica", true).Value); WindowSizeSetting = settingsService.GetSetting("WindowSize"); SidebarViewModel = sidebarViewModel; @@ -61,7 +62,7 @@ public class RootViewModel : RoutableScreen, IMainWindowPr OpenDebugger = ReactiveCommand.CreateFromTask(ExecuteOpenDebugger); Exit = ReactiveCommand.CreateFromTask(ExecuteExit); this.WhenAnyValue(vm => vm.Screen).Subscribe(UpdateTitleBarViewModel); - + Task.Run(() => { if (_updateService.Initialize()) @@ -85,6 +86,8 @@ public class RootViewModel : RoutableScreen, IMainWindowPr set => RaiseAndSetIfChanged(ref _titleBarViewModel, value); } + public static PluginSetting? WindowSizeSetting { get; private set; } + public void GoBack() { _router.GoBack(); @@ -95,8 +98,6 @@ public class RootViewModel : RoutableScreen, IMainWindowPr _router.GoForward(); } - public static PluginSetting? WindowSizeSetting { get; private set; } - private void UpdateTitleBarViewModel(IMainScreenViewModel? viewModel) { if (viewModel?.TitleBarViewModel != null) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 9d2fd980c..c2c394214 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -44,6 +44,19 @@ + + + + Enable Mica effect + + The Mica effect is the semi-transparent effect used by the application window, the colors are based on your wallpaper. + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index 5121af0f5..ca707b9e3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -30,9 +30,9 @@ public class GeneralTabViewModel : ActivatableViewModelBase private readonly IAutoRunProvider? _autoRunProvider; private readonly IDebugService _debugService; private readonly PluginSetting _defaultLayerBrushDescriptor; + private readonly INotificationService _notificationService; private readonly ISettingsService _settingsService; private readonly IUpdateService _updateService; - private readonly INotificationService _notificationService; private readonly IWindowService _windowService; private bool _startupWizardOpen; @@ -74,12 +74,14 @@ public class GeneralTabViewModel : ActivatableViewModelBase { UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged; UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged; + EnableMica.SettingChanged += EnableMicaOnSettingChanged; Dispatcher.UIThread.InvokeAsync(ApplyAutoRun); Disposable.Create(() => { UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged; UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged; + EnableMica.SettingChanged -= EnableMicaOnSettingChanged; _settingsService.SaveAllSettings(); }).DisposeWith(d); @@ -146,6 +148,7 @@ public class GeneralTabViewModel : ActivatableViewModelBase public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false); public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); + public PluginSetting EnableMica => _settingsService.GetSetting("UI.EnableMica", true); public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true); public PluginSetting UIAutoUpdate => _settingsService.GetSetting("UI.Updating.AutoInstall", true); public PluginSetting ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); @@ -238,4 +241,9 @@ public class GeneralTabViewModel : ActivatableViewModelBase _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); } } + + private void EnableMicaOnSettingChanged(object? sender, EventArgs e) + { + Shared.UI.SetMicaEnabled(EnableMica.Value); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml index 9487ac4eb..2b28230f7 100644 --- a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml +++ b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml @@ -9,9 +9,9 @@ x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView" x:DataType="currentUser:CurrentUserViewModel"> - + - + @@ -27,7 +27,7 @@ - + diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml index b94899712..d0717860f 100644 --- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml @@ -4,8 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:home="clr-namespace:Artemis.UI.Screens.Workshop.Home" - xmlns:controls="clr-namespace:Artemis.UI.Controls" - xmlns:acrylicBlur="clr-namespace:Artemis.UI.Controls.AcrylicBlur" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Workshop.Home.WorkshopHomeView" x:DataType="home:WorkshopHomeViewModel"> @@ -39,43 +37,42 @@ - - - - - - + + + + + - - - - - + + + + + + Featured submissions + Not yet implemented, here we'll show submissions we think are worth some extra attention. + + Recently updated + Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop. - - - - - - - - - - Test - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs index 863df049a..67391c0bd 100644 --- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs @@ -1,6 +1,10 @@ using System.Reactive; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.SubmissionWizard; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; using ReactiveUI; @@ -8,13 +12,22 @@ namespace Artemis.UI.Screens.Workshop.Home; public class WorkshopHomeViewModel : ActivatableViewModelBase, IWorkshopViewModel { - public WorkshopHomeViewModel(IRouter router) + private readonly IWindowService _windowService; + + public WorkshopHomeViewModel(IRouter router, IWindowService windowService) { + _windowService = windowService; + AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission); Navigate = ReactiveCommand.CreateFromTask(async r => await router.Navigate(r)); } - public ReactiveCommand Navigate { get; set; } + public ReactiveCommand AddSubmission { get; } + public ReactiveCommand Navigate { get; } + + private async Task ExecuteAddSubmission(CancellationToken arg) + { + await _windowService.ShowDialogAsync(); + } public EntryType? EntryType => null; - } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml index e46ea18ac..171d4c400 100644 --- a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml @@ -48,6 +48,8 @@ diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml new file mode 100644 index 000000000..7341a2307 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml @@ -0,0 +1,11 @@ + + + Welcome to the Workshop Submission Wizard 🧙 + Here we'll take you, step by step, through the process of uploading your submission to the workshop. + + diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml.cs new file mode 100644 index 000000000..9de30c623 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public partial class WelcomeStepView : ReactiveUserControl +{ + public WelcomeStepView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepViewModel.cs new file mode 100644 index 000000000..cf70f6410 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/WelcomeStepViewModel.cs @@ -0,0 +1,22 @@ +using System.Reactive; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public class WelcomeStepViewModel : SubmissionViewModel +{ + #region Overrides of SubmissionViewModel + + /// + public override ReactiveCommand Continue { get; } + + /// + public override ReactiveCommand GoBack { get; } = null!; + + public WelcomeStepViewModel() + { + ShowGoBack = false; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml new file mode 100644 index 000000000..18315e3fc --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs new file mode 100644 index 000000000..58dade5ef --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs @@ -0,0 +1,33 @@ +using System; +using System.Reactive.Disposables; +using Artemis.UI.Shared; +using Avalonia; +using Avalonia.Threading; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard; + +public partial class SubmissionWizardView : ReactiveAppWindow +{ + public SubmissionWizardView() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d)); + } + + private void Navigate(SubmissionViewModel viewModel) + { + try + { + Dispatcher.UIThread.Invoke(() => Frame.NavigateFromObject(viewModel)); + } + catch (Exception) + { + // ignored + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs new file mode 100644 index 000000000..fed2c3241 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardViewModel.cs @@ -0,0 +1,47 @@ +using System.Reactive; +using Artemis.UI.Screens.Workshop.CurrentUser; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; +using Artemis.UI.Shared; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard; + +public class SubmissionWizardViewModel : DialogViewModelBase +{ + private SubmissionViewModel _screen; + + public SubmissionWizardViewModel(CurrentUserViewModel currentUserViewModel) + { + _screen = new WelcomeStepViewModel(); + CurrentUserViewModel = currentUserViewModel; + } + + public CurrentUserViewModel CurrentUserViewModel { get; } + + public SubmissionViewModel Screen + { + get => _screen; + set => RaiseAndSetIfChanged(ref _screen, value); + } +} + +public abstract class SubmissionViewModel : ActivatableViewModelBase +{ + private bool _showFinish; + private bool _showGoBack = true; + + public abstract ReactiveCommand Continue { get; } + public abstract ReactiveCommand GoBack { get; } + + public bool ShowGoBack + { + get => _showGoBack; + set => RaiseAndSetIfChanged(ref _showGoBack, value); + } + + public bool ShowFinish + { + get => _showFinish; + set => RaiseAndSetIfChanged(ref _showFinish, value); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Styles/Artemis.axaml b/src/Artemis.UI/Styles/Artemis.axaml index 8e62c47f2..a121ec71b 100644 --- a/src/Artemis.UI/Styles/Artemis.axaml +++ b/src/Artemis.UI/Styles/Artemis.axaml @@ -13,7 +13,6 @@ - diff --git a/src/Artemis.props b/src/Artemis.props index 121967a89..8bbeacbcb 100644 --- a/src/Artemis.props +++ b/src/Artemis.props @@ -1,6 +1,6 @@ - 11.0.0 + 11.0.1 2.0.0 2.0.0-prerelease.83 2.88.3