diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 5b4bb470..f59ebfb8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,10 +15,10 @@ ] }, "csharpier": { - "version": "0.25.0", + "version": "0.26.3", "commands": [ "dotnet-csharpier" ] } } -} +} \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae9cc140..c5bb7927 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d1770b6..78b76803 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,10 +82,10 @@ jobs: sudo apt-get -y install libfuse2 dotnet tool install --framework net6.0 -g KuiperZone.PupNet - - name: Set up .NET 7 + - name: Set up .NET 8 uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: PupNet Build env: @@ -143,10 +143,10 @@ jobs: echo "Using version ${{ github.event.inputs.version }}" echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $env:GITHUB_ENV - - name: Set up .NET 7 + - name: Set up .NET 8 uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore @@ -197,8 +197,8 @@ jobs: # Zip each build - name: Zip Artifacts run: | - zip -r StabilityMatrix-win-x64.zip StabilityMatrix-win-x64/* - zip -r StabilityMatrix-linux-x64.zip StabilityMatrix-linux-x64/* + cd StabilityMatrix-win-x64 && zip -r ../StabilityMatrix-win-x64.zip ./. && cd $OLDPWD + cd StabilityMatrix-linux-x64 && zip -r ../StabilityMatrix-linux-x64.zip ./. && cd $OLDPWD - name: Create Github Release id: create_release diff --git a/.github/workflows/test-ui.yml b/.github/workflows/test-ui.yml index 244472bb..a0720e5d 100644 --- a/.github/workflows/test-ui.yml +++ b/.github/workflows/test-ui.yml @@ -18,7 +18,7 @@ jobs: - name: Set up .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install dependencies run: dotnet restore diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 68103040..1b623413 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -25,7 +25,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: '8.0.x' - name: Bump versions uses: SiqiLu/dotnet-bump-version@2.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 26905e47..ba31a0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,77 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.7.0-pre.2 +### Added +- Added System Information section to Settings +### Changed +- Moved Inference Settings to subpage +### Fixed +- Fixed crash when loading an empty settings file +- Improve Settings save and load performance with .NET 8 Source Generating Serialization +- Fixed ApplicationException during database shutdown + +## v2.7.0-pre.1 +### Fixed +- Fixed control character decoding that caused some progress bars to show as `\u2588` +- Fixed Python `rich` package's progress bars not showing in console +- Optimized ProgressRing animation bindings to reduce CPU usage +- Improved safety checks in custom control rendering to reduce potential graphical artifacts +- Improved console rendering safety with cursor line increment clamping, as potential fix for [#111](https://github.com/LykosAI/StabilityMatrix/issues/111) + +## v2.7.0-dev.4 +### Fixed +- Fixed [#290](https://github.com/LykosAI/StabilityMatrix/issues/290) - Model browser crash due to text trimming certain unicode characters + +## v2.7.0-dev.3 +### Added +- New package: [RuinedFooocus](https://github.com/runew0lf/RuinedFooocus) +#### Model Browser +- Right clicking anywhere on the model card will open the same menu as the three-dots button +- New model downloads will save trigger words in metadata, if available +- Model author username and avatar display, with clickable link to their profile +#### Checkpoints Page +- Added "Copy Trigger Words" option to the three-dots menu on the Checkpoints page (when data is available) +- Added trigger words on checkpoint card and tooltip +### Changed +#### Model Browser +- Improved number formatting with K/M suffixes for download and favorite counts +- Animated zoom effect on hovering over model images +#### Checkpoints Page +- Rearranged top row layout to use CommandBar +### Fixed +- Improved startup time and window load time after exiting dialogs + +## v2.7.0-dev.2 +### Added +#### General +- Added an X button to all search fields to instantly clear them (Esc key also works) +#### Outputs Page +- Added Refresh button to update gallery from file system changes +#### Checkpoints Page +- Added the ability to drag & drop checkpoints between different folders +## Changed +#### Outputs Page +- Updated button and menu layout +#### Packages Page +- Rearranged Add Package dialog slightly to accommodate longer package list +### Fixed +- Fixed InvalidOperation errors when signing into accounts shortly after signing out, while the previous account update is still running +- Fixed Outputs page reverting back to Shared Output Folder every time the page is reloaded +- Potentially fixed updates sometimes clearing settings or launching in the wrong directory + +## v2.7.0-dev.1 +### Added +- Accounts Settings Subpage + - Lykos Account sign-up and login - currently for Patreon OAuth connections but GitHub requests caching and settings sync are planned + - Supporters can now connect your Patreon accounts, then head to the Updates page to choose to receive auto-updates from the Dev or Preview channels + - CivitAI Account login with API key - enables downloading models from the Browser page that require CivitAI logins, more integrations like liking and commenting are also planned +- Updates Settings Subpage + - Toggle auto-update notifications and manually check for updates + - Choose between Stable, Preview, and Dev update channels +## Changed +- Model Browser page has been redesigned, featuring more information like rating and download counts + ## v2.6.7 ### Fixed - Fixed prerequisite install not unpacking due to improperly formatted 7z argument (Caused the "python310._pth FileNotFoundException") diff --git a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj index 75091167..eb84e3f3 100644 --- a/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj +++ b/StabilityMatrix.Avalonia.Diagnostics/StabilityMatrix.Avalonia.Diagnostics.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 win-x64;linux-x64;osx-x64;osx-arm64 enable enable diff --git a/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs b/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs index 3a729ab2..183538a1 100644 --- a/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs +++ b/StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs @@ -1,22 +1,25 @@ using System; using System.Threading; +using System.Threading.Tasks; using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Styling; using FluentAvalonia.UI.Media.Animation; +using Projektanker.Icons.Avalonia; namespace StabilityMatrix.Avalonia.Animations; public class BetterSlideNavigationTransition : BaseTransitionInfo { public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(167); - + /// /// Gets or sets the type of animation effect to play during the slide transition. /// - public SlideNavigationTransitionEffect Effect { get; set; } = SlideNavigationTransitionEffect.FromRight; + public SlideNavigationTransitionEffect Effect { get; set; } = + SlideNavigationTransitionEffect.FromRight; /// /// Gets or sets the HorizontalOffset used when animating from the Left or Right @@ -27,7 +30,12 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo /// Gets or sets the VerticalOffset used when animating from the Top or Bottom /// public double FromVerticalOffset { get; set; } = 56; - + + /// + /// Gets or sets the easing function applied to the slide transition. + /// + public Easing Easing { get; set; } = new SplineEasing(0.1, 0.9, 0.2, 1.0); + public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken) { double length = 0; @@ -52,24 +60,26 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo var animation = new Animation { - Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0), + Easing = Easing, Children = { new KeyFrame { Setters = { - new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, length), + new Setter( + isVertical + ? TranslateTransform.YProperty + : TranslateTransform.XProperty, + length + ), new Setter(Visual.OpacityProperty, 0d) }, Cue = new Cue(0d) }, new KeyFrame { - Setters= - { - new Setter(Visual.OpacityProperty, 1d) - }, + Setters = { new Setter(Visual.OpacityProperty, 1d) }, Cue = new Cue(0.05d) }, new KeyFrame @@ -77,7 +87,12 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo Setters = { new Setter(Visual.OpacityProperty, 1d), - new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, 0.0) + new Setter( + isVertical + ? TranslateTransform.YProperty + : TranslateTransform.XProperty, + 0.0 + ) }, Cue = new Cue(1d) } @@ -93,4 +108,22 @@ public class BetterSlideNavigationTransition : BaseTransitionInfo visual.Opacity = 1; } } + + public static BetterSlideNavigationTransition PageSlideFromLeft => + new() + { + Duration = TimeSpan.FromMilliseconds(300), + Effect = SlideNavigationTransitionEffect.FromLeft, + FromHorizontalOffset = 150, + Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1) + }; + + public static BetterSlideNavigationTransition PageSlideFromRight => + new() + { + Duration = TimeSpan.FromMilliseconds(300), + Effect = SlideNavigationTransitionEffect.FromRight, + FromHorizontalOffset = 150, + Easing = new SplineEasing(0.6, 0.4, 0.1, 0.1) + }; } diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index f33a4d5c..081ad14b 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -22,6 +22,8 @@ + + @@ -39,6 +41,7 @@ + @@ -62,6 +65,8 @@ + + + diff --git a/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs b/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs new file mode 100644 index 00000000..0f770a96 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/SettingsAccountLinkExpander.axaml.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Metadata; +using Avalonia.VisualTree; +using FluentAvalonia.UI.Controls; +using StabilityMatrix.Core.Processes; + +namespace StabilityMatrix.Avalonia.Controls; + +public class SettingsAccountLinkExpander : TemplatedControl +{ + private readonly List _items = new(); + + [Content] + public List Items => _items; + + // ReSharper disable MemberCanBePrivate.Global + public static readonly StyledProperty HeaderProperty = + HeaderedItemsControl.HeaderProperty.AddOwner(); + + public object? Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + public static readonly StyledProperty HeaderTargetUriProperty = AvaloniaProperty.Register< + SettingsAccountLinkExpander, + Uri? + >("HeaderTargetUri"); + + public Uri? HeaderTargetUri + { + get => GetValue(HeaderTargetUriProperty); + set => SetValue(HeaderTargetUriProperty, value); + } + + public static readonly StyledProperty IconSourceProperty = + SettingsExpander.IconSourceProperty.AddOwner(); + + public IconSource? IconSource + { + get => GetValue(IconSourceProperty); + set => SetValue(IconSourceProperty, value); + } + + public static readonly StyledProperty IsConnectedProperty = AvaloniaProperty.Register< + SettingsAccountLinkExpander, + bool + >("IsConnected"); + + public bool IsConnected + { + get => GetValue(IsConnectedProperty); + set => SetValue(IsConnectedProperty, value); + } + + public static readonly StyledProperty OnDescriptionProperty = + AvaloniaProperty.Register( + "OnDescription", + Languages.Resources.Label_Connected + ); + + public object? OnDescription + { + get => GetValue(OnDescriptionProperty); + set => SetValue(OnDescriptionProperty, value); + } + + public static readonly StyledProperty OnDescriptionExtraProperty = + AvaloniaProperty.Register("OnDescriptionExtra"); + + public object? OnDescriptionExtra + { + get => GetValue(OnDescriptionExtraProperty); + set => SetValue(OnDescriptionExtraProperty, value); + } + + public static readonly StyledProperty OffDescriptionProperty = + AvaloniaProperty.Register("OffDescription"); + + public object? OffDescription + { + get => GetValue(OffDescriptionProperty); + set => SetValue(OffDescriptionProperty, value); + } + + public static readonly StyledProperty ConnectCommandProperty = + AvaloniaProperty.Register( + nameof(ConnectCommand), + enableDataValidation: true + ); + + public ICommand? ConnectCommand + { + get => GetValue(ConnectCommandProperty); + set => SetValue(ConnectCommandProperty, value); + } + + public static readonly StyledProperty DisconnectCommandProperty = + AvaloniaProperty.Register( + nameof(DisconnectCommand), + enableDataValidation: true + ); + + public ICommand? DisconnectCommand + { + get => GetValue(DisconnectCommandProperty); + set => SetValue(DisconnectCommandProperty, value); + } + + /*public static readonly StyledProperty IsLoading2Property = AvaloniaProperty.Register( + nameof(IsLoading2)); + + public bool IsLoading2 + { + get => GetValue(IsLoading2Property); + set => SetValue(IsLoading2Property, value); + }*/ + + private bool _isLoading; + + public static readonly DirectProperty IsLoadingProperty = + AvaloniaProperty.RegisterDirect( + "IsLoading", + o => o.IsLoading, + (o, v) => o.IsLoading = v + ); + + public bool IsLoading + { + get => _isLoading; + set => SetAndRaise(IsLoadingProperty, ref _isLoading, value); + } + + // ReSharper restore MemberCanBePrivate.Global + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + // Bind tapped event on header + if ( + HeaderTargetUri is { } headerTargetUri + && e.NameScope.Find("PART_HeaderTextBlock") is { } headerTextBlock + ) + { + headerTextBlock.Tapped += (_, _) => + { + ProcessRunner.OpenUrl(headerTargetUri.ToString()); + }; + } + + if (e.NameScope.Find("PART_SettingsExpander") is { } expander) + { + expander.ItemsSource = Items; + } + + if (ConnectCommand is { } command) + { + var connectButton = e.NameScope.Get - - - + ClipToBounds="True" + CornerRadius="8"> + + + + + + + + + + - + + + - + + + + + + + + + + + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="info"> + Text="{Binding CivitModel.Type}" /> - + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="info"> + Text="{Binding CivitModel.BaseModelType}" /> - + HorizontalAlignment="Left" + VerticalAlignment="Top" + Classes="success" + IsVisible="{Binding ShowUpdateCard}"> + Text="{Binding UpdateCardText}" /> - + Background="#DD000000" + CornerRadius="8" + IsVisible="{Binding IsImporting}" + ZIndex="1" /> + IsVisible="{Binding IsImporting}" + Orientation="Vertical" + ZIndex="2"> + IsIndeterminate="False" + StartAngle="90" + Value="{Binding Value}" /> + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding Text, TargetNullValue=Importing...}" /> - - - - - - - - + + - - + + + - + + HorizontalAlignment="Stretch" + KeyDown="InputElement_OnKeyDown" + Text="{Binding SearchQuery, Mode=TwoWay}" + Watermark="{x:Static lang:Resources.Label_ModelSearchWatermark}"> + + + + - + - + - + - - + + - + + Content="{x:Static lang:Resources.Label_ShowNsfwContent}" + IsChecked="{Binding ShowNsfw, Mode=TwoWay}" /> - + @@ -304,15 +464,15 @@ + VerticalAlignment="Bottom" + Text="{x:Static lang:Resources.Label_DataProvidedByCivitAi}" /> + HorizontalAlignment="Center" + IsVisible="{Binding HasSearched}" + Orientation="Vertical"> @@ -321,30 +481,31 @@ - @@ -352,19 +513,19 @@ + FontSize="20" + IsVisible="{Binding NoResultsFound}" + Text="{Binding NoResultsText, FallbackValue=No results found}" /> diff --git a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs index 34e0887a..df465d31 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml.cs @@ -1,5 +1,9 @@ -using Avalonia.Markup.Xaml; +using System.Diagnostics; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Views; @@ -16,4 +20,21 @@ public partial class CheckpointBrowserPage : UserControlBase { AvaloniaXamlLoader.Load(this); } + + private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) + { + if (sender is not ScrollViewer scrollViewer) + return; + + var isAtEnd = scrollViewer.Offset == scrollViewer.ScrollBarMaximum; + Debug.WriteLine($"IsAtEnd: {isAtEnd}"); + } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is CheckpointBrowserViewModel viewModel) + { + viewModel.ClearSearchQuery(); + } + } } diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml index 1c8d8d0b..0ae40150 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml @@ -1,20 +1,21 @@  - + 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:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" + xmlns:ui="using:FluentAvalonia.UI.Controls" + xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" + xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" + xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:checkpointManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.CheckpointManager" + xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" + xmlns:avalonia="https://github.com/projektanker/icons.avalonia" + d:DataContext="{x:Static mocks:DesignData.CheckpointsPageViewModel}" + x:CompileBindings="True" + x:DataType="vm:CheckpointsPageViewModel" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650" + x:Class="StabilityMatrix.Avalonia.Views.CheckpointsPage"> + @@ -23,7 +24,7 @@ Color="#FF000000" Opacity="0.2" x:Key="TextDropShadowEffect" /> - + - + - + + + + + + - - - + IsVisible="{Binding IsConnectedModel}" /> + - + + + + + - + + MinWidth="220" + KeyDown="InputElement_OnKeyDown" + Text="{Binding SearchFilter, Mode=TwoWay}"> + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + - + VerticalAlignment="Center" /> - + - - + diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs index ee5c2946..34fcc7aa 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml.cs @@ -1,15 +1,16 @@ using System; using System.Linq; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; using DynamicData.Binding; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.ViewModels; +using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Core.Attributes; -using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Models.FileInterfaces; using CheckpointFolder = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFolder; namespace StabilityMatrix.Avalonia.Views; @@ -61,12 +62,36 @@ public partial class CheckpointsPage : UserControlBase } } - private static async void OnDrop(object? sender, DragEventArgs e) + private async void OnDrop(object? sender, DragEventArgs e) { var sourceDataContext = (e.Source as Control)?.DataContext; - if (sourceDataContext is CheckpointFolder folder) + switch (sourceDataContext) { - await folder.OnDrop(e); + case CheckpointFolder folder: + { + if (e.Data.Get("Context") is not CheckpointFile file) + return; + + var filePath = new FilePath(file.FilePath); + if (filePath.Directory?.FullPath != folder.DirectoryPath) + { + await folder.OnDrop(e); + } + break; + } + case CheckpointFile file: + { + if (e.Data.Get("Context") is not CheckpointFile dragFile) + return; + + var parentFolder = file.ParentFolder; + var dragFilePath = new FilePath(dragFile.FilePath); + if (dragFilePath.Directory?.FullPath != parentFolder.DirectoryPath) + { + await parentFolder.OnDrop(e); + } + break; + } } } @@ -79,7 +104,7 @@ public partial class CheckpointsPage : UserControlBase } } - private static void OnDragEnter(object? sender, DragEventArgs e) + private void OnDragEnter(object? sender, DragEventArgs e) { // Only allow Copy or Link as Drop Operations. e.DragEffects &= DragDropEffects.Copy | DragDropEffects.Link; @@ -92,9 +117,37 @@ public partial class CheckpointsPage : UserControlBase // Forward to view model var sourceDataContext = (e.Source as Control)?.DataContext; - if (sourceDataContext is CheckpointFolder folder) + switch (sourceDataContext) + { + case CheckpointFolder folder: + { + folder.IsExpanded = true; + if (e.Data.Get("Context") is not CheckpointFile file) + return; + + var filePath = new FilePath(file.FilePath); + folder.IsCurrentDragTarget = filePath.Directory?.FullPath != folder.DirectoryPath; + break; + } + case CheckpointFile file: + { + if (e.Data.Get("Context") is not CheckpointFile dragFile) + return; + + var parentFolder = file.ParentFolder; + var dragFilePath = new FilePath(dragFile.FilePath); + parentFolder.IsCurrentDragTarget = + dragFilePath.Directory?.FullPath != parentFolder.DirectoryPath; + break; + } + } + } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is CheckpointsPageViewModel vm) { - folder.IsCurrentDragTarget = true; + vm.ClearSearchQuery(); } } } diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml index 513144cd..2f453464 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml @@ -148,7 +148,6 @@ - @@ -322,8 +321,8 @@ - + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml new file mode 100644 index 00000000..98ab8228 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs new file mode 100644 index 00000000..f5b44579 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/LykosLoginDialog.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Dialogs; + +[Transient] +public partial class LykosLoginDialog : UserControl +{ + public LykosLoginDialog() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml new file mode 100644 index 00000000..e0769f20 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs new file mode 100644 index 00000000..0a32c242 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/OAuthConnectDialog.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Dialogs; + +[Transient] +public partial class OAuthConnectDialog : UserControlBase +{ + public OAuthConnectDialog() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs index 2dd5053f..00ae2a49 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml.cs @@ -34,6 +34,8 @@ public partial class PackageModificationDialog : UserControlBase textMate.SetGrammar(scope); textMate.SetTheme(options.LoadTheme(ThemeName.DarkPlus)); + + editor.Options.ShowBoxForControlCharacters = false; } EventManager.Instance.ScrollToBottomRequested += (_, _) => diff --git a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs index 57fc1c04..971d7c9a 100644 --- a/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using AsyncImageLoader; using Avalonia; using Avalonia.Controls; @@ -26,10 +27,12 @@ using StabilityMatrix.Avalonia.Animations; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Extensions; using StabilityMatrix.Avalonia.Languages; +using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.Update; using StabilityMatrix.Core.Processes; @@ -44,7 +47,7 @@ namespace StabilityMatrix.Avalonia.Views; public partial class MainWindow : AppWindowBase { private readonly INotificationService notificationService; - private readonly INavigationService navigationService; + private readonly INavigationService navigationService; private FlyoutBase? progressFlyout; @@ -58,7 +61,7 @@ public partial class MainWindow : AppWindowBase public MainWindow( INotificationService notificationService, - INavigationService navigationService + INavigationService navigationService ) { this.notificationService = notificationService; @@ -74,6 +77,8 @@ public partial class MainWindow : AppWindowBase TitleBar.ExtendsContentIntoTitleBar = true; TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; + navigationService.TypedNavigation += NavigationService_OnTypedNavigation; + EventManager.Instance.ToggleProgressFlyout += (_, _) => progressFlyout?.Hide(); EventManager.Instance.CultureChanged += (_, _) => SetDefaultFonts(); EventManager.Instance.UpdateAvailable += OnUpdateAvailable; @@ -158,6 +163,15 @@ public partial class MainWindow : AppWindowBase } } + private void NavigationService_OnTypedNavigation(object? sender, TypedNavigationEventArgs e) + { + var mainViewModel = (MainWindowViewModel)DataContext!; + + mainViewModel.SelectedCategory = mainViewModel.Pages + .Concat(mainViewModel.FooterPages) + .FirstOrDefault(x => x.GetType() == e.ViewModelType); + } + private void OnUpdateAvailable(object? sender, UpdateInfo? updateInfo) { Dispatcher.UIThread.Post(() => @@ -170,7 +184,7 @@ public partial class MainWindow : AppWindowBase var tip = this.FindControl("UpdateAvailableTeachingTip")!; tip.Target = target; - tip.Subtitle = $"{Compat.AppVersion} -> {updateInfo.Version}"; + tip.Subtitle = $"{Compat.AppVersion.ToDisplayString()} -> {updateInfo.Version}"; tip.IsOpen = true; } }); diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index 5f2da842..b9f29cfc 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -2,226 +2,243 @@ x:Class="StabilityMatrix.Avalonia.Views.OutputsPage" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit" xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" + xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" - xmlns:ui="using:FluentAvalonia.UI.Controls" - xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" - xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:models1="clr-namespace:StabilityMatrix.Avalonia.Models" - xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" xmlns:outputsPage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage" - xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard" - xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll" + xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard" + xmlns:ui="using:FluentAvalonia.UI.Controls" + xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}" - d:DesignHeight="450" - d:DesignWidth="700" + d:DesignHeight="650" + d:DesignWidth="800" x:CompileBindings="True" x:DataType="vm:OutputsPageViewModel" + Focusable="True" mc:Ignorable="d"> - - - - + + + + + + + + + + - - + - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + - + + VerticalAlignment="Top" + ItemsSource="{Binding Outputs}"> - + + Text="{x:Static lang:Resources.Action_Copy}" /> + Text="{x:Static lang:Resources.Action_OpenInExplorer}" /> + Text="{x:Static lang:Resources.Action_OpenInViewer}" /> + Text="{x:Static lang:Resources.Action_Delete}" /> - + - + + Text="{x:Static lang:Resources.Label_TextToImage}" /> + Text="{x:Static lang:Resources.Label_ImageToImage}" /> + Text="{x:Static lang:Resources.Label_Inpainting}" /> + CommandParameter="{Binding}" + HotKey="{x:Null}" + Text="{x:Static lang:Resources.Label_Upscale}"> - + diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs index 744d9482..b489897b 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs @@ -39,4 +39,12 @@ public partial class OutputsPage : UserControlBase e.Handled = true; } + + private void InputElement_OnKeyDown(object? sender, KeyEventArgs e) + { + if (e.Key == Key.Escape && DataContext is OutputsPageViewModel viewModel) + { + viewModel.ClearSearchQuery(); + } + } } diff --git a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml index 53525002..5cafe300 100644 --- a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml @@ -51,13 +51,17 @@ BorderBrush="#33000000" BorderThickness="2" CornerRadius="8"> - + - - + + - @@ -65,11 +69,11 @@ - - + diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml new file mode 100644 index 00000000..d5073ac8 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs new file mode 100644 index 00000000..0dbc0f50 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/AccountSettingsPage.axaml.cs @@ -0,0 +1,13 @@ +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; + +namespace StabilityMatrix.Avalonia.Views.Settings; + +[Singleton] +public partial class AccountSettingsPage : UserControlBase +{ + public AccountSettingsPage() + { + InitializeComponent(); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml index 428c0483..9eb69ab6 100644 --- a/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/Settings/InferenceSettingsPage.axaml @@ -1,16 +1,147 @@ - - Welcome to Avalonia! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs b/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs new file mode 100644 index 00000000..08dd277b --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Settings/UpdateSettingsPage.axaml.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Avalonia.Controls; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.Models; +using StabilityMatrix.Avalonia.ViewModels.Settings; +using StabilityMatrix.Core.Attributes; +using StabilityMatrix.Core.Models.Update; + +namespace StabilityMatrix.Avalonia.Views.Settings; + +[Singleton] +public partial class UpdateSettingsPage : UserControlBase +{ + public UpdateSettingsPage() + { + InitializeComponent(); + } + + private void ChannelListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + var listBox = (ListBox)sender!; + + if (e.AddedItems.Count == 0 || e.AddedItems[0] is not UpdateChannelCard item) + { + return; + } + + var vm = (UpdateSettingsViewModel)DataContext!; + + if (!vm.VerifyChannelSelection(item)) + { + listBox.Selection.Clear(); + + listBox.Selection.SelectedItem = vm.AvailableUpdateChannelCards.First( + c => c.UpdateChannel == UpdateChannel.Stable + ); + + vm.ShowLoginRequiredDialog(); + } + } +} diff --git a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml index b3e47a2b..a347b077 100644 --- a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml @@ -2,19 +2,14 @@ x:Class="StabilityMatrix.Avalonia.Views.SettingsPage" xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia" xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels" - xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" - xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters" - xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" - xmlns:inference="clr-namespace:StabilityMatrix.Avalonia.Models.Inference" - xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight" + xmlns:vmBase="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base" + xmlns:local="clr-namespace:StabilityMatrix.Avalonia" Focusable="True" d:DataContext="{x:Static mocks:DesignData.SettingsViewModel}" d:DesignHeight="700" @@ -24,513 +19,36 @@ mc:Ignorable="d"> - + + + 24 + 17 + 6,3 + Medium + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -