From c19c858de1c90ed63b46e139b3045cbe938ea3c8 Mon Sep 17 00:00:00 2001 From: Pilzinsel64 Date: Mon, 10 Nov 2025 15:57:51 +0100 Subject: [PATCH] add Avalonia Dialogs --- .../Dialogs/AvaloniaDialogBase.Statics.cs | 47 +++++ .../Dialogs/AvaloniaDialogBase.axaml | 8 + .../Dialogs/AvaloniaDialogBase.axaml.cs | 27 +++ .../Dialogs/AvaloniaFlyoutBase.Statics.cs | 33 +++ .../Dialogs/AvaloniaFlyoutBase.axaml | 78 +++++++ .../Dialogs/AvaloniaFlyoutBase.axaml.cs | 197 ++++++++++++++++++ Pilz/EnvironmentEx.cs | 27 +++ Pilz/Pilz.csproj | 2 +- Pilz/Runtime/RuntimeInformationsEx.cs | 13 ++ 9 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.Statics.cs create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml.cs create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.Statics.cs create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml create mode 100644 Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml.cs create mode 100644 Pilz/EnvironmentEx.cs diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.Statics.cs b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.Statics.cs new file mode 100644 index 0000000..f7b5731 --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.Statics.cs @@ -0,0 +1,47 @@ +using Avalonia.Controls; + +namespace Pilz.UI.AvaloniaUI.Dialogs; + +public partial class AvaloniaDialogBase +{ + public static T Show(string? title, object? icon, object? tag = null) where T : AvaloniaFlyoutBase + { + return Show(CreatePanelInstance(tag), title, icon); + } + + public static Task ShowDialog(Window parent, string? title, object? icon, object? tag = null) where T : AvaloniaFlyoutBase + { + return ShowDialog(CreatePanelInstance(tag), parent, title, icon); + } + + public static T Show(T dialogPanel, string? title, object? icon) where T : AvaloniaFlyoutBase + { + CreateForm(dialogPanel, title, icon, WindowStartupLocation.CenterScreen).Show(); + return dialogPanel; + } + + public static async Task ShowDialog(T dialogPanel, Window parent, string? title, object? icon) where T : AvaloniaFlyoutBase + { + await CreateForm(dialogPanel, title, icon, WindowStartupLocation.CenterOwner).ShowDialog(parent); + return dialogPanel; + } + + private static T CreatePanelInstance(object? tag) where T : AvaloniaFlyoutBase + { + var dialogPanel = Activator.CreateInstance(); + dialogPanel.Tag = tag; + return dialogPanel; + } + + private static AvaloniaDialogBase CreateForm(T dialogPanel, string? title, object? icon, WindowStartupLocation startPosition) where T : AvaloniaFlyoutBase + { + var dialog = new AvaloniaDialogBase + { + Title = title, + Icon = icon as WindowIcon, + WindowStartupLocation = startPosition, + }; + dialog.SetContent(dialogPanel); + return dialog; + } +} \ No newline at end of file diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml new file mode 100644 index 0000000..67408e9 --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml @@ -0,0 +1,8 @@ + + diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml.cs b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml.cs new file mode 100644 index 0000000..741ca79 --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaDialogBase.axaml.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls; + +namespace Pilz.UI.AvaloniaUI.Dialogs; + +public partial class AvaloniaDialogBase : Window +{ + protected AvaloniaFlyoutBase? dialogPanel; + + public AvaloniaFlyoutBase DialogPanel => dialogPanel ?? throw new NullReferenceException(); + + public AvaloniaDialogBase() + { + InitializeComponent(); + } + + public void SetContent(AvaloniaFlyoutBase content) + { + dialogPanel = content; + dialogPanel.OnClose += DialogPanelOnOnClose; + Content = content; + } + + private void DialogPanelOnOnClose(object? sender, EventArgs e) + { + Close(DialogPanel.Result); + } +} \ No newline at end of file diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.Statics.cs b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.Statics.cs new file mode 100644 index 0000000..3061ea4 --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.Statics.cs @@ -0,0 +1,33 @@ +using Avalonia.Controls; + +namespace Pilz.UI.AvaloniaUI.Dialogs; + +public partial class AvaloniaFlyoutBase +{ + // public static void Show(Control controlToAssociate, object? tag = null) + // { + // Show(controlToAssociate, null, null, tag: tag); + // } + // + // public static void Show(Control controlToAssociate, string? title, RadSvgImage? icon, object? tag = null) + // { + // Show(controlToAssociate, typeof(T), title, icon, tag: tag); + // } + // + // public static void Show(Control controlToAssociate, Type flyoutContentType, string? title, RadSvgImage? icon, object? tag = null) + // { + // tagToAssign = tag; + // titleToAssing = title; + // iconToAssign = icon; + // ParentContext = controlToAssociate; + // CloseFlyout(); // Ensure it's closed! + // RadFlyoutManager.Show(controlToAssociate, flyoutContentType); + // } + // + // protected static void CloseFlyout() + // { + // if (typeof(RadFlyoutManager).GetField("flyoutInstance", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetValue(null) is FlyoutScreen instance + // && instance.IsActive) + // RadFlyoutManager.Close(); + // } +} \ No newline at end of file diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml new file mode 100644 index 0000000..ede3ff0 --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml.cs b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml.cs new file mode 100644 index 0000000..2f4169a --- /dev/null +++ b/Pilz.UI.AvaloniaUI/Dialogs/AvaloniaFlyoutBase.axaml.cs @@ -0,0 +1,197 @@ +using System.ComponentModel; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Media; + +namespace Pilz.UI.AvaloniaUI.Dialogs; + +public partial class AvaloniaFlyoutBase : UserControl +{ + protected readonly BackgroundWorker bgWorker_LoadData = new(); + + public delegate void OnCloseEventHandler(object? sender, EventArgs e); + + public event OnCloseEventHandler? OnClose; + + public static IImage? CancelImage { get; set; } = null; + public static IImage? ConfirmImage { get; set; } = null; + + public static readonly StyledProperty MainContentProperty = AvaloniaProperty.Register(nameof(MainContent)); + public static readonly StyledProperty FooterContentProperty = AvaloniaProperty.Register(nameof(FooterContent)); + + [Browsable(false)] + public object? Result { get; protected set; } + + [DefaultValue(true)] + public bool RegisterDialogAccept { get; set; } = true; + + [DefaultValue(true)] + public bool RegisterDialogCancel { get; set; } = true; + + protected AvaloniaFlyoutBase() + { + InitializeComponent(); + + CancelButtonImage = CancelImage; + ConfirmButtonImage = ConfirmImage; + + bgWorker_LoadData.DoWork += BgWorker_LoadData_DoWork; + bgWorker_LoadData.RunWorkerCompleted += BgWorker_LoadData_RunWorkerCompleted; + } + + public object? MainContent + { + get => GetValue(MainContentProperty); + set => SetValue(MainContentProperty, value); + } + + public object? FooterContent + { + get => GetValue(FooterContentProperty); + set => SetValue(FooterContentProperty, value); + } + + [DefaultValue(true)] + public virtual bool ActionPanelVisible + { + get => StackPanelFooter.IsVisible; + set => StackPanelFooter.IsVisible = value; + } + + [DefaultValue(true)] + public virtual bool CancelButtonVisible + { + get => ButtonCancel.IsVisible; + set => ButtonCancel.IsVisible = value; + } + + [DefaultValue(true)] + public virtual bool ConfirmButtonVisible + { + get => ButtonOkay.IsVisible; + set => ButtonOkay.IsVisible = value; + } + + [DefaultValue(true)] + public virtual bool CancelButtonEnable + { + get => ButtonCancel.IsEnabled; + set => ButtonCancel.IsEnabled = value; + } + + [DefaultValue(true)] + public virtual bool ConfirmButtonEnable + { + get => ButtonOkay.IsEnabled; + set => ButtonOkay.IsEnabled = value; + } + + [DefaultValue(null)] + public virtual IImage? CancelButtonImage + { + get => ButtonCancel.ImageSource; + set => ButtonCancel.ImageSource = value; + } + + [DefaultValue(null)] + public virtual IImage? ConfirmButtonImage + { + get => ButtonOkay.ImageSource; + set => ButtonOkay.ImageSource = value; + } + + [Localizable(true)] + [DefaultValue("Okay")] + public virtual string? ConfirmButtonText + { + get => ButtonOkay.Text; + set => ButtonOkay.Text = value; + } + + [Localizable(true)] + [DefaultValue("Cancel")] + public virtual string? CancelButtonText + { + get => ButtonCancel.Text; + set => ButtonCancel.Text = value; + } + + [Localizable(true)] + [DefaultValue("")] + public virtual string? Title + { + get => LabelTitle.Text; + set + { + LabelTitle.Text = value; + SetShowTitlePanel(); + } + } + + [DefaultValue(null)] + public virtual IImage? TitleIcon + { + get => ImageTitle.Source; + set + { + ImageTitle.Source = value; + SetShowTitlePanel(); + } + } + + protected virtual void BgWorker_LoadData_DoWork(object? sender, DoWorkEventArgs e) + { + OnLoadData(e); + } + + protected virtual void BgWorker_LoadData_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e) + { + OnLoadDataCompleted(e); + } + + protected virtual void LoadData() + { + bgWorker_LoadData.RunWorkerAsync(); + } + + protected virtual void LoadData(object? argument) + { + bgWorker_LoadData.RunWorkerAsync(argument); + } + + protected virtual void OnLoadData(DoWorkEventArgs e) + { + } + + protected virtual void OnLoadDataCompleted(RunWorkerCompletedEventArgs e) + { + } + + protected void Close(object? result) + { + Result = result; + OnClose?.Invoke(this, EventArgs.Empty); + } + + protected virtual bool ValidateOK() + { + return true; + } + + protected virtual void SetShowTitlePanel() + { + StackPanelHeader.IsVisible = !string.IsNullOrWhiteSpace(Title) || TitleIcon != null; + } + + protected virtual void ButtonOkay_OnClick(object? sender, RoutedEventArgs e) + { + if (ValidateOK()) + Close(true); + } + + protected virtual void ButtonCancel_OnClick(object? sender, RoutedEventArgs e) + { + Close(null); + } +} \ No newline at end of file diff --git a/Pilz/EnvironmentEx.cs b/Pilz/EnvironmentEx.cs new file mode 100644 index 0000000..9a6b514 --- /dev/null +++ b/Pilz/EnvironmentEx.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Pilz.Runtime; + +namespace Pilz; + +public static class EnvironmentEx +{ + private static string? processPath = null; + + public static string? ProcessPath => processPath ??= GetProcessPath(); + + private static string? GetProcessPath() + { + if (RuntimeInformationsEx.IsOSPlatform(OSType.Linux, false) + && Environment.GetEnvironmentVariable("APPIMAGE") is { } appImagePath + && !string.IsNullOrWhiteSpace(appImagePath)) + return appImagePath; + +#if NET8_0_OR_GREATER + if (Environment.ProcessPath is { } processPath + && !string.IsNullOrWhiteSpace(processPath)) + return processPath; +#endif + + return Process.GetCurrentProcess().MainModule?.FileName; + } +} \ No newline at end of file diff --git a/Pilz/Pilz.csproj b/Pilz/Pilz.csproj index e7ec605..c808db8 100644 --- a/Pilz/Pilz.csproj +++ b/Pilz/Pilz.csproj @@ -5,7 +5,7 @@ latest enable annotations - 2.5.3 + 2.6.1 diff --git a/Pilz/Runtime/RuntimeInformationsEx.cs b/Pilz/Runtime/RuntimeInformationsEx.cs index 29d91ea..ba524de 100644 --- a/Pilz/Runtime/RuntimeInformationsEx.cs +++ b/Pilz/Runtime/RuntimeInformationsEx.cs @@ -76,4 +76,17 @@ public static class RuntimeInformationsEx { return (checkRealOS ? RealOSType : OSType) == os; } + + public static string GetRuntimeIdentifier() + { + var arch = RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(); + var os = OSType switch + { + OSType.Windows => "win", + OSType.Linux => "linux", + OSType.OSX => "osx", + _ => throw new NotSupportedException(), + }; + return $"{os}-{arch}"; + } } \ No newline at end of file