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.

355 lines
11 KiB

using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
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;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using Brushes = Avalonia.Media.Brushes;
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(
1 year ago
"_result",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected ContentDialogResult Result
{
1 year ago
get => (ContentDialogResult)ResultField.GetValue(this)!;
set => ResultField.SetValue(this, value);
}
1 year ago
[NotNull]
protected static readonly MethodInfo? HideCoreMethod = typeof(ContentDialog).GetMethod(
1 year ago
"HideCore",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected void HideCore()
{
HideCoreMethod.Invoke(this, null);
}
1 year ago
// Also get button properties to hide on command execution change
[NotNull]
protected static readonly FieldInfo? PrimaryButtonField = typeof(ContentDialog).GetField(
1 year ago
"_primaryButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? PrimaryButton
{
1 year ago
get => (Button?)PrimaryButtonField.GetValue(this)!;
set => PrimaryButtonField.SetValue(this, value);
}
1 year ago
[NotNull]
protected static readonly FieldInfo? SecondaryButtonField = typeof(ContentDialog).GetField(
1 year ago
"_secondaryButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? SecondaryButton
{
1 year ago
get => (Button?)SecondaryButtonField.GetValue(this)!;
set => SecondaryButtonField.SetValue(this, value);
}
1 year ago
[NotNull]
protected static readonly FieldInfo? CloseButtonField = typeof(ContentDialog).GetField(
1 year ago
"_closeButton",
BindingFlags.Instance | BindingFlags.NonPublic
);
protected Button? CloseButton
{
1 year ago
get => (Button?)CloseButtonField.GetValue(this)!;
set => CloseButtonField.SetValue(this, value);
}
1 year ago
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
1 year ago
private FABorder? backgroundPart;
protected override Type StyleKeyOverride { get; } = typeof(ContentDialog);
1 year ago
public static readonly StyledProperty<bool> IsFooterVisibleProperty = AvaloniaProperty.Register<
BetterContentDialog,
bool
>("IsFooterVisible", true);
public bool IsFooterVisible
{
get => GetValue(IsFooterVisibleProperty);
set => SetValue(IsFooterVisibleProperty, value);
}
1 year ago
public static readonly StyledProperty<ScrollBarVisibility> ContentVerticalScrollBarVisibilityProperty =
AvaloniaProperty.Register<BetterContentDialog, ScrollBarVisibility>(
"ContentScrollBarVisibility",
ScrollBarVisibility.Auto
);
public ScrollBarVisibility ContentVerticalScrollBarVisibility
{
get => GetValue(ContentVerticalScrollBarVisibilityProperty);
set => SetValue(ContentVerticalScrollBarVisibilityProperty, value);
}
1 year ago
public static readonly StyledProperty<double> MinDialogWidthProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MinDialogWidth");
public double MinDialogWidth
{
get => GetValue(MinDialogWidthProperty);
set => SetValue(MinDialogWidthProperty, value);
}
1 year ago
public static readonly StyledProperty<double> MaxDialogWidthProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MaxDialogWidth");
public double MaxDialogWidth
{
get => GetValue(MaxDialogWidthProperty);
set => SetValue(MaxDialogWidthProperty, value);
}
1 year ago
public static readonly StyledProperty<double> MaxDialogHeightProperty =
AvaloniaProperty.Register<BetterContentDialog, double>("MaxDialogHeight");
public double MaxDialogHeight
{
get => GetValue(MaxDialogHeightProperty);
set => SetValue(MaxDialogHeightProperty, value);
}
1 year ago
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)
&& (Content as Control)?.DataContext is ContentDialogViewModelBase vm
)
{
vm.OnCloseButtonClick();
}
}
}
private void TryBindButtons()
{
if ((Content as Control)?.DataContext is ContentDialogViewModelBase viewModel)
{
viewModel.PrimaryButtonClick += OnDialogButtonClick;
viewModel.SecondaryButtonClick += OnDialogButtonClick;
viewModel.CloseButtonClick += OnDialogButtonClick;
}
1 year ago
else if (
(Content as Control)?.DataContext
is ContentDialogProgressViewModelBase progressViewModel
)
{
progressViewModel.PrimaryButtonClick += OnDialogButtonClick;
progressViewModel.SecondaryButtonClick += OnDialogButtonClick;
progressViewModel.CloseButtonClick += OnDialogButtonClick;
}
1 year ago
// 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
{
1 year ago
PrimaryButton.IsVisible =
IsPrimaryButtonEnabled && !string.IsNullOrEmpty(PrimaryButtonText);
}
}
1 year ago
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
{
1 year ago
SecondaryButton.IsVisible =
IsSecondaryButtonEnabled && !string.IsNullOrEmpty(SecondaryButtonText);
}
}
1 year ago
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);
1 year ago
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;
1 year ago
// Set dialog bounds
if (MaxDialogWidth > 0)
{
faBorder!.MaxWidth = MaxDialogWidth;
}
if (MinDialogWidth > 0)
{
faBorder!.MinWidth = MinDialogWidth;
}
if (MaxDialogHeight > 0)
{
faBorder!.MaxHeight = MaxDialogHeight;
}
1 year ago
var border2 = faBorder?.Child as Border;
// Named Grid 'DialogSpace'
1 year ago
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;
1 year ago
// 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");
}
1 year ago
var subBorder = scrollViewer.Content as Border;
var subGrid = subBorder?.Child as Grid;
1 year ago
if (subGrid is null)
throw new InvalidOperationException("Could not find sub grid");
var contentControlTitle = subGrid.Children[0] as ContentControl;
// Hide title if empty
1 year ago
if (Title is null or string { Length: 0 })
{
contentControlTitle!.IsVisible = false;
}
1 year ago
// Set footer and scrollbar visibility states
actualBorder.IsVisible = IsFooterVisible;
scrollViewer.VerticalScrollBarVisibility = ContentVerticalScrollBarVisibility;
1 year ago
// Also call the vm's OnLoad
1 year ago
if (Content is Control { DataContext: ViewModelBase viewModel })
{
viewModel.OnLoaded();
Dispatcher.UIThread.InvokeAsync(viewModel.OnLoadedAsync).SafeFireAndForget();
}
}
}