mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-12 21:57:19 -04:00
Draw Avalonia Liberate icons from the shared generator
Replace the hand-composed vector layers with the shared rendering bound through EntryStatus.ButtonImage, and delete the geometry and stoplight brushes that only that composition used. The rendering is per-theme rather than a set of DynamicResource brushes, so each button re-renders its icon when the actual theme variant changes. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
This commit is contained in:
7 files changed
+23
-153
No files matched your search
@@ -31,9 +31,6 @@
|
||||
<SolidColorBrush x:Key="HyperlinkVisited" Color="Purple" />
|
||||
<SolidColorBrush x:Key="CancelRed" Color="FireBrick" />
|
||||
<SolidColorBrush x:Key="IconFill" Color="#231F20" />
|
||||
<SolidColorBrush x:Key="StoplightRed" Color="#F06060" />
|
||||
<SolidColorBrush x:Key="StoplightYellow" Color="#F0E160" />
|
||||
<SolidColorBrush x:Key="StoplightGreen" Color="#70FA70" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#bed2fa" />
|
||||
@@ -44,9 +41,6 @@
|
||||
<SolidColorBrush x:Key="HyperlinkVisited" Color="Orchid" />
|
||||
<SolidColorBrush x:Key="CancelRed" Color="#802727" />
|
||||
<SolidColorBrush x:Key="IconFill" Color="#DCE0DF" />
|
||||
<SolidColorBrush x:Key="StoplightRed" Color="#7d1f1f" />
|
||||
<SolidColorBrush x:Key="StoplightYellow" Color="#7d7d1f" />
|
||||
<SolidColorBrush x:Key="StoplightGreen" Color="#1f7d1f" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<StreamGeometry x:Key="QueuedIcon">M31,0 H49 V100 H31 M58,0 H76 V100 H58 M85,0 H103 V100 H85 M8,85 V122 H129 V85 H117 V109 H20 V85 H8 M0,36 V66 L24,51 M114,36 V66 L138,51</StreamGeometry>
|
||||
<StreamGeometry x:Key="QueueCompletedIcon">M0,0 H100 V100 H0 V0 M2,50 L36,82 L 93,27 L81,15 L36,59 L14,38</StreamGeometry>
|
||||
<StreamGeometry x:Key="QueueErrorIcon">M0,0 H100 V100 H0 V0 M15,71 L29,85 L50,64 L71,85 L85,71 L64,50 L85,29 L71,15 L50,36 L29,15 L15,29 L36,50</StreamGeometry>
|
||||
<StreamGeometry x:Key="BookErrorIcon">M32,0 a 32,32 0 0 1 0,64 a 32,32 0 0 1 0,-64 m 0,4 a 28,28 0 0 1 0,56 a 28,28 0 0 1 0,-56 m-21,24 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1</StreamGeometry>
|
||||
|
||||
<RotateTransform x:Key="Rotate45Transform" Angle="45" />
|
||||
|
||||
@@ -23,40 +22,6 @@
|
||||
M 78,6 L81.5,2.5 A 8,8 0 0 1 91.5,2 L98.5,9 A 8,8 0 0 1 97.5,19.5 L94,23
|
||||
</StreamGeometry>
|
||||
|
||||
<StreamGeometry x:Key="CollapseIcon">
|
||||
M0,2 A 2,2 0 0 1 2,0 H62 A2,2 0 0 1 64,2 V62 A 2,2 0 0 1 62,64 H 2 A 2,2 0 0 1 0,62 V2
|
||||
M 2,2 H62 V62 H2 V2
|
||||
M11,28 h42 a 1,1 0 0 1 1,1 v6 a 1,1 0 0 1 -1,1 h-42 a 1,1 0 0 1 -1,-1 v-6 a 1,1 0 0 1 1,-1
|
||||
</StreamGeometry>
|
||||
<StreamGeometry x:Key="VerticalBarIcon">M28,53 v-42 a 1,1 0 0 1 1,-1 h6 a 1,1 0 0 1 1,1 v42 a 1,1 0 0 1 -1,1 h-6 a 1,1 0 0 1 -1,-1</StreamGeometry>
|
||||
|
||||
|
||||
<CombinedGeometry x:Key="ExpandIcon" Geometry1="{StaticResource CollapseIcon}" Geometry2="{StaticResource VerticalBarIcon}" />
|
||||
|
||||
<StreamGeometry x:Key="StoplightBodyIcon">
|
||||
M0,12 A 12,12 0 0 1 12,0 H34 A 12,12 0 0 1 46,12 V88 A 12,12 0 0 1 34,100 H12 A 12,12 0 0 1 0,88 V12
|
||||
M20,8 H26 A 12,12 0 0 1 26,32 H20 A 12,12 0 0 1 20,8
|
||||
M20,38 H26 A 12,12 0 0 1 26,62 H20 A 12,12 0 0 1 20,38
|
||||
M20,68 H26 A 12,12 0 0 1 26,92 H20 A 12,12 0 0 1 20,68
|
||||
</StreamGeometry>
|
||||
|
||||
<StreamGeometry x:Key="PdfDownloadedIcon">
|
||||
M4,38.5 H3 A 3,3 0 0 1 0,35.5 V21.4 A 3,3 0 0 1 3,18.4 H4 V2 A 2,2 0 0 1 6,0 H30.5 L41,12 V18.4 A 3,3 0 0 1 45,21.4 V35.5 A 3,3 0 0 1 42,38.5 H41 V48.5 A 2,2 0 0 1 39,50.5 H6 A 2,2 0 0 1 4,48.5
|
||||
M6,38.5 H39 V48.5 H6 V38.5
|
||||
M6,18.4 V2 H29 V12 A 1,1 0 0 0 30,13 H39 V18.4
|
||||
M 4.3179,36 c 0,0 0.122,-14.969 0.122,-14.969 1.469,-0.194 2.939,-0.388 4.5,-0.362 1.561,0.026 3.214,0.27 4.357,0.944 1.143,0.674 1.775,1.776 2.015,2.959 0.24,1.184 0.087,2.449 -0.5,3.52 -0.587,1.071 -1.607,1.949 -2.816,2.352 -1.209,0.403 -2.607,0.332 -4.005,0.26 0,0 -0.031,5.265 -0.031,5.265 0,0 -3.673,0.122 -3.673,0.122 0,0 0.031,-0.092 0.031,-0.092
|
||||
m 3.643,-12.428 c 0,0 0.031,4.286 0.031,4.286 0.735,0.051 1.47,0.102 2.107,-0.056 0.638,-0.158 1.178,-0.526 1.459,-1.122 0.281,-0.597 0.301,-1.423 0.01,-2.005 -0.291,-0.582 -0.893,-0.918 -1.546,-1.061 -0.653,-0.143 -1.357,-0.092 -1.709,-0.066 -0.352,0.026 -0.352,0.026 -0.352,0.026
|
||||
m 9.428,12.428 c 2.265,0.245 4.531,0.49 6.674,0.066 2.143,-0.424 4.163,-1.515 5.285,-3.081 1.122,-1.566 1.347,-3.607 1.27,-5.306 -0.076,-1.699 -0.454,-3.056 -1.454,-4.219 -1,-1.163 -2.622,-2.133 -4.704,-2.505 -2.082,-0.373 -4.623,-0.148 -7.164,0.076 0,0 0.092,14.969 0.092,14.969
|
||||
m 3.49,-12.398 c 0,0 0,9.673 0,9.673 0.888,0.02 1.776,0.041 2.653,-0.179 0.877,-0.219 1.745,-0.679 2.367,-1.541 0.622,-0.862 1,-2.127 0.98,-3.403 -0.02,-1.275 -0.439,-2.561 -1.193,-3.337 -0.755,-0.776 -1.847,-1.041 -2.704,-1.158 -0.857,-0.117 -1.48,-0.087 -2.102,-0.056
|
||||
m 11.908,12.245 v-14.785 h8.969 v2.51 h-5.786 v3.612 h5.388 v2.51 h-5.449 v6.092
|
||||
</StreamGeometry>
|
||||
|
||||
<StreamGeometry x:Key="PdfDownArrowIcon">
|
||||
M29,44 V58.7498 H35.0491 A 1.5,1.5 0 0 1 36.1342,61.2861 L23.5607,73.8595 A 1.5,1.5 0 0 1 21.4393,73.8595 L8.8658,61.2861 A 1.5,1.5 0 0 1 9.9509,58.7498 H16 V44 A 1.5,1.5 0 0 1 17.5,42.5 H27.5 A 1.5,1.5 0 0 1 29,44
|
||||
</StreamGeometry>
|
||||
|
||||
<CombinedGeometry x:Key="PdfNotDownloadedIcon" Geometry1="{StaticResource PdfDownloadedIcon}" Geometry2="{StaticResource PdfDownArrowIcon}" />
|
||||
|
||||
<StreamGeometry x:Key="ImportIcon">
|
||||
M5.65,4.3 h-2.75 a2.9,2.25 0 0 0 -2.9,2.25 v7.2
|
||||
a2.9,2.25 0 0 0 2.9,2.25 h10.2 a2.9,2.25 0 0 0 2.9,-2.25 v-7.2 a2.9,2.25 0 0 0 -2.9,-2.25
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
using ReactiveUI;
|
||||
using Avalonia.Media;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace LibationAvalonia.ViewModels;
|
||||
|
||||
public class LiberateStatusButtonViewModel : ViewModelBase
|
||||
{
|
||||
public bool IsError { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool IsButtonEnabled { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool IsSeries { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool Expanded { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool RedVisible { get => field; set => this.RaiseAndSetIfChanged(ref field, value); } = true;
|
||||
public bool YellowVisible { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool GreenVisible { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool PdfDownloadedVisible { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public bool PdfNotDownloadedVisible { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
public IImage? ButtonImage { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using LibationFileManager;
|
||||
using Avalonia.Styling;
|
||||
using LibationFileManager;
|
||||
using LibationUiBase;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LibationAvalonia.ViewModels;
|
||||
@@ -23,19 +23,6 @@ partial class MainVM
|
||||
PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray());
|
||||
|
||||
BaseUtil.SetLoadImageDelegate(AvaloniaUtils.TryLoadImageOrDefault);
|
||||
BaseUtil.SetLoadResourceImageDelegate(LoadResourceImage);
|
||||
}
|
||||
private static Avalonia.Media.Imaging.Bitmap? LoadResourceImage(string resourceName)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = App.OpenAsset(resourceName);
|
||||
return new Avalonia.Media.Imaging.Bitmap(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Failed to load resource image: {ResourceName}", resourceName);
|
||||
return null;
|
||||
}
|
||||
BaseUtil.SetIsDarkModeDelegate(() => App.Current.ActualThemeVariant == ThemeVariant.Dark);
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,6 @@
|
||||
x:Class="LibationAvalonia.Views.LiberateStatusButton">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Path">
|
||||
<Setter Property="Fill" Value="{DynamicResource IconFill}" />
|
||||
<Setter Property="Stretch" Value="Uniform" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="Rectangle">
|
||||
<Setter Property="Width" Value="20" />
|
||||
<Setter Property="Height" Value="18" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid > Path">
|
||||
<Setter Property="Margin" Value="4,0,0,0" />
|
||||
<Setter Property="Width" Value="28.8" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemChromeDisabledLowColor}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemChromeDisabledLowColor}" />
|
||||
@@ -39,41 +23,11 @@
|
||||
IsEnabled="{Binding IsButtonEnabled}" Click="Button_Click" >
|
||||
|
||||
<Grid RowDefinitions="*,8*,*">
|
||||
<Viewbox
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Stretch="Uniform"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Panel>
|
||||
<Panel IsVisible="{Binding !IsError}">
|
||||
|
||||
<Panel IsVisible="{Binding IsSeries}">
|
||||
<Path IsVisible="{Binding Expanded}" Data="{StaticResource CollapseIcon}" />
|
||||
<Path IsVisible="{Binding !Expanded}" Data="{StaticResource ExpandIcon}" />
|
||||
</Panel>
|
||||
|
||||
<Grid
|
||||
IsVisible="{Binding !IsSeries}"
|
||||
HorizontalAlignment="Center"
|
||||
ColumnDefinitions="Auto,Auto">
|
||||
<Canvas Width="29.44" Height="64">
|
||||
<Rectangle Canvas.Left="5" Canvas.Top="5" IsVisible="{Binding RedVisible}" Fill="{DynamicResource StoplightRed}" />
|
||||
<Rectangle Canvas.Left="5" Canvas.Top="23" IsVisible="{Binding YellowVisible}" Fill="{DynamicResource StoplightYellow}" />
|
||||
<Rectangle Canvas.Left="5" Canvas.Top="42" IsVisible="{Binding GreenVisible}" Fill="{DynamicResource StoplightGreen}" />
|
||||
<Path Height="64" Stretch="Uniform" Data="{StaticResource StoplightBodyIcon}"/>
|
||||
</Canvas>
|
||||
<Path Grid.Column="1" IsVisible="{Binding PdfDownloadedVisible}" Data="{StaticResource PdfDownloadedIcon}"/>
|
||||
<Path Grid.Column="1" IsVisible="{Binding PdfNotDownloadedVisible}" Data="{StaticResource PdfNotDownloadedIcon}"/>
|
||||
</Grid>
|
||||
</Panel>
|
||||
|
||||
<Path
|
||||
Stretch="None" Width="64"
|
||||
IsVisible="{Binding IsError}"
|
||||
Fill="{DynamicResource CancelRed}"
|
||||
Data="{StaticResource BookErrorIcon}" />
|
||||
</Panel>
|
||||
</Viewbox>
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
Source="{Binding ButtonImage}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using DataLayer;
|
||||
using LibationAvalonia.ViewModels;
|
||||
using LibationUiBase.GridView;
|
||||
@@ -21,17 +22,15 @@ public partial class LiberateStatusButton : UserControl
|
||||
public static readonly StyledProperty<bool> IsUnavailableProperty =
|
||||
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(IsUnavailable));
|
||||
|
||||
public static readonly StyledProperty<bool> ExpandedProperty =
|
||||
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(Expanded));
|
||||
|
||||
public static readonly StyledProperty<bool> IsSeriesProperty =
|
||||
AvaloniaProperty.Register<LiberateStatusButton, bool>(nameof(IsSeries));
|
||||
public static readonly StyledProperty<IImage?> ButtonImageProperty =
|
||||
AvaloniaProperty.Register<LiberateStatusButton, IImage?>(nameof(ButtonImage));
|
||||
|
||||
public LiberatedStatus BookStatus { get => GetValue(BookStatusProperty); set => SetValue(BookStatusProperty, value); }
|
||||
public LiberatedStatus? PdfStatus { get => GetValue(PdfStatusProperty); set => SetValue(PdfStatusProperty, value); }
|
||||
public bool IsUnavailable { get => GetValue(IsUnavailableProperty); set => SetValue(IsUnavailableProperty, value); }
|
||||
public bool Expanded { get => GetValue(ExpandedProperty); set => SetValue(ExpandedProperty, value); }
|
||||
public bool IsSeries { get => GetValue(IsSeriesProperty); set => SetValue(IsSeriesProperty, value); }
|
||||
|
||||
/// <summary>The shared rendering of this entry's status, from <see cref="EntryStatus.ButtonImage"/>.</summary>
|
||||
public IImage? ButtonImage { get => GetValue(ButtonImageProperty); set => SetValue(ButtonImageProperty, value); }
|
||||
|
||||
private readonly LiberateStatusButtonViewModel viewModel = new();
|
||||
|
||||
@@ -40,14 +39,10 @@ public partial class LiberateStatusButton : UserControl
|
||||
InitializeComponent();
|
||||
button.DataContext = viewModel;
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
BookStatus = LiberatedStatus.PartialDownload;
|
||||
PdfStatus = null;
|
||||
IsSeries = true;
|
||||
}
|
||||
|
||||
DataContextChanged += LiberateStatusButton_DataContextChanged;
|
||||
|
||||
//The icon is rendered for a specific theme, so it has to be re-rendered when the theme changes.
|
||||
ActualThemeVariantChanged += (_, _) => (DataContext as GridEntry)?.Liberate?.Invalidate(nameof(EntryStatus.ButtonImage));
|
||||
}
|
||||
|
||||
private void LiberateStatusButton_DataContextChanged(object? sender, EventArgs e)
|
||||
@@ -55,35 +50,17 @@ public partial class LiberateStatusButton : UserControl
|
||||
//Force book status recheck when an entry is scrolled into view.
|
||||
//This will force a recheck for a partially downloaded file.
|
||||
var status = DataContext as LibraryBookEntry;
|
||||
status?.Liberate?.Invalidate(nameof(status.Liberate.BookStatus));
|
||||
status?.Liberate?.Invalidate(nameof(status.Liberate.BookStatus), nameof(status.Liberate.ButtonImage));
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e) => Click?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
if (change.Property == BookStatusProperty)
|
||||
{
|
||||
viewModel.IsError = BookStatus is LiberatedStatus.Error;
|
||||
viewModel.RedVisible = BookStatus is LiberatedStatus.NotLiberated;
|
||||
viewModel.YellowVisible = BookStatus is LiberatedStatus.PartialDownload;
|
||||
viewModel.GreenVisible = BookStatus is LiberatedStatus.Liberated;
|
||||
}
|
||||
else if (change.Property == PdfStatusProperty)
|
||||
{
|
||||
viewModel.PdfDownloadedVisible = PdfStatus is LiberatedStatus.Liberated;
|
||||
viewModel.PdfNotDownloadedVisible = PdfStatus is LiberatedStatus.NotLiberated;
|
||||
}
|
||||
else if (change.Property == IsSeriesProperty)
|
||||
{
|
||||
viewModel.IsSeries = IsSeries;
|
||||
}
|
||||
else if (change.Property == ExpandedProperty)
|
||||
{
|
||||
viewModel.Expanded = Expanded;
|
||||
}
|
||||
if (change.Property == ButtonImageProperty)
|
||||
viewModel.ButtonImage = ButtonImage;
|
||||
|
||||
viewModel.IsButtonEnabled = !viewModel.IsError && (!IsUnavailable || (BookStatus is LiberatedStatus.Liberated && PdfStatus is null or LiberatedStatus.Liberated));
|
||||
viewModel.IsButtonEnabled = BookStatus is not LiberatedStatus.Error && (!IsUnavailable || (BookStatus is LiberatedStatus.Liberated && PdfStatus is null or LiberatedStatus.Liberated));
|
||||
|
||||
base.OnPropertyChanged(change);
|
||||
}
|
||||
|
||||
@@ -110,8 +110,7 @@
|
||||
BookStatus="{Binding Liberate.BookStatus}"
|
||||
PdfStatus="{Binding Liberate.PdfStatus}"
|
||||
IsUnavailable="{Binding Liberate.IsUnavailable}"
|
||||
IsSeries="{Binding Liberate.IsSeries}"
|
||||
Expanded="{Binding Liberate.Expanded}"
|
||||
ButtonImage="{Binding Liberate.ButtonImage}"
|
||||
Click="LiberateButton_Click" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
|
||||
Reference in new issue
Block a user