diff --git a/demo/Sandbox/Views/MainWindow.axaml b/demo/Sandbox/Views/MainWindow.axaml index d1f30fd..eced496 100644 --- a/demo/Sandbox/Views/MainWindow.axaml +++ b/demo/Sandbox/Views/MainWindow.axaml @@ -7,27 +7,84 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Sandbox.Views.MainWindow" x:DataType="vm:MainWindowViewModel" + xmlns:sys="using:System" Icon="/Assets/avalonia-logo.ico" Title="Sandbox"> - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/PathPickerDemo.axaml b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml new file mode 100644 index 0000000..fb3cd05 --- /dev/null +++ b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs new file mode 100644 index 0000000..2651845 --- /dev/null +++ b/demo/Ursa.Demo/Pages/PathPickerDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class PathPickerDemo : UserControl +{ + public PathPickerDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 6b8d953..66038c1 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Styling; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Ursa.Controls; using Ursa.Themes.Semi; namespace Ursa.Demo.ViewModels; @@ -79,6 +80,7 @@ public partial class MainViewViewModel : ViewModelBase MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(), MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(), MenuKeys.AspectRatioLayout => new AspectRatioLayoutDemoViewModel(), + MenuKeys.PathPicker => new PathPickerDemoViewModel(), _ => throw new ArgumentOutOfRangeException(nameof(s), s, null) }; } diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 9b90094..b0afd2a 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -59,7 +59,8 @@ public class MenuViewModel : ViewModelBase new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar }, new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox }, new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon }, - new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.AspectRatioLayout ,Status = "WIP"}, + new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.AspectRatioLayout, Status = "New" }, + new() { MenuHeader = "PathPicker", Key = MenuKeys.PathPicker, Status = "WIP" }, }; } } @@ -115,4 +116,5 @@ public static class MenuKeys public const string MenuKeyTreeComboBox = "TreeComboBox"; public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon"; public const string AspectRatioLayout = "AspectRatioLayout"; + public const string PathPicker = "PathPicker"; } \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs new file mode 100644 index 0000000..307556d --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/PathPickerDemoViewModel.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public partial class PathPickerDemoViewModel : ViewModelBase +{ + [ObservableProperty] private string? _path; + [ObservableProperty] private IReadOnlyList? _paths; +} \ No newline at end of file diff --git a/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj b/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj index b0d99e6..a40b3b8 100644 --- a/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj +++ b/src/Ursa.ReactiveUIExtension/Ursa.ReactiveUIExtension.csproj @@ -14,14 +14,14 @@ 这个是一个Ursa拓展包。这个包整合并互相兼容了UrsaWindow和UrsaView与Avalonia.ReactiveUI的功能。【Avalonia.ReactiveUI参见:https://docs.avaloniaui.net/docs/concepts/reactiveui/】 - 1.0.1 + 1.0.2 https://github.com/irihitech/Ursa.Avalonia true snupkg - + diff --git a/src/Ursa.Themes.Semi/Controls/PathPicker.axaml b/src/Ursa.Themes.Semi/Controls/PathPicker.axaml new file mode 100644 index 0000000..07a243a --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/PathPicker.axaml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 0c8421c..541005b 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -55,5 +55,6 @@ + diff --git a/src/Ursa/Controls/PathPicker/PathPicker.cs b/src/Ursa/Controls/PathPicker/PathPicker.cs new file mode 100644 index 0000000..ff62a6b --- /dev/null +++ b/src/Ursa/Controls/PathPicker/PathPicker.cs @@ -0,0 +1,211 @@ +using System.Windows.Input; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; +using Avalonia.Threading; +using Irihi.Avalonia.Shared.Common; + +namespace Ursa.Controls; + +[TemplatePart(Name = "PART_Button", Type = typeof(Button))] +public class PathPicker : TemplatedControl +{ + public static readonly StyledProperty SelectedPathProperty = + AvaloniaProperty.Register( + nameof(SelectedPath), defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true, + validate: x => string.IsNullOrWhiteSpace(x) || File.Exists(x) || Directory.Exists(x)); + + + public static readonly StyledProperty SuggestedStartPathProperty = + AvaloniaProperty.Register( + nameof(SuggestedStartPath), string.Empty); + + public static readonly StyledProperty UsePickerTypeProperty = + AvaloniaProperty.Register( + nameof(UsePickerType)); + + public static readonly StyledProperty SuggestedFileNameProperty = + AvaloniaProperty.Register( + nameof(SuggestedFileName), string.Empty); + + public static readonly StyledProperty FileFilterProperty = AvaloniaProperty.Register( + nameof(FileFilter), string.Empty); + + public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register( + nameof(Title), string.Empty); + + public static readonly StyledProperty DefaultFileExtensionProperty = + AvaloniaProperty.Register( + nameof(DefaultFileExtension), string.Empty); + + public static readonly DirectProperty> SelectedPathsProperty = + AvaloniaProperty.RegisterDirect>( + nameof(SelectedPaths), o => o.SelectedPaths, (o, v) => o.SelectedPaths = v); + + public static readonly StyledProperty CommandProperty = AvaloniaProperty.Register( + nameof(Command)); + + public static readonly StyledProperty AllowMultipleProperty = AvaloniaProperty.Register( + nameof(AllowMultiple)); + + private Button? _button; + + private IReadOnlyList _selectedPaths = []; + + public PathPicker() + { + KeyBindings.Add(new KeyBinding + { + Command = new IRIHI_CommandBase(() => + { + if (!SelectedPathProperty.ValidateValue!.Invoke(SelectedPath)) return; + SelectedPaths = string.IsNullOrWhiteSpace(SelectedPath) ? Array.Empty() : [SelectedPath!]; + Command?.Execute(Task.FromResult(SelectedPaths)); + }), + Gesture = new KeyGesture(Key.Enter) + }); + } + + public bool AllowMultiple + { + get => GetValue(AllowMultipleProperty); + set => SetValue(AllowMultipleProperty, value); + } + + public ICommand? Command + { + get => GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + + public IReadOnlyList SelectedPaths + { + get => _selectedPaths; + private set => SetAndRaise(SelectedPathsProperty, ref _selectedPaths, value); + } + + public string SuggestedFileName + { + get => GetValue(SuggestedFileNameProperty); + set => SetValue(SuggestedFileNameProperty, value); + } + + public string DefaultFileExtension + { + get => GetValue(DefaultFileExtensionProperty); + set => SetValue(DefaultFileExtensionProperty, value); + } + + + public string Title + { + get => GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public string FileFilter + { + get => GetValue(FileFilterProperty); + set => SetValue(FileFilterProperty, value); + } + + public UsePickerTypes UsePickerType + { + get => GetValue(UsePickerTypeProperty); + set => SetValue(UsePickerTypeProperty, value); + } + + public string SuggestedStartPath + { + get => GetValue(SuggestedStartPathProperty); + set => SetValue(SuggestedStartPathProperty, value); + } + + public string? SelectedPath + { + get => GetValue(SelectedPathProperty); + set => SetValue(SelectedPathProperty, value); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == SelectedPathsProperty) + SelectedPath = SelectedPaths.Count > 0 ? SelectedPaths[0] : string.Empty; + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _button = e.NameScope.Find