Skip to content

Commit

Permalink
Implemented overlay dialogs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jp2masa committed Sep 2, 2024
1 parent 480df0a commit 9a2d393
Show file tree
Hide file tree
Showing 39 changed files with 676 additions and 402 deletions.
28 changes: 10 additions & 18 deletions samples/Movere.Sample/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
using Avalonia.Platform.Storage;
using Avalonia.ReactiveUI;

using Movere.Avalonia.Services;
using Movere.Sample.ViewModels;
using Movere.Sample.Views;
using Movere.Services;
using Movere.Storage;
using Movere.Win32;

Expand All @@ -31,29 +31,21 @@ public override void OnFrameworkInitializationCompleted()
{
var mainWindow = new MainWindow();

var messageDialogService = new MessageDialogService(mainWindow);
var customContentViewResolver = new CustomContentViewResolver();

var contentDialogService = new ContentDialogService<CustomContentViewModel, FormResult>(
mainWindow,
new CustomContentViewResolver());

var openFileDialogService = new OpenFileDialogService(mainWindow);
var saveFileDialogService = new SaveFileDialogService(mainWindow);

var printDialogService = new PrintDialogService(mainWindow);
var windowHost = new WindowDialogHost(this, mainWindow, customContentViewResolver);
var overlayHost = new OverlayDialogHost(this, mainWindow, customContentViewResolver);

mainWindow.DataContext = new MainWindowViewModel(
windowHost,
overlayHost,
() => AvaloniaOpenFile(mainWindow),
() => AvaloniaSaveFile(mainWindow),
#pragma warning disable CS0612 // Type or member is obsolete
() => AvaloniaOldOpenFile(mainWindow),
() => AvaloniaOldSaveFile(mainWindow),
() => AvaloniaOldSaveFile(mainWindow)
#pragma warning restore CS0612 // Type or member is obsolete
messageDialogService,
contentDialogService,
openFileDialogService,
saveFileDialogService,
printDialogService);
);

desktop.MainWindow = mainWindow;
}
Expand All @@ -78,7 +70,7 @@ private static AppBuilder BuildAvaloniaApp() =>
.UseMovereWin32()
.UseReactiveUI();

private static Task AvaloniaOpenFile(Window parent)
private static Task AvaloniaOpenFile(TopLevel parent)
{
var options = new FilePickerOpenOptions()
{
Expand All @@ -93,7 +85,7 @@ private static Task AvaloniaOpenFile(Window parent)
return parent.StorageProvider.OpenFilePickerAsync(options);
}

private static Task AvaloniaSaveFile(Window parent)
private static Task AvaloniaSaveFile(TopLevel parent)
{
var options = new FilePickerSaveOptions()
{
Expand Down
91 changes: 59 additions & 32 deletions samples/Movere.Sample/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,49 +25,57 @@ internal enum FormResult

internal class MainWindowViewModel : ReactiveObject
{
private readonly MessageDialogService _messageDialogService;
private readonly ContentDialogService<CustomContentViewModel, FormResult> _contentDialogService;
private sealed class DialogServices(IDialogHost host)
{
public IMessageDialogService Message { get; } =
new MessageDialogService(host);

public IContentDialogService<CustomContentViewModel, FormResult> Content { get; } =
new ContentDialogService<CustomContentViewModel, FormResult>(host);

private readonly OpenFileDialogService _openFileDialogService;
private readonly SaveFileDialogService _saveFileDialogService;
public IOpenFileDialogService OpenFile { get; } =
new OpenFileDialogService(host);

private readonly PrintDialogService _printDialogService;
public ISaveFileDialogService SaveFile { get; } =
new SaveFileDialogService(host);

public IPrintDialogService Print { get; } =
new PrintDialogService(host);
}

private readonly DialogServices _windowDialogServices;
private readonly DialogServices _overlayDialogServices;

private string _messageDialogResult = "Not opened yet";
private string _contentDialogResult = "Not opened yet";

private bool _useOverlayDialogs = false;

public MainWindowViewModel(
IDialogHost windowHost,
IDialogHost overlayHost,
Func<Task> avaloniaOpenFile,
Func<Task> avaloniaSaveFile,
Func<Task> avaloniaOldOpenFile,
Func<Task> avaloniaOldSaveFile,
MessageDialogService messageDialogService,
ContentDialogService<CustomContentViewModel, FormResult> contentDialogService,
OpenFileDialogService openFileDialogService,
SaveFileDialogService saveFileDialogService,
PrintDialogService printDialogService)
Func<Task> avaloniaOldSaveFile
)
{
_messageDialogService = messageDialogService;
_contentDialogService = contentDialogService;

_openFileDialogService = openFileDialogService;
_saveFileDialogService = saveFileDialogService;
_windowDialogServices = new DialogServices(windowHost);
_overlayDialogServices = new DialogServices(overlayHost);

_printDialogService = printDialogService;
ShowMessageCommand = ReactiveCommand.CreateFromTask(ShowMessageAsync);
ShowCustomContentCommand = ReactiveCommand.CreateFromTask(ShowCustomContentAsync);

ShowMessageCommand = ReactiveCommand.Create(ShowMessageAsync);
ShowCustomContentCommand = ReactiveCommand.Create(ShowCustomContentAsync);
OpenFileCommand = ReactiveCommand.CreateFromTask(OpenFileAsync);
SaveFileCommand = ReactiveCommand.CreateFromTask(SaveFileAsync);

OpenFileCommand = ReactiveCommand.Create(OpenFileAsync);
SaveFileCommand = ReactiveCommand.Create(SaveFileAsync);
PrintCommand = ReactiveCommand.CreateFromTask(PrintAsync);

PrintCommand = ReactiveCommand.Create(PrintAsync);
AvaloniaOpenFileCommand = ReactiveCommand.CreateFromTask(avaloniaOpenFile);
AvaloniaSaveFileCommand = ReactiveCommand.CreateFromTask(avaloniaSaveFile);

AvaloniaOpenFileCommand = ReactiveCommand.Create(avaloniaOpenFile);
AvaloniaSaveFileCommand = ReactiveCommand.Create(avaloniaSaveFile);

AvaloniaOldOpenFileCommand = ReactiveCommand.Create(avaloniaOldOpenFile);
AvaloniaOldSaveFileCommand = ReactiveCommand.Create(avaloniaOldSaveFile);
AvaloniaOldOpenFileCommand = ReactiveCommand.CreateFromTask(avaloniaOldOpenFile);
AvaloniaOldSaveFileCommand = ReactiveCommand.CreateFromTask(avaloniaOldSaveFile);
}

public string MessageDialogResult
Expand Down Expand Up @@ -100,9 +108,20 @@ public string ContentDialogResult

public ICommand AvaloniaOldSaveFileCommand { get; }

public bool UseOverlayDialogs
{
get => _useOverlayDialogs;
set => this.RaiseAndSetIfChanged(ref _useOverlayDialogs, value);
}

private DialogServices Dialogs =>
UseOverlayDialogs
? _overlayDialogServices
: _windowDialogServices;

private async Task ShowMessageAsync() =>
MessageDialogResult = (
await _messageDialogService.ShowMessageDialogAsync(
await Dialogs.Message.ShowMessageDialogAsync(
new MessageDialogOptions(
"Some really really really really really really really really really really really " +
"really really really really really really really really really really really really " +
Expand Down Expand Up @@ -157,17 +176,23 @@ select from value in field.WhenAnyValue(x => x.Value)
)
);

var result = await _contentDialogService.ShowDialogAsync(
var result = await Dialogs.Content.ShowDialogAsync(
ContentDialogOptions.Create("Custom content", vm, DialogActionSet.Create(actions, actions[2], actions[0]))
);

ContentDialogResult = $"Result: {result} ID: {id.Value} First Name: {firstName.Value} Last Name: {lastName.Value}";
}

private Task OpenFileAsync() =>
_openFileDialogService.ShowDialogAsync(new OpenFileDialogOptions() { AllowMultipleSelection = true });
Dialogs.OpenFile.ShowDialogAsync(
new OpenFileDialogOptions()
{
AllowMultipleSelection = true
}
);

private Task SaveFileAsync() => _saveFileDialogService.ShowDialogAsync();
private Task SaveFileAsync() =>
Dialogs.SaveFile.ShowDialogAsync();

private async Task PrintAsync()
{
Expand All @@ -179,7 +204,9 @@ private async Task PrintAsync()
using var document = new PrintDocument();

document.PrintPage += PrintDocument;
await _printDialogService.ShowDialogAsync(new PrintDialogOptions(document));

await Dialogs.Print
.ShowDialogAsync(new PrintDialogOptions(document));
}

private static void PrintDocument(object sender, PrintPageEventArgs e)
Expand Down
21 changes: 20 additions & 1 deletion samples/Movere.Sample/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:flex="clr-namespace:Avalonia.Labs.Panels;assembly=Avalonia.Labs.Panels"
xmlns:vm="clr-namespace:Movere.Sample.ViewModels"
x:Class="Movere.Sample.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Expand All @@ -9,9 +10,11 @@
Title="Movere Sample"
WindowStartupLocation="CenterScreen">

<Grid ColumnDefinitions="*,*,*">
<Grid ColumnDefinitions="*,*,*"
RowDefinitions="*, 8, Auto">

<StackPanel Grid.Column="1"
Grid.Row="0"
Margin="0,16"
Spacing="8">
<StackPanel.Styles>
Expand Down Expand Up @@ -54,6 +57,22 @@

</StackPanel>

<flex:FlexPanel
Grid.Column="0"
Grid.ColumnSpan="3"
Grid.Row="2"
Margin="16, 8"
Direction="Column"
AlignItems="FlexEnd"
ColumnSpacing="16"
RowSpacing="16">

<CheckBox
Content="Use Overlay Dialogs"
IsChecked="{Binding UseOverlayDialogs}"/>

</flex:FlexPanel>

</Grid>

</Window>
58 changes: 58 additions & 0 deletions src/Movere/Avalonia/Services/DialogHostBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;

using Autofac;

using Avalonia;
using Avalonia.Controls.Templates;

using Movere.Services;
using Movere.ViewModels;

namespace Movere.Avalonia.Services
{
public abstract class DialogHostBase : IMovereDialogHost
{
private readonly IContainer _container;

private protected DialogHostBase(Application application, IDataTemplate? dataTemplate = null)
{
var containerBuilder = new ContainerBuilder()
{
Properties =
{
["Application"] = application
}
};

containerBuilder
.RegisterAssemblyModules(typeof(WindowDialogHost).Assembly);

containerBuilder
.RegisterInstance<IDialogHost>(this);

_container = containerBuilder.Build();

var resolver = _container.Resolve<ViewResolver>();

DataTemplate = dataTemplate is null
? resolver
: new FuncDataTemplate(
x => dataTemplate.Match(x) || resolver.Match(x),
(x, _) => dataTemplate.Match(x) ? dataTemplate.Build(x) : resolver.Build(x)
);
}

protected IDataTemplate DataTemplate { get; }

IContainer IMovereDialogHost.Container =>
_container;

public abstract IObservable<TResult> ShowDialog<TContent, TResult>(
Func<IDialogView<TResult>, IDialogWindowViewModel<TContent>> viewModelFactory
);

public ValueTask DisposeAsync() =>
_container.DisposeAsync();
}
}
74 changes: 74 additions & 0 deletions src/Movere/Avalonia/Services/OverlayDialogHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;

using Avalonia;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;

using Movere.Services;
using Movere.ViewModels;
using Movere.Views;

namespace Movere.Avalonia.Services
{
internal sealed class OverlayDialogHost(Application application, Visual target, IDataTemplate? dataTemplate = null)
: DialogHostBase(application, dataTemplate)
{
private sealed class DialogView<TResult> : IDialogView<TResult>
{
private readonly OverlayLayer _layer;
private readonly DialogOverlay _overlay;

private readonly ISubject<TResult> _resultSubject = new Subject<TResult>();

public DialogView(OverlayLayer layer, DialogOverlay overlay)
{
_layer = layer;
_overlay = overlay;

Result = _resultSubject.AsObservable();
}

public IObservable<TResult> Result { get; }

public void Close(TResult result)
{
if (_overlay.OnClosing())
{
_layer.Children.Remove(_overlay);

_resultSubject.OnNext(result);
_resultSubject.OnCompleted();
}
}
}

public override IObservable<TResult> ShowDialog<TContent, TResult>(
Func<IDialogView<TResult>, IDialogWindowViewModel<TContent>> viewModelFactory
)
{
var layer = OverlayLayer.GetOverlayLayer(target);

if (layer is null)
{
return Observable.Create<TResult>(
observer =>
{
observer.OnError(new Exception());
return Disposable.Empty;
}
);
}

var overlay = new DialogOverlay() { DataTemplates = { DataTemplate } };
var view = new DialogView<TResult>(layer, overlay);

overlay.DataContext = viewModelFactory(view);
layer.Children.Add(overlay);

return view.Result;
}
}
}
Loading

0 comments on commit 9a2d393

Please sign in to comment.