Multi-Platform Package Manager for Stable Diffusion
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
10 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
namespace StabilityMatrix.Avalonia.Controls;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "PropertyCanBeMadeInitOnly.Global")]
public class BetterContentDialog : ContentDialog
{
#region Reflection Shenanigans for setting content dialog result
[NotNull]
protected static readonly FieldInfo? ResultField = typeof(ContentDialog).GetField(
"_result",BindingFlags.Instance | BindingFlags.NonPublic);
protected ContentDialogResult Result
{
get => (ContentDialogResult) ResultField.GetValue(this)!;
set => ResultField.SetValue(this, value);
}
[NotNull]
protected static readonly MethodInfo? HideCoreMethod = typeof(ContentDialog).GetMethod(
"HideCore", BindingFlags.Instance | BindingFlags.NonPublic);
protected void HideCore()
{
HideCoreMethod.Invoke(this, null);
}
// Also get button properties to hide on command execution change
[NotNull]
protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField(
"_primaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? PrimaryButton
{
get => (Button?) PrimaryButtonField.GetValue(this)!;
set => PrimaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField(
"_secondaryButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? SecondaryButton
{
get => (Button?) SecondaryButtonField.GetValue(this)!;
set => SecondaryButtonField.SetValue(this, value);
}
[NotNull]
protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField(
"_closeButton", BindingFlags.Instance | BindingFlags.NonPublic);
protected Button? CloseButton
{
get => (Button?) CloseButtonField.GetValue(this)!;
set => CloseButtonField.SetValue(this, value);
}
static BetterContentDialog()
{
if (ResultField is null)
{
throw new NullReferenceException("ResultField was not resolved");
}
if (HideCoreMethod is null)
{
throw new NullReferenceException("HideCoreMethod was not resolved");
}
if (PrimaryButtonField is null || SecondaryButtonField is null || CloseButtonField is null)
{
throw new NullReferenceException("Button fields were not resolved");
}
}
#endregion
protected override Type StyleKeyOverride { get; } = typeof(ContentDialog);
public static readonly StyledProperty<bool> IsFooterVisibleProperty = AvaloniaProperty.Register<BetterContentDialog, bool>(
"IsFooterVisible", true);
public bool IsFooterVisible
{
get => GetValue(IsFooterVisibleProperty);
set => SetValue(IsFooterVisibleProperty, value);
}
public static readonly StyledProperty<ScrollBarVisibility> ContentVerticalScrollBarVisibilityProperty
= AvaloniaProperty.Register<BetterContentDialog, ScrollBarVisibility>(
"ContentScrollBarVisibility", ScrollBarVisibility.Auto);
public ScrollBarVisibility ContentVerticalScrollBarVisibility
{
get => GetValue(ContentVerticalScrollBarVisibilityProperty);
set => SetValue(ContentVerticalScrollBarVisibilityProperty, value);
}
public static readonly StyledProperty<double> MinDialogWidthProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MinDialogWidth");
public double MinDialogWidth
{
get => GetValue(MinDialogWidthProperty);
set => SetValue(MinDialogWidthProperty, value);
}
public static readonly StyledProperty<double> MaxDialogWidthProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MaxDialogWidth");
public double MaxDialogWidth
{
get => GetValue(MaxDialogWidthProperty);
set => SetValue(MaxDialogWidthProperty, value);
}
public static readonly StyledProperty<double> MaxDialogHeightProperty = AvaloniaProperty.Register<BetterContentDialog, double>(
"MaxDialogHeight");
public double MaxDialogHeight
{
get => GetValue(MaxDialogHeightProperty);
set => SetValue(MaxDialogHeightProperty, value);
}
public static readonly StyledProperty<Thickness> ContentMarginProperty = AvaloniaProperty.Register<BetterContentDialog, Thickness>(
"ContentMargin");
public Thickness ContentMargin
{
get => GetValue(ContentMarginProperty);
set => SetValue(ContentMarginProperty, value);
}
public BetterContentDialog()
{
AddHandler(LoadedEvent, OnLoaded);
}
private void TryBindButtons()
{
if ((Content as Control)?.DataContext is ContentDialogViewModelBase viewModel)
{
viewModel.PrimaryButtonClick += OnDialogButtonClick;
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
}
else if ((Content as Control)?.DataContext is ContentDialogProgressViewModelBase progressViewModel)
{
progressViewModel.PrimaryButtonClick += OnDialogButtonClick;
progressViewModel.SecondaryButtonClick += OnDialogButtonClick;
progressViewModel.CloseButtonClick += OnDialogButtonClick;
}
// If commands provided, bind OnCanExecuteChanged to hide buttons
// otherwise link visibility to IsEnabled
if (PrimaryButton is not null)
{
if (PrimaryButtonCommand is not null)
{
PrimaryButtonCommand.CanExecuteChanged += (_, _) =>
PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);
// Also set initial state
PrimaryButton.IsEnabled = PrimaryButtonCommand.CanExecute(null);
}
else
{
PrimaryButton.IsVisible = IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText);
}
}
if (SecondaryButton is not null)
{
if (SecondaryButtonCommand is not null)
{
SecondaryButtonCommand.CanExecuteChanged += (_, _) =>
SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);
// Also set initial state
SecondaryButton.IsEnabled = SecondaryButtonCommand.CanExecute(null);
}
else
{
SecondaryButton.IsVisible = IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText);
}
}
if (CloseButton is not null)
{
if (CloseButtonCommand is not null)
{
CloseButtonCommand.CanExecuteChanged += (_, _) =>
CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);
// Also set initial state
CloseButton.IsEnabled = CloseButtonCommand.CanExecute(null);
}
}
}
protected void OnDialogButtonClick(object? sender, ContentDialogResult e)
{
Result = e;
HideCore();
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
TryBindButtons();
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var background = e.NameScope.Find<FABorder>("BackgroundElement");
if (background is not null)
{
background.Margin = ContentMargin;
}
}
private void OnLoaded(object? sender, RoutedEventArgs? e)
{
TryBindButtons();
// Find the named grid
// https://github.com/amwx/FluentAvalonia/blob/master/src/FluentAvalonia/Styling/
// ControlThemes/FAControls/ContentDialogStyles.axaml#L96
var border = VisualChildren[0] as Border;
var panel = border?.Child as Panel;
var faBorder = panel?.Children[0] as FABorder;
// Set dialog bounds
if (MaxDialogWidth > 0)
{
faBorder!.MaxWidth = MaxDialogWidth;
}
if (MinDialogWidth > 0)
{
faBorder!.MinWidth = MinDialogWidth;
}
if (MaxDialogHeight > 0)
{
faBorder!.MaxHeight = MaxDialogHeight;
}
var border2 = faBorder?.Child as Border;
// Named Grid 'DialogSpace'
if (border2?.Child is not Grid dialogSpaceGrid) throw new InvalidOperationException("Could not find DialogSpace grid");
var scrollViewer = dialogSpaceGrid.Children[0] as ScrollViewer;
var actualBorder = dialogSpaceGrid.Children[1] as Border;
// Get the parent border, which is what we want to hide
if (scrollViewer is null || actualBorder is null)
{
throw new InvalidOperationException("Could not find parent border");
}
var subBorder = scrollViewer.Content as Border;
var subGrid = subBorder?.Child as Grid;
if (subGrid is null) throw new InvalidOperationException("Could not find sub grid");
var contentControlTitle = subGrid.Children[0] as ContentControl;
// Hide title if empty
if (Title is null or string {Length: 0})
{
contentControlTitle!.IsVisible = false;
}
// Set footer and scrollbar visibility states
actualBorder.IsVisible = IsFooterVisible;
scrollViewer.VerticalScrollBarVisibility = ContentVerticalScrollBarVisibility;
// Also call the vm's OnLoad
if (Content is Control {DataContext: ViewModelBase viewModel})
{
viewModel.OnLoaded();
Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();
}
}
}