Browse Source

Merge pull request #295 from ionite34/selectable-image-buttons

Selectable image buttons
pull/240/head
JT 1 year ago committed by GitHub
parent
commit
81b8cc6f47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      StabilityMatrix.Avalonia/App.axaml
  2. 51
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.axaml
  3. 25
      StabilityMatrix.Avalonia/Controls/SelectableImageCard/SelectableImageButton.cs
  4. 26
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  5. 117
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  6. 41
      StabilityMatrix.Avalonia/Languages/Resources.resx
  7. 19
      StabilityMatrix.Avalonia/ViewModels/OutputsPage/OutputImageViewModel.cs
  8. 151
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  9. 190
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  10. 33
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

1
StabilityMatrix.Avalonia/App.axaml

@ -60,6 +60,7 @@
<StyleInclude Source="Controls/SharpenCard.axaml"/>
<StyleInclude Source="Controls/FreeUCard.axaml"/>
<StyleInclude Source="Controls/Paginator.axaml"/>
<StyleInclude Source="Controls/SelectableImageCard/SelectableImageButton.axaml"/>
<Style Selector="DockControl">
<Setter Property="(DockProperties.ControlRecycling)" Value="{StaticResource ControlRecyclingKey}" />

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

@ -0,0 +1,51 @@
<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">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
<selectableImageCard:SelectableImageButton
Source="https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg" />
</Border>
</Design.PreviewWith>
<!-- Add Styles Here -->
<Style Selector="selectableImageCard|SelectableImageButton">
<Setter Property="Template">
<ControlTemplate>
<Grid>
<CheckBox VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="14,8"
Padding="0"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ZIndex="100">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.RenderTransform>
<CheckBox.Styles>
<Style Selector="CheckBox">
<Setter Property="CornerRadius" Value="16" />
</Style>
</CheckBox.Styles>
</CheckBox>
<Button
Margin="0"
Padding="4"
CornerRadius="12"
Command="{TemplateBinding Command}"
CommandParameter="{TemplateBinding CommandParameter}">
<controls:BetterAdvancedImage
Width="300"
Height="300"
Stretch="UniformToFill"
CornerRadius="8"
ContextFlyout="{TemplateBinding ContextFlyout}"
Source="{TemplateBinding Source}" />
</Button>
</Grid>
</ControlTemplate>
</Setter>
</Style>
</Styles>

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

@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Controls;
namespace StabilityMatrix.Avalonia.Controls.SelectableImageCard;
public class SelectableImageButton : Button
{
public static readonly StyledProperty<bool?> IsSelectedProperty =
CheckBox.IsCheckedProperty.AddOwner<SelectableImageButton>();
public static readonly StyledProperty<string?> SourceProperty =
BetterAdvancedImage.SourceProperty.AddOwner<SelectableImageButton>();
public bool? IsSelected
{
get => GetValue(IsSelectedProperty);
set => SetValue(IsSelectedProperty, value);
}
public string? Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
}

26
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -7,6 +7,7 @@ using System.IO;
using System.Net.Http;
using System.Text;
using AvaloniaEdit.Utils;
using DynamicData.Binding;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Controls.CodeCompletion;
@ -20,6 +21,7 @@ using StabilityMatrix.Avalonia.ViewModels.CheckpointManager;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.ViewModels.Progress;
using StabilityMatrix.Avalonia.ViewModels.Inference;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Avalonia.ViewModels.Settings;
using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Database;
@ -29,6 +31,7 @@ using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Python;
@ -336,8 +339,27 @@ public static class DesignData
public static LaunchPageViewModel LaunchPageViewModel =>
Services.GetRequiredService<LaunchPageViewModel>();
public static OutputsPageViewModel OutputsPageViewModel =>
Services.GetRequiredService<OutputsPageViewModel>();
public static OutputsPageViewModel OutputsPageViewModel
{
get
{
var vm = Services.GetRequiredService<OutputsPageViewModel>();
vm.Outputs = new ObservableCollectionExtended<OutputImageViewModel>
{
new(
new LocalImageFile
{
AbsolutePath =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg",
RelativePath =
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg",
ImageType = LocalImageFileType.TextToImage
}
)
};
return vm;
}
}
public static PackageManagerViewModel PackageManagerViewModel
{

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

@ -113,6 +113,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Clear Selection.
/// </summary>
public static string Action_ClearSelection {
get {
return ResourceManager.GetString("Action_ClearSelection", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Close.
/// </summary>
@ -257,6 +266,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Open in Image Viewer.
/// </summary>
public static string Action_OpenInViewer {
get {
return ResourceManager.GetString("Action_OpenInViewer", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open on CivitAI.
/// </summary>
@ -392,6 +410,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Select All.
/// </summary>
public static string Action_SelectAll {
get {
return ResourceManager.GetString("Action_SelectAll", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Select Directory.
/// </summary>
@ -419,6 +446,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Send to Inference.
/// </summary>
public static string Action_SendToInference {
get {
return ResourceManager.GetString("Action_SendToInference", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show in Explorer.
/// </summary>
@ -896,6 +932,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Image to Image.
/// </summary>
public static string Label_ImageToImage {
get {
return ResourceManager.GetString("Label_ImageToImage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Import as Connected.
/// </summary>
@ -941,6 +986,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Inpainting.
/// </summary>
public static string Label_Inpainting {
get {
return ResourceManager.GetString("Label_Inpainting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Input.
/// </summary>
@ -1157,6 +1211,24 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to {0} images selected.
/// </summary>
public static string Label_NumImagesSelected {
get {
return ResourceManager.GetString("Label_NumImagesSelected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1 image selected.
/// </summary>
public static string Label_OneImageSelected {
get {
return ResourceManager.GetString("Label_OneImageSelected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Only available on Windows.
/// </summary>
@ -1166,6 +1238,33 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Output Folder.
/// </summary>
public static string Label_OutputFolder {
get {
return ResourceManager.GetString("Label_OutputFolder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Output Browser.
/// </summary>
public static string Label_OutputsPageTitle {
get {
return ResourceManager.GetString("Label_OutputsPageTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Output Type.
/// </summary>
public static string Label_OutputType {
get {
return ResourceManager.GetString("Label_OutputType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Package Environment.
/// </summary>
@ -1490,6 +1589,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Text to Image.
/// </summary>
public static string Label_TextToImage {
get {
return ResourceManager.GetString("Label_TextToImage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Theme.
/// </summary>
@ -1535,6 +1643,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Upscale.
/// </summary>
public static string Label_Upscale {
get {
return ResourceManager.GetString("Label_Upscale", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Output Sharing.
/// </summary>

41
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -687,4 +687,43 @@
<data name="Action_Copy" xml:space="preserve">
<value>Copy</value>
</data>
</root>
<data name="Action_OpenInViewer" xml:space="preserve">
<value>Open in Image Viewer</value>
</data>
<data name="Label_NumImagesSelected" xml:space="preserve">
<value>{0} images selected</value>
</data>
<data name="Label_OutputFolder" xml:space="preserve">
<value>Output Folder</value>
</data>
<data name="Label_OutputType" xml:space="preserve">
<value>Output Type</value>
</data>
<data name="Action_ClearSelection" xml:space="preserve">
<value>Clear Selection</value>
</data>
<data name="Action_SelectAll" xml:space="preserve">
<value>Select All</value>
</data>
<data name="Action_SendToInference" xml:space="preserve">
<value>Send to Inference</value>
</data>
<data name="Label_TextToImage" xml:space="preserve">
<value>Text to Image</value>
</data>
<data name="Label_ImageToImage" xml:space="preserve">
<value>Image to Image</value>
</data>
<data name="Label_Inpainting" xml:space="preserve">
<value>Inpainting</value>
</data>
<data name="Label_Upscale" xml:space="preserve">
<value>Upscale</value>
</data>
<data name="Label_OutputsPageTitle" xml:space="preserve">
<value>Output Browser</value>
</data>
<data name="Label_OneImageSelected" xml:space="preserve">
<value>1 image selected</value>
</data>
</root>

19
StabilityMatrix.Avalonia/ViewModels/OutputsPage/OutputImageViewModel.cs

@ -0,0 +1,19 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Models.Database;
namespace StabilityMatrix.Avalonia.ViewModels.OutputsPage;
public partial class OutputImageViewModel : ViewModelBase
{
public LocalImageFile ImageFile { get; }
[ObservableProperty]
private bool isSelected;
public OutputImageViewModel(LocalImageFile imageFile)
{
ImageFile = imageFile;
}
}

151
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -1,24 +1,30 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using AsyncImageLoader;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Avalonia.ViewModels.OutputsPage;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
@ -32,21 +38,23 @@ using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(OutputsPage))]
[View(typeof(Views.OutputsPage))]
public partial class OutputsPageViewModel : PageViewModelBase
{
private readonly ISettingsManager settingsManager;
private readonly INotificationService notificationService;
private readonly INavigationService navigationService;
public override string Title => "Outputs";
private readonly ILogger<OutputsPageViewModel> logger;
public override string Title => Resources.Label_OutputsPageTitle;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
public SourceCache<LocalImageFile, string> OutputsCache { get; } = new(p => p.AbsolutePath);
public SourceCache<OutputImageViewModel, string> OutputsCache { get; } =
new(p => p.ImageFile.AbsolutePath);
public IObservableCollection<LocalImageFile> Outputs { get; } =
new ObservableCollectionExtended<LocalImageFile>();
public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>();
public IEnumerable<SharedOutputType> OutputTypes { get; } = Enum.GetValues<SharedOutputType>();
@ -60,25 +68,41 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private SharedOutputType selectedOutputType;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NumImagesSelected))]
private int numItemsSelected;
public bool CanShowOutputTypes => SelectedCategory.Name.Equals("Shared Output Folder");
public string NumImagesSelected =>
NumItemsSelected == 1
? Resources.Label_OneImageSelected
: string.Format(Resources.Label_NumImagesSelected, NumItemsSelected);
public OutputsPageViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
INotificationService notificationService,
INavigationService navigationService
INavigationService navigationService,
ILogger<OutputsPageViewModel> logger
)
{
this.settingsManager = settingsManager;
this.notificationService = notificationService;
this.navigationService = navigationService;
this.logger = logger;
OutputsCache
.Connect()
.DeferUntilLoaded()
.SortBy(x => x.CreatedAt, SortDirection.Descending)
.SortBy(x => x.ImageFile.CreatedAt, SortDirection.Descending)
.ObserveOn(SynchronizationContext.Current)
.Bind(Outputs)
.Subscribe();
.WhenPropertyChanged(p => p.IsSelected)
.Subscribe(_ =>
{
NumItemsSelected = Outputs.Count(o => o.IsSelected);
});
if (!settingsManager.IsLibraryDirSet || Design.IsDesignMode)
return;
@ -152,16 +176,29 @@ public partial class OutputsPageViewModel : PageViewModelBase
GetOutputs(path);
}
public async Task OnImageClick(LocalImageFile item)
public async Task OnImageClick(OutputImageViewModel item)
{
// Select image if we're in "select mode"
if (NumItemsSelected > 0)
{
item.IsSelected = !item.IsSelected;
}
else
{
await ShowImageDialog(item);
}
}
public async Task ShowImageDialog(OutputImageViewModel item)
{
var currentIndex = Outputs.IndexOf(item);
var image = new ImageSource(new FilePath(item.AbsolutePath));
var image = new ImageSource(new FilePath(item.ImageFile.AbsolutePath));
// Preload
await image.GetBitmapAsync();
var vm = new ImageViewerViewModel { ImageSource = image, LocalImageFile = item };
var vm = new ImageViewerViewModel { ImageSource = image, LocalImageFile = item.ImageFile };
using var onNext = Observable
.FromEventPattern<DirectionalNavigationEventArgs>(
@ -180,14 +217,14 @@ public partial class OutputsPageViewModel : PageViewModelBase
{
var newImage = Outputs[newIndex];
var newImageSource = new ImageSource(
new FilePath(newImage.AbsolutePath)
new FilePath(newImage.ImageFile.AbsolutePath)
);
// Preload
await newImageSource.GetBitmapAsync();
sender.ImageSource = newImageSource;
sender.LocalImageFile = newImage;
sender.LocalImageFile = newImage.ImageFile;
currentIndex = newIndex;
}
@ -207,9 +244,22 @@ public partial class OutputsPageViewModel : PageViewModelBase
public async Task OpenImage(string imagePath) => await ProcessRunner.OpenFileBrowser(imagePath);
public async Task DeleteImage(LocalImageFile? item)
public async Task DeleteImage(OutputImageViewModel? item)
{
if (item?.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath)
var confirmationDialog = new BetterContentDialog
{
Title = "Are you sure you want to delete this image?",
Content = "This action cannot be undone.",
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsSecondaryButtonEnabled = true,
};
var dialogResult = await confirmationDialog.ShowAsync();
if (dialogResult != ContentDialogResult.Primary)
return;
if (item?.ImageFile.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath)
{
return;
}
@ -223,7 +273,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
return;
}
Outputs.Remove(item);
OutputsCache.Remove(item);
// Invalidate cache
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
@ -244,6 +294,67 @@ public partial class OutputsPageViewModel : PageViewModelBase
EventManager.Instance.OnInferenceUpscaleRequested(image);
}
public void ClearSelection()
{
foreach (var output in Outputs)
{
output.IsSelected = false;
}
}
public void SelectAll()
{
foreach (var output in Outputs)
{
output.IsSelected = true;
}
}
public async Task DeleteAllSelected()
{
var confirmationDialog = new BetterContentDialog
{
Title = $"Are you sure you want to delete {NumItemsSelected} images?",
Content = "This action cannot be undone.",
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsSecondaryButtonEnabled = true,
};
var dialogResult = await confirmationDialog.ShowAsync();
if (dialogResult != ContentDialogResult.Primary)
return;
var selected = Outputs.Where(o => o.IsSelected).ToList();
Debug.Assert(selected.Count == NumItemsSelected);
foreach (var output in selected)
{
if (output?.ImageFile.GetFullPath(settingsManager.ImagesDirectory) is not { } imagePath)
{
continue;
}
// Delete the file
var imageFile = new FilePath(imagePath);
var result = await notificationService.TryAsync(imageFile.DeleteAsync());
if (!result.IsSuccessful)
{
continue;
}
OutputsCache.Remove(output);
// Invalidate cache
if (ImageLoader.AsyncImageLoader is FallbackRamCachedWebImageLoader loader)
{
loader.RemoveAllNamesFromCache(imageFile.Name);
}
}
NumItemsSelected = 0;
ClearSelection();
}
private void GetOutputs(string directory)
{
if (!settingsManager.IsLibraryDirSet)
@ -257,9 +368,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
var list = Directory
.EnumerateFiles(directory, "*.png", SearchOption.AllDirectories)
.Select(file => LocalImageFile.FromPath(file))
.OrderByDescending(f => f.CreatedAt);
.Select(file => new OutputImageViewModel(LocalImageFile.FromPath(file)))
.OrderByDescending(f => f.ImageFile.CreatedAt);
OutputsCache.EditDiff(list, (x, y) => x.AbsolutePath == y.AbsolutePath);
OutputsCache.EditDiff(list, (x, y) => x.ImageFile.AbsolutePath == y.ImageFile.AbsolutePath);
}
}

190
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -11,9 +11,10 @@
xmlns:vm="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:models1="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:database="clr-namespace:StabilityMatrix.Core.Models.Database;assembly=StabilityMatrix.Core"
xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
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"
d:DataContext="{x:Static mocks:DesignData.OutputsPageViewModel}"
d:DesignHeight="450"
d:DesignWidth="700"
@ -21,41 +22,95 @@
x:DataType="vm:OutputsPageViewModel"
mc:Ignorable="d">
<Grid RowDefinitions="Auto, *" Margin="16">
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,16"
HorizontalAlignment="Left">
<Grid RowDefinitions="Auto, *">
<TextBlock Text="Output Folder" Margin="4"/>
<ComboBox Grid.Row="1" ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
MinWidth="150">
<ComboBox.Styles>
<Style
Selector="ComboBox /template/ ContentControl#ContentPresenter &gt; StackPanel &gt; TextBlock:nth-child(2)">
<Setter Property="IsVisible" Value="False" />
</Style>
</ComboBox.Styles>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models1:PackageOutputCategory}">
<StackPanel>
<TextBlock
Margin="0,4,0,4"
Text="{Binding Name, Mode=OneWay}" />
<TextBlock Text="{Binding Path, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<Grid RowDefinitions="Auto, *" Margin="8,0"
IsVisible="{Binding CanShowOutputTypes}">
<TextBlock Text="Output Type" Margin="4"/>
<ComboBox Grid.Row="1" ItemsSource="{Binding OutputTypes}"
SelectedItem="{Binding SelectedOutputType}"
MinWidth="150"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"/>
</Grid>
</StackPanel>
<Grid Grid.Row="0" Margin="0,0,0,16"
HorizontalAlignment="Stretch"
RowDefinitions="Auto, Auto"
ColumnDefinitions="Auto, Auto, Auto, *, Auto, Auto">
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="{x:Static lang:Resources.Label_OutputFolder}"
Margin="4" />
<ComboBox Grid.Column="0"
Grid.Row="1" ItemsSource="{Binding Categories}"
CornerRadius="8"
Margin="4,0"
SelectedItem="{Binding SelectedCategory}"
VerticalAlignment="Stretch"
MinWidth="150">
<ComboBox.Styles>
<Style
Selector="ComboBox /template/ ContentControl#ContentPresenter &gt; StackPanel &gt; TextBlock:nth-child(2)">
<Setter Property="IsVisible" Value="False" />
</Style>
</ComboBox.Styles>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type models1:PackageOutputCategory}">
<StackPanel>
<TextBlock
Margin="0,4,0,4"
Text="{Binding Name, Mode=OneWay}" />
<TextBlock Text="{Binding Path, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="0"
Grid.Column="1"
Text="{x:Static lang:Resources.Label_OutputType}"
Margin="4"
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"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center" />
<TextBlock Grid.Row="1"
Grid.Column="3"
IsVisible="{Binding !!NumItemsSelected}"
FontSize="16"
Margin="8, 0"
VerticalAlignment="Center"
TextAlignment="Center"
HorizontalAlignment="Right"
Text="{Binding NumImagesSelected, FallbackValue=1234 images selected}" />
<Button Grid.Row="1"
Grid.Column="4"
VerticalAlignment="Bottom"
CornerRadius="8"
Padding="12, 0"
Height="40"
Classes="danger"
Command="{Binding DeleteAllSelected}"
IsVisible="{Binding !!NumItemsSelected}" >
<avalonia:Icon Value="fa-solid fa-trash-can"/>
</Button>
<Button Grid.Row="1"
Grid.Column="5"
Content="{x:Static lang:Resources.Action_ClearSelection}"
VerticalAlignment="Bottom"
CornerRadius="8"
Margin="8, 0"
Height="40"
Command="{Binding ClearSelection}"
IsVisible="{Binding !!NumItemsSelected}" />
<Button Grid.Row="1"
Grid.Column="5"
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">
<ItemsRepeater
@ -65,65 +120,66 @@
<UniformGridLayout MinColumnSpacing="16" MinRowSpacing="16" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate DataType="{x:Type database:LocalImageFile}">
<Button
Margin="0"
Padding="4"
<DataTemplate DataType="{x:Type outputsPage:OutputImageViewModel}">
<selectableImageCard:SelectableImageButton
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).OnImageClick}"
CommandParameter="{Binding }">
<controls:BetterAdvancedImage
Width="300"
Height="300"
Stretch="UniformToFill"
Source="{Binding AbsolutePath}" />
<Button.ContextFlyout>
CommandParameter="{Binding }"
IsSelected="{Binding IsSelected}"
Source="{Binding ImageFile.AbsolutePath}">
<selectableImageCard:SelectableImageButton.ContextFlyout>
<ui:FAMenuFlyout>
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="{x:Static lang:Resources.Action_Delete}"
IconSource="Delete"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).DeleteImage}"
CommandParameter="{Binding }" />
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="{x:Static lang:Resources.Action_Copy}"
IconSource="Copy"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).CopyImage}"
CommandParameter="{Binding AbsolutePath}" />
CommandParameter="{Binding ImageFile.AbsolutePath}" />
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="{x:Static lang:Resources.Action_OpenInExplorer}"
IconSource="Folder"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).OpenImage}"
CommandParameter="{Binding AbsolutePath}" />
CommandParameter="{Binding ImageFile.AbsolutePath}" />
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="{x:Static lang:Resources.Action_OpenInViewer}"
IconSource="Image"
IsVisible="{Binding !!$parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).NumItemsSelected}"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).ShowImageDialog}"
CommandParameter="{Binding }" />
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="{x:Static lang:Resources.Action_Delete}"
IconSource="Delete"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).DeleteImage}"
CommandParameter="{Binding }" />
<ui:MenuFlyoutSeparator
IsVisible="{Binding GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}" />
IsVisible="{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ui:MenuFlyoutSubItem Text="Send to Inference" IconSource="Share"
IsVisible="{Binding GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}">
<ui:MenuFlyoutSubItem Text="{x:Static lang:Resources.Action_SendToInference}"
IconSource="Share"
IsVisible="{Binding ImageFile.GenerationParameters, Converter={x:Static ObjectConverters.IsNotNull}}">
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="Text to Image"
Text="{x:Static lang:Resources.Label_TextToImage}"
IconSource="FullScreenMaximize"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToTextToImage}"
CommandParameter="{Binding }" />
<ui:MenuFlyoutItem
HotKey="{x:Null}"
Text="Image to Image"
Text="{x:Static lang:Resources.Label_ImageToImage}"
IsEnabled="False"
IconSource="ImageCopy"
CommandParameter="{Binding }" />
<ui:MenuFlyoutItem
Text="Inpainting"
Text="{x:Static lang:Resources.Label_Inpainting}"
IconSource="ImageEdit"
IsEnabled="False"
HotKey="{x:Null}"
CommandParameter="{Binding }" />
<ui:MenuFlyoutItem
Text="Upscale"
Text="{x:Static lang:Resources.Label_Upscale}"
HotKey="{x:Null}"
Command="{Binding $parent[ItemsRepeater].((vm:OutputsPageViewModel)DataContext).SendToUpscale}"
CommandParameter="{Binding }">
@ -135,8 +191,8 @@
</ui:MenuFlyoutItem>
</ui:MenuFlyoutSubItem>
</ui:FAMenuFlyout>
</Button.ContextFlyout>
</Button>
</selectableImageCard:SelectableImageButton.ContextFlyout>
</selectableImageCard:SelectableImageButton>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>

33
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -79,20 +79,41 @@
<MenuItem Header="Symlink"
Command="{Binding ToggleSharedModelSymlink}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelSymlink}"/>
<CheckBox IsChecked="{Binding IsSharedModelSymlink}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Config"
Command="{Binding ToggleSharedModelConfig}"
IsVisible="{Binding CanUseConfigMethod}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelConfig}"/>
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding IsSharedModelConfig}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="None"
Command="{Binding ToggleSharedModelNone}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding IsSharedModelDisabled}"/>
<CheckBox IsChecked="{Binding IsSharedModelDisabled}"
Margin="8,0,0,0"
Padding="0"
Width="28" Height="28">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<!-- ReSharper enable Xaml.RedundantResource -->
@ -104,7 +125,11 @@
<CheckBox Margin="8,0,0,0"
Padding="0"
Width="28" Height="28"
IsChecked="{Binding UseSharedOutput}"/>
IsChecked="{Binding UseSharedOutput}">
<CheckBox.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</CheckBox.RenderTransform>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />

Loading…
Cancel
Save