diff --git a/demo/Sandbox/App.axaml.cs b/demo/Sandbox/App.axaml.cs index 3ead0df..e086f87 100644 --- a/demo/Sandbox/App.axaml.cs +++ b/demo/Sandbox/App.axaml.cs @@ -1,4 +1,5 @@ using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; @@ -21,10 +22,7 @@ public partial class App : Application // Line below is needed to remove Avalonia data validation. // Without this line you will get duplicate validations from both Avalonia and CT BindingPlugins.DataValidators.RemoveAt(0); - desktop.MainWindow = new MainWindow - { - DataContext = new MainWindowViewModel(), - }; + desktop.MainWindow = new MainSplashWindow(); } base.OnFrameworkInitializationCompleted(); diff --git a/demo/Sandbox/Views/MainSplashWindow.axaml b/demo/Sandbox/Views/MainSplashWindow.axaml new file mode 100644 index 0000000..e5e6b73 --- /dev/null +++ b/demo/Sandbox/Views/MainSplashWindow.axaml @@ -0,0 +1,12 @@ + + Welcome to Avalonia Splash Window! + diff --git a/demo/Sandbox/Views/MainSplashWindow.axaml.cs b/demo/Sandbox/Views/MainSplashWindow.axaml.cs new file mode 100644 index 0000000..58546f1 --- /dev/null +++ b/demo/Sandbox/Views/MainSplashWindow.axaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using System.Timers; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Threading; +using Sandbox.ViewModels; +using Ursa.Controls; + +namespace Sandbox.Views; + +public partial class MainSplashWindow : SplashWindow +{ + public MainSplashWindow() + { + InitializeComponent(); + } + + protected override async Task CreateNextWindow() + { + return new MainWindow() + { + DataContext = new MainWindowViewModel() + }; + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/App.axaml.cs b/demo/Ursa.Demo/App.axaml.cs index 980608c..92e928f 100644 --- a/demo/Ursa.Demo/App.axaml.cs +++ b/demo/Ursa.Demo/App.axaml.cs @@ -18,9 +18,9 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow() + desktop.MainWindow = new MvvmSplashWindow() { - DataContext = new MainViewViewModel(), + DataContext = new SplashViewModel() }; } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView) diff --git a/demo/Ursa.Demo/ViewModels/SplashViewModel.cs b/demo/Ursa.Demo/ViewModels/SplashViewModel.cs new file mode 100644 index 0000000..c2e2d14 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/SplashViewModel.cs @@ -0,0 +1,38 @@ +using System; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using Irihi.Avalonia.Shared.Contracts; + +namespace Ursa.Demo.ViewModels; + +public partial class SplashViewModel: ObservableObject, IDialogContext +{ + [ObservableProperty] private double _progress; + private Random _r = new(); + + public SplashViewModel() + { + DispatcherTimer.Run(OnUpdate, TimeSpan.FromMilliseconds(20), DispatcherPriority.Default); + } + + private bool OnUpdate() + { + Progress += 10 * _r.NextDouble(); + if (Progress <= 100) + { + return true; + } + else + { + RequestClose?.Invoke(this, true); + return false; + } + } + + public void Close() + { + RequestClose?.Invoke(this, false); + } + + public event EventHandler? RequestClose; +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainSplashWindow.axaml b/demo/Ursa.Demo/Views/MainSplashWindow.axaml new file mode 100644 index 0000000..8f40138 --- /dev/null +++ b/demo/Ursa.Demo/Views/MainSplashWindow.axaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainSplashWindow.axaml.cs b/demo/Ursa.Demo/Views/MainSplashWindow.axaml.cs new file mode 100644 index 0000000..662a15e --- /dev/null +++ b/demo/Ursa.Demo/Views/MainSplashWindow.axaml.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ursa.Controls; +using Ursa.Demo.ViewModels; + +namespace Ursa.Demo.Views; + +public partial class MainSplashWindow : SplashWindow +{ + public MainSplashWindow() + { + InitializeComponent(); + } + + protected override async Task CreateNextWindow() + { + return new MainWindow() + { + DataContext = new MainViewViewModel() + }; + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml b/demo/Ursa.Demo/Views/MainWindow.axaml index ed3ec2b..6136fae 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml +++ b/demo/Ursa.Demo/Views/MainWindow.axaml @@ -13,6 +13,7 @@ d:DesignWidth="800" x:CompileBindings="True" x:DataType="viewModels:MainWindowViewModel" + WindowStartupLocation="CenterScreen" Icon="/Assets/Ursa.ico" IsFullScreenButtonVisible="{OnPlatform True, macOS=False}" IsManagedResizerVisible="{OnPlatform False, Linux=True}" diff --git a/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml b/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml new file mode 100644 index 0000000..986c465 --- /dev/null +++ b/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml.cs b/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml.cs new file mode 100644 index 0000000..a1bf93b --- /dev/null +++ b/demo/Ursa.Demo/Views/MvvmSplashWindow.axaml.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ursa.Controls; +using Ursa.Demo.ViewModels; + +namespace Ursa.Demo.Views; + +public partial class MvvmSplashWindow : SplashWindow +{ + public MvvmSplashWindow() + { + InitializeComponent(); + } + + protected override async Task CreateNextWindow() + { + if (this.DialogResult is true) + { + return new MainWindow() + { + DataContext = new MainViewViewModel() + }; + } + return null; + } +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/SplashWindow.axaml b/src/Ursa.Themes.Semi/Controls/SplashWindow.axaml new file mode 100644 index 0000000..4127e94 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/SplashWindow.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + Full + + + None + + + + + \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 541005b..ce51210 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -56,5 +56,6 @@ + diff --git a/src/Ursa/Windows/SplashWindow.cs b/src/Ursa/Windows/SplashWindow.cs new file mode 100644 index 0000000..ee8feb7 --- /dev/null +++ b/src/Ursa/Windows/SplashWindow.cs @@ -0,0 +1,86 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Irihi.Avalonia.Shared.Contracts; + +namespace Ursa.Controls; + +public abstract class SplashWindow: Window +{ + protected override Type StyleKeyOverride => typeof(SplashWindow); + + public static readonly StyledProperty CountDownProperty = AvaloniaProperty.Register( + nameof(CountDown)); + + public TimeSpan? CountDown + { + get => GetValue(CountDownProperty); + set => SetValue(CountDownProperty, value); + } + + static SplashWindow() + { + DataContextProperty.Changed.AddClassHandler((window, e) => + window.OnDataContextChange(e)); + } + + private void OnDataContextChange(AvaloniaPropertyChangedEventArgs args) + { + if (args.OldValue.Value is IDialogContext oldContext) oldContext.RequestClose -= OnContextRequestClose; + + if (args.NewValue.Value is IDialogContext newContext) newContext.RequestClose += OnContextRequestClose; + } + + private void OnContextRequestClose(object? sender, object? args) + { + DialogResult = args; + Close(); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + if (CountDown != null && CountDown != TimeSpan.Zero) + { + DispatcherTimer.RunOnce(Close, CountDown.Value); + } + } + + protected object? DialogResult { get; private set; } + + protected virtual Task CanClose() => Task.FromResult(true); + protected abstract Task CreateNextWindow(); + + private bool _canClose; + + protected override sealed async void OnClosing(WindowClosingEventArgs e) + { + VerifyAccess(); + if (!_canClose) + { + e.Cancel = true; + _canClose = await CanClose(); + if (_canClose) + { + var nextWindow = await CreateNextWindow(); + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime && nextWindow is not null) + { + lifetime.MainWindow = nextWindow; + } + nextWindow?.Show(); + Close(); + if (DataContext is IDialogContext idc) + { + // unregister in advance in case developer try to raise event again. + idc.RequestClose -= OnContextRequestClose; + idc.Close(); + } + return; + } + } + base.OnClosing(e); + + } +} \ No newline at end of file