diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs
index f6fa07dbde..486d14661e 100644
--- a/samples/ControlCatalog.Android/MainActivity.cs
+++ b/samples/ControlCatalog.Android/MainActivity.cs
@@ -5,7 +5,7 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
- [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity
{
}
diff --git a/samples/ControlCatalog.Android/Resources/values-night/colors.xml b/samples/ControlCatalog.Android/Resources/values-night/colors.xml
new file mode 100644
index 0000000000..3d47b6fc58
--- /dev/null
+++ b/samples/ControlCatalog.Android/Resources/values-night/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #212121
+
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 9f06525821..7ed2d67379 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -147,9 +147,6 @@
-
-
-
diff --git a/samples/ControlCatalog/Pages/ScrollSnapPage.xaml b/samples/ControlCatalog/Pages/ScrollSnapPage.xaml
deleted file mode 100644
index fa206f0dff..0000000000
--- a/samples/ControlCatalog/Pages/ScrollSnapPage.xaml
+++ /dev/null
@@ -1,222 +0,0 @@
-
-
- Scrollviewer can snap supported content both vertically and horizontally. Snapping occurs from scrolling with touch or pen.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Vertical Snapping
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Horizontal Snapping
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs
deleted file mode 100644
index 384dc67c66..0000000000
--- a/samples/ControlCatalog/Pages/ScrollSnapPage.xaml.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System.Collections.Generic;
-using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.Markup.Xaml;
-using MiniMvvm;
-
-namespace ControlCatalog.Pages
-{
- public class ScrollSnapPageViewModel : ViewModelBase
- {
- private SnapPointsType _snapPointsType;
- private SnapPointsAlignment _snapPointsAlignment;
- private bool _areSnapPointsRegular;
-
- public ScrollSnapPageViewModel()
- {
-
- AvailableSnapPointsType = new List()
- {
- SnapPointsType.None,
- SnapPointsType.Mandatory,
- SnapPointsType.MandatorySingle
- };
-
- AvailableSnapPointsAlignment = new List()
- {
- SnapPointsAlignment.Near,
- SnapPointsAlignment.Center,
- SnapPointsAlignment.Far,
- };
- }
-
- public bool AreSnapPointsRegular
- {
- get => _areSnapPointsRegular;
- set => this.RaiseAndSetIfChanged(ref _areSnapPointsRegular, value);
- }
-
- public SnapPointsType SnapPointsType
- {
- get => _snapPointsType;
- set => this.RaiseAndSetIfChanged(ref _snapPointsType, value);
- }
-
- public SnapPointsAlignment SnapPointsAlignment
- {
- get => _snapPointsAlignment;
- set => this.RaiseAndSetIfChanged(ref _snapPointsAlignment, value);
- }
- public List AvailableSnapPointsType { get; }
- public List AvailableSnapPointsAlignment { get; }
- }
-
- public class ScrollSnapPage : UserControl
- {
- public ScrollSnapPage()
- {
- this.InitializeComponent();
-
- DataContext = new ScrollSnapPageViewModel();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- }
-}
diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
index 1a3d61eb85..4af61c3399 100644
--- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
+++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
@@ -3,35 +3,267 @@
xmlns:pages="using:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.ScrollViewerPage"
x:DataType="pages:ScrollViewerPageViewModel">
-
- Allows for horizontal and vertical content scrolling. Supports snapping on touch and pointer wheel scrolling.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Allows for horizontal and vertical content scrolling. Supports snapping on touch and pointer wheel scrolling.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Scrollviewer can snap supported content both vertically and horizontally. Snapping occurs from scrolling with touch or pen.
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+ Vertical Snapping
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Horizontal Snapping
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
index a097f1f951..7082ca1bf6 100644
--- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
@@ -12,6 +12,9 @@ namespace ControlCatalog.Pages
private bool _enableInertia;
private ScrollBarVisibility _horizontalScrollVisibility;
private ScrollBarVisibility _verticalScrollVisibility;
+ private SnapPointsType _snapPointsType;
+ private SnapPointsAlignment _snapPointsAlignment;
+ private bool _areSnapPointsRegular;
public ScrollViewerPageViewModel()
{
@@ -23,6 +26,20 @@ namespace ControlCatalog.Pages
ScrollBarVisibility.Disabled,
};
+ AvailableSnapPointsType = new List()
+ {
+ SnapPointsType.None,
+ SnapPointsType.Mandatory,
+ SnapPointsType.MandatorySingle
+ };
+
+ AvailableSnapPointsAlignment = new List()
+ {
+ SnapPointsAlignment.Near,
+ SnapPointsAlignment.Center,
+ SnapPointsAlignment.Far,
+ };
+
HorizontalScrollVisibility = ScrollBarVisibility.Auto;
VerticalScrollVisibility = ScrollBarVisibility.Auto;
AllowAutoHide = true;
@@ -54,6 +71,26 @@ namespace ControlCatalog.Pages
}
public List AvailableVisibility { get; }
+
+ public bool AreSnapPointsRegular
+ {
+ get => _areSnapPointsRegular;
+ set => this.RaiseAndSetIfChanged(ref _areSnapPointsRegular, value);
+ }
+
+ public SnapPointsType SnapPointsType
+ {
+ get => _snapPointsType;
+ set => this.RaiseAndSetIfChanged(ref _snapPointsType, value);
+ }
+
+ public SnapPointsAlignment SnapPointsAlignment
+ {
+ get => _snapPointsAlignment;
+ set => this.RaiseAndSetIfChanged(ref _snapPointsAlignment, value);
+ }
+ public List AvailableSnapPointsType { get; }
+ public List AvailableSnapPointsAlignment { get; }
}
public class ScrollViewerPage : UserControl
diff --git a/samples/VirtualizationDemo/App.axaml b/samples/VirtualizationDemo/App.axaml
new file mode 100644
index 0000000000..f5f06ffb6a
--- /dev/null
+++ b/samples/VirtualizationDemo/App.axaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/VirtualizationDemo/App.axaml.cs b/samples/VirtualizationDemo/App.axaml.cs
new file mode 100644
index 0000000000..5ac5c9a92b
--- /dev/null
+++ b/samples/VirtualizationDemo/App.axaml.cs
@@ -0,0 +1,20 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace VirtualizationDemo;
+
+public partial class App : Application
+{
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ desktop.MainWindow = new MainWindow();
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/samples/VirtualizationDemo/App.xaml b/samples/VirtualizationDemo/App.xaml
deleted file mode 100644
index eb5f0e4dca..0000000000
--- a/samples/VirtualizationDemo/App.xaml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/samples/VirtualizationDemo/App.xaml.cs b/samples/VirtualizationDemo/App.xaml.cs
deleted file mode 100644
index 81b80c1f40..0000000000
--- a/samples/VirtualizationDemo/App.xaml.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Markup.Xaml;
-
-namespace VirtualizationDemo
-{
- public class App : Application
- {
- public override void Initialize()
- {
- AvaloniaXamlLoader.Load(this);
- }
-
- public override void OnFrameworkInitializationCompleted()
- {
- if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
- desktop.MainWindow = new MainWindow();
- base.OnFrameworkInitializationCompleted();
- }
- }
-}
diff --git a/samples/VirtualizationDemo/Assets/chat.json b/samples/VirtualizationDemo/Assets/chat.json
new file mode 100644
index 0000000000..cc628b534a
--- /dev/null
+++ b/samples/VirtualizationDemo/Assets/chat.json
@@ -0,0 +1,190 @@
+{
+ "chat": [
+ {
+ "sender": "Alice",
+ "message": "Hey Bob! How was your weekend?",
+ "timestamp": "2023-04-01T10:00:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "It was great, thanks for asking. I went on a camping trip with some friends. How about you?",
+ "timestamp": "2023-04-01T10:01:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "My weekend was pretty chill. I just stayed home and caught up on some TV shows.",
+ "timestamp": "2023-04-01T10:03:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "That sounds relaxing. What shows did you watch?",
+ "timestamp": "2023-04-01T10:05:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "I watched the new season of 'Stranger Things' and started watching 'Ozark'. Have you seen them?",
+ "timestamp": "2023-04-01T10:07:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "Yeah, I've seen both of those. They're really good! What do you think of them so far?",
+ "timestamp": "2023-04-01T10:10:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "I'm really enjoying 'Stranger Things', but 'Ozark' is a bit darker than I expected. I'm only a few episodes in though, so we'll see how it goes.",
+ "timestamp": "2023-04-01T10:12:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "Yeah, 'Ozark' can be intense at times, but it's really well done. Keep watching, it gets even better.",
+ "timestamp": "2023-04-01T10:15:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Thanks for the recommendation, I'll definitely keep watching. So, how's work been for you lately?",
+ "timestamp": "2023-04-01T10:20:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "It's been pretty busy, but I'm managing. How about you?",
+ "timestamp": "2023-04-01T10:22:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Same here, things have been pretty hectic. But it keeps us on our toes, right?",
+ "timestamp": "2023-04-01T10:25:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "Absolutely. Hey, have you heard about the new project we're starting next week?",
+ "timestamp": "2023-04-01T10:30:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "No, I haven't. What's it about?",
+ "timestamp": "2023-04-01T10:32:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "It's a big project for a new client, and it's going to require a lot of extra hours from all of us. But the pay is going to be great,so it's definitely worth the extra effort. I'll fill you in on the details later, but for now, let's just enjoy our coffee break, shall we?",
+ "timestamp": "2023-04-01T10:35:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Sounds good to me. I could use a break right about now.",
+ "timestamp": "2023-04-01T10:40:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "Me too. So, have you tried the new caf� down the street yet?",
+ "timestamp": "2023-04-01T10:45:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "No, I haven't. Is it any good?",
+ "timestamp": "2023-04-01T10:47:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "It's really good! They have the best croissants I've ever tasted.",
+ "timestamp": "2023-04-01T10:50:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Hmm, I'll have to try it out sometime. Do they have any vegan options?",
+ "timestamp": "2023-04-01T10:52:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "I'm not sure, but I think they do. You should ask them the next time you go there.",
+ "timestamp": "2023-04-01T10:55:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Thanks for the suggestion. I'm always looking for good vegan options around here.",
+ "timestamp": "2023-04-01T11:00:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "No problem. So, have you made any plans for the weekend yet?",
+ "timestamp": "2023-04-01T11:05:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Not yet. I was thinking of maybe going for a hike or something. What about you?",
+ "timestamp": "2023-04-01T11:07:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "I haven't made any plans either. Maybe we could do something together?",
+ "timestamp": "2023-04-01T11:10:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "That sounds like a great idea! Let's plan on it.",
+ "timestamp": "2023-04-01T11:12:00"
+ },
+ {
+ "sender": "Bob",
+ "message": "Awesome. I'll check out some hiking trails and let you know which ones look good.",
+ "timestamp": "2023-04-01T11:15:00"
+ },
+ {
+ "sender": "Alice",
+ "message": "Sounds good. I can't wait!",
+ "timestamp": "2023-04-01T11:20:00"
+ },
+ {
+ "sender": "John",
+ "message": "Hey Lisa, how was your day?",
+ "timestamp": "2023-04-01T18:00:00"
+ },
+ {
+ "sender": "Lisa",
+ "message": "It was good, thanks for asking. How about you?",
+ "timestamp": "2023-04-01T18:05:00"
+ },
+ {
+ "sender": "John",
+ "message": "Eh, it was alright. Work was pretty busy, but nothing too crazy.",
+ "timestamp": "2023-04-01T18:10:00"
+ },
+ {
+ "sender": "Lisa",
+ "message": "Yeah, I know what you mean. My boss has been on my case lately about meeting our deadlines.",
+ "timestamp": "2023-04-01T18:15:00"
+ },
+ {
+ "sender": "John",
+ "message": "That sucks. Are you feeling stressed out?",
+ "timestamp": "2023-04-01T18:20:00"
+ },
+ {
+ "sender": "Lisa",
+ "message": "A little bit, yeah. But I'm trying to stay positive and focus on getting my work done.",
+ "timestamp": "2023-04-01T18:25:00"
+ },
+ {
+ "sender": "John",
+ "message": "That's a good attitude to have. Have you tried doing some meditation or other relaxation techniques?",
+ "timestamp": "2023-04-01T18:30:00"
+ },
+ {
+ "sender": "Lisa",
+ "message": "I haven't, but I've been thinking about it. Do you have any suggestions?",
+ "timestamp": "2023-04-01T18:35:00"
+ },
+ {
+ "sender": "John",
+ "message": "Sure, I could send you some links to guided meditations that I've found helpful. And there are also some great apps out there that can help you with relaxation.",
+ "timestamp": "2023-04-01T18:40:00"
+ },
+ {
+ "sender": "Lisa",
+ "message": "That would be awesome, thanks so much!",
+ "timestamp": "2023-04-01T18:45:00"
+ }
+ ]
+}
+
diff --git a/samples/VirtualizationDemo/MainWindow.axaml b/samples/VirtualizationDemo/MainWindow.axaml
new file mode 100644
index 0000000000..04e75450bf
--- /dev/null
+++ b/samples/VirtualizationDemo/MainWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/VirtualizationDemo/MainWindow.axaml.cs b/samples/VirtualizationDemo/MainWindow.axaml.cs
new file mode 100644
index 0000000000..533dc00aa1
--- /dev/null
+++ b/samples/VirtualizationDemo/MainWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia;
+using Avalonia.Controls;
+using VirtualizationDemo.ViewModels;
+
+namespace VirtualizationDemo;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ this.AttachDevTools();
+ DataContext = new MainWindowViewModel();
+ }
+}
diff --git a/samples/VirtualizationDemo/MainWindow.xaml b/samples/VirtualizationDemo/MainWindow.xaml
deleted file mode 100644
index 3aee63c246..0000000000
--- a/samples/VirtualizationDemo/MainWindow.xaml
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
-
-
-
-
-
- Horiz. ScrollBar
-
- Vert. ScrollBar
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/VirtualizationDemo/MainWindow.xaml.cs b/samples/VirtualizationDemo/MainWindow.xaml.cs
deleted file mode 100644
index cea200dcec..0000000000
--- a/samples/VirtualizationDemo/MainWindow.xaml.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-using VirtualizationDemo.ViewModels;
-
-namespace VirtualizationDemo
-{
- public class MainWindow : Window
- {
- public MainWindow()
- {
- this.InitializeComponent();
- this.AttachDevTools();
- DataContext = new MainWindowViewModel();
- }
-
- private void InitializeComponent()
- {
- AvaloniaXamlLoader.Load(this);
- }
- }
-}
diff --git a/samples/VirtualizationDemo/Models/Chat.cs b/samples/VirtualizationDemo/Models/Chat.cs
new file mode 100644
index 0000000000..813e8650f5
--- /dev/null
+++ b/samples/VirtualizationDemo/Models/Chat.cs
@@ -0,0 +1,23 @@
+using System;
+using System.IO;
+using System.Text.Json;
+
+namespace VirtualizationDemo.Models;
+
+public class ChatFile
+{
+ public ChatMessage[]? Chat { get; set; }
+
+ public static ChatFile Load(string path)
+ {
+ var options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ using var s = File.OpenRead(path);
+ return JsonSerializer.Deserialize(s, options)!;
+ }
+}
+
+public record ChatMessage(string Sender, string Message, DateTimeOffset Timestamp);
diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs
index febda46450..87212b6daa 100644
--- a/samples/VirtualizationDemo/Program.cs
+++ b/samples/VirtualizationDemo/Program.cs
@@ -1,15 +1,14 @@
using Avalonia;
-namespace VirtualizationDemo
+namespace VirtualizationDemo;
+
+class Program
{
- class Program
- {
- public static AppBuilder BuildAvaloniaApp()
- => AppBuilder.Configure()
- .UsePlatformDetect()
- .LogToTrace();
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .LogToTrace();
- public static int Main(string[] args)
- => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
- }
+ public static int Main(string[] args)
+ => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
diff --git a/samples/VirtualizationDemo/ViewModels/ChatPageViewModel.cs b/samples/VirtualizationDemo/ViewModels/ChatPageViewModel.cs
new file mode 100644
index 0000000000..c0abe62bd5
--- /dev/null
+++ b/samples/VirtualizationDemo/ViewModels/ChatPageViewModel.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+using VirtualizationDemo.Models;
+
+namespace VirtualizationDemo.ViewModels;
+
+public class ChatPageViewModel
+{
+ public ChatPageViewModel()
+ {
+ var chat = ChatFile.Load(Path.Combine("Assets", "chat.json"));
+ Messages = new(chat.Chat ?? Array.Empty());
+ }
+
+ public ObservableCollection Messages { get; }
+}
diff --git a/samples/VirtualizationDemo/ViewModels/ExpanderItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ExpanderItemViewModel.cs
new file mode 100644
index 0000000000..a17fc2d303
--- /dev/null
+++ b/samples/VirtualizationDemo/ViewModels/ExpanderItemViewModel.cs
@@ -0,0 +1,21 @@
+using MiniMvvm;
+
+namespace VirtualizationDemo.ViewModels;
+
+public class ExpanderItemViewModel : ViewModelBase
+{
+ private string? _header;
+ private bool _isExpanded;
+
+ public string? Header
+ {
+ get => _header;
+ set => RaiseAndSetIfChanged(ref _header, value);
+ }
+
+ public bool IsExpanded
+ {
+ get => _isExpanded;
+ set => RaiseAndSetIfChanged(ref _isExpanded, value);
+ }
+}
diff --git a/samples/VirtualizationDemo/ViewModels/ExpanderPageViewModel.cs b/samples/VirtualizationDemo/ViewModels/ExpanderPageViewModel.cs
new file mode 100644
index 0000000000..f2807a803b
--- /dev/null
+++ b/samples/VirtualizationDemo/ViewModels/ExpanderPageViewModel.cs
@@ -0,0 +1,17 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace VirtualizationDemo.ViewModels;
+
+internal class ExpanderPageViewModel
+{
+ public ExpanderPageViewModel()
+ {
+ Items = new(Enumerable.Range(0, 100).Select(x => new ExpanderItemViewModel
+ {
+ Header = $"Item {x}",
+ }));
+ }
+
+ public ObservableCollection Items { get; set; }
+}
diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
deleted file mode 100644
index 9ba505ffe5..0000000000
--- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using MiniMvvm;
-
-namespace VirtualizationDemo.ViewModels
-{
- internal class ItemViewModel : ViewModelBase
- {
- private string _prefix;
- private int _index;
- private double _height = double.NaN;
-
- public ItemViewModel(int index, string prefix = "Item")
- {
- _prefix = prefix;
- _index = index;
- }
-
- public string Header => $"{_prefix} {_index}";
-
- public double Height
- {
- get => _height;
- set => this.RaiseAndSetIfChanged(ref _height, value);
- }
- }
-}
diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
index 96dbbc1a83..478e40187e 100644
--- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
+++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
@@ -1,160 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive;
-using Avalonia.Collections;
-using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.Layout;
-using Avalonia.Controls.Selection;
-using MiniMvvm;
+using MiniMvvm;
-namespace VirtualizationDemo.ViewModels
-{
- internal class MainWindowViewModel : ViewModelBase
- {
- private int _itemCount = 200;
- private string _newItemString = "New Item";
- private int _newItemIndex;
- private AvaloniaList _items;
- private string _prefix = "Item";
- private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
- private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
- private Orientation _orientation = Orientation.Vertical;
-
- public MainWindowViewModel()
- {
- this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems);
- RecreateCommand = MiniCommand.Create(() => Recreate());
-
- AddItemCommand = MiniCommand.Create(() => AddItem());
-
- RemoveItemCommand = MiniCommand.Create(() => Remove());
-
- SelectFirstCommand = MiniCommand.Create(() => SelectItem(0));
-
- SelectLastCommand = MiniCommand.Create(() => SelectItem(Items.Count - 1));
- }
-
- public string NewItemString
- {
- get { return _newItemString; }
- set { this.RaiseAndSetIfChanged(ref _newItemString, value); }
- }
-
- public int ItemCount
- {
- get { return _itemCount; }
- set { this.RaiseAndSetIfChanged(ref _itemCount, value); }
- }
-
- public SelectionModel Selection { get; } = new SelectionModel();
-
- public AvaloniaList Items
- {
- get { return _items; }
- private set { this.RaiseAndSetIfChanged(ref _items, value); }
- }
-
- public Orientation Orientation
- {
- get { return _orientation; }
- set { this.RaiseAndSetIfChanged(ref _orientation, value); }
- }
-
- public IEnumerable Orientations =>
- Enum.GetValues(typeof(Orientation)).Cast();
-
- public ScrollBarVisibility HorizontalScrollBarVisibility
- {
- get { return _horizontalScrollBarVisibility; }
- set { this.RaiseAndSetIfChanged(ref _horizontalScrollBarVisibility, value); }
- }
+namespace VirtualizationDemo.ViewModels;
- public ScrollBarVisibility VerticalScrollBarVisibility
- {
- get { return _verticalScrollBarVisibility; }
- set { this.RaiseAndSetIfChanged(ref _verticalScrollBarVisibility, value); }
- }
-
- public IEnumerable ScrollBarVisibilities =>
- Enum.GetValues(typeof(ScrollBarVisibility)).Cast();
-
- public MiniCommand AddItemCommand { get; private set; }
- public MiniCommand RecreateCommand { get; private set; }
- public MiniCommand RemoveItemCommand { get; private set; }
- public MiniCommand SelectFirstCommand { get; private set; }
- public MiniCommand SelectLastCommand { get; private set; }
-
- public void RandomizeSize()
- {
- var random = new Random();
-
- foreach (var i in Items)
- {
- i.Height = random.Next(240) + 10;
- }
- }
-
- public void ResetSize()
- {
- foreach (var i in Items)
- {
- i.Height = double.NaN;
- }
- }
-
- private void ResizeItems(int count)
- {
- if (Items == null)
- {
- var items = Enumerable.Range(0, count)
- .Select(x => new ItemViewModel(x));
- Items = new AvaloniaList(items);
- }
- else if (count > Items.Count)
- {
- var items = Enumerable.Range(Items.Count, count - Items.Count)
- .Select(x => new ItemViewModel(x));
- Items.AddRange(items);
- }
- else if (count < Items.Count)
- {
- Items.RemoveRange(count, Items.Count - count);
- }
- }
-
- private void AddItem()
- {
- var index = Items.Count;
-
- if (Selection.SelectedItems.Count > 0)
- {
- index = Selection.SelectedIndex;
- }
-
- Items.Insert(index, new ItemViewModel(_newItemIndex++, NewItemString));
- }
-
- private void Remove()
- {
- if (Selection.SelectedItems.Count > 0)
- {
- Items.RemoveAll(Selection.SelectedItems.ToList());
- }
- }
-
- private void Recreate()
- {
- _prefix = _prefix == "Item" ? "Recreated" : "Item";
- var items = Enumerable.Range(0, _itemCount)
- .Select(x => new ItemViewModel(x, _prefix));
- Items = new AvaloniaList(items);
- }
-
- private void SelectItem(int index)
- {
- Selection.SelectedIndex = index;
- }
- }
+internal class MainWindowViewModel : ViewModelBase
+{
+ public PlaygroundPageViewModel Playground { get; } = new();
+ public ChatPageViewModel Chat { get; } = new();
+ public ExpanderPageViewModel Expanders { get; } = new();
}
diff --git a/samples/VirtualizationDemo/ViewModels/PlaygroundItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/PlaygroundItemViewModel.cs
new file mode 100644
index 0000000000..584ef4600b
--- /dev/null
+++ b/samples/VirtualizationDemo/ViewModels/PlaygroundItemViewModel.cs
@@ -0,0 +1,17 @@
+using MiniMvvm;
+
+namespace VirtualizationDemo.ViewModels;
+
+public class PlaygroundItemViewModel : ViewModelBase
+{
+ private string? _header;
+
+ public PlaygroundItemViewModel(int index) => Header = $"Item {index}";
+ public PlaygroundItemViewModel(string? header) => Header = header;
+
+ public string? Header
+ {
+ get => _header;
+ set => RaiseAndSetIfChanged(ref _header, value);
+ }
+}
diff --git a/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs b/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs
new file mode 100644
index 0000000000..98ab91b0a6
--- /dev/null
+++ b/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Selection;
+using MiniMvvm;
+
+namespace VirtualizationDemo.ViewModels;
+
+public class PlaygroundPageViewModel : ViewModelBase
+{
+ private SelectionMode _selectionMode = SelectionMode.Multiple;
+ private int _scrollToIndex = 500;
+ private string? _newItemHeader = "New Item 1";
+
+ public PlaygroundPageViewModel()
+ {
+ Items = new(Enumerable.Range(0, 1000).Select(x => new PlaygroundItemViewModel(x)));
+ Selection = new();
+ }
+
+ public ObservableCollection Items { get; }
+
+ public bool Multiple
+ {
+ get => _selectionMode.HasAnyFlag(SelectionMode.Multiple);
+ set => SetSelectionMode(SelectionMode.Multiple, value);
+ }
+
+ public bool Toggle
+ {
+ get => _selectionMode.HasAnyFlag(SelectionMode.Toggle);
+ set => SetSelectionMode(SelectionMode.Toggle, value);
+ }
+
+ public bool AlwaysSelected
+ {
+ get => _selectionMode.HasAnyFlag(SelectionMode.AlwaysSelected);
+ set => SetSelectionMode(SelectionMode.AlwaysSelected, value);
+ }
+
+ public SelectionModel Selection { get; }
+
+ public SelectionMode SelectionMode
+ {
+ get => _selectionMode;
+ set => RaiseAndSetIfChanged(ref _selectionMode, value);
+ }
+
+ public int ScrollToIndex
+ {
+ get => _scrollToIndex;
+ set => RaiseAndSetIfChanged(ref _scrollToIndex, value);
+ }
+
+ public string? NewItemHeader
+ {
+ get => _newItemHeader;
+ set => RaiseAndSetIfChanged(ref _newItemHeader, value);
+ }
+
+ public void ExecuteScrollToIndex()
+ {
+ Selection.Select(ScrollToIndex);
+ }
+
+ public void RandomizeScrollToIndex()
+ {
+ var rnd = new Random();
+ ScrollToIndex = rnd.Next(Items.Count);
+ }
+
+ public void AddAtSelectedIndex()
+ {
+ if (Selection.SelectedIndex == -1)
+ return;
+ Items.Insert(Selection.SelectedIndex, new(NewItemHeader));
+ }
+
+ public void DeleteSelectedItem()
+ {
+ var count = Selection.Count;
+ for (var i = count - 1; i >= 0; i--)
+ Items.RemoveAt(Selection.SelectedIndexes[i]);
+ }
+
+ private void SetSelectionMode(SelectionMode mode, bool value)
+ {
+ if (value)
+ SelectionMode |= mode;
+ else
+ SelectionMode &= ~mode;
+ }
+}
diff --git a/samples/VirtualizationDemo/Views/ChatPageView.axaml b/samples/VirtualizationDemo/Views/ChatPageView.axaml
new file mode 100644
index 0000000000..fc182f15ae
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/ChatPageView.axaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/VirtualizationDemo/Views/ChatPageView.axaml.cs b/samples/VirtualizationDemo/Views/ChatPageView.axaml.cs
new file mode 100644
index 0000000000..b5c90db69c
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/ChatPageView.axaml.cs
@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace VirtualizationDemo.Views;
+
+public partial class ChatPageView : UserControl
+{
+ public ChatPageView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/samples/VirtualizationDemo/Views/ExpanderPageView.axaml b/samples/VirtualizationDemo/Views/ExpanderPageView.axaml
new file mode 100644
index 0000000000..972d885229
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/ExpanderPageView.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/VirtualizationDemo/Views/ExpanderPageView.axaml.cs b/samples/VirtualizationDemo/Views/ExpanderPageView.axaml.cs
new file mode 100644
index 0000000000..df3689cf24
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/ExpanderPageView.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace VirtualizationDemo.Views;
+
+public partial class ExpanderPageView : UserControl
+{
+ public ExpanderPageView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml b/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml
new file mode 100644
index 0000000000..52bc6fd27a
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+ Multiple
+ Toggle
+ AlwaysSelected
+ AutoScrollToSelectedItem
+ WrapSelection
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml.cs b/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml.cs
new file mode 100644
index 0000000000..5282475778
--- /dev/null
+++ b/samples/VirtualizationDemo/Views/PlaygroundPageView.axaml.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Linq;
+using System.Threading;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+
+namespace VirtualizationDemo.Views;
+
+public partial class PlaygroundPageView : UserControl
+{
+ private DispatcherTimer _timer;
+
+ public PlaygroundPageView()
+ {
+ InitializeComponent();
+
+ _timer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromMilliseconds(500),
+ };
+
+ _timer.Tick += TimerTick;
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ _timer.Start();
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+ _timer.Stop();
+ }
+
+ private void TimerTick(object? sender, EventArgs e)
+ {
+ var message = $"Realized {list.GetRealizedContainers().Count()} of {list.ItemsPanelRoot?.Children.Count}";
+ itemCount.Text = message;
+ }
+}
diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj
index 81b30c6cbe..3ac7aab589 100644
--- a/samples/VirtualizationDemo/VirtualizationDemo.csproj
+++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj
@@ -1,19 +1,24 @@
- Exe
+ WinExe
net6.0
+ true
+
+
+
-
-
+
+
+
+
+ PreserveNewest
+
-
-
-
-
-
+
+
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index eb4b6bf6a0..b2cd150933 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -32,10 +32,6 @@ namespace Avalonia.Android
{
lifetime.View = View;
}
-
- Window?.ClearFlags(WindowManagerFlags.TranslucentStatus);
- Window?.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
-
base.OnCreate(savedInstanceState);
SetContentView(View);
diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
index 35d1b06e6a..251a177432 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
@@ -2,11 +2,10 @@
using System.Collections.Generic;
using Android.OS;
using Android.Views;
-using AndroidX.AppCompat.App;
using AndroidX.Core.View;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Platform;
-using static Avalonia.Controls.Platform.IInsetsManager;
+using Avalonia.Media;
namespace Avalonia.Android.Platform
{
@@ -20,6 +19,7 @@ namespace Avalonia.Android.Platform
private bool? _systemUiVisibility;
private SystemBarTheme? _statusBarTheme;
private bool? _isDefaultSystemBarLightTheme;
+ private Color? _systemBarColor;
public event EventHandler SafeAreaChanged;
@@ -36,6 +36,16 @@ namespace Avalonia.Android.Platform
}
WindowCompat.SetDecorFitsSystemWindows(_activity.Window, !value);
+
+ if(value)
+ {
+ _activity.Window.AddFlags(WindowManagerFlags.TranslucentStatus);
+ _activity.Window.AddFlags(WindowManagerFlags.TranslucentNavigation);
+ }
+ else
+ {
+ SystemBarColor = _systemBarColor;
+ }
}
}
@@ -71,7 +81,7 @@ namespace Avalonia.Android.Platform
var renderScaling = _topLevel.RenderScaling;
var inset = insets.GetInsets(
- (DisplayEdgeToEdge ?
+ (_displayEdgeToEdge ?
WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() |
WindowInsetsCompat.Type.DisplayCutout() :
0) | WindowInsetsCompat.Type.Ime());
@@ -81,8 +91,8 @@ namespace Avalonia.Android.Platform
return new Thickness(inset.Left / renderScaling,
inset.Top / renderScaling,
inset.Right / renderScaling,
- (imeInset.Bottom > 0 && ((_usesLegacyLayouts && !DisplayEdgeToEdge) || !_usesLegacyLayouts) ?
- imeInset.Bottom - navBarInset.Bottom :
+ (imeInset.Bottom > 0 && ((_usesLegacyLayouts && !_displayEdgeToEdge) || !_usesLegacyLayouts) ?
+ imeInset.Bottom - (_displayEdgeToEdge ? 0 : navBarInset.Bottom) :
inset.Bottom) / renderScaling);
}
@@ -93,6 +103,7 @@ namespace Avalonia.Android.Platform
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
{
NotifySafeAreaChanged(SafeAreaPadding);
+ insets = ViewCompat.OnApplyWindowInsets(v, insets);
return insets;
}
@@ -146,8 +157,6 @@ namespace Avalonia.Android.Platform
compat.AppearanceLightStatusBars = value == Controls.Platform.SystemBarTheme.Light;
compat.AppearanceLightNavigationBars = value == Controls.Platform.SystemBarTheme.Light;
-
- AppCompatDelegate.DefaultNightMode = isDefault ? AppCompatDelegate.ModeNightFollowSystem : compat.AppearanceLightStatusBars ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
}
}
@@ -190,10 +199,36 @@ namespace Avalonia.Android.Platform
}
}
+ public Color? SystemBarColor
+ {
+ get => _systemBarColor;
+ set
+ {
+ _systemBarColor = value;
+
+ if (_systemBarColor is { } color && !_displayEdgeToEdge && _activity.Window != null)
+ {
+ _activity.Window.ClearFlags(WindowManagerFlags.TranslucentStatus);
+ _activity.Window.ClearFlags(WindowManagerFlags.TranslucentNavigation);
+ _activity.Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
+
+ var androidColor = global::Android.Graphics.Color.Argb(color.A, color.R, color.G, color.B);
+ _activity.Window.SetStatusBarColor(androidColor);
+
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
+ {
+ // As we can only change the navigation bar's foreground api 26 and newer, we only change the background color if running on those versions
+ _activity.Window.SetNavigationBarColor(androidColor);
+ }
+ }
+ }
+ }
+
internal void ApplyStatusBarState()
{
IsSystemBarVisible = _systemUiVisibility;
SystemBarTheme = _statusBarTheme;
+ SystemBarColor = _systemBarColor;
}
private class InsetsAnimationCallback : WindowInsetsAnimationCompat.Callback
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 17726e6353..126c488d59 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -9,6 +9,7 @@ using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
+using AndroidX.AppCompat.App;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@@ -286,6 +287,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_ => null,
};
}
+
+ AppCompatDelegate.DefaultNightMode = themeVariant == PlatformThemeVariant.Light ? AppCompatDelegate.ModeNightNo : AppCompatDelegate.ModeNightYes;
}
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index 9fbf680a5c..0c22213d33 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -1,6 +1,6 @@
using System;
-using Avalonia.Reactive;
using Avalonia.Data;
+using Avalonia.Reactive;
namespace Avalonia
{
@@ -34,8 +34,8 @@ namespace Avalonia
///
public static IObservable