Browse Source

better image control & model browser improvements

pull/55/head
JT 1 year ago
parent
commit
4b960a6571
  1. 140
      StabilityMatrix.Avalonia/Controls/BetterImage.cs
  2. 44
      StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml
  3. 1
      StabilityMatrix.Avalonia/Styles/ThemeColors.axaml
  4. 10
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserCardViewModel.cs
  5. 42
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
  6. 33
      StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

140
StabilityMatrix.Avalonia/Controls/BetterImage.cs

@ -0,0 +1,140 @@
using Avalonia;
using Avalonia.Automation;
using Avalonia.Automation.Peers;
using Avalonia.Controls;
using Avalonia.Controls.Automation.Peers;
using Avalonia.Media;
using Avalonia.Metadata;
namespace StabilityMatrix.Avalonia.Controls;
public class BetterImage : Control
{
/// <summary>
/// Defines the <see cref="Source"/> property.
/// </summary>
public static readonly StyledProperty<IImage?> SourceProperty =
AvaloniaProperty.Register<BetterImage, IImage?>(nameof(Source));
/// <summary>
/// Defines the <see cref="Stretch"/> property.
/// </summary>
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<BetterImage, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
/// Defines the <see cref="StretchDirection"/> property.
/// </summary>
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<BetterImage, StretchDirection>(
nameof(StretchDirection),
StretchDirection.Both);
static BetterImage()
{
AffectsRender<BetterImage>(SourceProperty, StretchProperty, StretchDirectionProperty);
AffectsMeasure<BetterImage>(SourceProperty, StretchProperty, StretchDirectionProperty);
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<BetterImage>(
AutomationControlType.Image);
}
/// <summary>
/// Gets or sets the image that will be displayed.
/// </summary>
[Content]
public IImage? Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
/// <summary>
/// Gets or sets a value controlling how the image will be stretched.
/// </summary>
public Stretch Stretch
{
get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Gets or sets a value controlling in what direction the image will be stretched.
/// </summary>
public StretchDirection StretchDirection
{
get { return GetValue(StretchDirectionProperty); }
set { SetValue(StretchDirectionProperty, value); }
}
/// <inheritdoc />
protected override bool BypassFlowDirectionPolicies => true;
/// <summary>
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public sealed override void Render(DrawingContext context)
{
var source = Source;
if (source != null && Bounds.Width > 0 && Bounds.Height > 0)
{
Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = source.Size;
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
.WithX(0)
.WithY(0)
.Intersect(viewPort);
Rect sourceRect = new Rect(sourceSize)
.CenterRect(new Rect(destRect.Size / scale))
.WithX(0)
.WithY(0);
context.DrawImage(source, sourceRect, destRect);
}
}
/// <summary>
/// Measures the control.
/// </summary>
/// <param name="availableSize">The available size.</param>
/// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size availableSize)
{
var source = Source;
var result = new Size();
if (source != null)
{
result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
}
return result;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var source = Source;
if (source != null)
{
var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
else
{
return new Size();
}
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ImageAutomationPeer(this);
}
}

44
StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml

@ -6,7 +6,8 @@
<StackPanel> <StackPanel>
<Button Classes="success" Content="Success Button" Margin="8" HorizontalAlignment="Center" /> <Button Classes="success" Content="Success Button" Margin="8" HorizontalAlignment="Center" />
<Button Classes="danger" Content="Danger Button" Margin="8" HorizontalAlignment="Center" /> <Button Classes="danger" Content="Danger Button" Margin="8" HorizontalAlignment="Center" />
<Button Classes="success" Content="Disabled Button" Margin="8" IsEnabled="False" /> <Button Classes="info" Content="Info Button" Margin="8" HorizontalAlignment="Center" />
<Button Content="Disabled Button" Margin="8" IsEnabled="False" />
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
@ -92,4 +93,45 @@
</Style> </Style>
</Style> </Style>
</Style> </Style>
<!-- Info -->
<Style Selector="Button.info">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeLightBlueColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeLightBlueColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
</Style>
<Style Selector="^:pointerover">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeBlueColor}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBlueColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
</Style>
<Style Selector="^:pressed">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ThemeDarkBlueColor}" />
<Setter Property="BorderBrush" Value="{DynamicResource ThemeDarkBlueColor}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
</Style>
<Style Selector="^:disabled">
<Style Selector="^ /template/ ui|FABorder#Root">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
</Style>
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
</Style>
</Style>
</Styles> </Styles>

1
StabilityMatrix.Avalonia/Styles/ThemeColors.axaml

@ -9,6 +9,7 @@
<Color x:Key="ThemePurpleColor">#9C27B0</Color> <Color x:Key="ThemePurpleColor">#9C27B0</Color>
<Color x:Key="ThemeDeepPurpleColor">#673AB7</Color> <Color x:Key="ThemeDeepPurpleColor">#673AB7</Color>
<Color x:Key="ThemeIndigoColor">#3F51B5</Color> <Color x:Key="ThemeIndigoColor">#3F51B5</Color>
<Color x:Key="ThemeDarkBlueColor">#1A72BD</Color>
<Color x:Key="ThemeBlueColor">#2196F3</Color> <Color x:Key="ThemeBlueColor">#2196F3</Color>
<Color x:Key="ThemeLightBlueColor">#03A9F4</Color> <Color x:Key="ThemeLightBlueColor">#03A9F4</Color>
<Color x:Key="ThemeCyanColor">#00BCD4</Color> <Color x:Key="ThemeCyanColor">#00BCD4</Color>

10
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserCardViewModel.cs

@ -5,8 +5,10 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices; using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@ -51,8 +53,6 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
return; return;
} }
if (Design.IsDesignMode) return;
UpdateImage().SafeFireAndForget(); UpdateImage().SafeFireAndForget();
// Update image when nsfw setting changes // Update image when nsfw setting changes
@ -79,10 +79,12 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
return; return;
} }
var assetStream = AssetLoader.Open(new Uri("avares://StabilityMatrix.Avalonia/Assets/noimage.png"));
// Otherwise Default image // Otherwise Default image
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
CardImage = new Bitmap("Assets/noimage.png"); CardImage = new Bitmap(assetStream);
}); });
} }
@ -174,6 +176,8 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
}); });
})); }));
await downloadTask;
// var downloadResult = await snackbarService.TryAsync(downloadTask, "Could not download file"); // var downloadResult = await snackbarService.TryAsync(downloadTask, "Could not download file");
// Failed download handling // Failed download handling

42
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

@ -55,6 +55,8 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase
[ObservableProperty] private bool noResultsFound; [ObservableProperty] private bool noResultsFound;
[ObservableProperty] private string noResultsText; [ObservableProperty] private string noResultsText;
private List<CheckpointBrowserCardViewModel> allModelCards = new();
public IEnumerable<CivitPeriod> AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>(); public IEnumerable<CivitPeriod> AllCivitPeriods => Enum.GetValues(typeof(CivitPeriod)).Cast<CivitPeriod>();
public IEnumerable<CivitSortMode> AllSortModes => Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>(); public IEnumerable<CivitSortMode> AllSortModes => Enum.GetValues(typeof(CivitSortMode)).Cast<CivitSortMode>();
@ -203,8 +205,11 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase
{ {
var updateCards = models var updateCards = models
.Select(model => new CheckpointBrowserCardViewModel(model, .Select(model => new CheckpointBrowserCardViewModel(model,
downloadService, settingsManager)); downloadService, settingsManager)).ToList();
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(updateCards); allModelCards = updateCards;
ModelCards =
new ObservableCollection<CheckpointBrowserCardViewModel>(
updateCards.Where(FilterModelCardsPredicate));
} }
TotalPages = metadata?.TotalPages ?? 1; TotalPages = metadata?.TotalPages ?? 1;
CanGoToPreviousPage = CurrentPageNumber > 1; CanGoToPreviousPage = CurrentPageNumber > 1;
@ -310,22 +315,27 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase
// On changes to ModelCards, update the view source // On changes to ModelCards, update the view source
partial void OnModelCardsChanged(ObservableCollection<CheckpointBrowserCardViewModel>? value) partial void OnModelCardsChanged(ObservableCollection<CheckpointBrowserCardViewModel>? value)
{ {
if (value is null) // if (value is null)
{ // {
ModelCardsView = null; // ModelCardsView = null;
} // }
// Create new view // // Create new view
var view = new DataGridCollectionView(value) // var view = new DataGridCollectionView(value)
{ // {
Filter = FilterModelCardsPredicate, // Filter = FilterModelCardsPredicate,
}; // };
ModelCardsView = view; // ModelCardsView = view;
} }
partial void OnShowNsfwChanged(bool value) partial void OnShowNsfwChanged(bool value)
{ {
settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled = value); settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled = value);
ModelCardsView?.Refresh(); // ModelCardsView?.Refresh();
var updateCards = allModelCards
.Select(model => new CheckpointBrowserCardViewModel(model.CivitModel,
downloadService, settingsManager))
.Where(FilterModelCardsPredicate);
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>(updateCards);
if (!HasSearched) if (!HasSearched)
return; return;
@ -370,9 +380,9 @@ public partial class CheckpointBrowserViewModel : PageViewModelBase
private void UpdateResultsText() private void UpdateResultsText()
{ {
NoResultsFound = (ModelCards?.Count ?? 0) <= 0; NoResultsFound = ModelCards?.Count <= 0;
NoResultsText = ModelCards?.Count > 0 NoResultsText = allModelCards?.Count > 0
? $"{ModelCards.Count} results hidden by filters" ? $"{allModelCards.Count} results hidden by filters"
: "No results found"; : "No results found";
} }

33
StabilityMatrix.Avalonia/Views/CheckpointBrowserPage.axaml

@ -6,7 +6,7 @@
xmlns:controls1="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:controls1="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:DataType="viewModels:CheckpointBrowserViewModel" x:DataType="viewModels:CheckpointBrowserViewModel"
d:DataContext="{x:Static designData:DesignData.CheckpointBrowserViewModel}" d:DataContext="{x:Static designData:DesignData.CheckpointBrowserViewModel}"
x:CompileBindings="True" x:CompileBindings="True"
@ -17,7 +17,7 @@
<controls1:Card <controls1:Card
Margin="8" Margin="8"
MaxHeight="450" MaxHeight="450"
CornerRadius="20" CornerRadius="8"
Name="ModelCard" Name="ModelCard"
Width="330"> Width="330">
@ -34,17 +34,17 @@
Text="{Binding CivitModel.ModelVersions[0].Name, FallbackValue=''}" Text="{Binding CivitModel.ModelVersions[0].Name, FallbackValue=''}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<Grid> <Grid>
<Image <controls1:BetterImage
Margin="0,8,0,8" Margin="0,8,0,8"
MaxHeight="300" MaxHeight="300"
Source="{Binding CardImage}" Source="{Binding CardImage}"
Stretch="UniformToFill" /> Stretch="UniformToFill"/>
<!-- Appearance="Info" -->
<Button <Button
Command="{Binding OpenModelCommand}" Command="{Binding OpenModelCommand}"
CommandParameter="{Binding CivitModel}" CommandParameter="{Binding CivitModel}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Classes="info"
Margin="0,16,8,0" Margin="0,16,8,0"
VerticalAlignment="Top"> VerticalAlignment="Top">
<Button.Content> <Button.Content>
@ -74,7 +74,7 @@
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="0,8,0,8" Margin="0,8,0,8"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding IsImporting, FallbackValue=True}" /> IsVisible="{Binding IsImporting}" />
<StackPanel <StackPanel
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Orientation="Vertical" Orientation="Vertical"
@ -83,12 +83,16 @@
<controls1:ProgressRing <controls1:ProgressRing
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsIndeterminate="False" IsIndeterminate="False"
Width="120"
Height="120"
StartAngle="90"
EndAngle="450"
Value="{Binding Value}" Value="{Binding Value}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="0,8,0,0" Margin="0,8,0,0"
Text="{Binding Text, FallbackValue=Importing...}" Text="{Binding Text, TargetNullValue=Importing...}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</StackPanel> </StackPanel>
</Grid> </Grid>
@ -154,18 +158,18 @@
Margin="8,0,8,0" Margin="8,0,8,0"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Width="80"> Width="80">
<StackPanel Orientation="Horizontal"> <Grid>
<controls1:ProgressRing <controls1:ProgressRing
Height="20" MinHeight="16"
IsIndeterminate="True" IsIndeterminate="True"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding SearchModelsCommand.IsRunning}" IsVisible="{Binding SearchModelsCommand.IsRunning}"
Width="20" /> MinWidth="16" />
<TextBlock <TextBlock
Text="Search" Text="Search"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding !SearchModelsCommand.IsRunning}" /> IsVisible="{Binding !SearchModelsCommand.IsRunning}" />
</StackPanel> </Grid>
</Button> </Button>
</Grid> </Grid>
<DockPanel> <DockPanel>
@ -195,13 +199,13 @@
<CheckBox <CheckBox
Content="Show NSFW Content" Content="Show NSFW Content"
FontSize="12"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsChecked="{Binding ShowNsfw, Mode=TwoWay}" IsChecked="{Binding ShowNsfw, Mode=TwoWay}"
Margin="8,8,8,0" /> Margin="8,8,8,0" />
</DockPanel> </DockPanel>
</StackPanel> </StackPanel>
<ScrollViewer Grid.Row="1"> <ScrollViewer Grid.Row="1">
<ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}" <ItemsRepeater ItemTemplate="{StaticResource CivitModelTemplate}"
ItemsSource="{Binding ModelCards}"> ItemsSource="{Binding ModelCards}">
@ -252,9 +256,10 @@
IsVisible="{Binding NoResultsFound}" /> IsVisible="{Binding NoResultsFound}" />
<controls1:ProgressRing <controls1:ProgressRing
Grid.Row="0" Grid.Row="1"
Grid.RowSpan="3"
IsIndeterminate="True" IsIndeterminate="True"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding ShowMainLoadingSpinner, FallbackValue=False}" /> IsVisible="{Binding ShowMainLoadingSpinner, FallbackValue=False}" />
</Grid> </Grid>
</UserControl> </UserControl>

Loading…
Cancel
Save