diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 7b363cb..bcc040c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Setup dotnet
uses: actions/setup-dotnet@v5
@@ -32,7 +32,7 @@ jobs:
run: touch $OUTPUT_PATH/.nojekyll
- name: Commit wwwroot to GitHub Pages
- uses: JamesIves/github-pages-deploy-action@v4.5.0
+ uses: JamesIves/github-pages-deploy-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
diff --git a/.github/workflows/pack-nightly.yml b/.github/workflows/pack-nightly.yml
index 2e82c13..40a9554 100644
--- a/.github/workflows/pack-nightly.yml
+++ b/.github/workflows/pack-nightly.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Get Version
run: |
@@ -62,7 +62,7 @@ jobs:
run: dotnet nuget push "nugets/*.nupkg" --api-key ${{ secrets.IRIHI_NUGET_API_KEY }} --source irihi.tech --skip-duplicate
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: nugets
path: nugets
diff --git a/.github/workflows/pack.yml b/.github/workflows/pack.yml
index 4d8d8f2..d68411c 100644
--- a/.github/workflows/pack.yml
+++ b/.github/workflows/pack.yml
@@ -47,7 +47,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Pack Semi.Avalonia
if: ${{ inputs.Semi_Avalonia }}
@@ -70,7 +70,7 @@ jobs:
run: dotnet nuget push "nugets/*.nupkg" --api-key ${{ secrets.NUGET_ORG_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: nugets
path: nugets
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index ee4062e..51e3b14 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -75,11 +75,11 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Publish win-x64
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r win-x64 -c Release -o publish --sc /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.win-x64
path: |
@@ -91,13 +91,13 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Enable Native AOT in .csproj
run: sed -i 's##true#' demo/Semi.Avalonia.Demo.Desktop/Semi.Avalonia.Demo.Desktop.csproj
- name: Publish win-x64 AOT
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r win-x64 -c Release -o publish
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.win-x64.NativeAOT
path: |
@@ -109,11 +109,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Publish linux-x64
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r linux-x64 -c Release -o publish --sc /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.linux-x64
path: |
@@ -125,13 +125,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Enable Native AOT in .csproj
run: sed -i 's##true#' demo/Semi.Avalonia.Demo.Desktop/Semi.Avalonia.Demo.Desktop.csproj
- name: Publish linux-x64 AOT
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r linux-x64 -c Release -o publish
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.linux-x64.NativeAOT
path: |
@@ -143,11 +143,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Publish linux-x64 DRM
run: dotnet publish demo/Semi.Avalonia.Demo.Drm -r linux-x64 -c Release -o publish --sc /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Drm.linux-x64
path: |
@@ -159,13 +159,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Enable Native AOT in .csproj
run: sed -i 's##true#' demo/Semi.Avalonia.Demo.Drm/Semi.Avalonia.Demo.Drm.csproj
- name: Publish linux-x64 AOT
run: dotnet publish demo/Semi.Avalonia.Demo.Drm -r linux-x64 -c Release -o publish
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Drm.linux-x64.NativeAOT
path: |
@@ -177,11 +177,11 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Publish osx-arm64
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r osx-arm64 -c Release -o publish --sc /p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.osx-arm64
path: |
@@ -193,13 +193,13 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Enable Native AOT in .csproj
run: sed -i '' 's##true#' demo/Semi.Avalonia.Demo.Desktop/Semi.Avalonia.Demo.Desktop.csproj
- name: Publish osx-arm64 AOT
run: dotnet publish demo/Semi.Avalonia.Demo.Desktop -r osx-arm64 -c Release -o publish
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: Semi.Avalonia.Demo.Desktop.osx-arm64.NativeAOT
path: |
@@ -211,7 +211,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout
- uses: actions/checkout@v5
+ uses: actions/checkout@v6
- name: Install Android workload
run: dotnet workload install android
- name: Restore Dependencies
@@ -219,7 +219,7 @@ jobs:
- name: Publish Android
run: dotnet publish demo/Semi.Avalonia.Demo.Android -c Release -f net10.0-android --no-restore -o publish /p:RuntimeIdentifier=android-arm64
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4.6.2
+ uses: actions/upload-artifact@v7
with:
name: android-arm64
path: publish/*Signed.apk
diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml
index 36c5a97..8775683 100644
--- a/.github/workflows/release-tag.yml
+++ b/.github/workflows/release-tag.yml
@@ -41,7 +41,7 @@ jobs:
needs: [ nuget,publish ]
runs-on: ubuntu-latest
steps:
- - uses: actions/download-artifact@v4.3.0
+ - uses: actions/download-artifact@v8
- name: Display structure of downloaded files
run: ls -R
@@ -62,7 +62,7 @@ jobs:
run: ls -R
- name: Release
- uses: softprops/action-gh-release@v2.3.2
+ uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
with:
generate_release_notes: true
diff --git a/Nuget.Config b/Nuget.Config
new file mode 100644
index 0000000..0b58b3c
--- /dev/null
+++ b/Nuget.Config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/demo/Directory.Packages.props b/demo/Directory.Packages.props
index 7a071fa..500b78f 100644
--- a/demo/Directory.Packages.props
+++ b/demo/Directory.Packages.props
@@ -1,13 +1,12 @@
true
- 11.3.11
- 11.3.11
- 3.119.1
+ 12.0.0-rc2
+ 12.0.0-rc2
+ 3.119.3-preview.1.1
-
@@ -15,13 +14,13 @@
+
-
+
-
-
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo.Android/Application.cs b/demo/Semi.Avalonia.Demo.Android/Application.cs
new file mode 100644
index 0000000..611e568
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo.Android/Application.cs
@@ -0,0 +1,20 @@
+using Android.App;
+using Android.Runtime;
+using Avalonia;
+using Avalonia.Android;
+
+namespace Semi.Avalonia.Demo.Android;
+
+[Application]
+public class Application : AvaloniaAndroidApplication
+{
+ protected Application(nint javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
+ {
+ return base.CustomizeAppBuilder(builder)
+ .WithSourceHanSansCNFont();
+ }
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo.Android/MainActivity.cs b/demo/Semi.Avalonia.Demo.Android/MainActivity.cs
index 012a48d..0bf0b35 100644
--- a/demo/Semi.Avalonia.Demo.Android/MainActivity.cs
+++ b/demo/Semi.Avalonia.Demo.Android/MainActivity.cs
@@ -1,6 +1,5 @@
using Android.App;
using Android.Content.PM;
-using Avalonia;
using Avalonia.Android;
namespace Semi.Avalonia.Demo.Android;
@@ -12,11 +11,4 @@ namespace Semi.Avalonia.Demo.Android;
MainLauncher = true,
LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
-public class MainActivity : AvaloniaMainActivity
-{
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder)
- .WithSourceHanSansCNFont();
- }
-}
\ No newline at end of file
+public class MainActivity : AvaloniaMainActivity;
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo.Android/Semi.Avalonia.Demo.Android.csproj b/demo/Semi.Avalonia.Demo.Android/Semi.Avalonia.Demo.Android.csproj
index ef02e0c..c65aded 100644
--- a/demo/Semi.Avalonia.Demo.Android/Semi.Avalonia.Demo.Android.csproj
+++ b/demo/Semi.Avalonia.Demo.Android/Semi.Avalonia.Demo.Android.csproj
@@ -2,7 +2,7 @@
Exe
net10.0-android
- 21
+ 23
enable
com.irihitech.Semi.Avalonia
1
@@ -13,6 +13,12 @@
Semi.Avalonia.Demo.Android
+
+
+ false
+ true
+
+
diff --git a/demo/Semi.Avalonia.Demo/App.axaml b/demo/Semi.Avalonia.Demo/App.axaml
index 99f40c7..76a705d 100644
--- a/demo/Semi.Avalonia.Demo/App.axaml
+++ b/demo/Semi.Avalonia.Demo/App.axaml
@@ -11,7 +11,7 @@
-
+
diff --git a/demo/Semi.Avalonia.Demo/App.axaml.cs b/demo/Semi.Avalonia.Demo/App.axaml.cs
index 5a67b89..b617ffe 100644
--- a/demo/Semi.Avalonia.Demo/App.axaml.cs
+++ b/demo/Semi.Avalonia.Demo/App.axaml.cs
@@ -12,6 +12,9 @@ public partial class App : Application
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
+#if DEBUG
+ this.AttachDeveloperTools();
+#endif
this.DataContext = new ApplicationViewModel();
}
@@ -22,7 +25,7 @@ public partial class App : Application
case IClassicDesktopStyleApplicationLifetime desktop:
// 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);
+ // BindingPlugins.DataValidators.RemoveAt(0);
desktop.MainWindow = new MainWindow();
break;
case ISingleViewApplicationLifetime singleView:
diff --git a/demo/Semi.Avalonia.Demo/Controls/ColorDetailControl.cs b/demo/Semi.Avalonia.Demo/Controls/ColorDetailControl.cs
index 06a2ce8..c2ac11e 100644
--- a/demo/Semi.Avalonia.Demo/Controls/ColorDetailControl.cs
+++ b/demo/Semi.Avalonia.Demo/Controls/ColorDetailControl.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using Avalonia.Input.Platform;
using Avalonia.Media;
using Semi.Avalonia.Demo.Converters;
diff --git a/demo/Semi.Avalonia.Demo/Pages/AutoCompleteBoxDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/AutoCompleteBoxDemo.axaml
index 614efa4..f2afbb7 100644
--- a/demo/Semi.Avalonia.Demo/Pages/AutoCompleteBoxDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/AutoCompleteBoxDemo.axaml
@@ -28,7 +28,7 @@
@@ -69,12 +69,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/CalendarDatePickerDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/CalendarDatePickerDemo.axaml
index 4730e62..83f4f0a 100644
--- a/demo/Semi.Avalonia.Demo/Pages/CalendarDatePickerDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/CalendarDatePickerDemo.axaml
@@ -33,6 +33,6 @@
Margin="0,0,0,8"
CustomDateFormatString="ddd, MMM d"
SelectedDateFormat="Custom" />
-
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml
new file mode 100644
index 0000000..d736ef0
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml.cs
new file mode 100644
index 0000000..672f242
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/CarouselPageDemo.axaml.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class CarouselPageDemo : UserControl
+{
+ public CarouselPageDemo()
+ {
+ InitializeComponent();
+ }
+
+ private void OnPrevious(object? sender, RoutedEventArgs e)
+ {
+ if (DemoCarousel.SelectedIndex > 0)
+ DemoCarousel.SelectedIndex--;
+ }
+
+ private void OnNext(object? sender, RoutedEventArgs e)
+ {
+ var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
+ if (DemoCarousel.SelectedIndex < pageCount - 1)
+ DemoCarousel.SelectedIndex++;
+ }
+
+ private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
+ {
+ if (StatusText == null)
+ return;
+ var pageCount = (DemoCarousel.Pages as IList)?.Count ?? 0;
+ StatusText.Text = $"Page {DemoCarousel.SelectedIndex + 1} of {pageCount}";
+ }
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml
new file mode 100644
index 0000000..bf9359e
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml
@@ -0,0 +1,94 @@
+
+
+
+ Bottom
+ Collapsed
+ Right
+
+
+ Auto
+ Collapsed
+ Visible
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml.cs
new file mode 100644
index 0000000..38f6b0f
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/CommandBarDemo.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class CommandBarDemo : UserControl
+{
+ public CommandBarDemo()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml
new file mode 100644
index 0000000..983fff3
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml.cs
new file mode 100644
index 0000000..c5d29e3
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/ContentPageDemo.axaml.cs
@@ -0,0 +1,93 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Layout;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class ContentPageDemo : UserControl
+{
+ private static readonly Color[] PageColors =
+ [
+ Color.FromRgb(0xE3, 0xF2, 0xFD), // blue
+ Color.FromRgb(0xF3, 0xE5, 0xF5), // purple
+ Color.FromRgb(0xE8, 0xF5, 0xE9), // green
+ Color.FromRgb(0xFF, 0xF8, 0xE1), // amber
+ Color.FromRgb(0xFB, 0xE9, 0xE7), // deep orange
+ ];
+
+ private int _pageCount;
+
+ public ContentPageDemo()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PushAsync(MakePage("Root Page", "ContentPage inside a NavigationPage.\nUse the options to navigate."));
+ UpdateStatus();
+ }
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ _pageCount++;
+ await DemoNav.PushAsync(MakePage($"Page {_pageCount}", $"ContentPage #{_pageCount}.\nNavigate back using the back button."));
+ UpdateStatus();
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopAsync();
+ UpdateStatus();
+ }
+
+ private async void OnPopToRoot(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopToRootAsync();
+ _pageCount = 0;
+ UpdateStatus();
+ }
+
+ private void UpdateStatus()
+ {
+ StatusText.Text = $"Depth: {DemoNav.StackDepth} | Current: {DemoNav.CurrentPage?.Header}";
+ }
+
+ private ContentPage MakePage(string header, string body) =>
+ new ContentPage
+ {
+ Header = header,
+ Background = new SolidColorBrush(PageColors[_pageCount % PageColors.Length]),
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 10,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = header,
+ FontSize = 20,
+ FontWeight = FontWeight.SemiBold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ Foreground = Brushes.Black,
+ },
+ new TextBlock
+ {
+ Text = body,
+ FontSize = 13,
+ Opacity = 0.7,
+ TextWrapping = TextWrapping.Wrap,
+ TextAlignment = TextAlignment.Center,
+ MaxWidth = 260,
+ Foreground = Brushes.Black,
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/DataGridDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/DataGridDemo.axaml.cs
index 6744fb7..1de46c8 100644
--- a/demo/Semi.Avalonia.Demo/Pages/DataGridDemo.axaml.cs
+++ b/demo/Semi.Avalonia.Demo/Pages/DataGridDemo.axaml.cs
@@ -1,4 +1,6 @@
+using Avalonia;
using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
using Semi.Avalonia.Demo.ViewModels;
namespace Semi.Avalonia.Demo.Pages;
diff --git a/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml
new file mode 100644
index 0000000..e5affdc
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml.cs
new file mode 100644
index 0000000..7e0316f
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/DrawerPageDemo.axaml.cs
@@ -0,0 +1,68 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class DrawerPageDemo : UserControl
+{
+ public DrawerPageDemo()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e)
+ {
+ base.OnLoaded(e);
+ DemoDrawer.Opened += OnDrawerStatusChanged;
+ DemoDrawer.Closed += OnDrawerStatusChanged;
+ }
+
+ protected override void OnUnloaded(RoutedEventArgs e)
+ {
+ base.OnUnloaded(e);
+ DemoDrawer.Opened -= OnDrawerStatusChanged;
+ DemoDrawer.Closed -= OnDrawerStatusChanged;
+ }
+
+ private void OnDrawerStatusChanged(object? sender, EventArgs e) => UpdateStatus();
+
+ private void OnToggleDrawer(object? sender, RoutedEventArgs e)
+ {
+ DemoDrawer.IsOpen = !DemoDrawer.IsOpen;
+ }
+
+ private void OnGestureChanged(object? sender, RoutedEventArgs e)
+ {
+ DemoDrawer.IsGestureEnabled = GestureCheck.IsChecked == true;
+ }
+
+ private void OnMenuSelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (DrawerMenu.SelectedItem is ListBoxItem item)
+ {
+ DemoDrawer.Content = new ContentPage
+ {
+ Header = item.Content?.ToString(),
+ Content = new TextBlock
+ {
+ Text = $"{item.Content} page content",
+ FontSize = 16,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Foreground = Brushes.Black,
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+ DemoDrawer.IsOpen = false;
+ }
+ }
+
+ private void UpdateStatus()
+ {
+ StatusText.Text = $"Drawer: {(DemoDrawer.IsOpen ? "Open" : "Closed")}";
+ }
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/HeaderedContentControlDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/HeaderedContentControlDemo.axaml
index b778b13..59baf5e 100644
--- a/demo/Semi.Avalonia.Demo/Pages/HeaderedContentControlDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/HeaderedContentControlDemo.axaml
@@ -36,10 +36,20 @@
-
-
-
+
+ Real GroupBox
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/HighContrastDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/HighContrastDemo.axaml
index 82f2ad8..fd7b76c 100644
--- a/demo/Semi.Avalonia.Demo/Pages/HighContrastDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/HighContrastDemo.axaml
@@ -1,4 +1,4 @@
-
+ PlaceholderText="Input Icon Name" />
+/// Shared helpers for ControlCatalog demo pages.
+///
+internal static class NavigationDemoHelper
+{
+ ///
+ /// Pastel background brushes cycled by page index.
+ ///
+ internal static readonly IBrush[] PageBrushes =
+ [
+ new SolidColorBrush(Color.Parse("#BBDEFB")),
+ new SolidColorBrush(Color.Parse("#C8E6C9")),
+ new SolidColorBrush(Color.Parse("#FFE0B2")),
+ new SolidColorBrush(Color.Parse("#E1BEE7")),
+ new SolidColorBrush(Color.Parse("#FFCDD2")),
+ new SolidColorBrush(Color.Parse("#B2EBF2"))
+ ];
+
+ internal static IBrush GetPageBrush(int index) =>
+ PageBrushes[(index % PageBrushes.Length + PageBrushes.Length) % PageBrushes.Length];
+
+ ///
+ /// Creates a simple demo ContentPage with a centered title and subtitle.
+ ///
+ internal static ContentPage MakePage(string header, string body, int colorIndex) =>
+ new()
+ {
+ Header = header,
+ Background = GetPageBrush(colorIndex),
+ Content = new StackPanel
+ {
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center,
+ Spacing = 8,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = header,
+ FontSize = 20,
+ FontWeight = FontWeight.SemiBold,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ Foreground = Brushes.Black,
+ },
+ new TextBlock
+ {
+ Text = body,
+ FontSize = 13,
+ Opacity = 0.7,
+ TextWrapping = TextWrapping.Wrap,
+ TextAlignment = TextAlignment.Center,
+ MaxWidth = 260,
+ Foreground = Brushes.Black,
+ }
+ }
+ },
+ HorizontalContentAlignment = HorizontalAlignment.Stretch,
+ VerticalContentAlignment = VerticalAlignment.Stretch
+ };
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml
new file mode 100644
index 0000000..055e20a
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml.cs
new file mode 100644
index 0000000..88a61d3
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/NavigationPageDemo.axaml.cs
@@ -0,0 +1,66 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class NavigationPageDemo : UserControl
+{
+ private int _pageCount;
+
+ public NavigationPageDemo()
+ {
+ InitializeComponent();
+ Loaded += OnLoaded;
+ }
+
+ private async void OnLoaded(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PushAsync(NavigationDemoHelper.MakePage("Home", "Welcome!\nUse the buttons to push and pop pages.", 0), null);
+ UpdateStatus();
+ }
+
+ private async void OnPush(object? sender, RoutedEventArgs e)
+ {
+ _pageCount++;
+ var page = NavigationDemoHelper.MakePage($"Page {_pageCount}", $"This is page {_pageCount}.", _pageCount);
+ NavigationPage.SetHasNavigationBar(page, HasNavBarCheck.IsChecked == true);
+ NavigationPage.SetHasBackButton(page, HasBackButtonCheck.IsChecked == true);
+ await DemoNav.PushAsync(page);
+ UpdateStatus();
+ }
+
+ private async void OnPop(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopAsync();
+ UpdateStatus();
+ }
+
+ private async void OnPopToRoot(object? sender, RoutedEventArgs e)
+ {
+ await DemoNav.PopToRootAsync();
+ _pageCount = 0;
+ UpdateStatus();
+ }
+
+ private void OnHasNavBarChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoNav == null)
+ return;
+ if (DemoNav.CurrentPage != null)
+ NavigationPage.SetHasNavigationBar(DemoNav.CurrentPage, HasNavBarCheck.IsChecked == true);
+ }
+
+ private void OnHasBackButonChanged(object? sender, RoutedEventArgs e)
+ {
+ if (DemoNav == null)
+ return;
+ if (DemoNav.CurrentPage != null)
+ NavigationPage.SetHasBackButton(DemoNav.CurrentPage, HasBackButtonCheck.IsChecked == true);
+ }
+
+ private void UpdateStatus()
+ {
+ StatusText.Text = $"Depth: {DemoNav.StackDepth}";
+ HeaderText.Text = $"Current: {DemoNav.CurrentPage?.Header ?? "(none)"}";
+ }
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/NumericUpDownDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/NumericUpDownDemo.axaml
index b249c23..8fdd184 100644
--- a/demo/Semi.Avalonia.Demo/Pages/NumericUpDownDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/NumericUpDownDemo.axaml
@@ -28,15 +28,15 @@
@@ -51,6 +51,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/Overview.axaml b/demo/Semi.Avalonia.Demo/Pages/Overview.axaml
index 5ff313c..748caf9 100644
--- a/demo/Semi.Avalonia.Demo/Pages/Overview.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/Overview.axaml
@@ -353,12 +353,12 @@
-
+
@@ -368,12 +368,12 @@
-
+
@@ -383,12 +383,12 @@
-
+
@@ -398,12 +398,12 @@
-
+
@@ -413,12 +413,12 @@
-
+
@@ -428,12 +428,12 @@
-
+
@@ -443,12 +443,12 @@
-
+
diff --git a/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs
index 5d6be69..33b7283 100644
--- a/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs
+++ b/demo/Semi.Avalonia.Demo/Pages/PaletteDemo.axaml.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using Avalonia.Input.Platform;
using Avalonia.Threading;
using Semi.Avalonia.Demo.ViewModels;
diff --git a/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml
new file mode 100644
index 0000000..1e74885
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml.cs
new file mode 100644
index 0000000..1247fc4
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/PipsPagerDemo.axaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class PipsPagerDemo : UserControl
+{
+ public PipsPagerDemo()
+ {
+ InitializeComponent();
+ }
+}
+
diff --git a/demo/Semi.Avalonia.Demo/Pages/TabControlDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/TabControlDemo.axaml
index dc2398c..bb41fb4 100644
--- a/demo/Semi.Avalonia.Demo/Pages/TabControlDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/TabControlDemo.axaml
@@ -34,14 +34,10 @@
-
-
-
-
-
+
+
+
+
@@ -57,14 +53,10 @@
-
-
-
-
-
+
+
+
+
@@ -82,14 +74,10 @@
Background="Transparent"
Theme="{StaticResource CardBorder}">
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
diff --git a/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml
new file mode 100644
index 0000000..0f78430
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml.cs
new file mode 100644
index 0000000..9c2b097
--- /dev/null
+++ b/demo/Semi.Avalonia.Demo/Pages/TabbedPageDemo.axaml.cs
@@ -0,0 +1,84 @@
+using System.Collections;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+
+namespace Semi.Avalonia.Demo.Pages;
+
+public partial class TabbedPageDemo : UserControl
+{
+ private int _tabCounter = 3;
+
+ public TabbedPageDemo()
+ {
+ InitializeComponent();
+ }
+
+ private void OnAddTab(object? sender, RoutedEventArgs e)
+ {
+ var idx = ++_tabCounter;
+ var page = new ContentPage
+ {
+ Header = $"Tab {idx}",
+ Content = new StackPanel
+ {
+ Margin = new Thickness(16),
+ Spacing = 8,
+ Children =
+ {
+ new TextBlock
+ {
+ Text = $"Tab {idx}",
+ FontSize = 24,
+ FontWeight = FontWeight.Bold,
+ },
+ new TextBlock
+ {
+ Text = $"This tab was added dynamically (tab #{idx}).",
+ Opacity = 0.7,
+ TextWrapping = TextWrapping.Wrap,
+ }
+ }
+ }
+ };
+
+ ((IList)DemoTabs.Pages!).Add(page);
+ UpdateStatus();
+ }
+
+ private void OnRemoveTab(object? sender, RoutedEventArgs e)
+ {
+ var pages = (IList)DemoTabs.Pages!;
+ if (pages.Count > 1)
+ {
+ pages.RemoveAt(pages.Count - 1);
+ UpdateStatus();
+ }
+ }
+
+ private void OnPlacementChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (DemoTabs == null) return;
+ DemoTabs.TabPlacement = PlacementCombo.SelectedIndex switch
+ {
+ 1 => TabPlacement.Bottom,
+ 2 => TabPlacement.Left,
+ 3 => TabPlacement.Right,
+ _ => TabPlacement.Top
+ };
+ }
+
+ private void OnSelectionChanged(object? sender, PageSelectionChangedEventArgs e)
+ {
+ UpdateStatus();
+ }
+
+ private void UpdateStatus()
+ {
+ if (StatusText == null) return;
+ var pages = (IList)DemoTabs.Pages!;
+ var pageName = (DemoTabs.SelectedPage as ContentPage)?.Header?.ToString() ?? "—";
+ StatusText.Text = $"{pages.Count} tab{(pages.Count != 1 ? "s" : "")} | Selected: {pageName} ({DemoTabs.SelectedIndex})";
+ }
+}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/TextBoxDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/TextBoxDemo.axaml
index 2ba6d0b..5fc0ae7 100644
--- a/demo/Semi.Avalonia.Demo/Pages/TextBoxDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/TextBoxDemo.axaml
@@ -23,36 +23,36 @@
+ PlaceholderText="Large" />
+ PlaceholderText="Default" />
+ PlaceholderText="Small" />
+ PlaceholderText="Disabled" />
+ PlaceholderText="Bordered" />
-
+
diff --git a/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml
deleted file mode 100644
index 7ed1930..0000000
--- a/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml.cs b/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml.cs
deleted file mode 100644
index 5c511cb..0000000
--- a/demo/Semi.Avalonia.Demo/Pages/TreeDataGridDemo.axaml.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Input;
-using Semi.Avalonia.Demo.ViewModels;
-
-namespace Semi.Avalonia.Demo.Pages;
-
-public partial class TreeDataGridDemo : UserControl
-{
- public TreeDataGridDemo()
- {
- InitializeComponent();
- this.DataContext = new TreeDataGridDemoViewModel();
- }
-
- private void SelectedPath_KeyDown(object? sender, KeyEventArgs e)
- {
- if (e.Key == Key.Enter && DataContext is TreeDataGridDemoViewModel vm)
- {
- vm.FilesContext.SelectedPath = (sender as TextBox)?.Text;
- }
- }
-}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/Pages/VariablesDemo.axaml b/demo/Semi.Avalonia.Demo/Pages/VariablesDemo.axaml
index a30eac7..fe9bdaa 100644
--- a/demo/Semi.Avalonia.Demo/Pages/VariablesDemo.axaml
+++ b/demo/Semi.Avalonia.Demo/Pages/VariablesDemo.axaml
@@ -1,4 +1,4 @@
-
+ PlaceholderText="Input Variable Category/ResourceKey/Type/Value/Description" />
+ Header="CopyText">
-
+
diff --git a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/FilesPageViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/FilesPageViewModel.cs
deleted file mode 100644
index a01d430..0000000
--- a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/FilesPageViewModel.cs
+++ /dev/null
@@ -1,363 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using Avalonia.Controls;
-using Avalonia.Controls.Models.TreeDataGrid;
-using Avalonia.Controls.Selection;
-using Avalonia.Threading;
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace Semi.Avalonia.Demo.ViewModels;
-
-public partial class FilesPageViewModel : ObservableObject
-{
- public IList Drives { get; }
- public HierarchicalTreeDataGridSource Source { get; }
- [ObservableProperty] private string _selectedDrive;
- private string? _selectedPath;
- [ObservableProperty] private FileNodeViewModel? _root;
-
- public string? SelectedPath
- {
- get => _selectedPath;
- set => SetSelectedPath(value);
- }
-
- partial void OnSelectedDriveChanged(string value)
- {
- Root = new FileNodeViewModel(value, true, true);
- if (Source is not null)
- {
- Source.Items = [Root];
- }
- }
-
- public FilesPageViewModel()
- {
- Drives = DriveInfo.GetDrives().Select(x => x.Name).ToList();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- SelectedDrive = @"C:\";
- }
- else
- {
- SelectedDrive = Drives.FirstOrDefault() ?? "/";
- }
-
- Source = new HierarchicalTreeDataGridSource([])
- {
- Columns =
- {
- new CheckBoxColumn(
- null,
- x => x.IsChecked,
- (o, v) => o.IsChecked = v,
- options: new CheckBoxColumnOptions
- {
- CanUserResizeColumn = false,
- }),
- new HierarchicalExpanderColumn(
- new TemplateColumn(
- "Name",
- "FileNameCell",
- "FileNameEditCell",
- new GridLength(1, GridUnitType.Star),
- new TemplateColumnOptions
- {
- CompareAscending = FileNodeViewModel.SortAscending(vm => vm.Name),
- CompareDescending = FileNodeViewModel.SortDescending(vm => vm.Name),
- IsTextSearchEnabled = true,
- TextSearchValueSelector = vm => vm.Name
- }),
- vm => vm.Children,
- vm => vm.HasChildren,
- vm => vm.IsExpanded),
- new TextColumn(
- "Size",
- vm => vm.Size,
- options: new TextColumnOptions
- {
- CompareAscending = FileNodeViewModel.SortAscending(x => x.Size),
- CompareDescending = FileNodeViewModel.SortDescending(x => x.Size),
- }),
- new TextColumn(
- "Modified",
- x => x.Modified,
- options: new TextColumnOptions
- {
- CompareAscending = FileNodeViewModel.SortAscending(x => x.Modified),
- CompareDescending = FileNodeViewModel.SortDescending(x => x.Modified),
- }),
- }
- };
- Source.RowSelection!.SingleSelect = false;
- Source.RowSelection.SelectionChanged += SelectionChanged;
- }
-
- private void SelectionChanged(object? sender, TreeSelectionModelSelectionChangedEventArgs e)
- {
- var selectedPath = Source.RowSelection?.SelectedItem?.Path;
- this.SetProperty(ref _selectedPath, selectedPath, nameof(SelectedPath));
-
- foreach (var i in e.DeselectedItems)
- Trace.WriteLine($"Deselected '{i?.Path}'");
- foreach (var i in e.SelectedItems)
- Trace.WriteLine($"Selected '{i?.Path}'");
- }
- private void SetSelectedPath(string? path)
- {
- if (string.IsNullOrEmpty(path))
- {
- Source.RowSelection!.Clear();
- return;
- }
-
- var components = new Stack();
- DirectoryInfo? d = null;
-
- if (File.Exists(path))
- {
- var f = new FileInfo(path);
- components.Push(f.Name);
- d = f.Directory;
- }
- else if (Directory.Exists(path))
- {
- d = new DirectoryInfo(path);
- }
-
- while (d is not null)
- {
- components.Push(d.Name);
- d = d.Parent;
- }
-
- var index = IndexPath.Unselected;
-
- if (components.Count > 0)
- {
- var drive = components.Pop();
- var driveIndex = Drives.FindIndex(x => string.Equals(x, drive, StringComparison.OrdinalIgnoreCase));
-
- if (driveIndex >= 0)
- SelectedDrive = Drives[driveIndex];
-
- var node = Root;
- index = new IndexPath(0);
-
- while (node is not null && components.Count > 0)
- {
- node.IsExpanded = true;
-
- var component = components.Pop();
- var i = node.Children.FindIndex(x => string.Equals(x.Name, component, StringComparison.OrdinalIgnoreCase));
- node = i >= 0 ? node.Children[i] : null;
- index = i >= 0 ? index.Append(i) : default;
- }
- }
-
- Source.Items = [Root!];
- Source.RowSelection!.SelectedIndex = index;
- }
-}
-
-public partial class FileNodeViewModel : ObservableObject, IEditableObject
-{
- [ObservableProperty] private string _path;
- [ObservableProperty] private string _name;
- private string? _undoName;
- [ObservableProperty] private long? _size;
- [ObservableProperty] private DateTimeOffset? _modified;
- private FileSystemWatcher? _watcher;
- private ObservableCollection? _children;
- [ObservableProperty] private bool _hasChildren = true;
- [ObservableProperty] private bool _isExpanded;
-
- public FileNodeViewModel(string path, bool isDirectory, bool isRoot = false)
- {
- Path = path;
- Name = isRoot ? path : System.IO.Path.GetFileName(Path);
- IsExpanded = isRoot;
- IsDirectory = isDirectory;
- HasChildren = isDirectory;
-
- if (!isDirectory)
- {
- var info = new FileInfo(path);
- Size = info.Length;
- Modified = info.LastWriteTimeUtc;
- }
- }
-
- public bool IsChecked { get; set; }
- public bool IsDirectory { get; }
- public IReadOnlyList Children => _children ??= LoadChildren();
-
- private ObservableCollection LoadChildren()
- {
- if (!IsDirectory)
- {
- throw new NotSupportedException();
- }
-
- var options = new EnumerationOptions { IgnoreInaccessible = true };
- var result = new ObservableCollection();
-
- foreach (var d in Directory.EnumerateDirectories(Path, "*", options))
- {
- result.Add(new FileNodeViewModel(d, true));
- }
-
- foreach (var f in Directory.EnumerateFiles(Path, "*", options))
- {
- result.Add(new FileNodeViewModel(f, false));
- }
-
- _watcher = new FileSystemWatcher
- {
- Path = Path,
- NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite,
- };
-
- _watcher.Changed += OnChanged;
- _watcher.Created += OnCreated;
- _watcher.Deleted += OnDeleted;
- _watcher.Renamed += OnRenamed;
- _watcher.EnableRaisingEvents = true;
-
- if (result.Count == 0)
- HasChildren = false;
-
- return result;
- }
-
- public static Comparison SortAscending(Func selector)
- {
- return (x, y) =>
- {
- if (x is null && y is null)
- return 0;
- else if (x is null)
- return -1;
- else if (y is null)
- return 1;
- if (x.IsDirectory == y.IsDirectory)
- return Comparer.Default.Compare(selector(x), selector(y));
- else if (x.IsDirectory)
- return -1;
- else
- return 1;
- };
- }
-
- public static Comparison SortDescending(Func selector)
- {
- return (x, y) =>
- {
- if (x is null && y is null)
- return 0;
- else if (x is null)
- return 1;
- else if (y is null)
- return -1;
- if (x.IsDirectory == y.IsDirectory)
- return Comparer.Default.Compare(selector(y), selector(x));
- else if (x.IsDirectory)
- return -1;
- else
- return 1;
- };
- }
-
- void IEditableObject.BeginEdit() => _undoName = Name;
- void IEditableObject.CancelEdit() => Name = _undoName ?? string.Empty;
- void IEditableObject.EndEdit() => _undoName = null;
-
- private void OnChanged(object sender, FileSystemEventArgs e)
- {
- if (e.ChangeType == WatcherChangeTypes.Changed && File.Exists(e.FullPath))
- {
- Dispatcher.UIThread.Post(() =>
- {
- foreach (var child in _children!)
- {
- if (child.Path == e.FullPath)
- {
- if (!child.IsDirectory)
- {
- var info = new FileInfo(e.FullPath);
- child.Size = info.Length;
- child.Modified = info.LastWriteTimeUtc;
- }
-
- break;
- }
- }
- });
- }
- }
-
- private void OnCreated(object sender, FileSystemEventArgs e)
- {
- Dispatcher.UIThread.Post(() =>
- {
- var node = new FileNodeViewModel(
- e.FullPath,
- File.GetAttributes(e.FullPath).HasFlag(FileAttributes.Directory));
- _children!.Add(node);
- });
- }
-
- private void OnDeleted(object sender, FileSystemEventArgs e)
- {
- Dispatcher.UIThread.Post(() =>
- {
- for (var i = 0; i < _children!.Count; ++i)
- {
- if (_children[i].Path == e.FullPath)
- {
- _children.RemoveAt(i);
- Debug.WriteLine($"Removed {e.FullPath}");
- break;
- }
- }
- });
- }
-
- private void OnRenamed(object sender, RenamedEventArgs e)
- {
- Dispatcher.UIThread.Post(() =>
- {
- foreach (var child in _children!)
- {
- if (child.Path == e.OldFullPath)
- {
- child.Path = e.FullPath;
- child.Name = e.Name ?? string.Empty;
- break;
- }
- }
- });
- }
-}
-
-internal static class ListExtensions
-{
- public static int FindIndex(this IEnumerable source, Func predicate)
- {
- int i = 0;
- foreach (var item in source)
- {
- if (predicate(item))
- return i;
- i++;
- }
-
- return -1;
- }
-}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/SongsPageViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/SongsPageViewModel.cs
deleted file mode 100644
index 79e49c0..0000000
--- a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/SongsPageViewModel.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Controls.Models.TreeDataGrid;
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace Semi.Avalonia.Demo.ViewModels;
-
-public class SongsPageViewModel : ObservableObject
-{
- public FlatTreeDataGridSource Songs { get; }
-
- public SongsPageViewModel()
- {
- var songs = new ObservableCollection(Song.Songs.Select(a => new SongViewModel()
- {
- Title = a.Title,
- Artist = a.Artist,
- Album = a.Album,
- CountOfComment = a.CountOfComment,
- IsSelected = false
- }));
-
- Songs = new FlatTreeDataGridSource(songs)
- {
- Columns =
- {
- new CheckBoxColumn(
- "IsSelected",
- a => a.IsSelected,
- (model, b) => { model.IsSelected = b; },
- new GridLength(108, GridUnitType.Pixel)),
- new TextColumn(
- "Title",
- a => a.Title,
- (o, a) => o.Title = a,
- new GridLength(6, GridUnitType.Star)),
- new TextColumn("Artist",
- a => a.Artist,
- (o, a) => o.Artist = a,
- new GridLength(6, GridUnitType.Star)),
- new TemplateColumn("Album",
- "AlbumCell",
- "AlbumEditCell",
- new GridLength(6, GridUnitType.Star)),
- new TemplateColumn(
- "Comments",
- "CommentsCell",
- "CommentsEditCell",
- new GridLength(6, GridUnitType.Star)),
- }
- };
- }
-}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/TreeDataGridDemoViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/TreeDataGridDemoViewModel.cs
deleted file mode 100644
index 69d7379..0000000
--- a/demo/Semi.Avalonia.Demo/ViewModels/TreeDataGridDemo/TreeDataGridDemoViewModel.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace Semi.Avalonia.Demo.ViewModels;
-
-public class TreeDataGridDemoViewModel : ObservableObject
-{
- public SongsPageViewModel SongsContext { get; } = new();
- public FilesPageViewModel FilesContext { get; } = new();
-}
\ No newline at end of file
diff --git a/demo/Semi.Avalonia.Demo/ViewModels/VariablesDemoViewModel.cs b/demo/Semi.Avalonia.Demo/ViewModels/VariablesDemoViewModel.cs
index 639d02c..cea2ab4 100644
--- a/demo/Semi.Avalonia.Demo/ViewModels/VariablesDemoViewModel.cs
+++ b/demo/Semi.Avalonia.Demo/ViewModels/VariablesDemoViewModel.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia;
diff --git a/demo/Semi.Avalonia.Demo/Views/MainView.axaml b/demo/Semi.Avalonia.Demo/Views/MainView.axaml
index fa86d0c..b33d608 100644
--- a/demo/Semi.Avalonia.Demo/Views/MainView.axaml
+++ b/demo/Semi.Avalonia.Demo/Views/MainView.axaml
@@ -16,9 +16,9 @@
@@ -37,48 +37,44 @@
+ Content="{StaticResource SemiIconSidebar}"
+ Theme="{DynamicResource IconBorderlessToggleSwitch}" />
-
+
-
+
+ Content="{StaticResource SemiIconGlobe}"
+ Theme="{DynamicResource IconBorderlessButton}" />
+ Content="{StaticResource SemiIconGithubLogo}"
+ Theme="{DynamicResource IconBorderlessButton}" />
+ Theme="{DynamicResource IconBorderlessToggleSwitch}" />
-
@@ -25,20 +21,39 @@
-
+ CornerRadius="{TemplateBinding CornerRadius}">
+
+
+
+
+
+
+
+
+
+
+
@@ -46,17 +61,31 @@
-
+
+
+
+
+
-
-
@@ -98,27 +127,27 @@
x:Key="LineTabItem"
BasedOn="{StaticResource BaseTabItem}"
TargetType="TabItem">
-
-
-
@@ -150,15 +179,15 @@
TargetType="TabItem">
-
-
@@ -207,16 +236,20 @@
TargetType="TabItem">
-
+
-
diff --git a/src/Semi.Avalonia/Controls/TabbedPage.axaml b/src/Semi.Avalonia/Controls/TabbedPage.axaml
new file mode 100644
index 0000000..64b68b7
--- /dev/null
+++ b/src/Semi.Avalonia/Controls/TabbedPage.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Controls/TextBox.axaml b/src/Semi.Avalonia/Controls/TextBox.axaml
index f76022e..36243fa 100644
--- a/src/Semi.Avalonia/Controls/TextBox.axaml
+++ b/src/Semi.Avalonia/Controls/TextBox.axaml
@@ -22,8 +22,30 @@
IsEnabled="{Binding $parent[TextBox].CanPaste}" />
+
+
+
+
+
+
+
@@ -67,11 +89,11 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
@@ -133,6 +155,10 @@
+
+
+
+
diff --git a/src/Semi.Avalonia/Controls/TitleBar.axaml b/src/Semi.Avalonia/Controls/TitleBar.axaml
deleted file mode 100644
index 2755724..0000000
--- a/src/Semi.Avalonia/Controls/TitleBar.axaml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Controls/TreeView.axaml b/src/Semi.Avalonia/Controls/TreeView.axaml
index 11488a8..c086004 100644
--- a/src/Semi.Avalonia/Controls/TreeView.axaml
+++ b/src/Semi.Avalonia/Controls/TreeView.axaml
@@ -1,7 +1,7 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Controls/_index.axaml b/src/Semi.Avalonia/Controls/_index.axaml
index 084607d..612ab61 100644
--- a/src/Semi.Avalonia/Controls/_index.axaml
+++ b/src/Semi.Avalonia/Controls/_index.axaml
@@ -6,22 +6,26 @@
+
-
+
+
+
+
@@ -32,7 +36,9 @@
+
+
@@ -46,17 +52,18 @@
+
-
+
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Converters/KeyToPathConverter.cs b/src/Semi.Avalonia/Converters/KeyToPathConverter.cs
deleted file mode 100644
index 3b37329..0000000
--- a/src/Semi.Avalonia/Converters/KeyToPathConverter.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using Avalonia;
-using Avalonia.Data.Converters;
-using Avalonia.Metadata;
-
-namespace Semi.Avalonia.Converters;
-
-public class KeyToPathConverter: IValueConverter
-{
- [Content]
- public IDictionary Resources { get; } = new Dictionary();
-
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- if(value is string s && Resources.TryGetValue(s, out var v))
- return v;
- return AvaloniaProperty.UnsetValue;
- }
-
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
-}
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Converters/PlacementToRenderTransformOriginConverter.cs b/src/Semi.Avalonia/Converters/PlacementToRenderTransformOriginConverter.cs
index a370678..d93fc7c 100644
--- a/src/Semi.Avalonia/Converters/PlacementToRenderTransformOriginConverter.cs
+++ b/src/Semi.Avalonia/Converters/PlacementToRenderTransformOriginConverter.cs
@@ -2,56 +2,36 @@ using System;
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Data.Converters;
+using Irihi.Avalonia.Shared.Converters;
namespace Semi.Avalonia.Converters;
-public class PlacementToRenderTransformOriginConverter: IValueConverter
+public class PlacementToRenderTransformOriginConverter : MarkupValueConverter
{
- public static PlacementToRenderTransformOriginConverter Instance { get; } = new PlacementToRenderTransformOriginConverter();
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not PlacementMode p)
{
return new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
}
- switch (p)
- {
- case PlacementMode.Bottom:
- return new RelativePoint(0.5, 0.0, RelativeUnit.Relative);
- case PlacementMode.Left:
- return new RelativePoint(1.0, 0.5, RelativeUnit.Relative);
- case PlacementMode.Right:
- return new RelativePoint(0.0, 0.5, RelativeUnit.Relative);
- case PlacementMode.Top:
- return new RelativePoint(0.5, 1.0, RelativeUnit.Relative);
- case PlacementMode.Pointer:
- return new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
- case PlacementMode.Center:
- case PlacementMode.AnchorAndGravity:
- return new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
- case PlacementMode.BottomEdgeAlignedLeft:
- return new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
- case PlacementMode.BottomEdgeAlignedRight:
- return new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
- case PlacementMode.LeftEdgeAlignedTop:
- return new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
- case PlacementMode.LeftEdgeAlignedBottom:
- return new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
- case PlacementMode.RightEdgeAlignedTop:
- return new RelativePoint(0.0, 1.0, RelativeUnit.Relative);
- case PlacementMode.RightEdgeAlignedBottom:
- return new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
- case PlacementMode.TopEdgeAlignedLeft:
- return new RelativePoint(0.0, 1.0, RelativeUnit.Relative);
- case PlacementMode.TopEdgeAlignedRight:
- return new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
- }
- return new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
- }
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
+ return p switch
+ {
+ PlacementMode.Bottom => new RelativePoint(0.5, 0.0, RelativeUnit.Relative),
+ PlacementMode.Left => new RelativePoint(1.0, 0.5, RelativeUnit.Relative),
+ PlacementMode.Right => new RelativePoint(0.0, 0.5, RelativeUnit.Relative),
+ PlacementMode.Top => new RelativePoint(0.5, 1.0, RelativeUnit.Relative),
+ PlacementMode.Pointer => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
+ PlacementMode.Center or PlacementMode.AnchorAndGravity => new RelativePoint(0.5, 0.5, RelativeUnit.Relative),
+ PlacementMode.BottomEdgeAlignedLeft => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
+ PlacementMode.BottomEdgeAlignedRight => new RelativePoint(1.0, 0.0, RelativeUnit.Relative),
+ PlacementMode.LeftEdgeAlignedTop => new RelativePoint(1.0, 1.0, RelativeUnit.Relative),
+ PlacementMode.LeftEdgeAlignedBottom => new RelativePoint(1.0, 0.0, RelativeUnit.Relative),
+ PlacementMode.RightEdgeAlignedTop => new RelativePoint(0.0, 1.0, RelativeUnit.Relative),
+ PlacementMode.RightEdgeAlignedBottom => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
+ PlacementMode.TopEdgeAlignedLeft => new RelativePoint(0.0, 1.0, RelativeUnit.Relative),
+ PlacementMode.TopEdgeAlignedRight => new RelativePoint(1.0, 1.0, RelativeUnit.Relative),
+ _ => new RelativePoint(0.5, 0.5, RelativeUnit.Relative)
+ };
}
}
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Converters/PositionToAngleConverter.cs b/src/Semi.Avalonia/Converters/PositionToAngleConverter.cs
index 1fd9f7a..a769907 100644
--- a/src/Semi.Avalonia/Converters/PositionToAngleConverter.cs
+++ b/src/Semi.Avalonia/Converters/PositionToAngleConverter.cs
@@ -1,27 +1,18 @@
using System;
using System.Globalization;
-using Avalonia.Data.Converters;
+using Irihi.Avalonia.Shared.Converters;
namespace Semi.Avalonia.Converters;
-public class PositionToAngleConverter: IValueConverter
+public class PositionToAngleConverter : MarkupValueConverter
{
- public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- if (value is double d)
- {
- return d * 3.6;
- }
-
- return 0;
+ return value is double d ? d * 3.6 : 0;
}
- public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ public override object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- if (value is double d)
- {
- return d / 3.6;
- }
- return 0;
+ return value is double d ? d / 3.6 : 0;
}
}
\ No newline at end of file
diff --git a/src/Semi.Avalonia/Converters/TreeViewItemIndentConverter.cs b/src/Semi.Avalonia/Converters/TreeViewItemIndentConverter.cs
deleted file mode 100644
index e448131..0000000
--- a/src/Semi.Avalonia/Converters/TreeViewItemIndentConverter.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using Avalonia;
-using Avalonia.Data.Converters;
-
-namespace Semi.Avalonia.Converters;
-
-public class TreeViewItemIndentConverter : IMultiValueConverter
-{
- public static readonly TreeViewItemIndentConverter Instance = new();
-
- public object? Convert(IList