diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml
index 5a8e65ed22..84f54293ef 100644
--- a/samples/BindingDemo/App.xaml
+++ b/samples/BindingDemo/App.xaml
@@ -2,13 +2,6 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App">
-
-
-
-
-
-
-
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 8f32fa01dd..3b847adcbb 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -6,18 +6,34 @@
x:Class="ControlCatalog.App">
+
+
+
+
+ #33000000
+ #99000000
+ #FFE6E6E6
+ #FF000000
+
+
+ #33FFFFFF
+ #99FFFFFF
+ #FF1F1F1F
+ #FFFFFFFF
+
+
+ #FF0078D7
+ #FF005A9E
+
+
-
-
-
-
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 6c99eb5289..d71d51f068 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -16,7 +16,6 @@ namespace ControlCatalog
private readonly Styles _themeStylesContainer = new();
private FluentTheme? _fluentTheme;
private SimpleTheme? _simpleTheme;
- private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
private IStyle? _colorPickerFluent, _colorPickerSimple;
private IStyle? _dataGridFluent, _dataGridSimple;
@@ -33,16 +32,12 @@ namespace ControlCatalog
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
- _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
- _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
_dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
- _fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
- _fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
- SetThemeVariant(CatalogTheme.FluentLight);
+ SetCatalogThemes(CatalogTheme.Fluent);
}
public override void OnFrameworkInitializationCompleted()
@@ -61,19 +56,12 @@ namespace ControlCatalog
private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
- public static void SetThemeVariant(CatalogTheme theme)
+ public static void SetCatalogThemes(CatalogTheme theme)
{
var app = (App)Current!;
var prevTheme = app._prevTheme;
app._prevTheme = theme;
- var shouldReopenWindow = theme switch
- {
- CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
- CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
- CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
- CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
- _ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
- };
+ var shouldReopenWindow = prevTheme != theme;
if (app._themeStylesContainer.Count == 0)
{
@@ -81,36 +69,16 @@ namespace ControlCatalog
app._themeStylesContainer.Add(new Style());
app._themeStylesContainer.Add(new Style());
}
-
- if (theme == CatalogTheme.FluentLight)
- {
- app._fluentTheme!.Mode = FluentThemeMode.Light;
- app._themeStylesContainer[0] = app._fluentTheme;
- app._themeStylesContainer[1] = app._colorPickerFluent!;
- app._themeStylesContainer[2] = app._dataGridFluent!;
- }
- else if (theme == CatalogTheme.FluentDark)
+
+ if (theme == CatalogTheme.Fluent)
{
- app._fluentTheme!.Mode = FluentThemeMode.Dark;
- app._themeStylesContainer[0] = app._fluentTheme;
+ app._themeStylesContainer[0] = app._fluentTheme!;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
- else if (theme == CatalogTheme.SimpleLight)
- {
- app._simpleTheme!.Mode = SimpleThemeMode.Light;
- app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
- app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
- app._themeStylesContainer[0] = app._simpleTheme;
- app._themeStylesContainer[1] = app._colorPickerSimple!;
- app._themeStylesContainer[2] = app._dataGridSimple!;
- }
- else if (theme == CatalogTheme.SimpleDark)
+ else if (theme == CatalogTheme.Simple)
{
- app._simpleTheme!.Mode = SimpleThemeMode.Dark;
- app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
- app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
- app._themeStylesContainer[0] = app._simpleTheme;
+ app._themeStylesContainer[0] = app._simpleTheme!;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 83776ec2c1..0695d9d17a 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -168,6 +168,9 @@
+
+
+
@@ -201,14 +204,22 @@
Full
+
+
+ Default
+ Light
+ Dark
+
+
- FluentLight
- FluentDark
- SimpleLight
- SimpleDark
+ Fluent
+ Simple
PlatformThemeVariant.Light,
- CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
- CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
- CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
- _ => throw new ArgumentOutOfRangeException()
- });
+ App.SetCatalogThemes(theme);
+ }
+ };
+ var themeVariants = this.Get("ThemeVariants");
+ themeVariants.SelectedItem = Application.Current!.RequestedThemeVariant;
+ themeVariants.SelectionChanged += (sender, e) =>
+ {
+ if (themeVariants.SelectedItem is ThemeVariant themeVariant)
+ {
+ Application.Current!.RequestedThemeVariant = themeVariant;
}
};
@@ -118,25 +119,13 @@ namespace ControlCatalog
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
- var themes = this.Get("Themes");
- var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
- var newTheme = (currentTheme, e.ThemeVariant) switch
- {
- (CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
- (CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
- (CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
- (CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
- _ => currentTheme
- };
- themes.SelectedItem = newTheme;
-
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
- Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
- Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
- Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
+ Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
+ Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
+ Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
diff --git a/samples/ControlCatalog/Models/CatalogTheme.cs b/samples/ControlCatalog/Models/CatalogTheme.cs
index 37224ed26e..79b3182d20 100644
--- a/samples/ControlCatalog/Models/CatalogTheme.cs
+++ b/samples/ControlCatalog/Models/CatalogTheme.cs
@@ -2,9 +2,7 @@
{
public enum CatalogTheme
{
- FluentLight,
- FluentDark,
- SimpleLight,
- SimpleDark
+ Fluent,
+ Simple
}
}
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
index 47753f56b6..fc3ad9b895 100644
--- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
@@ -15,11 +15,11 @@
Spacing="16">
A simple DatePicker
-
-
+
@@ -31,7 +31,7 @@
-
@@ -42,12 +42,12 @@
A DatePicker with day formatted and year hidden.
-
-
+
@@ -58,15 +58,15 @@
-
+
A simple TimePicker.
-
-
+
@@ -77,7 +77,7 @@
-
@@ -88,11 +88,11 @@
A TimePicker with minute increments specified.
-
-
+
@@ -105,11 +105,11 @@
A TimePicker using a 12-hour clock.
-
-
+
@@ -122,11 +122,11 @@
A TimePicker using a 24-hour clock.
-
-
+
diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml b/samples/ControlCatalog/Pages/FlyoutsPage.axaml
index c4d0bc3e67..54aa9d1b67 100644
--- a/samples/ControlCatalog/Pages/FlyoutsPage.axaml
+++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml
@@ -26,31 +26,31 @@
-
-
+
-
-
+
-
-
@@ -70,7 +70,7 @@
-
+
@@ -78,21 +78,21 @@
-
-
+
-
@@ -215,7 +215,7 @@
-
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
index 5ca4ca9bdd..6bf29765f4 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
@@ -62,7 +62,7 @@
-
+
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml b/samples/ControlCatalog/Pages/SplitViewPage.xaml
index 61bfb490b8..2edd895349 100644
--- a/samples/ControlCatalog/Pages/SplitViewPage.xaml
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml
@@ -32,7 +32,7 @@
- SystemControlBackgroundChromeMediumLowBrush
+ CatalogChromeMediumColor
Red
Blue
Green
@@ -48,7 +48,7 @@
-
@@ -89,11 +89,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index 6bb428e2c7..6511e2136a 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -9,7 +9,7 @@
@@ -101,7 +115,7 @@
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
IsVisible="False"
- CornerRadius="{DynamicResource ControlCornerRadius}"/>
+ CornerRadius="4"/>
@@ -136,18 +150,18 @@
-
-
-
+
+
+
diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml
index f601f9f78f..cf3e5e445a 100644
--- a/samples/Sandbox/App.axaml
+++ b/samples/Sandbox/App.axaml
@@ -3,6 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App">
-
+
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
index 35a391f2cb..d4c7137fdc 100644
--- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
@@ -14,11 +14,7 @@ namespace Avalonia.Collections
///
/// The type of the dictionary key.
/// The type of the dictionary value.
- public class AvaloniaDictionary : IDictionary,
- IDictionary,
- INotifyCollectionChanged,
- INotifyPropertyChanged
- where TKey : notnull
+ public class AvaloniaDictionary : IAvaloniaDictionary where TKey : notnull
{
private Dictionary _inner;
@@ -29,6 +25,14 @@ namespace Avalonia.Collections
{
_inner = new Dictionary();
}
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AvaloniaDictionary(int capacity)
+ {
+ _inner = new Dictionary(capacity);
+ }
///
/// Occurs when the collection changes.
@@ -62,6 +66,10 @@ namespace Avalonia.Collections
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
+ IEnumerable IReadOnlyDictionary.Keys => _inner.Keys;
+
+ IEnumerable IReadOnlyDictionary.Values => _inner.Values;
+
///
/// Gets or sets the named resource.
///
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
new file mode 100644
index 0000000000..e350a019d4
--- /dev/null
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Reactive;
+
+namespace Avalonia.Collections
+{
+ ///
+ /// Defines extension methods for working with s.
+ ///
+ public static class AvaloniaDictionaryExtensions
+ {
+ ///
+ /// Invokes an action for each item in a collection and subsequently each item added or
+ /// removed from the collection.
+ ///
+ /// The key type of the collection items.
+ /// The value type of the collection items.
+ /// The collection.
+ ///
+ /// An action called initially for each item in the collection and subsequently for each
+ /// item added to the collection. The parameters passed are the index in the collection and
+ /// the item.
+ ///
+ ///
+ /// An action called for each item removed from the collection. The parameters passed are
+ /// the index in the collection and the item.
+ ///
+ ///
+ /// An action called when the collection is reset. This will be followed by calls to
+ /// for each item present in the collection after the reset.
+ ///
+ ///
+ /// Indicates if a weak subscription should be used to track changes to the collection.
+ ///
+ /// A disposable used to terminate the subscription.
+ internal static IDisposable ForEachItem(
+ this IAvaloniaReadOnlyDictionary collection,
+ Action added,
+ Action removed,
+ Action reset,
+ bool weakSubscription = false)
+ where TKey : notnull
+ {
+ void Add(IEnumerable items)
+ {
+ foreach (KeyValuePair pair in items)
+ {
+ added(pair.Key, pair.Value);
+ }
+ }
+
+ void Remove(IEnumerable items)
+ {
+ foreach (KeyValuePair pair in items)
+ {
+ removed(pair.Key, pair.Value);
+ }
+ }
+
+ NotifyCollectionChangedEventHandler handler = (_, e) =>
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ Add(e.NewItems!);
+ break;
+
+ case NotifyCollectionChangedAction.Move:
+ case NotifyCollectionChangedAction.Replace:
+ Remove(e.OldItems!);
+ int newIndex = e.NewStartingIndex;
+ if(newIndex > e.OldStartingIndex)
+ {
+ newIndex -= e.OldItems!.Count;
+ }
+ Add(e.NewItems!);
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ Remove(e.OldItems!);
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ if (reset == null)
+ {
+ throw new InvalidOperationException(
+ "Reset called on collection without reset handler.");
+ }
+
+ reset();
+ Add(collection);
+ break;
+ }
+ };
+
+ Add(collection);
+
+ if (weakSubscription)
+ {
+ return collection.WeakSubscribe(handler);
+ }
+ else
+ {
+ collection.CollectionChanged += handler;
+
+ return Disposable.Create(() => collection.CollectionChanged -= handler);
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Collections/IAvaloniaDictionary.cs b/src/Avalonia.Base/Collections/IAvaloniaDictionary.cs
new file mode 100644
index 0000000000..b79cfe2b9c
--- /dev/null
+++ b/src/Avalonia.Base/Collections/IAvaloniaDictionary.cs
@@ -0,0 +1,13 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Avalonia.Collections
+{
+ public interface IAvaloniaDictionary
+ : IDictionary,
+ IAvaloniaReadOnlyDictionary,
+ IDictionary
+ where TKey : notnull
+ {
+ }
+}
diff --git a/src/Avalonia.Base/Collections/IAvaloniaReadOnlyDictionary.cs b/src/Avalonia.Base/Collections/IAvaloniaReadOnlyDictionary.cs
new file mode 100644
index 0000000000..d772de2f59
--- /dev/null
+++ b/src/Avalonia.Base/Collections/IAvaloniaReadOnlyDictionary.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+
+namespace Avalonia.Collections
+{
+ public interface IAvaloniaReadOnlyDictionary
+ : IReadOnlyDictionary,
+ INotifyCollectionChanged,
+ INotifyPropertyChanged
+ where TKey : notnull
+ {
+ }
+}
diff --git a/src/Avalonia.Base/Controls/IResourceDictionary.cs b/src/Avalonia.Base/Controls/IResourceDictionary.cs
index 3a68dde31e..2bd1f65638 100644
--- a/src/Avalonia.Base/Controls/IResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/IResourceDictionary.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Avalonia.Styling;
#nullable enable
@@ -13,5 +14,10 @@ namespace Avalonia.Controls
/// Gets a collection of child resource dictionaries.
///
IList MergedDictionaries { get; }
+
+ ///
+ /// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
+ ///
+ IDictionary ThemeDictionaries { get; }
}
}
diff --git a/src/Avalonia.Base/Controls/IResourceNode.cs b/src/Avalonia.Base/Controls/IResourceNode.cs
index d6c900f97f..d2fa3c7af3 100644
--- a/src/Avalonia.Base/Controls/IResourceNode.cs
+++ b/src/Avalonia.Base/Controls/IResourceNode.cs
@@ -1,5 +1,5 @@
-using System;
-using Avalonia.Metadata;
+using Avalonia.Metadata;
+using Avalonia.Styling;
namespace Avalonia.Controls
{
@@ -23,6 +23,7 @@ namespace Avalonia.Controls
/// Tries to find a resource within the object.
///
/// The resource key.
+ /// Theme used to select theme dictionary.
///
/// When this method returns, contains the value associated with the specified key,
/// if the key is found; otherwise, null.
@@ -30,6 +31,6 @@ namespace Avalonia.Controls
///
/// True if the resource if found, otherwise false.
///
- bool TryGetResource(object key, out object? value);
+ bool TryGetResource(object key, ThemeVariant? theme, out object? value);
}
}
diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs
index d6197c50c6..5123803f6e 100644
--- a/src/Avalonia.Base/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs
@@ -1,9 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
+using Avalonia.Media;
+using Avalonia.Styling;
namespace Avalonia.Controls
{
@@ -15,6 +18,7 @@ namespace Avalonia.Controls
private Dictionary