Browse Source

Added ctrl+scroll for zoom to inference & outputs image browsers

pull/240/head
JT 1 year ago
parent
commit
4fa10cb27e
  1. 2
      CHANGELOG.md
  2. 3
      StabilityMatrix.Avalonia/App.axaml
  3. 23
      StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml
  4. 40
      StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml.cs
  5. 14
      StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollContentPresenter.cs
  6. 69
      StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.axaml
  7. 5
      StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.cs
  8. 6
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
  9. 37
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.cs
  10. 54
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  11. 2
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  12. 2
      StabilityMatrix.Avalonia/Languages/Resources.resx
  13. 6
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs
  14. 2
      StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs
  15. 13
      StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs
  16. 12
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  17. 18
      StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml
  18. 38
      StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml
  19. 14
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  20. 31
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs
  21. 3
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  22. 6
      StabilityMatrix.Core/Models/Settings/Settings.cs
  23. 10
      StabilityMatrix.Core/Models/Settings/Size.cs
  24. 3
      StabilityMatrix.Core/Services/ISettingsManager.cs
  25. 45
      StabilityMatrix.Core/Services/SettingsManager.cs

2
CHANGELOG.md

@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2
- Added filtering of "incompatible" packages (ones that do not support your GPU) to all installers
- This can be overridden by checking the new "Show All Packages" checkbox
- Added more launch options for Fooocus, such as the `--preset` option
- Added Ctrl+ScrollWheel to change image size in the inference output gallery and new Outputs page
- Added "No Images Found" placeholder for non-connected models on the Checkpoints tab
### Changed
- If ComfyUI for Inference is chosen during the One-Click Installer, the Inference page will be opened after installation instead of the Launch page
- Changed all package installs & updates to use git commands instead of downloading zip files

3
StabilityMatrix.Avalonia/App.axaml

@ -20,6 +20,8 @@
<ResourceInclude Source="Controls/CodeCompletion/CompletionListThemes.axaml"/>
<ResourceInclude Source="Styles/ContextMenuStyles.axaml"/>
<ResourceInclude Source="Controls/EditorFlyouts.axaml"/>
<ResourceInclude Source="Controls/Scroll/BetterScrollViewer.axaml"/>
<ResourceInclude Source="Controls/ImageFolderCard.axaml"/>
</ResourceDictionary.MergedDictionaries>
<idcr:ControlRecycling x:Key="ControlRecyclingKey" />
@ -56,7 +58,6 @@
<StyleInclude Source="Controls/FrameCarousel.axaml"/>
<StyleInclude Source="Controls/CodeCompletion/CompletionWindow.axaml"/>
<StyleInclude Source="Controls/SelectImageCard.axaml"/>
<StyleInclude Source="Controls/ImageFolderCard.axaml"/>
<StyleInclude Source="Controls/SharpenCard.axaml"/>
<StyleInclude Source="Controls/FreeUCard.axaml"/>
<StyleInclude Source="Controls/Paginator.axaml"/>

23
StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml

@ -1,4 +1,4 @@
<Styles
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:StabilityMatrix.Avalonia.Controls"
@ -8,6 +8,7 @@
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vmInference="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Inference"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
x:DataType="vmInference:ImageFolderCardViewModel">
<Design.PreviewWith>
@ -18,7 +19,8 @@
</Panel>
</Design.PreviewWith>
<Style Selector="controls|ImageFolderCard">
<ControlTheme x:Key="{x:Type controls:ImageFolderCard}" TargetType="controls:ImageFolderCard">
<Setter Property="Background" Value="Transparent" />
<!-- Set Defaults -->
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Focusable" Value="True" />
@ -54,13 +56,15 @@
</TextBox.InnerRightContent>
</TextBox>
<ScrollViewer
<scroll:BetterScrollViewer
Grid.Row="1"
BringIntoViewOnFocusChange="False"
HorizontalScrollBarVisibility="Disabled"
IsScrollInertiaEnabled="True"
VerticalSnapPointsType="Mandatory">
<ItemsRepeater
Name="ImageRepeater"
x:Name="ImageRepeater"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
ItemsSource="{Binding LocalImages}"
@ -198,8 +202,8 @@
<Border ClipToBounds="True" CornerRadius="8">
<Grid RowDefinitions="*,Auto">
<controls:BetterAdvancedImage
Width="150"
Height="190"
Width="{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Width}"
Height="{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Height}"
Source="{Binding AbsolutePath}"
Stretch="UniformToFill"
StretchDirection="Both" />
@ -207,7 +211,7 @@
<Border
Grid.Row="1"
MinHeight="20"
MaxWidth="150"
Width="{Binding $parent[ItemsRepeater].((vmInference:ImageFolderCardViewModel)DataContext).ImageSize.Width}"
Padding="4,0,0,0"
VerticalAlignment="Bottom"
Classes="theme-dark"
@ -216,6 +220,7 @@
VerticalAlignment="Center"
FontSize="12"
Text="{Binding FileNameWithoutExtension}"
TextAlignment="Center"
TextTrimming="CharacterEllipsis" />
</Border>
</Grid>
@ -224,10 +229,10 @@
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</scroll:BetterScrollViewer>
</Grid>
</controls:Card>
</ControlTemplate>
</Setter>
</Style>
</Styles>
</ControlTheme>
</ResourceDictionary>

40
StabilityMatrix.Avalonia/Controls/ImageFolderCard.axaml.cs

@ -1,11 +1,23 @@
using Avalonia.Input;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models.Settings;
namespace StabilityMatrix.Avalonia.Controls;
[Transient]
public class ImageFolderCard : DropTargetTemplatedControlBase
{
private ItemsRepeater? imageRepeater;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
imageRepeater = e.NameScope.Find<ItemsRepeater>("ImageRepeater");
base.OnApplyTemplate(e);
}
/// <inheritdoc />
protected override void DropHandler(object? sender, DragEventArgs e)
{
@ -19,4 +31,30 @@ public class ImageFolderCard : DropTargetTemplatedControlBase
base.DragOverHandler(sender, e);
e.Handled = true;
}
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (e.KeyModifiers != KeyModifiers.Control)
return;
if (DataContext is not ImageFolderCardViewModel vm)
return;
if (e.Delta.Y > 0)
{
if (vm.ImageSize.Height >= 500)
return;
vm.ImageSize += new Size(15, 19);
}
else
{
if (vm.ImageSize.Height <= 200)
return;
vm.ImageSize -= new Size(15, 19);
}
imageRepeater?.InvalidateArrange();
imageRepeater?.InvalidateMeasure();
e.Handled = true;
}
}

14
StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollContentPresenter.cs

@ -0,0 +1,14 @@
using Avalonia.Controls.Presenters;
using Avalonia.Input;
namespace StabilityMatrix.Avalonia.Controls.Scroll;
public class BetterScrollContentPresenter : ScrollContentPresenter
{
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
if (e.KeyModifiers == KeyModifiers.Control)
return;
base.OnPointerWheelChanged(e);
}
}

69
StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.axaml

@ -0,0 +1,69 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll">
<Design.PreviewWith>
<scroll:BetterScrollViewer Width="200" Height="200"
HorizontalScrollBarVisibility="Auto">
<StackPanel Spacing="20" Width="210">
<TextBlock>Item 1</TextBlock>
<TextBlock>Item 2</TextBlock>
<TextBlock>Item 3</TextBlock>
<TextBlock>Item 4</TextBlock>
<TextBlock>Item 5</TextBlock>
<TextBlock>Item 6</TextBlock>
<TextBlock>Item 7</TextBlock>
<TextBlock>Item 8</TextBlock>
<TextBlock>Item 9</TextBlock>
</StackPanel>
</scroll:BetterScrollViewer>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type scroll:BetterScrollViewer}" TargetType="scroll:BetterScrollViewer">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<scroll:BetterScrollContentPresenter x:Name="PART_ContentPresenter"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
HorizontalSnapPointsType="{TemplateBinding HorizontalSnapPointsType}"
VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}"
HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}"
VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}"
Padding="{TemplateBinding Padding}"
ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}">
<scroll:BetterScrollContentPresenter.GestureRecognizers>
<ScrollGestureRecognizer
CanHorizontallyScroll="{Binding CanHorizontallyScroll, ElementName=PART_ContentPresenter}"
CanVerticallyScroll="{Binding CanVerticallyScroll, ElementName=PART_ContentPresenter}"
IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}" />
</scroll:BetterScrollContentPresenter.GestureRecognizers>
</scroll:BetterScrollContentPresenter>
<ScrollBar Name="PART_HorizontalScrollBar"
Orientation="Horizontal"
Grid.Row="1" />
<ScrollBar Name="PART_VerticalScrollBar"
Orientation="Vertical"
Grid.Column="1" />
<Panel x:Name="PART_ScrollBarsSeparator"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource ScrollViewerScrollBarsSeparatorBackground}"
Opacity="0">
<Panel.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.1" />
</Transitions>
</Panel.Transitions>
</Panel>
</Grid>
</ControlTemplate>
</Setter>
<Style Selector="^[IsExpanded=true] /template/ Panel#PART_ScrollBarsSeparator">
<Setter Property="Opacity" Value="1" />
</Style>
</ControlTheme>
</ResourceDictionary>

5
StabilityMatrix.Avalonia/Controls/Scroll/BetterScrollViewer.cs

@ -0,0 +1,5 @@
using Avalonia.Controls;
namespace StabilityMatrix.Avalonia.Controls.Scroll;
public class BetterScrollViewer : ScrollViewer { }

6
StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml

@ -14,7 +14,9 @@
<Style Selector="selectableImageCard|SelectableImageButton">
<Setter Property="Template">
<ControlTemplate>
<Grid>
<Grid
Width="{Binding ImageWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Height="{Binding ImageHeight, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<CheckBox VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="14,8"
@ -37,8 +39,6 @@
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}">
<controls:BetterAdvancedImage
Width="300"
Height="300"
Stretch="UniformToFill"
CornerRadius="8"
ContextFlyout="{TemplateBinding ContextFlyout}"

37
StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.cs

@ -1,15 +1,46 @@
using Avalonia;
using System;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace StabilityMatrix.Avalonia.Controls.SelectableImageCard;
public class SelectableImageButton : Button
{
public static readonly StyledProperty<bool?> IsSelectedProperty =
CheckBox.IsCheckedProperty.AddOwner<SelectableImageButton>();
ToggleButton.IsCheckedProperty.AddOwner<SelectableImageButton>();
public static readonly StyledProperty<string?> SourceProperty =
BetterAdvancedImage.SourceProperty.AddOwner<SelectableImageButton>();
AdvancedImage.SourceProperty.AddOwner<SelectableImageButton>();
public static readonly StyledProperty<double> ImageWidthProperty = AvaloniaProperty.Register<
SelectableImageButton,
double
>("ImageWidth", 300);
public static readonly StyledProperty<double> ImageHeightProperty = AvaloniaProperty.Register<
SelectableImageButton,
double
>("ImageHeight", 300);
static SelectableImageButton()
{
AffectsRender<SelectableImageButton>(ImageWidthProperty, ImageHeightProperty);
AffectsArrange<SelectableImageButton>(ImageWidthProperty, ImageHeightProperty);
}
public double ImageHeight
{
get => GetValue(ImageHeightProperty);
set => SetValue(ImageHeightProperty, value);
}
public double ImageWidth
{
get => GetValue(ImageWidthProperty);
set => SetValue(ImageWidthProperty, value);
}
public bool? IsSelected
{

54
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -6,6 +6,7 @@ using System.IO;
using System.Net.Http;
using System.Text;
using AvaloniaEdit.Utils;
using DynamicData;
using DynamicData.Binding;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Controls.CodeCompletion;
@ -173,6 +174,55 @@ public static class DesignData
InstallerViewModel.SelectedPackage = InstallerViewModel.AvailablePackages[0];
InstallerViewModel.ReleaseNotes = "## Release Notes\nThis is a test release note.";
ObservableCacheEx.AddOrUpdate(
CheckpointsPageViewModel.CheckpointFoldersCache,
new CheckpointFolder[]
{
new(settingsManager, downloadService, modelFinder, notificationService)
{
DirectoryPath = "Models/StableDiffusion",
DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>()
{
new()
{
FilePath = "~/Models/StableDiffusion/electricity-light.safetensors",
Title = "Auroral Background",
PreviewImagePath =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/"
+ "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg",
ConnectedModel = new ConnectedModelInfo
{
VersionName = "Lightning Auroral",
BaseModel = "SD 1.5",
ModelName = "Auroral Background",
ModelType = CivitModelType.Model,
FileMetadata = new CivitFileMetadata
{
Format = CivitModelFormat.SafeTensor,
Fp = CivitModelFpType.fp16,
Size = CivitModelSize.pruned,
}
}
},
new()
{
FilePath = "~/Models/Lora/model.safetensors",
Title = "Some model"
},
},
},
new(settingsManager, downloadService, modelFinder, notificationService)
{
Title = "Lora",
DirectoryPath = "Packages/Lora",
DisplayedCheckpointFiles = new ObservableCollectionExtended<CheckpointFile>
{
new() { FilePath = "~/Models/Lora/lora_v2.pt", Title = "Best Lora v2", }
}
}
}
);
/*// Checkpoints page
CheckpointsPageViewModel.CheckpointFolders =
new CheckpointFolder[]
@ -618,8 +668,8 @@ The gallery images are often inpainted, but you will get something very similar
get
{
var list = new CompletionList { IsFiltering = true };
list.CompletionData.AddRange(SampleCompletionData);
list.FilteredCompletionData.AddRange(list.CompletionData);
ExtensionMethods.AddRange(list.CompletionData, SampleCompletionData);
ExtensionMethods.AddRange(list.FilteredCompletionData, list.CompletionData);
list.SelectItem("te", true);
return list;
}

2
StabilityMatrix.Avalonia/Languages/Resources.Designer.cs generated

@ -942,7 +942,7 @@ namespace StabilityMatrix.Avalonia.Languages {
}
/// <summary>
/// Looks up a localized string similar to Import as Connected.
/// Looks up a localized string similar to Import with Metadata.
/// </summary>
public static string Label_ImportAsConnected {
get {

2
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -379,7 +379,7 @@
<value>Drop file here to import</value>
</data>
<data name="Label_ImportAsConnected" xml:space="preserve">
<value>Import as Connected</value>
<value>Import with Metadata</value>
</data>
<data name="Label_ImportAsConnectedExplanation" xml:space="preserve">
<value>Search for connected metadata on new local imports</value>

6
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFile.cs

@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using NLog;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Extensions;
@ -260,6 +261,11 @@ public partial class CheckpointFile : ViewModelBase
.Where(File.Exists)
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(checkpointFile.PreviewImagePath))
{
checkpointFile.PreviewImagePath = Assets.NoImage.ToString();
}
yield return checkpointFile;
}
}

2
StabilityMatrix.Avalonia/ViewModels/CheckpointManager/CheckpointFolder.cs

@ -102,7 +102,7 @@ public partial class CheckpointFolder : ViewModelBase
public IObservableCollection<CheckpointFile> CheckpointFiles { get; } =
new ObservableCollectionExtended<CheckpointFile>();
public IObservableCollection<CheckpointFile> DisplayedCheckpointFiles { get; } =
public IObservableCollection<CheckpointFile> DisplayedCheckpointFiles { get; set; } =
new ObservableCollectionExtended<CheckpointFile>();
public CheckpointFolder(

13
StabilityMatrix.Avalonia/ViewModels/Inference/ImageFolderCardViewModel.cs

@ -25,6 +25,7 @@ using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Settings;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using SortDirection = DynamicData.Binding.SortDirection;
@ -44,6 +45,9 @@ public partial class ImageFolderCardViewModel : ViewModelBase
[ObservableProperty]
private string? searchQuery;
[ObservableProperty]
private Size imageSize = new(150, 190);
/// <summary>
/// Collection of local image files
/// </summary>
@ -76,6 +80,13 @@ public partial class ImageFolderCardViewModel : ViewModelBase
.SortBy(file => file.LastModifiedAt, SortDirection.Descending)
.Bind(LocalImages)
.Subscribe();
settingsManager.RelayPropertyFor(
this,
vm => vm.ImageSize,
settings => settings.InferenceImageSize,
delay: TimeSpan.FromMilliseconds(250)
);
}
private static bool SearchPredicate(LocalImageFile file, string? query)
@ -117,7 +128,7 @@ public partial class ImageFolderCardViewModel : ViewModelBase
public override async Task OnLoadedAsync()
{
await base.OnLoadedAsync();
ImageSize = settingsManager.Settings.InferenceImageSize;
imageIndexService.RefreshIndexForAllCollections().SafeFireAndForget();
}

12
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -31,6 +31,7 @@ using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Size = StabilityMatrix.Core.Models.Settings.Size;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
@ -75,6 +76,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private string searchQuery;
[ObservableProperty]
private Size imageSize = new(300, 300);
public bool CanShowOutputTypes =>
SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
@ -132,6 +136,13 @@ public partial class OutputsPageViewModel : PageViewModelBase
{
NumItemsSelected = Outputs.Count(o => o.IsSelected);
});
settingsManager.RelayPropertyFor(
this,
vm => vm.ImageSize,
settings => settings.OutputsImageSize,
delay: TimeSpan.FromMilliseconds(250)
);
}
public override void OnLoaded()
@ -172,6 +183,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
SelectedCategory = Categories.First();
SelectedOutputType = SharedOutputType.All;
SearchQuery = string.Empty;
ImageSize = settingsManager.Settings.OutputsImageSize;
var path =
CanShowOutputTypes && SelectedOutputType != SharedOutputType.All

18
StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

@ -21,7 +21,7 @@
<controls:Card
Margin="8"
MaxHeight="450"
CornerRadius="8"
CornerRadius="12"
Name="ModelCard"
Width="330">
@ -66,6 +66,7 @@
<controls:BetterAdvancedImage
Margin="0,8,0,8"
Height="300"
Width="300"
StretchDirection="Both"
CornerRadius="8"
VerticalContentAlignment="Top"
@ -150,6 +151,8 @@
</StackPanel>
<Rectangle
Fill="#DD000000"
RadiusX="8"
RadiusY="8"
HorizontalAlignment="Stretch"
Margin="0,8,0,8"
VerticalAlignment="Stretch"
@ -177,16 +180,17 @@
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Classes="accent"
Margin="0,8,0,0"
Command="{Binding ImportCommand}"
CommandParameter="{Binding CivitModel}"
IsEnabled="{Binding !IsImporting}"
HorizontalAlignment="Stretch"
Margin="0,8,0,0">
CornerRadius="6"
HorizontalAlignment="Stretch">
<Button.Content>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{x:Static lang:Resources.Label_ImportLatest}" />
@ -197,6 +201,7 @@
</Button>
<Button Grid.Column="1"
CornerRadius="6"
Margin="8,8,0,0"
Classes="accent"
IsEnabled="{Binding !IsImporting}"
@ -244,7 +249,7 @@
</Button>
</Grid>
<DockPanel>
<StackPanel Margin="4, 8" Orientation="Vertical">
<StackPanel Margin="8, 8,4,8" Orientation="Vertical">
<Label Content="{x:Static lang:Resources.Label_Sort}" />
<ComboBox
ItemsSource="{Binding AllSortModes}"
@ -286,7 +291,8 @@
</StackPanel>
<ScrollViewer Grid.Row="1">
<ScrollViewer Grid.Row="1"
Margin="8,0,8,0">
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}"
ItemsSource="{Binding ModelCards}">
<ItemsRepeater.Layout>

38
StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml

@ -30,7 +30,7 @@
Height="18"
Margin="4,0,0,0"
Padding="3"
Width="40">
Width="48">
<TextBlock
FontSize="10"
FontWeight="Medium"
@ -47,9 +47,10 @@
DragDrop.AllowDrop="False"
Background="Transparent"
BorderThickness="2"
Margin="8">
Margin="4">
<controls:Card Width="260">
<controls:Card MaxWidth="330" Width="330"
CornerRadius="12">
<!-- Right click menu for a checkpoint file -->
<controls:Card.ContextFlyout>
<ui:FAMenuFlyout>
@ -65,9 +66,9 @@
<Grid>
<!-- Main contents, hidden when IsLoading is true -->
<StackPanel MinHeight="70">
<Grid ColumnDefinitions="*,*,*,*,*" RowDefinitions="*,Auto,0.1*" IsVisible="{Binding !IsLoading}">
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto,*,Auto" IsVisible="{Binding !IsLoading}">
<StackPanel
Grid.ColumnSpan="4"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
IsEnabled="True"
Margin="0,0,0,4"
@ -121,15 +122,17 @@
</StackPanel>
<controls:BetterAdvancedImage Grid.Row="1"
Grid.Column="0" Grid.ColumnSpan="5"
Margin="0,0,0,4"
CornerRadius="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0, 4,0,4"
CornerRadius="8"
Source="{Binding PreviewImagePath}"
Stretch="UniformToFill"
MaxHeight="250"
Height="300"
Width="300"
IsVisible="{Binding $parent[ItemsControl].((vm:CheckpointsPageViewModel)DataContext).ShowConnectedModelImages}" />
<Grid Grid.Column="4" Grid.Row="0">
<Grid Grid.Column="1" Grid.Row="0">
<Button
Background="Transparent"
BorderBrush="Transparent"
@ -169,7 +172,6 @@
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="2"
IsEnabled="True"
Margin="4,4"
@ -178,15 +180,15 @@
ToolTip.Tip="{Binding FileName}"
VerticalAlignment="Bottom" />
<ItemsRepeater
Grid.Column="3"
Grid.ColumnSpan="2"
Grid.Column="1"
Grid.Row="2"
HorizontalAlignment="Right"
ItemTemplate="{StaticResource BadgeTemplate}"
ItemsSource="{Binding Badges}"
VerticalAlignment="Bottom">
<ItemsRepeater.Layout>
<UniformGridLayout/>
<UniformGridLayout />
</ItemsRepeater.Layout>
</ItemsRepeater>
</Grid>
@ -213,8 +215,9 @@
<!-- Checkpoint Folder Expander -->
<DataTemplate DataType="{x:Type checkpointManager:CheckpointFolder}" x:Key="CheckpointFolderGridDataTemplate">
<Expander
CornerRadius="8"
IsExpanded="{Binding IsExpanded}"
Margin="8"
Margin="4"
Padding="8,8,8,8"
IsVisible="{Binding IsCategoryEnabled, FallbackValue=True}">
@ -268,7 +271,8 @@
ItemTemplate="{StaticResource CheckpointFileDataTemplate}"
ItemsSource="{Binding DisplayedCheckpointFiles}">
<ItemsRepeater.Layout>
<UniformGridLayout Orientation="Horizontal"/>
<UniformGridLayout Orientation="Horizontal"
MinColumnSpacing="4" MinRowSpacing="4"/>
</ItemsRepeater.Layout>
<TextBlock Text="Hi"/>
</ItemsRepeater>
@ -280,7 +284,7 @@
IsVisible="{Binding !CheckpointFiles.Count}"/>
<!-- Blurred background for drag and drop -->
<Border
CornerRadius="8"
CornerRadius="4"
Grid.RowSpan="4"
IsEnabled="False"
IsVisible="{Binding IsDragBlurEnabled}">

14
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -15,6 +15,7 @@
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"
d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}"
d:DesignHeight="450"
d:DesignWidth="700"
@ -32,7 +33,6 @@
Margin="4" />
<ComboBox Grid.Column="0"
Grid.Row="1" ItemsSource="{Binding Categories}"
CornerRadius="8"
Margin="4,0"
SelectedItem="{Binding SelectedCategory}"
VerticalAlignment="Stretch"
@ -62,7 +62,6 @@
IsVisible="{Binding CanShowOutputTypes}" />
<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding OutputTypes}"
IsVisible="{Binding CanShowOutputTypes}"
CornerRadius="8"
SelectedItem="{Binding SelectedOutputType}"
MinWidth="150"
Margin="4,0"
@ -94,7 +93,6 @@
<Button Grid.Row="1"
Grid.Column="5"
VerticalAlignment="Bottom"
CornerRadius="8"
Padding="12, 0"
Height="40"
Classes="danger"
@ -107,7 +105,6 @@
Grid.Column="6"
Content="{x:Static lang:Resources.Action_ClearSelection}"
VerticalAlignment="Bottom"
CornerRadius="8"
Margin="8, 0"
Height="40"
Command="{Binding ClearSelection}"
@ -117,15 +114,16 @@
Content="{x:Static lang:Resources.Action_SelectAll}"
VerticalAlignment="Bottom"
Classes="accent"
CornerRadius="8"
Margin="8, 0"
Height="40"
Command="{Binding SelectAll}"
IsVisible="{Binding !NumItemsSelected}" />
</Grid>
<ScrollViewer Grid.Row="1">
<scroll:BetterScrollViewer Grid.Row="1"
PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<ItemsRepeater
x:Name="ImageRepeater"
ItemsSource="{Binding Outputs}"
VerticalAlignment="Top">
<ItemsRepeater.Layout>
@ -134,6 +132,8 @@
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type outputsPage:OutputImageViewModel}">
<selectableImageCard:SelectableImageButton
ImageHeight="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Height}"
ImageWidth="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Width}"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).OnImageClick}"
CommandParameter="{Binding }"
IsSelected="{Binding IsSelected}"
@ -208,6 +208,6 @@
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</scroll:BetterScrollViewer>
</Grid>
</controls:UserControlBase>

31
StabilityMatrix.Avalonia/Views/OutputsPage.axaml.cs

@ -1,5 +1,8 @@
using StabilityMatrix.Avalonia.Controls;
using Avalonia.Input;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models.Settings;
namespace StabilityMatrix.Avalonia.Views;
@ -10,4 +13,30 @@ public partial class OutputsPage : UserControlBase
{
InitializeComponent();
}
private void ScrollViewer_MouseWheelChanged(object? sender, PointerWheelEventArgs e)
{
if (e.KeyModifiers != KeyModifiers.Control)
return;
if (DataContext is not OutputsPageViewModel vm)
return;
if (e.Delta.Y > 0)
{
if (vm.ImageSize.Height >= 500)
return;
vm.ImageSize += new Size(10, 10);
}
else
{
if (vm.ImageSize.Height <= 200)
return;
vm.ImageSize -= new Size(10, 10);
}
ImageRepeater.InvalidateArrange();
ImageRepeater.InvalidateMeasure();
e.Handled = true;
}
}

3
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -31,7 +31,8 @@
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type packageManager:PackageCardViewModel}">
<controls:Card Padding="8">
<controls:Card Padding="8"
CornerRadius="8">
<Grid RowDefinitions="Auto, Auto, Auto, Auto"
ColumnDefinitions="*,Auto">

6
StabilityMatrix.Core/Models/Settings/Settings.cs

@ -1,4 +1,5 @@
using System.Globalization;
using System.Drawing;
using System.Globalization;
using System.Text.Json.Serialization;
using Semver;
using StabilityMatrix.Core.Converters.Json;
@ -93,6 +94,9 @@ public class Settings
public HashSet<int> FavoriteModels { get; set; } = new();
public Size InferenceImageSize { get; set; } = new(150, 190);
public Size OutputsImageSize { get; set; } = new(300, 300);
public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)
{
RemoveInstalledPackageAndUpdateActive(package.Id);

10
StabilityMatrix.Core/Models/Settings/Size.cs

@ -0,0 +1,10 @@
namespace StabilityMatrix.Core.Models.Settings;
public record struct Size(double Width, double Height)
{
public static Size operator +(Size current, Size other) =>
new(current.Width + other.Width, current.Height + other.Height);
public static Size operator -(Size current, Size other) =>
new(current.Width - other.Width, current.Height - other.Height);
}

3
StabilityMatrix.Core/Services/ISettingsManager.cs

@ -69,7 +69,8 @@ public interface ISettingsManager
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty,
bool setInitial = false
bool setInitial = false,
TimeSpan? delay = null
)
where T : INotifyPropertyChanged;

45
StabilityMatrix.Core/Services/SettingsManager.cs

@ -165,7 +165,8 @@ public class SettingsManager : ISettingsManager
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty,
bool setInitial = false
bool setInitial = false,
TimeSpan? delay = null
)
where T : INotifyPropertyChanged
{
@ -217,7 +218,14 @@ public class SettingsManager : ISettingsManager
if (IsLibraryDirSet)
{
SaveSettingsAsync().SafeFireAndForget();
if (delay != null)
{
SaveSettingsDelayed(delay.Value).SafeFireAndForget();
}
else
{
SaveSettingsAsync().SafeFireAndForget();
}
}
else
{
@ -656,4 +664,37 @@ public class SettingsManager : ISettingsManager
{
return Task.Run(SaveSettings);
}
private CancellationTokenSource? delayedSaveCts;
private Task SaveSettingsDelayed(TimeSpan delay)
{
var cts = new CancellationTokenSource();
var oldCancellationToken = Interlocked.Exchange(ref delayedSaveCts, cts);
try
{
oldCancellationToken?.Cancel();
}
catch (ObjectDisposedException) { }
return Task.Run(
async () =>
{
try
{
await Task.Delay(delay, cts.Token);
await SaveSettingsAsync();
}
catch (TaskCanceledException) { }
finally
{
cts.Dispose();
}
},
CancellationToken.None
);
}
}

Loading…
Cancel
Save