Browse Source

faster outputs page with Task.Run & avalonia.labs async image

pull/629/head
JT 9 months ago
parent
commit
b394e5b701
  1. 2
      StabilityMatrix.Avalonia/App.axaml
  2. 5
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
  3. 8
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.cs
  4. 70
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  5. 43
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  6. 2
      StabilityMatrix.Core/Models/Database/LocalImageFile.cs

2
StabilityMatrix.Avalonia/App.axaml

@ -4,6 +4,7 @@
xmlns:local="using:StabilityMatrix.Avalonia"
xmlns:idcr="using:Dock.Avalonia.Controls.Recycling"
xmlns:styling="clr-namespace:FluentAvalonia.Styling;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
Name="Stability Matrix"
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
@ -80,6 +81,7 @@
<StyleInclude Source="Controls/Inference/FreeUCard.axaml"/>
<StyleInclude Source="Controls/Inference/ControlNetCard.axaml"/>
<StyleInclude Source="Controls/Inference/PromptExpansionCard.axaml"/>
<controls:ControlThemes/>
<Style Selector="DockControl">
<Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />

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

@ -1,7 +1,8 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls">
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
@ -38,7 +39,7 @@
CornerRadius="12"
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}">
<controls:BetterAdvancedImage
<controls1:AsyncImage
Stretch="UniformToFill"
CornerRadius="8"
ContextFlyout="{TemplateBinding ContextFlyout}"

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

@ -11,8 +11,10 @@ public class SelectableImageButton : Button
public static readonly StyledProperty<bool?> IsSelectedProperty =
ToggleButton.IsCheckedProperty.AddOwner<SelectableImageButton>();
public static readonly StyledProperty<string?> SourceProperty =
AdvancedImage.SourceProperty.AddOwner<SelectableImageButton>();
public static readonly StyledProperty<Uri?> SourceProperty = AvaloniaProperty.Register<
SelectableImageButton,
Uri?
>("Source");
public static readonly StyledProperty<double> ImageWidthProperty = AvaloniaProperty.Register<
SelectableImageButton,
@ -48,7 +50,7 @@ public class SelectableImageButton : Button
set => SetValue(IsSelectedProperty, value);
}
public string? Source
public Uri? Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);

70
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader;
@ -28,8 +31,6 @@ using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Exceptions;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
@ -86,6 +87,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private bool isConsolidating;
[ObservableProperty]
private bool isLoading;
public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected =>
@ -95,6 +99,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
private string[] allowedExtensions = [".png", ".webp"];
private PackageOutputCategory? lastOutputCategory;
private CancellationTokenSource getOutputsTokenSource = new();
public OutputsPageViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
@ -113,7 +120,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
// Observable predicate from SearchQuery changes
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchQuery)
.Throttle(TimeSpan.FromMilliseconds(50))!
.Throttle(TimeSpan.FromMilliseconds(100))!
.Select(property => searcher.GetPredicate(property.Value))
.AsObservable();
@ -122,9 +129,14 @@ public partial class OutputsPageViewModel : PageViewModelBase
.DeferUntilLoaded()
.Filter(searchPredicate)
.Transform(file => new OutputImageViewModel(file))
.SortBy(vm => vm.ImageFile.CreatedAt, SortDirection.Descending)
.Sort(
SortExpressionComparer<OutputImageViewModel>
.Descending(vm => vm.ImageFile.CreatedAt)
.ThenByDescending(vm => vm.ImageFile.FileName)
)
.Bind(Outputs)
.WhenPropertyChanged(p => p.IsSelected)
.Throttle(TimeSpan.FromMilliseconds(50))
.Subscribe(_ =>
{
NumItemsSelected = Outputs.Count(o => o.IsSelected);
@ -159,6 +171,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
SelectedOutputType ??= SharedOutputType.All;
SearchQuery = string.Empty;
ImageSize = settingsManager.Settings.OutputsImageSize;
lastOutputCategory = SelectedCategory;
var path =
CanShowOutputTypes && SelectedOutputType != SharedOutputType.All
@ -169,7 +182,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
partial void OnSelectedCategoryChanged(PackageOutputCategory? oldValue, PackageOutputCategory? newValue)
{
if (oldValue == newValue || newValue == null)
if (oldValue == newValue || oldValue == null || newValue == null)
return;
var path =
@ -177,11 +190,12 @@ public partial class OutputsPageViewModel : PageViewModelBase
? Path.Combine(newValue.Path, SelectedOutputType.ToString())
: SelectedCategory.Path;
GetOutputs(path);
lastOutputCategory = newValue;
}
partial void OnSelectedOutputTypeChanged(SharedOutputType? oldValue, SharedOutputType? newValue)
{
if (oldValue == newValue || newValue == null)
if (oldValue == newValue || oldValue == null || newValue == null)
return;
var path =
@ -262,8 +276,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
public void Refresh()
{
Dispatcher.UIThread.Post(() => RefreshCategories());
Dispatcher.UIThread.Post(OnLoaded);
Dispatcher.UIThread.Post(RefreshCategories);
OnLoaded();
}
public async Task DeleteImage(OutputImageViewModel? item)
@ -517,20 +531,38 @@ public partial class OutputsPageViewModel : PageViewModelBase
return;
}
var files = Directory
.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
.Where(path => allowedExtensions.Contains(new FilePath(path).Extension))
.Select(file => LocalImageFile.FromPath(file))
.ToList();
if (files.Count == 0)
if (lastOutputCategory?.Path.Equals(directory) is not true)
{
OutputsCache.Clear();
}
else
{
OutputsCache.EditDiff(files);
}
getOutputsTokenSource.Cancel();
getOutputsTokenSource = new CancellationTokenSource();
IsLoading = true;
Task.Run(
() =>
{
var sw = Stopwatch.StartNew();
var files = Directory
.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
.Where(file => allowedExtensions.Contains(new FilePath(file).Extension))
.Select(file => LocalImageFile.FromPath(file));
OutputsCache.EditDiff(
files,
(oldItem, newItem) => oldItem.AbsolutePath == newItem.AbsolutePath
);
sw.Stop();
Console.WriteLine(
$"Finished with {OutputsCache.Count} files, took {sw.Elapsed.TotalMilliseconds}ms"
);
Dispatcher.UIThread.Post(() => IsLoading = false);
},
getOutputsTokenSource.Token
);
}
private void RefreshCategories()

43
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -16,6 +16,8 @@
xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}"
d:DesignHeight="650"
d:DesignWidth="800"
@ -23,6 +25,13 @@
x:DataType="vm:OutputsPageViewModel"
Focusable="True"
mc:Ignorable="d">
<controls:UserControlBase.Resources>
<converters:BooleanChoiceMultiConverter x:Key="BooleanChoiceMultiConverter" />
<system:String x:Key="LoadingClass">loading</system:String>
<system:String x:Key="EmptyClass"> </system:String>
</controls:UserControlBase.Resources>
<Grid Margin="16" RowDefinitions="Auto,*">
<Grid
Grid.Row="0"
@ -138,7 +147,8 @@
<ui:CommandBarSeparator />
<ui:CommandBarButton
Command="{Binding SelectAll}"
IconSource="SelectAll"
IconSource="SelectAll"
IsEnabled="{Binding !IsLoading}"
Label="{x:Static lang:Resources.Action_SelectAll}" />
<ui:CommandBarButton
Command="{Binding ClearSelection}"
@ -147,9 +157,28 @@
Label="{x:Static lang:Resources.Action_ClearSelection}" />
<ui:CommandBarSeparator />
<ui:CommandBarButton
IconSource="Refresh"
IconSource="Refresh"
Classes.loading="{Binding IsLoading}"
IsEnabled="{Binding !IsLoading}"
Command="{Binding Refresh}"
Label="{x:Static lang:Resources.Action_Refresh}" />
Label="{x:Static lang:Resources.Action_Refresh}">
<ui:CommandBarButton.Styles>
<Style Selector="ui|CommandBarButton:disabled.loading">
<Style Selector="^ ui|SymbolIcon">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="INFINITE">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="0"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Style>
</ui:CommandBarButton.Styles>
</ui:CommandBarButton>
</ui:CommandBar.PrimaryCommands>
<ui:CommandBar.SecondaryCommands>
@ -161,13 +190,15 @@
</ui:CommandBar>
</Grid>
<scroll:BetterScrollViewer Grid.Row="1" PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<scroll:BetterScrollViewer Grid.Row="1"
x:Name="MainScrollViewer"
PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<ItemsRepeater
x:Name="ImageRepeater"
VerticalAlignment="Top"
ItemsSource="{Binding Outputs}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="8" MinRowSpacing="8" />
<WrapLayout Orientation="Horizontal" HorizontalSpacing="12" VerticalSpacing="12"/>
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type outputsPage:OutputImageViewModel}">
@ -177,7 +208,7 @@
ImageHeight="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Height}"
ImageWidth="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ImageSize.Width}"
IsSelected="{Binding IsSelected}"
Source="{Binding ImageFile.AbsolutePath}">
Source="{Binding ImageFile.AbsolutePathUriString}">
<selectableImageCard:SelectableImageButton.ContextFlyout>
<ui:FAMenuFlyout>
<ui:MenuFlyoutItem

2
StabilityMatrix.Core/Models/Database/LocalImageFile.cs

@ -50,6 +50,8 @@ public record LocalImageFile
/// </summary>
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(AbsolutePath);
public Uri AbsolutePathUriString => new($"file://{AbsolutePath}");
public (string? Parameters, string? ParametersJson, string? SMProject, string? ComfyNodes) ReadMetadata()
{
if (AbsolutePath.EndsWith("webp"))

Loading…
Cancel
Save