Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement IStorageProvider OpenFile/OpenFolder/SaveFile and MessageBox dialogs #153

Merged
merged 71 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
1081b42
snapshot of file picker storage api
tomlm Nov 23, 2024
fd11af0
storage provider
tomlm Nov 23, 2024
e9fe1ea
file picker
tomlm Nov 23, 2024
23eba99
move dialog files into core
tomlm Nov 23, 2024
17290da
hey it displays (kinda)
tomlm Nov 23, 2024
263b23f
things mo betta
tomlm Nov 23, 2024
fa5716d
another slice of heavon
tomlm Nov 23, 2024
80049ef
auto center and size using WindowLocation
tomlm Nov 23, 2024
6d6b830
Clean up dialog layout
tomlm Nov 23, 2024
0edcddf
move things around, implement folder save first cut
tomlm Nov 23, 2024
b2e4991
update namespaces
tomlm Nov 23, 2024
1bf2950
file type filtering
tomlm Nov 24, 2024
6ce9beb
show dialog results
tomlm Nov 24, 2024
439a91e
clean up warningsc
tomlm Nov 24, 2024
26a5987
multiple select works
tomlm Nov 24, 2024
bd1821e
update storagefile and storage folder display attributes
tomlm Nov 24, 2024
d256979
update display attributes
tomlm Nov 24, 2024
8d8bacb
add unit tests for storage
tomlm Nov 24, 2024
4ab1583
cleanup sample
tomlm Nov 25, 2024
7c3f6e1
lint pass
tomlm Nov 25, 2024
3672beb
remove TEMP dependency
tomlm Nov 25, 2024
2024c0e
fix unit tests
tomlm Nov 25, 2024
da46975
merge
tomlm Nov 25, 2024
3e1d8b0
change compairon to work on unix
tomlm Nov 25, 2024
b2c6197
more lint
tomlm Nov 25, 2024
a03adbf
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 25, 2024
2015d17
linnnnnnnnt
tomlm Nov 25, 2024
a06a94a
lintlint
tomlm Nov 25, 2024
af01d95
lintlintlint
tomlm Nov 25, 2024
4e9b322
it's a unit test for xsake
tomlm Nov 25, 2024
f37efa0
gah
tomlm Nov 25, 2024
5200d1a
oh
tomlm Nov 25, 2024
99cd7fa
Automated JetBrains cleanup
github-actions[bot] Nov 25, 2024
f4784cd
code rabbit changes
tomlm Nov 25, 2024
7c44cce
Update src/Consolonia.Core/Controls/FileSavePicker.axaml.cs
tomlm Nov 25, 2024
9bdc400
Update src/Consolonia.Core/Controls/FileOpenPicker.axaml.cs
tomlm Nov 25, 2024
dcb3c1a
Update src/Consolonia.Core/Infrastructure/SystemStorageFolder.cs
tomlm Nov 25, 2024
e83a7cb
coderabbit changes
tomlm Nov 25, 2024
e8c9cd6
hmmm
tomlm Nov 25, 2024
38a3906
this check is needed, ReSharper is smoking crack
tomlm Nov 25, 2024
98ec862
fix unit test
tomlm Nov 25, 2024
9437f05
um
tomlm Nov 25, 2024
6d6c133
no filter for save
tomlm Nov 25, 2024
a0cd574
Automated JetBrains cleanup
github-actions[bot] Nov 25, 2024
d083d59
rewrote the file handling for save dialog
tomlm Nov 25, 2024
b701be0
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 25, 2024
d30f691
null checks in new code
tomlm Nov 25, 2024
55eee41
jesus
tomlm Nov 25, 2024
adad00e
Automated JetBrains cleanup
github-actions[bot] Nov 25, 2024
b27dcef
add messagebox control
tomlm Nov 26, 2024
6db37e3
change to stack panel
tomlm Nov 26, 2024
e6133b9
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 26, 2024
f2e6352
lint
tomlm Nov 26, 2024
6c796b0
Automated JetBrains cleanup
github-actions[bot] Nov 26, 2024
d8d993b
added default constructors for lint
tomlm Nov 26, 2024
a4a4468
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 26, 2024
d286527
I totally don't understand why this was still there.
tomlm Nov 26, 2024
519d451
Automated JetBrains cleanup
github-actions[bot] Nov 26, 2024
0a33999
Remove InitializeComponent generated function as it's not only not us…
tomlm Nov 26, 2024
a581d25
code cleanup
tomlm Nov 26, 2024
fe97462
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 26, 2024
54b4167
fix datetime sensityify in unit tests on linux
tomlm Nov 26, 2024
d328507
lint
tomlm Nov 26, 2024
331f8e4
Automated JetBrains cleanup
github-actions[bot] Nov 26, 2024
c547054
changes based on jinek code review.
tomlm Nov 29, 2024
21972f8
Merge branch 'tomlm/storageProvider' of https://github.com/jinek/Cons…
tomlm Nov 29, 2024
99393d3
fix focus on messgebox
tomlm Nov 29, 2024
41a5240
use discard for not used argments
tomlm Nov 29, 2024
cd74f8d
remove unused usings
tomlm Nov 29, 2024
088be14
Automated JetBrains cleanup
github-actions[bot] Nov 29, 2024
824defb
tomlm/storageProvider_CA2007_2 (#155)
jinek Nov 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Authors>https://github.com/jinek/Consolonia/graphs/contributors</Authors>
<Description>Text User Interface implementation of Avalonia UI (GUI Framework)</Description>
<Copyright>Copyright © Evgeny Gorbovoy 2021 - 2022</Copyright>
<NoWarn>AVA3001</NoWarn>
</PropertyGroup>
<PropertyGroup>
<AvaloniaVersion>11.0.9</AvaloniaVersion>
Expand Down
1 change: 1 addition & 0 deletions src/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ dotnet_diagnostic.ca1043.severity = none # todo: why not to use indexer rather t
dotnet_diagnostic.ca1814.severity = none # sometimes we need rectangle array
dotnet_diagnostic.ca1058.severity = none # check why new documentation warns not to derive from ApplicationException. The purpose of last is to distinguish system exceptions like OOM, AssemblyLoadException etc from application logic
dotnet_diagnostic.ca2000.severity = none # exceptions which are listed in the rule can be applied to any type
dotnet_diagnostic.CA2007.severity = none # we are assuming task continuation thread is fine by default
dotnet_diagnostic.ca2213.severity = none # keeping field does not mean necessarity to dispose - it can be dispose somwhere else
dotnet_diagnostic.ca2248.severity = none # https://github.com/dotnet/roslyn-analyzers/issues/4432
dotnet_diagnostic.ca1062.severity = none # we are fine with NRE - no need for additional checks
Expand Down
11 changes: 9 additions & 2 deletions src/Consolonia.Core/Consolonia.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Core.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Core.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<ItemGroup>
<AvaloniaXaml Remove="Controls\FileOpen\**" />
<Compile Remove="Controls\FileOpen\**" />
<EmbeddedResource Remove="Controls\FileOpen\**" />
<None Remove="Controls\FileOpen\**" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.FreeDesktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Skia" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="$(AvaloniaVersion)" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.8" />
<PackageReference Include="Unicode.net" Version="2.0.0" />
<PackageReference Include="Wcwidth" Version="2.0.0" />
</ItemGroup>


</Project>

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
using Avalonia.VisualTree;
using Consolonia.Core.Helpers;

namespace Consolonia.Themes.TurboVision.Templates.Controls.Dialog
namespace Consolonia.Core.Controls.Dialog
{
public class DialogHost
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:drawing1="clr-namespace:Consolonia.Core.Drawing;assembly=Consolonia.Core"
x:Class="Consolonia.Themes.TurboVision.Templates.Controls.DialogWrap">
x:Class="Consolonia.Core.Controls.Dialog.DialogWrap">

<Panel>
<Border Background="{drawing1:ConsoleBrush Mode=Shaded}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Reactive;
using Avalonia.VisualTree;
using Consolonia.Themes.TurboVision.Templates.Controls.Dialog;

namespace Consolonia.Themes.TurboVision.Templates.Controls
namespace Consolonia.Core.Controls.Dialog
{
[SuppressMessage("Usage", "PartialTypeWithSinglePart",
Justification = "Partial class required for XAML code generation.")]
Expand Down Expand Up @@ -52,11 +50,6 @@ private void SetNewSize(Size newSize)
Height = newSize.Height;
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}

public void SetContent(DialogWindow dialogWindow)
{
FoundContentPresenter.Content = dialogWindow;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.VisualTree;
using Consolonia.Core.Controls.Dialog;

// ReSharper disable MemberCanBeProtected.Global

namespace Consolonia.Themes.TurboVision.Templates.Controls.Dialog
namespace Consolonia.Core.Controls
{
[TemplatePart("PART_ContentPresenter", typeof(ContentPresenter))]
public class DialogWindow : UserControl
Expand All @@ -24,10 +25,20 @@ public class DialogWindow : UserControl
public static readonly StyledProperty<bool> IsCloseButtonVisibleProperty =
AvaloniaProperty.Register<DialogWindow, bool>(nameof(IsCloseButtonVisible), true);

public static readonly StyledProperty<WindowStartupLocation> WindowStartupLocationProperty =
AvaloniaProperty.Register<DialogWindow, WindowStartupLocation>(nameof(WindowStartupLocation));

public static readonly StyledProperty<bool> CanResizeProperty =
AvaloniaProperty.Register<DialogWindow, bool>(nameof(CanResize), true);

public static readonly StyledProperty<string> IconProperty =
AvaloniaProperty.Register<DialogWindow, string>(nameof(Icon));

private Size _contentSize;
private ContentPresenter _partContentPresenter;

private TaskCompletionSource _taskCompletionSource;
private TaskCompletionSource<object> _taskCompletionSource;


static DialogWindow()
{
Expand Down Expand Up @@ -61,6 +72,43 @@ public bool IsCloseButtonVisible
// ReSharper disable once MemberCanBePrivate.Global
public bool CancelOnEscape { get; set; } = true;


/// <summary>
/// Enables or disables resizing of the window.
/// </summary>
public bool CanResize
{
get => GetValue(CanResizeProperty);
set => SetValue(CanResizeProperty, value);
}

/// <summary>
/// Gets or sets the icon of the window.
/// </summary>
public string Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}

/// <summary>
/// Gets or sets the startup location of the window.
/// </summary>
public WindowStartupLocation WindowStartupLocation
{
get => GetValue(WindowStartupLocationProperty);
set => SetValue(WindowStartupLocationProperty, value);
}

/// <summary>
/// Gets or sets the window position in screen coordinates.
/// </summary>
//public PixelPoint Position
//{
// get => PlatformImpl?.Position ?? PixelPoint.Origin;
// set => PlatformImpl?.Move(value);
//}

// ReSharper disable once UnusedMember.Global Used by template
public void CloseClick()
{
Expand All @@ -83,31 +131,60 @@ protected override Size ArrangeOverride(Size finalSize)
return arrangeOverride;
}

private void ShowDialogInternal(Visual parent)
protected void ShowDialogInternal(Visual parent)
{
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
Width = (ushort)(parent.Bounds.Width * .9);
Height = (ushort)(parent.Bounds.Height * .9);
if (parent is Window window) window.SizeChanged += Window_SizeChanged;
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

DialogHost dialogHost = GetDialogHost(parent);
dialogHost.OpenInternal(this);
}

private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
var window = (Window)sender;
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
Width = (ushort)(window.Bounds.Width * .9);
Height = (ushort)(window.Bounds.Height * .9);
}
}

// ReSharper disable once VirtualMemberNeverOverridden.Global overriden in other packages, why resharper suggests this?
public virtual void CloseDialog()
public virtual void CloseDialog(object result = null)
{
DialogHost dialogHost = GetDialogHost(this);
dialogHost.PopInternal(this);
_taskCompletionSource.SetResult();
if (Parent is Window window) window.SizeChanged -= Window_SizeChanged;
_taskCompletionSource.SetResult(result);
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

public async Task ShowDialogAsync(Control parent)
{
if (_taskCompletionSource != null)
throw new InvalidOperationException("Dialog is already shown.");

_taskCompletionSource = new TaskCompletionSource<object>();
ShowDialogInternal(parent);
await _taskCompletionSource.Task;
}

public Task ShowDialogAsync(Control parent)
public async Task<T> ShowDialogAsync<T>(Control parent)
{
if (_taskCompletionSource != null)
throw new NotImplementedException();
throw new InvalidOperationException("Dialog is already shown.");

_taskCompletionSource = new TaskCompletionSource();
_taskCompletionSource = new TaskCompletionSource<object>();
ShowDialogInternal(parent);
return _taskCompletionSource.Task;
object result = await _taskCompletionSource.Task;
return (T)result;
tomlm marked this conversation as resolved.
Show resolved Hide resolved
}

private static DialogHost GetDialogHost(Visual parent)
protected static DialogHost GetDialogHost(Visual parent)
{
var window = parent.FindAncestorOfType<Window>(true);
DialogHost dialogHost = window!.GetValue(DialogHost.DialogHostProperty);
Expand Down
69 changes: 69 additions & 0 deletions src/Consolonia.Core/Controls/FileOpenPicker.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<controls:DialogWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:storage="clr-namespace:Avalonia.Platform.Storage;assembly=Avalonia.Base"
xmlns:controls="clr-namespace:Consolonia.Core.Controls"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:DataType="controls:FileOpenPickerViewModel"
x:Class="Consolonia.Core.Controls.FileOpenPicker"
Icon="📰"
CancelOnEscape="True"
Title="{Binding Options.Title}">
<Grid RowDefinitions="Auto * Auto Auto">
<Grid ColumnDefinitions="Auto *">
<TextBlock Text="📁 " />
<TextBox Text="{Binding CurrentFolderPath, Mode=TwoWay}"
Grid.Column="1" />
</Grid>
tomlm marked this conversation as resolved.
Show resolved Hide resolved
<ListBox x:Name="ItemsListBox"
Grid.Row="1"
ItemsSource="{Binding Items}"
SelectionMode="{Binding SelectionMode}"
SelectionChanged="ListBox_SelectionChanged"
DoubleTapped="OnDoubleTapped">
<ListBox.DataTemplates>
<DataTemplate DataType="{x:Type storage:IStorageFolder}">
<Grid ColumnDefinitions="Auto * "
Background="Transparent">
<TextBlock Text="📁"
Margin="0 0 1 0" />
<TextBlock Grid.Column="1"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type storage:IStorageFile}">
<Grid ColumnDefinitions="Auto *"
Background="Transparent">
<TextBlock Text="📰"
Margin="0 0 1 0" />
<TextBlock Grid.Column="1"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
tomlm marked this conversation as resolved.
Show resolved Hide resolved
<ComboBox Grid.Row="2"
ItemsSource="{Binding Options.FileTypeFilter}"
SelectedIndex="{Binding SelectedFilterIndex}"
HorizontalAlignment="Right">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type storage:FilePickerFileType}">
<TextBlock Text="{Binding Name}"
HorizontalAlignment="Stretch" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<StackPanel Grid.Row="3"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="1">
<Button x:Name="OkButton"
Content="OK"
Click="OnOK"
IsEnabled="{Binding HasSelection}" />
<Button x:Name="CancelButton"
Content="Cancel"
Click="OnCancel" />
</StackPanel>
tomlm marked this conversation as resolved.
Show resolved Hide resolved
</Grid>
</controls:DialogWindow>
83 changes: 83 additions & 0 deletions src/Consolonia.Core/Controls/FileOpenPicker.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;

namespace Consolonia.Core.Controls
{
public partial class FileOpenPicker : DialogWindow
{
public FileOpenPicker()
: this(new FilePickerOpenOptions())
{
}

public FileOpenPicker(FilePickerOpenOptions options)
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;

DataContext = new FileOpenPickerViewModel(options);
InitializeComponent();
CancelButton.Focus();
}

/// <summary>
/// Gets the view model associated with this picker.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the DataContext is null or not of type FileOpenPickerViewModel.</exception>
private FileOpenPickerViewModel ViewModel =>
DataContext as FileOpenPickerViewModel
?? throw new InvalidOperationException("DataContext is not properly initialized.");

private void OnDoubleTapped(object sender, TappedEventArgs e)
{
var listbox = (ListBox)sender;
if (listbox.SelectedItem is IStorageFolder folder)
{
ViewModel.CurrentFolder = folder;
ViewModel.CurrentFolderPath = folder.Path.LocalPath;
ViewModel.SelectedFiles.Clear();
}
else if (listbox.SelectedItem is IStorageFile file)
{
CloseDialog(new[] { file });
}
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

private void OnOK(object sender, RoutedEventArgs e)
{
CloseDialog(ViewModel.SelectedFiles);
}

private void OnCancel(object sender, RoutedEventArgs e)
{
CloseDialog();
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ViewModel.SelectionMode == SelectionMode.Single)
{
if (e.AddedItems.Count > 0 &&
e.AddedItems[0] is IStorageFile file)
{
ViewModel.SelectedFiles.Clear();
ViewModel.SelectedFiles.Add(file);
}
}
else
{
foreach (object item in e.AddedItems)
if (item is IStorageFile file)
ViewModel.SelectedFiles.Add(file);

foreach (object item in e.RemovedItems)
if (item is IStorageFile file)
ViewModel.SelectedFiles.Remove(file);
}

ViewModel.HasSelection = ViewModel.SelectedFiles.Count > 0;
}
tomlm marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading