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.
 
 
 

382 lines
12 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.ViewModels.Base;
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
private FABorder? backgroundPart;
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> MinDialogHeightProperty = AvaloniaProperty.Register<
BetterContentDialog,
double
>("MinDialogHeight");
public double MinDialogHeight
{
get => GetValue(MaxDialogHeightProperty);
set => SetValue(MaxDialogHeightProperty, 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 static readonly StyledProperty<bool> CloseOnClickOutsideProperty = AvaloniaProperty.Register<
BetterContentDialog,
bool
>("CloseOnClickOutside");
/// <summary>
/// Whether to close the dialog when clicking outside of it (on the blurred background)
/// </summary>
public bool CloseOnClickOutside
{
get => GetValue(CloseOnClickOutsideProperty);
set => SetValue(CloseOnClickOutsideProperty, value);
}
public BetterContentDialog()
{
AddHandler(LoadedEvent, OnLoaded);
}
/// <inheritdoc />
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (CloseOnClickOutside)
{
if (e.Source is Popup || backgroundPart is null)
return;
var point = e.GetPosition(this);
if (!backgroundPart.Bounds.Contains(point))
{
// Use vm if available
if ((Content as Control)?.DataContext is ContentDialogViewModelBase vm)
{
vm.OnCloseButtonClick();
}
else
{
Hide(ContentDialogResult.None);
}
}
}
}
private void TryBindButtons()
{
if ((Content as Control)?.DataContext is ContentDialogViewModelBase viewModel)
{
viewModel.PrimaryButtonClick += OnDialogButtonClick;
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
}
else if (Content is ContentDialogViewModelBase viewModelDirect)
{
viewModelDirect.PrimaryButtonClick += OnDialogButtonClick;
viewModelDirect.SecondaryButtonClick += OnDialogButtonClick;
viewModelDirect.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)
{
Dispatcher.UIThread.Post(() =>
{
Result = e;
HideCore();
});
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
TryBindButtons();
}
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
backgroundPart = e.NameScope.Find<FABorder>("BackgroundElement");
if (backgroundPart is not null)
{
backgroundPart.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();
}
}
}