Merge remote-tracking branch 'upstream/master' into dbus-codegen
# Conflicts: # src/Avalonia.X11/X11Window.cs
This commit is contained in:
@@ -145,10 +145,14 @@ dotnet_diagnostic.CS1591.severity = suggestion
|
||||
|
||||
# CS0162: Remove unreachable code
|
||||
dotnet_diagnostic.CS0162.severity = error
|
||||
# CA1018: Mark attributes with AttributeUsageAttribute
|
||||
dotnet_diagnostic.CA1018.severity = error
|
||||
# CA1304: Specify CultureInfo
|
||||
dotnet_diagnostic.CA1304.severity = warning
|
||||
# CA1802: Use literals where appropriate
|
||||
dotnet_diagnostic.CA1802.severity = warning
|
||||
# CA1813: Avoid unsealed attributes
|
||||
dotnet_diagnostic.CA1813.severity = error
|
||||
# CA1815: Override equals and operator equals on value types
|
||||
dotnet_diagnostic.CA1815.severity = warning
|
||||
# CA1820: Test for empty strings using string length
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
|
||||
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
|
||||
"src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj",
|
||||
"src\\Avalonia.Controls.ItemsRepeater\\Avalonia.Controls.ItemsRepeater.csproj",
|
||||
"src\\Avalonia.Controls\\Avalonia.Controls.csproj",
|
||||
"src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
|
||||
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
|
||||
|
||||
@@ -236,6 +236,10 @@ EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.SourceGenerator", "src\tools\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator.csproj", "{5E9C0032-E460-4BC1-BCC7-6448F34DD679}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -559,6 +563,14 @@ Global
|
||||
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -626,6 +638,7 @@ Global
|
||||
{29E25263-3CC3-4D55-A042-00BA136867D4} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
|
||||
{5E9C0032-E460-4BC1-BCC7-6448F34DD679} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
|
||||
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
|
||||
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
|
||||
|
||||
@@ -66,7 +66,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
|
||||
_isModal = isDialog;
|
||||
|
||||
WindowBaseImpl::Show(activate, isDialog);
|
||||
|
||||
GetWindowState(&_actualWindowState);
|
||||
HideOrShowTrafficLights();
|
||||
|
||||
return SetWindowState(_lastWindowState);
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="BindingDemo.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
|
||||
@@ -6,18 +6,34 @@
|
||||
x:Class="ControlCatalog.App">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<!-- Custom controls defined in other assemblies -->
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Resources used only in the control catalog -->
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<Color x:Key="CatalogBaseLowColor">#33000000</Color>
|
||||
<Color x:Key="CatalogBaseMediumColor">#99000000</Color>
|
||||
<Color x:Key="CatalogChromeMediumColor">#FFE6E6E6</Color>
|
||||
<Color x:Key="CatalogBaseHighColor">#FF000000</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="CatalogBaseLowColor">#33FFFFFF</Color>
|
||||
<Color x:Key="CatalogBaseMediumColor">#99FFFFFF</Color>
|
||||
<Color x:Key="CatalogChromeMediumColor">#FF1F1F1F</Color>
|
||||
<Color x:Key="CatalogBaseHighColor">#FFFFFFFF</Color>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
|
||||
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
|
||||
|
||||
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
|
||||
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
|
||||
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
|
||||
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
|
||||
<StyleInclude x:Key="ColorPickerSimple" Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
|
||||
<ResourceInclude x:Key="FluentAccentColors" Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
|
||||
<ResourceInclude x:Key="FluentBaseLightColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
|
||||
<ResourceInclude x:Key="FluentBaseDarkColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
|
||||
<ResourceInclude x:Key="FluentBaseColors" Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
<Application.Styles>
|
||||
|
||||
@@ -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)
|
||||
|
||||
if (theme == CatalogTheme.Fluent)
|
||||
{
|
||||
app._fluentTheme!.Mode = FluentThemeMode.Light;
|
||||
app._themeStylesContainer[0] = app._fluentTheme;
|
||||
app._themeStylesContainer[0] = app._fluentTheme!;
|
||||
app._themeStylesContainer[1] = app._colorPickerFluent!;
|
||||
app._themeStylesContainer[2] = app._dataGridFluent!;
|
||||
}
|
||||
else if (theme == CatalogTheme.FluentDark)
|
||||
else if (theme == CatalogTheme.Simple)
|
||||
{
|
||||
app._fluentTheme!.Mode = FluentThemeMode.Dark;
|
||||
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)
|
||||
{
|
||||
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!;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
|
||||
<ProjectReference Include="..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
|
||||
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<controls:HamburgerMenu Name="Sidebar">
|
||||
<TabItem Header="Composition">
|
||||
<controls:HamburgerMenu Name="Sidebar">
|
||||
<TabItem Header="Composition">
|
||||
<pages:CompositionPage/>
|
||||
</TabItem>
|
||||
<TabItem Header="Acrylic">
|
||||
@@ -168,6 +168,9 @@
|
||||
<TabItem Header="TextBlock">
|
||||
<pages:TextBlockPage />
|
||||
</TabItem>
|
||||
<TabItem Header="Theme Variants">
|
||||
<pages:ThemePage />
|
||||
</TabItem>
|
||||
<TabItem Header="ToggleSwitch">
|
||||
<pages:ToggleSwitchPage />
|
||||
</TabItem>
|
||||
@@ -201,14 +204,22 @@
|
||||
<SystemDecorations>Full</SystemDecorations>
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="ThemeVariants"
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberBinding="{Binding Key, x:DataType=ThemeVariant}"
|
||||
SelectedIndex="0">
|
||||
<ComboBox.Items>
|
||||
<ThemeVariant>Default</ThemeVariant>
|
||||
<ThemeVariant>Light</ThemeVariant>
|
||||
<ThemeVariant>Dark</ThemeVariant>
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="Themes"
|
||||
HorizontalAlignment="Stretch"
|
||||
SelectedIndex="0">
|
||||
<ComboBox.Items>
|
||||
<models:CatalogTheme>FluentLight</models:CatalogTheme>
|
||||
<models:CatalogTheme>FluentDark</models:CatalogTheme>
|
||||
<models:CatalogTheme>SimpleLight</models:CatalogTheme>
|
||||
<models:CatalogTheme>SimpleDark</models:CatalogTheme>
|
||||
<models:CatalogTheme>Fluent</models:CatalogTheme>
|
||||
<models:CatalogTheme>Simple</models:CatalogTheme>
|
||||
</ComboBox.Items>
|
||||
</ComboBox>
|
||||
<ComboBox x:Name="TransparencyLevels"
|
||||
|
||||
@@ -9,6 +9,7 @@ using Avalonia.Media;
|
||||
using Avalonia.Media.Immutable;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Styling;
|
||||
using ControlCatalog.Models;
|
||||
using ControlCatalog.Pages;
|
||||
|
||||
@@ -42,16 +43,16 @@ namespace ControlCatalog
|
||||
{
|
||||
if (themes.SelectedItem is CatalogTheme theme)
|
||||
{
|
||||
App.SetThemeVariant(theme);
|
||||
|
||||
((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch
|
||||
{
|
||||
CatalogTheme.FluentLight => PlatformThemeVariant.Light,
|
||||
CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
|
||||
CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
|
||||
CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
App.SetCatalogThemes(theme);
|
||||
}
|
||||
};
|
||||
var themeVariants = this.Get<ComboBox>("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<ComboBox>("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)
|
||||
{
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
{
|
||||
public enum CatalogTheme
|
||||
{
|
||||
FluentLight,
|
||||
FluentDark,
|
||||
SimpleLight,
|
||||
SimpleDark
|
||||
Fluent,
|
||||
Simple
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
Spacing="16">
|
||||
<TextBlock FontSize="18">A simple DatePicker</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<DatePicker />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
@@ -31,7 +31,7 @@
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<DatePicker >
|
||||
<DataValidationErrors.Error>
|
||||
@@ -42,12 +42,12 @@
|
||||
|
||||
<TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<DatePicker x:Name="Control2" DayFormat="d (ddd)"
|
||||
YearVisible="False" />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
@@ -58,15 +58,15 @@
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
<Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" />
|
||||
<Border Background="{DynamicResource CatalogBaseLowColor}" BorderThickness="1" Margin="15" />
|
||||
|
||||
<TextBlock FontSize="18">A simple TimePicker.</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<TimePicker />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
@@ -77,7 +77,7 @@
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<TimePicker>
|
||||
<DataValidationErrors.Error>
|
||||
@@ -88,11 +88,11 @@
|
||||
|
||||
<TextBlock FontSize="18">A TimePicker with minute increments specified.</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<TimePicker MinuteIncrement="15" />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
@@ -105,11 +105,11 @@
|
||||
|
||||
<TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<TimePicker ClockIdentifier="12HourClock"/>
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
@@ -122,11 +122,11 @@
|
||||
|
||||
<TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<TimePicker ClockIdentifier="24HourClock" />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Padding="15">
|
||||
<TextBlock.Text>
|
||||
<x:String>
|
||||
|
||||
@@ -26,31 +26,31 @@
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock FontSize="18" Text="Button with a Flyout" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<Button Content="Click Me!" Flyout="{StaticResource BasicFlyout}" />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Name="ButtonFlyoutXamlText" Padding="15" />
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock FontSize="18" Text="MenuFlyout" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<Button Content="Click Me!" Flyout="{StaticResource SharedMenuFlyout}" />
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Name="MenuFlyoutXamlText" Padding="15" />
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock FontSize="18" Text="Attached Flyouts" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}"
|
||||
HorizontalAlignment="Left"
|
||||
Height="100"
|
||||
Name="AttachedFlyoutPanel">
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
</Panel>
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Name="AttachedFlyoutXamlText" Padding="15" />
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
@@ -78,21 +78,21 @@
|
||||
|
||||
<TextBlock FontSize="18" Text="Sharing Flyouts" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<StackPanel Orientation="Horizontal" Spacing="30">
|
||||
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
|
||||
<Button Content="Launch Flyout on this button" Flyout="{StaticResource SharedMenuFlyout}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
|
||||
<Panel Background="{DynamicResource CatalogBaseLowColor}">
|
||||
<TextBlock Name="SharedFlyoutXamlText" Padding="15" />
|
||||
</Panel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock FontSize="18" Text="Flyout Placements" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<UniformGrid Columns="3">
|
||||
<UniformGrid.Styles>
|
||||
@@ -215,7 +215,7 @@
|
||||
|
||||
<TextBlock FontSize="18" Text="Flyout ShowMode" />
|
||||
<StackPanel>
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1" Padding="15">
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
<WrapPanel.Styles>
|
||||
|
||||
@@ -70,7 +70,6 @@ namespace ControlCatalog.Pages
|
||||
|
||||
_currentScale = 1;
|
||||
Vector3 currentOffset = default;
|
||||
bool isZooming = false;
|
||||
|
||||
CompositionVisual? compositionVisual = null;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<Button x:Name="scrollToRandom">Scroll to Random</Button>
|
||||
<Button x:Name="scrollToSelected">Scroll to Selected</Button>
|
||||
</StackPanel>
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" Margin="0 0 0 16">
|
||||
<Border BorderThickness="1" BorderBrush="{DynamicResource CatalogBaseMediumColor}" Margin="0 0 0 16">
|
||||
<ScrollViewer Name="scroller"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<TextBlock Text="PaneBackground" />
|
||||
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10">
|
||||
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem>
|
||||
<ComboBoxItem Tag="{DynamicResource CatalogChromeMediumColor}">CatalogChromeMediumColor</ComboBoxItem>
|
||||
<ComboBoxItem Tag="Red">Red</ComboBoxItem>
|
||||
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem>
|
||||
<ComboBoxItem Tag="Green">Green</ComboBoxItem>
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
|
||||
<Border BorderBrush="{DynamicResource CatalogBaseLowColor}"
|
||||
BorderThickness="1">
|
||||
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}-->
|
||||
<SplitView Name="SplitView"
|
||||
@@ -77,7 +77,7 @@
|
||||
<Border Width="48">
|
||||
<Viewbox Width="24" Height="24" HorizontalAlignment="Left">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
|
||||
<Path Fill="{DynamicResource CatalogBaseHighColor}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
@@ -89,11 +89,11 @@
|
||||
</SplitView.Pane>
|
||||
|
||||
<Grid>
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource CatalogBaseHighColor}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
|
||||
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource CatalogBaseHighColor}" />
|
||||
</Grid>
|
||||
|
||||
</SplitView>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<WrapPanel.Styles>
|
||||
<Style Selector="Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CatalogBaseMediumColor}" />
|
||||
<Setter Property="Padding" Value="2" />
|
||||
<Setter Property="Margin" Value="10" />
|
||||
<Setter Property="Width" Value="200" />
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<UserControl x:Class="ControlCatalog.Pages.ThemePage"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:pages="clr-namespace:ControlCatalog.Pages"
|
||||
d:DesignWidth="300"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="DemoBackground">Black</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="DemoBackground">White</SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="{x:Static pages:ThemePage.Pink}">
|
||||
<SolidColorBrush x:Key="DemoBackground">#ffe5ea</SolidColorBrush>
|
||||
<SolidColorBrush x:Key="NormalBackgroundBrush" Color="#ffc0cb" />
|
||||
<SolidColorBrush x:Key="PointerOverBackgroundBrush" Color="#ffb3c0" />
|
||||
<SolidColorBrush x:Key="PressedBackgroundBrush" Color="#ff4d6c" />
|
||||
<SolidColorBrush x:Key="NormalBorderBrush" Color="#ff8096" />
|
||||
<SolidColorBrush x:Key="PointerOverBorderBrush" Color="#ff8096" />
|
||||
<SolidColorBrush x:Key="PressedBorderBrush" Color="#ff4d6c" />
|
||||
|
||||
<!-- Override colors for fluent theme -->
|
||||
<StaticResource x:Key="ButtonBackground" ResourceKey="NormalBackgroundBrush" />
|
||||
<StaticResource x:Key="ButtonBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
|
||||
<StaticResource x:Key="ButtonBackgroundPressed" ResourceKey="PressedBackgroundBrush" />
|
||||
<StaticResource x:Key="ButtonBorderBrush" ResourceKey="NormalBorderBrush" />
|
||||
<StaticResource x:Key="ButtonBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
|
||||
<StaticResource x:Key="ButtonBorderBrushPressed" ResourceKey="PressedBorderBrush" />
|
||||
<StaticResource x:Key="TextControlBackground" ResourceKey="NormalBackgroundBrush" />
|
||||
<StaticResource x:Key="TextControlBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
|
||||
<StaticResource x:Key="TextControlBackgroundFocused" ResourceKey="PointerOverBackgroundBrush" />
|
||||
<StaticResource x:Key="TextControlBorderBrush" ResourceKey="NormalBorderBrush" />
|
||||
<StaticResource x:Key="TextControlBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
|
||||
<StaticResource x:Key="TextControlBorderBrushFocused" ResourceKey="PressedBorderBrush" />
|
||||
<StaticResource x:Key="ComboBoxBackground" ResourceKey="NormalBackgroundBrush" />
|
||||
<StaticResource x:Key="ComboBoxBackgroundPointerOver" ResourceKey="PointerOverBackgroundBrush" />
|
||||
<StaticResource x:Key="ComboBoxBackgroundPressed" ResourceKey="PressedBackgroundBrush" />
|
||||
<StaticResource x:Key="ComboBoxBorderBrush" ResourceKey="NormalBorderBrush" />
|
||||
<StaticResource x:Key="ComboBoxBorderBrushPointerOver" ResourceKey="PointerOverBorderBrush" />
|
||||
<StaticResource x:Key="ComboBoxBorderBrushPressed" ResourceKey="PressedBorderBrush" />
|
||||
<!-- Override colors for default theme -->
|
||||
<StaticResource x:Key="ThemeControlMidBrush" ResourceKey="NormalBackgroundBrush" />
|
||||
<StaticResource x:Key="ThemeControlHighBrush" ResourceKey="PressedBackgroundBrush" />
|
||||
<StaticResource x:Key="ThemeBorderLowBrush" ResourceKey="NormalBorderBrush" />
|
||||
<StaticResource x:Key="ThemeBorderMidBrush" ResourceKey="PointerOverBorderBrush" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ThemeVariantScope x:Name="ThemeVariantScope">
|
||||
<Border Background="{DynamicResource DemoBackground}"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Padding="4"
|
||||
CornerRadius="4">
|
||||
<Grid RowDefinitions="Auto, 4, Auto, 4, Auto, 4, Auto" ColumnDefinitions="150, 150">
|
||||
<ComboBox Grid.Column="0" Grid.Row="0" x:Name="Selector" HorizontalAlignment="Stretch">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<TextBlock Text="{Binding TargetNullValue=Unset}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Column="0" Grid.Row="2" Text="Username:" VerticalAlignment="Center" />
|
||||
<TextBlock Grid.Column="0" Grid.Row="4" Text="Password:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Grid.Row="2" Watermark="Input here" HorizontalAlignment="Stretch" />
|
||||
<TextBox Grid.Column="1" Grid.Row="4" Watermark="Input here" HorizontalAlignment="Stretch" />
|
||||
<Button Grid.Column="1" Grid.Row="6" Content="Login" HorizontalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ThemeVariantScope>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,37 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace ControlCatalog.Pages
|
||||
{
|
||||
public class ThemePage : UserControl
|
||||
{
|
||||
public static ThemeVariant Pink { get; } = new("Pink", ThemeVariant.Light);
|
||||
|
||||
public ThemePage()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var selector = this.FindControl<ComboBox>("Selector")!;
|
||||
var themeVariantScope = this.FindControl<ThemeVariantScope>("ThemeVariantScope")!;
|
||||
|
||||
selector.Items = new[]
|
||||
{
|
||||
ThemeVariant.Default,
|
||||
ThemeVariant.Dark,
|
||||
ThemeVariant.Light,
|
||||
Pink
|
||||
};
|
||||
selector.SelectedIndex = 0;
|
||||
|
||||
selector.SelectionChanged += (_, _) =>
|
||||
{
|
||||
if (selector.SelectedItem is ThemeVariant theme)
|
||||
{
|
||||
themeVariantScope.RequestedThemeVariant = theme;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Rendering;
|
||||
|
||||
namespace GpuInterop
|
||||
{
|
||||
@@ -8,9 +9,9 @@ namespace GpuInterop
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
this.AttachDevTools();
|
||||
this.Renderer.DrawFps = true;
|
||||
Renderer.Diagnostics.DebugOverlays = RendererDebugOverlays.Fps;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
||||
@@ -38,8 +38,8 @@ static class VulkanBufferHelper
|
||||
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
|
||||
physicalDevice,
|
||||
memoryRequirements.MemoryTypeBits,
|
||||
MemoryPropertyFlags.MemoryPropertyHostCoherentBit |
|
||||
MemoryPropertyFlags.MemoryPropertyHostVisibleBit)
|
||||
MemoryPropertyFlags.HostCoherentBit |
|
||||
MemoryPropertyFlags.HostVisibleBit)
|
||||
};
|
||||
|
||||
api.AllocateMemory(device, memoryAllocateInfo, null, out memory).ThrowOnError();
|
||||
@@ -77,4 +77,4 @@ static class VulkanBufferHelper
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Input;
|
||||
using Silk.NET.Vulkan;
|
||||
using SilkNetDemo;
|
||||
|
||||
@@ -25,7 +24,7 @@ namespace Avalonia.Vulkan
|
||||
var commandPoolCreateInfo = new CommandPoolCreateInfo
|
||||
{
|
||||
SType = StructureType.CommandPoolCreateInfo,
|
||||
Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit,
|
||||
Flags = CommandPoolCreateFlags.ResetCommandBufferBit,
|
||||
QueueFamilyIndex = queueFamilyIndex
|
||||
};
|
||||
|
||||
@@ -109,7 +108,7 @@ namespace Avalonia.Vulkan
|
||||
var fenceCreateInfo = new FenceCreateInfo()
|
||||
{
|
||||
SType = StructureType.FenceCreateInfo,
|
||||
Flags = FenceCreateFlags.FenceCreateSignaledBit
|
||||
Flags = FenceCreateFlags.SignaledBit
|
||||
};
|
||||
|
||||
api.CreateFence(device, fenceCreateInfo, null, out _fence);
|
||||
@@ -134,7 +133,7 @@ namespace Avalonia.Vulkan
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit
|
||||
};
|
||||
|
||||
_api.BeginCommandBuffer(InternalHandle, beginInfo);
|
||||
|
||||
@@ -208,7 +208,7 @@ unsafe class VulkanContent : IDisposable
|
||||
api.CmdBindDescriptorSets(commandBufferHandle, PipelineBindPoint.Graphics,
|
||||
_pipelineLayout,0,1, &dset, null);
|
||||
|
||||
api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.FragmentBit, 0,
|
||||
api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit, 0,
|
||||
(uint)Marshal.SizeOf<VertextPushConstant>(), &vertexConstant);
|
||||
api.CmdBindVertexBuffers(commandBufferHandle, 0, 1, _vertexBuffer, 0);
|
||||
api.CmdBindIndexBuffer(commandBufferHandle, _indexBuffer, 0, IndexType.Uint16);
|
||||
@@ -237,14 +237,14 @@ unsafe class VulkanContent : IDisposable
|
||||
SrcSubresource =
|
||||
new ImageSubresourceLayers
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ImageAspectColorBit,
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
BaseArrayLayer = 0,
|
||||
LayerCount = 1,
|
||||
MipLevel = 0
|
||||
},
|
||||
DstSubresource = new ImageSubresourceLayers
|
||||
{
|
||||
AspectMask = ImageAspectFlags.ImageAspectColorBit,
|
||||
AspectMask = ImageAspectFlags.ColorBit,
|
||||
BaseArrayLayer = 0,
|
||||
LayerCount = 1,
|
||||
MipLevel = 0
|
||||
@@ -326,19 +326,19 @@ unsafe class VulkanContent : IDisposable
|
||||
var imageCreateInfo = new ImageCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
ImageType = ImageType.ImageType2D,
|
||||
ImageType = ImageType.Type2D,
|
||||
Format = Format.D32Sfloat,
|
||||
Extent =
|
||||
new Extent3D((uint?)size.Width,
|
||||
(uint?)size.Height, 1),
|
||||
MipLevels = 1,
|
||||
ArrayLayers = 1,
|
||||
Samples = SampleCountFlags.SampleCount1Bit,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
Tiling = ImageTiling.Optimal,
|
||||
Usage = ImageUsageFlags.ImageUsageDepthStencilAttachmentBit,
|
||||
Usage = ImageUsageFlags.DepthStencilAttachmentBit,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
|
||||
Flags = ImageCreateFlags.CreateMutableFormatBit
|
||||
};
|
||||
|
||||
var api = _context.Api;
|
||||
@@ -355,7 +355,7 @@ unsafe class VulkanContent : IDisposable
|
||||
AllocationSize = memoryRequirements.Size,
|
||||
MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api,
|
||||
_context.PhysicalDevice,
|
||||
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
|
||||
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
|
||||
};
|
||||
|
||||
api.AllocateMemory(device, memoryAllocateInfo, null,
|
||||
@@ -369,14 +369,14 @@ unsafe class VulkanContent : IDisposable
|
||||
ComponentSwizzle.B,
|
||||
ComponentSwizzle.A);
|
||||
|
||||
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectDepthBit,
|
||||
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.DepthBit,
|
||||
0, 1, 0, 1);
|
||||
|
||||
var imageViewCreateInfo = new ImageViewCreateInfo
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = _depthImage,
|
||||
ViewType = ImageViewType.ImageViewType2D,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = Format.D32Sfloat,
|
||||
Components = componentMapping,
|
||||
SubresourceRange = subresourceRange
|
||||
@@ -406,7 +406,7 @@ unsafe class VulkanContent : IDisposable
|
||||
var colorAttachment = new AttachmentDescription()
|
||||
{
|
||||
Format = Format.R8G8B8A8Unorm,
|
||||
Samples = SampleCountFlags.SampleCount1Bit,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
LoadOp = AttachmentLoadOp.Clear,
|
||||
StoreOp = AttachmentStoreOp.Store,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
@@ -418,7 +418,7 @@ unsafe class VulkanContent : IDisposable
|
||||
var depthAttachment = new AttachmentDescription()
|
||||
{
|
||||
Format = Format.D32Sfloat,
|
||||
Samples = SampleCountFlags.SampleCount1Bit,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
LoadOp = AttachmentLoadOp.Clear,
|
||||
StoreOp = AttachmentStoreOp.DontCare,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
@@ -431,10 +431,10 @@ unsafe class VulkanContent : IDisposable
|
||||
{
|
||||
SrcSubpass = Vk.SubpassExternal,
|
||||
DstSubpass = 0,
|
||||
SrcStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
|
||||
SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
SrcAccessMask = 0,
|
||||
DstStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit,
|
||||
DstAccessMask = AccessFlags.AccessColorAttachmentWriteBit
|
||||
DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit
|
||||
};
|
||||
|
||||
var colorAttachmentReference = new AttachmentReference()
|
||||
@@ -498,14 +498,14 @@ unsafe class VulkanContent : IDisposable
|
||||
var vertShaderStageInfo = new PipelineShaderStageCreateInfo()
|
||||
{
|
||||
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||
Stage = ShaderStageFlags.ShaderStageVertexBit,
|
||||
Stage = ShaderStageFlags.VertexBit,
|
||||
Module = _vertShader,
|
||||
PName = (byte*)pname,
|
||||
};
|
||||
var fragShaderStageInfo = new PipelineShaderStageCreateInfo()
|
||||
{
|
||||
SType = StructureType.PipelineShaderStageCreateInfo,
|
||||
Stage = ShaderStageFlags.ShaderStageFragmentBit,
|
||||
Stage = ShaderStageFlags.FragmentBit,
|
||||
Module = _fragShader,
|
||||
PName = (byte*)pname,
|
||||
};
|
||||
@@ -564,7 +564,7 @@ unsafe class VulkanContent : IDisposable
|
||||
RasterizerDiscardEnable = false,
|
||||
PolygonMode = PolygonMode.Fill,
|
||||
LineWidth = 1,
|
||||
CullMode = CullModeFlags.CullModeNone,
|
||||
CullMode = CullModeFlags.None,
|
||||
DepthBiasEnable = false
|
||||
};
|
||||
|
||||
@@ -572,7 +572,7 @@ unsafe class VulkanContent : IDisposable
|
||||
{
|
||||
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||
SampleShadingEnable = false,
|
||||
RasterizationSamples = SampleCountFlags.SampleCount1Bit
|
||||
RasterizationSamples = SampleCountFlags.Count1Bit
|
||||
};
|
||||
|
||||
var depthStencilCreateInfo = new PipelineDepthStencilStateCreateInfo()
|
||||
@@ -587,10 +587,10 @@ unsafe class VulkanContent : IDisposable
|
||||
|
||||
var colorBlendAttachmentState = new PipelineColorBlendAttachmentState()
|
||||
{
|
||||
ColorWriteMask = ColorComponentFlags.ColorComponentABit |
|
||||
ColorComponentFlags.ColorComponentRBit |
|
||||
ColorComponentFlags.ColorComponentGBit |
|
||||
ColorComponentFlags.ColorComponentBBit,
|
||||
ColorWriteMask = ColorComponentFlags.ABit |
|
||||
ColorComponentFlags.RBit |
|
||||
ColorComponentFlags.GBit |
|
||||
ColorComponentFlags.BBit,
|
||||
BlendEnable = false
|
||||
};
|
||||
|
||||
@@ -617,14 +617,14 @@ unsafe class VulkanContent : IDisposable
|
||||
{
|
||||
Offset = 0,
|
||||
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
|
||||
StageFlags = ShaderStageFlags.ShaderStageVertexBit
|
||||
StageFlags = ShaderStageFlags.VertexBit
|
||||
};
|
||||
|
||||
var fragPushConstantRange = new PushConstantRange()
|
||||
{
|
||||
//Offset = vertexPushConstantRange.Size,
|
||||
Size = (uint)Marshal.SizeOf<VertextPushConstant>(),
|
||||
StageFlags = ShaderStageFlags.ShaderStageFragmentBit
|
||||
StageFlags = ShaderStageFlags.FragmentBit
|
||||
};
|
||||
|
||||
var layoutBindingInfo = new DescriptorSetLayoutBinding
|
||||
|
||||
@@ -86,12 +86,12 @@ public unsafe class VulkanContext : IDisposable
|
||||
var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT
|
||||
{
|
||||
SType = StructureType.DebugUtilsMessengerCreateInfoExt,
|
||||
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt,
|
||||
MessageType = DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt,
|
||||
MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.WarningBitExt |
|
||||
DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt,
|
||||
MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.ValidationBitExt |
|
||||
DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt,
|
||||
PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
using Silk.NET.Vulkan.Extensions.KHR;
|
||||
using SilkNetDemo;
|
||||
|
||||
namespace GpuInterop.VulkanDemo;
|
||||
|
||||
public class VulkanDemoControl : DrawingSurfaceDemoBase
|
||||
{
|
||||
private Instance _vkInstance;
|
||||
private Vk _api;
|
||||
|
||||
class VulkanResources : IAsyncDisposable
|
||||
{
|
||||
public VulkanContext Context { get; }
|
||||
|
||||
@@ -54,8 +54,8 @@ public unsafe class VulkanImage : IDisposable
|
||||
Size = size;
|
||||
MipLevels = 1;//mipLevels;
|
||||
_imageUsageFlags =
|
||||
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit |
|
||||
ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit;
|
||||
ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit |
|
||||
ImageUsageFlags.TransferSrcBit | ImageUsageFlags.SampledBit;
|
||||
|
||||
//MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
|
||||
|
||||
@@ -72,19 +72,19 @@ public unsafe class VulkanImage : IDisposable
|
||||
{
|
||||
PNext = exportable ? &externalMemoryCreateInfo : null,
|
||||
SType = StructureType.ImageCreateInfo,
|
||||
ImageType = ImageType.ImageType2D,
|
||||
ImageType = ImageType.Type2D,
|
||||
Format = Format,
|
||||
Extent =
|
||||
new Extent3D((uint?)Size.Width,
|
||||
(uint?)Size.Height, 1),
|
||||
MipLevels = MipLevels,
|
||||
ArrayLayers = 1,
|
||||
Samples = SampleCountFlags.SampleCount1Bit,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
Tiling = Tiling,
|
||||
Usage = _imageUsageFlags,
|
||||
SharingMode = SharingMode.Exclusive,
|
||||
InitialLayout = ImageLayout.Undefined,
|
||||
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
|
||||
Flags = ImageCreateFlags.CreateMutableFormatBit
|
||||
};
|
||||
|
||||
Api
|
||||
@@ -128,7 +128,7 @@ public unsafe class VulkanImage : IDisposable
|
||||
MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex(
|
||||
Api,
|
||||
_physicalDevice,
|
||||
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit)
|
||||
memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit)
|
||||
};
|
||||
|
||||
Api.AllocateMemory(_device, memoryAllocateInfo, null,
|
||||
@@ -146,7 +146,7 @@ public unsafe class VulkanImage : IDisposable
|
||||
ComponentSwizzle.Identity,
|
||||
ComponentSwizzle.Identity);
|
||||
|
||||
AspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||
AspectFlags = ImageAspectFlags.ColorBit;
|
||||
|
||||
var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1);
|
||||
|
||||
@@ -154,7 +154,7 @@ public unsafe class VulkanImage : IDisposable
|
||||
{
|
||||
SType = StructureType.ImageViewCreateInfo,
|
||||
Image = InternalHandle.Value,
|
||||
ViewType = ImageViewType.ImageViewType2D,
|
||||
ViewType = ImageViewType.Type2D,
|
||||
Format = Format,
|
||||
Components = componentMapping,
|
||||
SubresourceRange = subresourceRange
|
||||
@@ -168,7 +168,7 @@ public unsafe class VulkanImage : IDisposable
|
||||
|
||||
_currentLayout = ImageLayout.Undefined;
|
||||
|
||||
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
||||
TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.NoneKhr);
|
||||
}
|
||||
|
||||
public int ExportFd()
|
||||
|
||||
@@ -29,7 +29,7 @@ internal static class VulkanMemoryHelper
|
||||
AccessFlags destinationAccessMask,
|
||||
uint mipLevels)
|
||||
{
|
||||
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1);
|
||||
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, mipLevels, 0, 1);
|
||||
|
||||
var barrier = new ImageMemoryBarrier
|
||||
{
|
||||
@@ -46,8 +46,8 @@ internal static class VulkanMemoryHelper
|
||||
|
||||
api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.PipelineStageAllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
@@ -56,4 +56,4 @@ internal static class VulkanMemoryHelper
|
||||
1,
|
||||
barrier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
@@ -7,9 +6,7 @@ using Avalonia.Platform;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.Vulkan;
|
||||
using Metsys.Bson;
|
||||
using Silk.NET.Vulkan;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace GpuInterop.VulkanDemo;
|
||||
|
||||
@@ -84,7 +81,7 @@ class VulkanSwapchainImage : ISwapchainImage
|
||||
|
||||
_image.TransitionLayout(buffer.InternalHandle,
|
||||
ImageLayout.Undefined, AccessFlags.None,
|
||||
ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessColorAttachmentReadBit);
|
||||
ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentReadBit);
|
||||
|
||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
buffer.Submit(null,null,null, null, new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="IntegrationTestApp.App">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Light"/>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="Mobile Sandbox"
|
||||
x:Class="MobileSandbox.App">
|
||||
x:Class="MobileSandbox.App"
|
||||
RequestedThemeVariant="Dark">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark" />
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<Application xmlns="https://github.com/avaloniaui">
|
||||
<Application.Styles>
|
||||
<SimpleTheme Mode="Light" />
|
||||
<SimpleTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<Application xmlns="https://github.com/avaloniaui">
|
||||
<Application.Styles>
|
||||
<SimpleTheme Mode="Light" />
|
||||
<SimpleTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -26,6 +26,20 @@
|
||||
IsHitTestVisible="False" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Command="{Binding ToggleDrawLayoutTimeGraph}" Header="Draw layout time graph">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox BorderThickness="0"
|
||||
IsChecked="{Binding DrawLayoutTimeGraph}"
|
||||
IsHitTestVisible="False" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Command="{Binding ToggleDrawRenderTimeGraph}" Header="Draw render time graph">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox BorderThickness="0"
|
||||
IsChecked="{Binding DrawRenderTimeGraph}"
|
||||
IsHitTestVisible="False" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Tests">
|
||||
<MenuItem Command="{Binding ResizeWindow}" Header="Resize window" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Rendering;
|
||||
using RenderDemo.ViewModels;
|
||||
using MiniMvvm;
|
||||
|
||||
@@ -11,13 +13,26 @@ namespace RenderDemo
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
InitializeComponent();
|
||||
this.AttachDevTools();
|
||||
|
||||
var vm = new MainWindowViewModel();
|
||||
vm.WhenAnyValue(x => x.DrawDirtyRects).Subscribe(x => Renderer.DrawDirtyRects = x);
|
||||
vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => Renderer.DrawFps = x);
|
||||
this.DataContext = vm;
|
||||
|
||||
void BindOverlay(Expression<Func<MainWindowViewModel, bool>> expr, RendererDebugOverlays overlay)
|
||||
=> vm.WhenAnyValue(expr).Subscribe(x =>
|
||||
{
|
||||
var diagnostics = Renderer.Diagnostics;
|
||||
diagnostics.DebugOverlays = x ?
|
||||
diagnostics.DebugOverlays | overlay :
|
||||
diagnostics.DebugOverlays & ~overlay;
|
||||
});
|
||||
|
||||
BindOverlay(x => x.DrawDirtyRects, RendererDebugOverlays.DirtyRects);
|
||||
BindOverlay(x => x.DrawFps, RendererDebugOverlays.Fps);
|
||||
BindOverlay(x => x.DrawLayoutTimeGraph, RendererDebugOverlays.LayoutTimeGraph);
|
||||
BindOverlay(x => x.DrawRenderTimeGraph, RendererDebugOverlays.RenderTimeGraph);
|
||||
|
||||
DataContext = vm;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
||||
@@ -1,49 +1,66 @@
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using MiniMvvm;
|
||||
|
||||
namespace RenderDemo.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private bool drawDirtyRects = false;
|
||||
private bool drawFps = true;
|
||||
private double width = 800;
|
||||
private double height = 600;
|
||||
private bool _drawDirtyRects;
|
||||
private bool _drawFps = true;
|
||||
private bool _drawLayoutTimeGraph;
|
||||
private bool _drawRenderTimeGraph;
|
||||
private double _width = 800;
|
||||
private double _height = 600;
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
ToggleDrawDirtyRects = MiniCommand.Create(() => DrawDirtyRects = !DrawDirtyRects);
|
||||
ToggleDrawFps = MiniCommand.Create(() => DrawFps = !DrawFps);
|
||||
ToggleDrawLayoutTimeGraph = MiniCommand.Create(() => DrawLayoutTimeGraph = !DrawLayoutTimeGraph);
|
||||
ToggleDrawRenderTimeGraph = MiniCommand.Create(() => DrawRenderTimeGraph = !DrawRenderTimeGraph);
|
||||
ResizeWindow = MiniCommand.CreateFromTask(ResizeWindowAsync);
|
||||
}
|
||||
|
||||
public bool DrawDirtyRects
|
||||
{
|
||||
get => drawDirtyRects;
|
||||
set => this.RaiseAndSetIfChanged(ref drawDirtyRects, value);
|
||||
get => _drawDirtyRects;
|
||||
set => RaiseAndSetIfChanged(ref _drawDirtyRects, value);
|
||||
}
|
||||
|
||||
public bool DrawFps
|
||||
{
|
||||
get => drawFps;
|
||||
set => this.RaiseAndSetIfChanged(ref drawFps, value);
|
||||
get => _drawFps;
|
||||
set => RaiseAndSetIfChanged(ref _drawFps, value);
|
||||
}
|
||||
|
||||
public bool DrawLayoutTimeGraph
|
||||
{
|
||||
get => _drawLayoutTimeGraph;
|
||||
set => RaiseAndSetIfChanged(ref _drawLayoutTimeGraph, value);
|
||||
}
|
||||
|
||||
public bool DrawRenderTimeGraph
|
||||
{
|
||||
get => _drawRenderTimeGraph;
|
||||
set => RaiseAndSetIfChanged(ref _drawRenderTimeGraph, value);
|
||||
}
|
||||
|
||||
public double Width
|
||||
{
|
||||
get => width;
|
||||
set => this.RaiseAndSetIfChanged(ref width, value);
|
||||
get => _width;
|
||||
set => RaiseAndSetIfChanged(ref _width, value);
|
||||
}
|
||||
|
||||
public double Height
|
||||
{
|
||||
get => height;
|
||||
set => this.RaiseAndSetIfChanged(ref height, value);
|
||||
get => _height;
|
||||
set => RaiseAndSetIfChanged(ref _height, value);
|
||||
}
|
||||
|
||||
public MiniCommand ToggleDrawDirtyRects { get; }
|
||||
public MiniCommand ToggleDrawFps { get; }
|
||||
public MiniCommand ToggleDrawLayoutTimeGraph { get; }
|
||||
public MiniCommand ToggleDrawRenderTimeGraph { get; }
|
||||
public MiniCommand ResizeWindow { get; }
|
||||
|
||||
private async Task ResizeWindowAsync()
|
||||
|
||||
@@ -52,6 +52,14 @@ namespace ControlSamples
|
||||
var (oldBounds, newBounds) = change.GetOldAndNewValue<Rect>();
|
||||
EnsureSplitViewMode(oldBounds, newBounds);
|
||||
}
|
||||
|
||||
if (change.Property == SelectedItemProperty)
|
||||
{
|
||||
if (_splitView is not null && _splitView.DisplayMode == SplitViewDisplayMode.Overlay)
|
||||
{
|
||||
_splitView.SetValue(SplitView.IsPaneOpenProperty, false, Avalonia.Data.BindingPriority.Animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSplitViewMode(Rect oldBounds, Rect newBounds)
|
||||
@@ -60,12 +68,12 @@ namespace ControlSamples
|
||||
{
|
||||
var threshold = ExpandedModeThresholdWidth;
|
||||
|
||||
if (newBounds.Width >= threshold && oldBounds.Width < threshold)
|
||||
if (newBounds.Width >= threshold)
|
||||
{
|
||||
_splitView.DisplayMode = SplitViewDisplayMode.Inline;
|
||||
_splitView.IsPaneOpen = true;
|
||||
}
|
||||
else if (newBounds.Width < threshold && oldBounds.Width >= threshold)
|
||||
else if (newBounds.Width < threshold)
|
||||
{
|
||||
_splitView.DisplayMode = SplitViewDisplayMode.Overlay;
|
||||
_splitView.IsPaneOpen = false;
|
||||
|
||||
@@ -20,6 +20,21 @@
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Color x:Key="HamburgerBaseHighColor">#99FFFFFF</Color>
|
||||
<Color x:Key="HamburgerChromeMediumColor">#FF1F1F1F</Color>
|
||||
<Color x:Key="HamburgerAltHighColor">#FF000000</Color>
|
||||
<Color x:Key="HamburgerChromeLowColor">#FF171717</Color>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<Color x:Key="HamburgerBaseHighColor">#99000000</Color>
|
||||
<Color x:Key="HamburgerChromeMediumColor">#FFE6E6E6</Color>
|
||||
<Color x:Key="HamburgerAltHighColor">#FFFFFFFF</Color>
|
||||
<Color x:Key="HamburgerChromeLowColor">#FFF2F2F2</Color>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<x:Double x:Key="PaneCompactWidth">40</x:Double>
|
||||
<x:Double x:Key="PaneExpandWidth">220</x:Double>
|
||||
<x:Double x:Key="HeaderHeight">36</x:Double>
|
||||
@@ -36,7 +51,6 @@
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
|
||||
@@ -64,7 +78,7 @@
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
|
||||
<Setter Property="Border.Background" Value="{DynamicResource HamburgerChromeLowColor}" />
|
||||
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
|
||||
</Style>
|
||||
@@ -101,7 +115,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
|
||||
IsVisible="False"
|
||||
CornerRadius="{DynamicResource ControlCornerRadius}"/>
|
||||
CornerRadius="4"/>
|
||||
<ContentPresenter Name="PART_ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Margin="0"
|
||||
@@ -121,9 +135,9 @@
|
||||
<Setter Property="Background" Value="{DynamicResource ThemeControlHighlightMidBrush}"/>
|
||||
|
||||
<Style Selector="^ /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemChromeLowColor}" />
|
||||
<Setter Property="Background" Value="{DynamicResource HamburgerChromeLowColor}" />
|
||||
<Setter Property="BoxShadow" Value="{StaticResource NavigationItemShadow}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource HamburgerBaseHighColor}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
@@ -136,18 +150,18 @@
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:pressed /template/ Border#PART_LayoutRoot">
|
||||
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
|
||||
<Setter Property="Border.Background" Value="{DynamicResource HamburgerChromeLowColor}" />
|
||||
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource HamburgerBaseHighColor}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<!-- HamburgerMenu -->
|
||||
<ControlTheme x:Key="{x:Type catalog:HamburgerMenu}" TargetType="catalog:HamburgerMenu">
|
||||
<Setter Property="Padding" Value="12 8 4 0" />
|
||||
<Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" />
|
||||
<Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" />
|
||||
<Setter Property="PaneBackground" Value="{DynamicResource HamburgerChromeMediumColor}" />
|
||||
<Setter Property="Background" Value="{DynamicResource HamburgerChromeMediumColor}" />
|
||||
<Setter Property="ContentBackground" Value="{DynamicResource HamburgerAltHighColor}" />
|
||||
<Setter Property="ItemContainerTheme" Value="{StaticResource HamburgerMenuTabItem}"/>
|
||||
<Setter Property="TabStripPlacement" Value="Left" />
|
||||
<Setter Property="Template">
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Sandbox.App">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark"/>
|
||||
<FluentTheme />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -4,11 +4,11 @@ using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Android.Platform
|
||||
{
|
||||
internal class AndroidSystemNavigationManager : ISystemNavigationManager
|
||||
internal class AndroidSystemNavigationManagerImpl : ISystemNavigationManagerImpl
|
||||
{
|
||||
public event EventHandler<RoutedEventArgs> BackRequested;
|
||||
|
||||
public AndroidSystemNavigationManager(IActivityNavigationService? navigationService)
|
||||
public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService)
|
||||
{
|
||||
if(navigationService != null)
|
||||
{
|
||||
|
||||
@@ -31,9 +31,7 @@ using Android.Graphics.Drawables;
|
||||
|
||||
namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
{
|
||||
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
|
||||
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
|
||||
ITopLevelWithSystemNavigationManager
|
||||
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
|
||||
{
|
||||
private readonly IGlPlatformSurface _gl;
|
||||
private readonly IFramebufferPlatformSurface _framebuffer;
|
||||
@@ -41,6 +39,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
|
||||
private readonly AndroidMotionEventsHelper _pointerHelper;
|
||||
private readonly AndroidInputMethod<ViewImpl> _textInputMethod;
|
||||
private readonly INativeControlHostImpl _nativeControlHost;
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
|
||||
private ViewImpl _view;
|
||||
|
||||
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
|
||||
@@ -57,10 +58,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
|
||||
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
|
||||
|
||||
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
|
||||
StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
|
||||
_nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
|
||||
_storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
|
||||
|
||||
SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
|
||||
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
|
||||
}
|
||||
|
||||
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
|
||||
@@ -294,14 +295,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
|
||||
public double Scaling => RenderScaling;
|
||||
|
||||
public ITextInputMethodImpl TextInputMethod => _textInputMethod;
|
||||
|
||||
public INativeControlHostImpl NativeControlHost { get; }
|
||||
|
||||
public IStorageProvider StorageProvider { get; }
|
||||
|
||||
public ISystemNavigationManager SystemNavigationManager { get; }
|
||||
|
||||
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
|
||||
{
|
||||
if (TransparencyLevel != transparencyLevel)
|
||||
@@ -386,6 +379,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual object TryGetFeature(Type featureType)
|
||||
{
|
||||
if (featureType == typeof(IStorageProvider))
|
||||
{
|
||||
return _storageProvider;
|
||||
}
|
||||
|
||||
if (featureType == typeof(ITextInputMethodImpl))
|
||||
{
|
||||
return _textInputMethod;
|
||||
}
|
||||
|
||||
if (featureType == typeof(ISystemNavigationManagerImpl))
|
||||
{
|
||||
return _systemNavigationManager;
|
||||
}
|
||||
|
||||
if (featureType == typeof(INativeControlHostImpl))
|
||||
{
|
||||
return _nativeControlHost;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal class AvaloniaInputConnection : BaseInputConnection
|
||||
|
||||
@@ -14,11 +14,7 @@ namespace Avalonia.Collections
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
|
||||
public class AvaloniaDictionary<TKey, TValue> : IDictionary<TKey, TValue>,
|
||||
IDictionary,
|
||||
INotifyCollectionChanged,
|
||||
INotifyPropertyChanged
|
||||
where TKey : notnull
|
||||
public class AvaloniaDictionary<TKey, TValue> : IAvaloniaDictionary<TKey, TValue> where TKey : notnull
|
||||
{
|
||||
private Dictionary<TKey, TValue> _inner;
|
||||
|
||||
@@ -29,6 +25,14 @@ namespace Avalonia.Collections
|
||||
{
|
||||
_inner = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvaloniaDictionary{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
public AvaloniaDictionary(int capacity)
|
||||
{
|
||||
_inner = new Dictionary<TKey, TValue>(capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the collection changes.
|
||||
@@ -62,6 +66,10 @@ namespace Avalonia.Collections
|
||||
|
||||
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _inner.Keys;
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _inner.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the named resource.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using Avalonia.Reactive;
|
||||
|
||||
namespace Avalonia.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines extension methods for working with <see cref="AvaloniaList{T}"/>s.
|
||||
/// </summary>
|
||||
public static class AvaloniaDictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes an action for each item in a collection and subsequently each item added or
|
||||
/// removed from the collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The key type of the collection items.</typeparam>
|
||||
/// <typeparam name="TValue">The value type of the collection items.</typeparam>
|
||||
/// <param name="collection">The collection.</param>
|
||||
/// <param name="added">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <param name="removed">
|
||||
/// An action called for each item removed from the collection. The parameters passed are
|
||||
/// the index in the collection and the item.
|
||||
/// </param>
|
||||
/// <param name="reset">
|
||||
/// An action called when the collection is reset. This will be followed by calls to
|
||||
/// <paramref name="added"/> for each item present in the collection after the reset.
|
||||
/// </param>
|
||||
/// <param name="weakSubscription">
|
||||
/// Indicates if a weak subscription should be used to track changes to the collection.
|
||||
/// </param>
|
||||
/// <returns>A disposable used to terminate the subscription.</returns>
|
||||
internal static IDisposable ForEachItem<TKey, TValue>(
|
||||
this IAvaloniaReadOnlyDictionary<TKey, TValue> collection,
|
||||
Action<TKey, TValue> added,
|
||||
Action<TKey, TValue> removed,
|
||||
Action reset,
|
||||
bool weakSubscription = false)
|
||||
where TKey : notnull
|
||||
{
|
||||
void Add(IEnumerable items)
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> pair in items)
|
||||
{
|
||||
added(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void Remove(IEnumerable items)
|
||||
{
|
||||
foreach (KeyValuePair<TKey, TValue> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Avalonia.Collections
|
||||
{
|
||||
public interface IAvaloniaDictionary<TKey, TValue>
|
||||
: IDictionary<TKey, TValue>,
|
||||
IAvaloniaReadOnlyDictionary<TKey, TValue>,
|
||||
IDictionary
|
||||
where TKey : notnull
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Avalonia.Collections
|
||||
{
|
||||
public interface IAvaloniaReadOnlyDictionary<TKey, TValue>
|
||||
: IReadOnlyDictionary<TKey, TValue>,
|
||||
INotifyCollectionChanged,
|
||||
INotifyPropertyChanged
|
||||
where TKey : notnull
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
IList<IResourceProvider> MergedDictionaries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
|
||||
/// </summary>
|
||||
IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="key">The resource key.</param>
|
||||
/// <param name="theme">Theme used to select theme dictionary.</param>
|
||||
/// <param name="value">
|
||||
/// 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
|
||||
/// <returns>
|
||||
/// True if the resource if found, otherwise false.
|
||||
/// </returns>
|
||||
bool TryGetResource(object key, out object? value);
|
||||
bool TryGetResource(object key, ThemeVariant? theme, out object? value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<object, object?>? _inner;
|
||||
private IResourceHost? _owner;
|
||||
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
|
||||
private AvaloniaDictionary<ThemeVariant, IResourceProvider>? _themeDictionary;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
|
||||
@@ -69,14 +73,14 @@ namespace Avalonia.Controls
|
||||
_mergedDictionaries.ForEachItem(
|
||||
x =>
|
||||
{
|
||||
if (Owner is object)
|
||||
if (Owner is not null)
|
||||
{
|
||||
x.AddOwner(Owner);
|
||||
}
|
||||
},
|
||||
x =>
|
||||
{
|
||||
if (Owner is object)
|
||||
if (Owner is not null)
|
||||
{
|
||||
x.RemoveOwner(Owner);
|
||||
}
|
||||
@@ -88,6 +92,34 @@ namespace Avalonia.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_themeDictionary == null)
|
||||
{
|
||||
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IResourceProvider>(2);
|
||||
_themeDictionary.ForEachItem(
|
||||
(_, x) =>
|
||||
{
|
||||
if (Owner is not null)
|
||||
{
|
||||
x.AddOwner(Owner);
|
||||
}
|
||||
},
|
||||
(_, x) =>
|
||||
{
|
||||
if (Owner is not null)
|
||||
{
|
||||
x.RemoveOwner(Owner);
|
||||
}
|
||||
},
|
||||
() => throw new NotSupportedException("Dictionary reset not supported"));
|
||||
}
|
||||
return _themeDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
bool IResourceNode.HasResources
|
||||
{
|
||||
get
|
||||
@@ -152,16 +184,47 @@ namespace Avalonia.Controls
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetResource(object key, out object? value)
|
||||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
|
||||
{
|
||||
if (TryGetValue(key, out value))
|
||||
return true;
|
||||
|
||||
if (_themeDictionary is not null)
|
||||
{
|
||||
IResourceProvider? themeResourceProvider;
|
||||
if (theme is not null && theme != ThemeVariant.Default)
|
||||
{
|
||||
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)
|
||||
&& themeResourceProvider.TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var themeInherit = theme.InheritVariant;
|
||||
while (themeInherit is not null)
|
||||
{
|
||||
if (_themeDictionary.TryGetValue(themeInherit, out themeResourceProvider)
|
||||
&& themeResourceProvider.TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
themeInherit = themeInherit.InheritVariant;
|
||||
}
|
||||
}
|
||||
|
||||
if (_themeDictionary.TryGetValue(ThemeVariant.Default, out themeResourceProvider)
|
||||
&& themeResourceProvider.TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mergedDictionaries != null)
|
||||
{
|
||||
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (_mergedDictionaries[i].TryGetResource(key, out value))
|
||||
if (_mergedDictionaries[i].TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -248,7 +311,7 @@ namespace Avalonia.Controls
|
||||
|
||||
var hasResources = _inner?.Count > 0;
|
||||
|
||||
if (_mergedDictionaries is object)
|
||||
if (_mergedDictionaries is not null)
|
||||
{
|
||||
foreach (var i in _mergedDictionaries)
|
||||
{
|
||||
@@ -256,6 +319,14 @@ namespace Avalonia.Controls
|
||||
hasResources |= i.HasResources;
|
||||
}
|
||||
}
|
||||
if (_themeDictionary is not null)
|
||||
{
|
||||
foreach (var i in _themeDictionary.Values)
|
||||
{
|
||||
i.AddOwner(owner);
|
||||
hasResources |= i.HasResources;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasResources)
|
||||
{
|
||||
@@ -273,7 +344,7 @@ namespace Avalonia.Controls
|
||||
|
||||
var hasResources = _inner?.Count > 0;
|
||||
|
||||
if (_mergedDictionaries is object)
|
||||
if (_mergedDictionaries is not null)
|
||||
{
|
||||
foreach (var i in _mergedDictionaries)
|
||||
{
|
||||
@@ -281,6 +352,14 @@ namespace Avalonia.Controls
|
||||
hasResources |= i.HasResources;
|
||||
}
|
||||
}
|
||||
if (_themeDictionary is not null)
|
||||
{
|
||||
foreach (var i in _themeDictionary.Values)
|
||||
{
|
||||
i.RemoveOwner(owner);
|
||||
hasResources |= i.HasResources;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasResources)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Reactive;
|
||||
using Avalonia.Styling;
|
||||
|
||||
@@ -41,21 +39,66 @@ namespace Avalonia.Controls
|
||||
control = control ?? throw new ArgumentNullException(nameof(control));
|
||||
key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
|
||||
IResourceNode? current = control;
|
||||
return control.TryFindResource(key, null, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the specified resource by searching up the logical tree and then global styles.
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
/// <param name="theme">Theme used to select theme dictionary.</param>
|
||||
/// <param name="key">The resource key.</param>
|
||||
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
|
||||
public static object? FindResource(this IResourceHost control, ThemeVariant? theme, object key)
|
||||
{
|
||||
control = control ?? throw new ArgumentNullException(nameof(control));
|
||||
key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
|
||||
if (control.TryFindResource(key, theme, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return AvaloniaProperty.UnsetValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to the specified resource by searching up the logical tree and then global styles.
|
||||
/// </summary>
|
||||
/// <param name="control">The control.</param>
|
||||
/// <param name="key">The resource key.</param>
|
||||
/// <param name="theme">Theme used to select theme dictionary.</param>
|
||||
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
|
||||
/// <returns>True if the resource was found; otherwise false.</returns>
|
||||
public static bool TryFindResource(this IResourceHost control, object key, ThemeVariant? theme, out object? value)
|
||||
{
|
||||
control = control ?? throw new ArgumentNullException(nameof(control));
|
||||
key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
|
||||
IResourceHost? current = control;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (current.TryGetResource(key, out value))
|
||||
if (current.TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
current = (current as IStyleHost)?.StylingParent as IResourceNode;
|
||||
current = (current as IStyleHost)?.StylingParent as IResourceHost;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IResourceNode.TryGetResource" />
|
||||
public static bool TryGetResource(this IResourceHost control, object key, out object? value)
|
||||
{
|
||||
control = control ?? throw new ArgumentNullException(nameof(control));
|
||||
key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
|
||||
return control.TryGetResource(key, null, out value);
|
||||
}
|
||||
|
||||
public static IObservable<object?> GetResourceObservable(
|
||||
this IResourceHost control,
|
||||
@@ -95,24 +138,49 @@ namespace Avalonia.Controls
|
||||
protected override void Initialize()
|
||||
{
|
||||
_target.ResourcesChanged += ResourcesChanged;
|
||||
if (_target is StyledElement themeStyleable)
|
||||
{
|
||||
themeStyleable.PropertyChanged += PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Deinitialize()
|
||||
{
|
||||
_target.ResourcesChanged -= ResourcesChanged;
|
||||
if (_target is StyledElement themeStyleable)
|
||||
{
|
||||
themeStyleable.PropertyChanged -= PropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Subscribed(IObserver<object?> observer, bool first)
|
||||
{
|
||||
observer.OnNext(Convert(_target.FindResource(_key)));
|
||||
observer.OnNext(GetValue());
|
||||
}
|
||||
|
||||
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
|
||||
{
|
||||
PublishNext(Convert(_target.FindResource(_key)));
|
||||
PublishNext(GetValue());
|
||||
}
|
||||
|
||||
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
|
||||
private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == StyledElement.ActualThemeVariantProperty)
|
||||
{
|
||||
PublishNext(GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
private object? GetValue()
|
||||
{
|
||||
if (_target is not StyledElement themeStyleable
|
||||
|| !_target.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
|
||||
{
|
||||
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
|
||||
}
|
||||
|
||||
return _converter?.Invoke(value) ?? value;
|
||||
}
|
||||
}
|
||||
|
||||
private class FloatingResourceObservable : LightweightObservableBase<object?>
|
||||
@@ -134,7 +202,7 @@ namespace Avalonia.Controls
|
||||
_target.OwnerChanged += OwnerChanged;
|
||||
_owner = _target.Owner;
|
||||
|
||||
if (_owner is object)
|
||||
if (_owner is not null)
|
||||
{
|
||||
_owner.ResourcesChanged += ResourcesChanged;
|
||||
}
|
||||
@@ -148,43 +216,68 @@ namespace Avalonia.Controls
|
||||
|
||||
protected override void Subscribed(IObserver<object?> observer, bool first)
|
||||
{
|
||||
if (_target.Owner is object)
|
||||
if (_target.Owner is not null)
|
||||
{
|
||||
observer.OnNext(Convert(_target.Owner.FindResource(_key)));
|
||||
observer.OnNext(GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void PublishNext()
|
||||
{
|
||||
if (_target.Owner is object)
|
||||
if (_target.Owner is not null)
|
||||
{
|
||||
PublishNext(Convert(_target.Owner.FindResource(_key)));
|
||||
PublishNext(GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void OwnerChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_owner is object)
|
||||
if (_owner is not null)
|
||||
{
|
||||
_owner.ResourcesChanged -= ResourcesChanged;
|
||||
}
|
||||
if (_owner is StyledElement styleable)
|
||||
{
|
||||
styleable.PropertyChanged += PropertyChanged;
|
||||
}
|
||||
|
||||
_owner = _target.Owner;
|
||||
|
||||
if (_owner is object)
|
||||
if (_owner is not null)
|
||||
{
|
||||
_owner.ResourcesChanged += ResourcesChanged;
|
||||
}
|
||||
if (_owner is StyledElement styleable2)
|
||||
{
|
||||
styleable2.PropertyChanged += PropertyChanged;
|
||||
}
|
||||
|
||||
PublishNext();
|
||||
}
|
||||
|
||||
private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == StyledElement.ActualThemeVariantProperty)
|
||||
{
|
||||
PublishNext();
|
||||
}
|
||||
}
|
||||
|
||||
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
|
||||
{
|
||||
PublishNext();
|
||||
}
|
||||
|
||||
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
|
||||
private object? GetValue()
|
||||
{
|
||||
if (!(_target.Owner is StyledElement themeStyleable)
|
||||
|| !_target.Owner.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
|
||||
{
|
||||
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
|
||||
}
|
||||
|
||||
return _converter?.Invoke(value) ?? value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Logging;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -24,6 +25,7 @@ namespace Avalonia.Layout
|
||||
private bool _disposed;
|
||||
private bool _queued;
|
||||
private bool _running;
|
||||
private int _totalPassCount;
|
||||
|
||||
public LayoutManager(ILayoutRoot owner)
|
||||
{
|
||||
@@ -33,6 +35,8 @@ namespace Avalonia.Layout
|
||||
|
||||
public virtual event EventHandler? LayoutUpdated;
|
||||
|
||||
internal Action<LayoutPassTiming>? LayoutPassTimed { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void InvalidateMeasure(Layoutable control)
|
||||
{
|
||||
@@ -116,10 +120,9 @@ namespace Avalonia.Layout
|
||||
|
||||
if (!_running)
|
||||
{
|
||||
Stopwatch? stopwatch = null;
|
||||
|
||||
const LogEventLevel timingLogLevel = LogEventLevel.Information;
|
||||
bool captureTiming = Logger.IsEnabled(timingLogLevel, LogArea.Layout);
|
||||
var captureTiming = LayoutPassTimed is not null || Logger.IsEnabled(timingLogLevel, LogArea.Layout);
|
||||
var startingTimestamp = 0L;
|
||||
|
||||
if (captureTiming)
|
||||
{
|
||||
@@ -129,8 +132,7 @@ namespace Avalonia.Layout
|
||||
_toMeasure.Count,
|
||||
_toArrange.Count);
|
||||
|
||||
stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
startingTimestamp = Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
_toMeasure.BeginLoop(MaxPasses);
|
||||
@@ -139,6 +141,7 @@ namespace Avalonia.Layout
|
||||
try
|
||||
{
|
||||
_running = true;
|
||||
++_totalPassCount;
|
||||
|
||||
for (var pass = 0; pass < MaxPasses; ++pass)
|
||||
{
|
||||
@@ -160,9 +163,10 @@ namespace Avalonia.Layout
|
||||
|
||||
if (captureTiming)
|
||||
{
|
||||
stopwatch!.Stop();
|
||||
var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
|
||||
LayoutPassTimed?.Invoke(new LayoutPassTiming(_totalPassCount, elapsed));
|
||||
|
||||
Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(this, "Layout pass finished in {Time}", stopwatch.Elapsed);
|
||||
Logger.TryGet(timingLogLevel, LogArea.Layout)?.Log(this, "Layout pass finished in {Time}", elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ namespace Avalonia.Media
|
||||
/// </summary>
|
||||
public Point BaselineOrigin
|
||||
{
|
||||
get => _baselineOrigin ?? default;
|
||||
get => PlatformImpl.Item.BaselineOrigin;
|
||||
set => Set(ref _baselineOrigin, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
|
||||
public override void Justify(TextLine textLine)
|
||||
{
|
||||
var lineImpl = textLine as TextLineImpl;
|
||||
|
||||
if(lineImpl is null)
|
||||
if (textLine is not TextLineImpl lineImpl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -34,14 +32,9 @@ namespace Avalonia.Media.TextFormatting
|
||||
return;
|
||||
}
|
||||
|
||||
var textLineBreak = lineImpl.TextLineBreak;
|
||||
|
||||
if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
|
||||
if (lineImpl.TextLineBreak is { TextEndOfLine: not null, IsSplit: false })
|
||||
{
|
||||
if (textLineBreak.RemainingRuns is null || textLineBreak.RemainingRuns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var breakOportunities = new Queue<int>();
|
||||
@@ -107,7 +100,8 @@ namespace Avalonia.Media.TextFormatting
|
||||
var glyphIndex = glyphRun.FindGlyphIndex(characterIndex);
|
||||
var glyphInfo = shapedBuffer.GlyphInfos[glyphIndex];
|
||||
|
||||
shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex, glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
|
||||
shapedBuffer.GlyphInfos[glyphIndex] = new GlyphInfo(glyphInfo.GlyphIndex,
|
||||
glyphInfo.GlyphCluster, glyphInfo.GlyphAdvance + spacing);
|
||||
}
|
||||
|
||||
glyphRun.GlyphInfos = shapedBuffer.GlyphInfos;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia.Media.TextFormatting.Unicode;
|
||||
using Avalonia.Utilities;
|
||||
@@ -22,68 +21,55 @@ namespace Avalonia.Media.TextFormatting
|
||||
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
|
||||
TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null)
|
||||
{
|
||||
var textWrapping = paragraphProperties.TextWrapping;
|
||||
FlowDirection resolvedFlowDirection;
|
||||
TextLineBreak? nextLineBreak = null;
|
||||
IReadOnlyList<TextRun>? textRuns;
|
||||
var objectPool = FormattingObjectPool.Instance;
|
||||
var fontManager = FontManager.Current;
|
||||
|
||||
var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool,
|
||||
out var textEndOfLine, out var textSourceLength);
|
||||
// we've wrapped the previous line and need to continue wrapping: ignore the textSource and do that instead
|
||||
if (previousLineBreak is WrappingTextLineBreak wrappingTextLineBreak
|
||||
&& wrappingTextLineBreak.AcquireRemainingRuns() is { } remainingRuns
|
||||
&& paragraphProperties.TextWrapping != TextWrapping.NoWrap)
|
||||
{
|
||||
return PerformTextWrapping(remainingRuns, true, firstTextSourceIndex, paragraphWidth,
|
||||
paragraphProperties, previousLineBreak.FlowDirection, previousLineBreak, objectPool);
|
||||
}
|
||||
|
||||
RentedList<TextRun>? fetchedRuns = null;
|
||||
RentedList<TextRun>? shapedTextRuns = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (previousLineBreak?.RemainingRuns is { } remainingRuns)
|
||||
{
|
||||
resolvedFlowDirection = previousLineBreak.FlowDirection;
|
||||
textRuns = remainingRuns;
|
||||
nextLineBreak = previousLineBreak;
|
||||
shapedTextRuns = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
|
||||
out resolvedFlowDirection);
|
||||
textRuns = shapedTextRuns;
|
||||
fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine,
|
||||
out var textSourceLength);
|
||||
|
||||
if (nextLineBreak == null && textEndOfLine != null)
|
||||
{
|
||||
nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
||||
}
|
||||
shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager,
|
||||
out var resolvedFlowDirection);
|
||||
|
||||
if (nextLineBreak == null && textEndOfLine != null)
|
||||
{
|
||||
nextLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
||||
}
|
||||
|
||||
TextLineImpl textLine;
|
||||
|
||||
switch (textWrapping)
|
||||
switch (paragraphProperties.TextWrapping)
|
||||
{
|
||||
case TextWrapping.NoWrap:
|
||||
{
|
||||
// perf note: if textRuns comes from remainingRuns above, it's very likely coming from this class
|
||||
// which already uses an array: ToArray() won't ever be called in this case
|
||||
var textRunArray = textRuns as TextRun[] ?? textRuns.ToArray();
|
||||
|
||||
textLine = new TextLineImpl(textRunArray, firstTextSourceIndex, textSourceLength,
|
||||
var textLine = new TextLineImpl(shapedTextRuns.ToArray(), firstTextSourceIndex,
|
||||
textSourceLength,
|
||||
paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak);
|
||||
|
||||
textLine.FinalizeLine();
|
||||
textLine.FinalizeLine();
|
||||
|
||||
break;
|
||||
return textLine;
|
||||
}
|
||||
case TextWrapping.WrapWithOverflow:
|
||||
case TextWrapping.Wrap:
|
||||
{
|
||||
textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth,
|
||||
paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool, fontManager);
|
||||
break;
|
||||
return PerformTextWrapping(shapedTextRuns, false, firstTextSourceIndex, paragraphWidth,
|
||||
paragraphProperties, resolvedFlowDirection, nextLineBreak, objectPool);
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(textWrapping));
|
||||
throw new ArgumentOutOfRangeException(nameof(paragraphProperties.TextWrapping));
|
||||
}
|
||||
|
||||
return textLine;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -108,15 +94,16 @@ namespace Avalonia.Media.TextFormatting
|
||||
for (var i = 0; i < textRuns.Count; i++)
|
||||
{
|
||||
var currentRun = textRuns[i];
|
||||
var currentRunLength = currentRun.Length;
|
||||
|
||||
if (currentLength + currentRun.Length < length)
|
||||
if (currentLength + currentRunLength < length)
|
||||
{
|
||||
currentLength += currentRun.Length;
|
||||
currentLength += currentRunLength;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var firstCount = currentRun.Length >= 1 ? i + 1 : i;
|
||||
var firstCount = currentRunLength >= 1 ? i + 1 : i;
|
||||
|
||||
if (firstCount > 1)
|
||||
{
|
||||
@@ -128,13 +115,13 @@ namespace Avalonia.Media.TextFormatting
|
||||
|
||||
var secondCount = textRuns.Count - firstCount;
|
||||
|
||||
if (currentLength + currentRun.Length == length)
|
||||
if (currentLength + currentRunLength == length)
|
||||
{
|
||||
var second = secondCount > 0 ? objectPool.TextRunLists.Rent() : null;
|
||||
|
||||
if (second != null)
|
||||
{
|
||||
var offset = currentRun.Length >= 1 ? 1 : 0;
|
||||
var offset = currentRunLength >= 1 ? 1 : 0;
|
||||
|
||||
for (var j = 0; j < secondCount; j++)
|
||||
{
|
||||
@@ -249,49 +236,49 @@ namespace Avalonia.Media.TextFormatting
|
||||
switch (currentRun)
|
||||
{
|
||||
case UnshapedTextRun shapeableRun:
|
||||
{
|
||||
groupedRuns.Clear();
|
||||
groupedRuns.Add(shapeableRun);
|
||||
|
||||
var text = shapeableRun.Text;
|
||||
var properties = shapeableRun.Properties;
|
||||
|
||||
while (index + 1 < processedRuns.Count)
|
||||
{
|
||||
if (processedRuns[index + 1] is not UnshapedTextRun nextRun)
|
||||
groupedRuns.Clear();
|
||||
groupedRuns.Add(shapeableRun);
|
||||
|
||||
var text = shapeableRun.Text;
|
||||
var properties = shapeableRun.Properties;
|
||||
|
||||
while (index + 1 < processedRuns.Count)
|
||||
{
|
||||
if (processedRuns[index + 1] is not UnshapedTextRun nextRun)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (shapeableRun.BidiLevel == nextRun.BidiLevel
|
||||
&& TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
|
||||
&& CanShapeTogether(properties, nextRun.Properties))
|
||||
{
|
||||
groupedRuns.Add(nextRun);
|
||||
index++;
|
||||
shapeableRun = nextRun;
|
||||
text = joinedText;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (shapeableRun.BidiLevel == nextRun.BidiLevel
|
||||
&& TryJoinContiguousMemories(text, nextRun.Text, out var joinedText)
|
||||
&& CanShapeTogether(properties, nextRun.Properties))
|
||||
{
|
||||
groupedRuns.Add(nextRun);
|
||||
index++;
|
||||
shapeableRun = nextRun;
|
||||
text = joinedText;
|
||||
continue;
|
||||
}
|
||||
var shaperOptions = new TextShaperOptions(
|
||||
properties.CachedGlyphTypeface,
|
||||
properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
|
||||
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
|
||||
|
||||
ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var shaperOptions = new TextShaperOptions(
|
||||
properties.CachedGlyphTypeface,
|
||||
properties.FontRenderingEmSize, shapeableRun.BidiLevel, properties.CultureInfo,
|
||||
paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing);
|
||||
|
||||
ShapeTogether(groupedRuns, text, shaperOptions, textShaper, shapedRuns);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
shapedRuns.Add(currentRun);
|
||||
{
|
||||
shapedRuns.Add(currentRun);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,7 +640,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
/// </summary>
|
||||
/// <returns>The empty text line.</returns>
|
||||
public static TextLineImpl CreateEmptyTextLine(int firstTextSourceIndex, double paragraphWidth,
|
||||
TextParagraphProperties paragraphProperties, FontManager fontManager)
|
||||
TextParagraphProperties paragraphProperties)
|
||||
{
|
||||
var flowDirection = paragraphProperties.FlowDirection;
|
||||
var properties = paragraphProperties.DefaultTextRunProperties;
|
||||
@@ -675,21 +662,21 @@ namespace Avalonia.Media.TextFormatting
|
||||
/// Performs text wrapping returns a list of text lines.
|
||||
/// </summary>
|
||||
/// <param name="textRuns"></param>
|
||||
/// <param name="canReuseTextRunList">Whether <see cref="textRuns"/> can be reused to store the split runs.</param>
|
||||
/// <param name="firstTextSourceIndex">The first text source index.</param>
|
||||
/// <param name="paragraphWidth">The paragraph width.</param>
|
||||
/// <param name="paragraphProperties">The text paragraph properties.</param>
|
||||
/// <param name="resolvedFlowDirection"></param>
|
||||
/// <param name="currentLineBreak">The current line break if the line was explicitly broken.</param>
|
||||
/// <param name="objectPool">A pool used to get reusable formatting objects.</param>
|
||||
/// <param name="fontManager">The font manager to use.</param>
|
||||
/// <returns>The wrapped text line.</returns>
|
||||
private static TextLineImpl PerformTextWrapping(IReadOnlyList<TextRun> textRuns, int firstTextSourceIndex,
|
||||
double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection,
|
||||
TextLineBreak? currentLineBreak, FormattingObjectPool objectPool, FontManager fontManager)
|
||||
private static TextLineImpl PerformTextWrapping(List<TextRun> textRuns, bool canReuseTextRunList,
|
||||
int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties,
|
||||
FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak, FormattingObjectPool objectPool)
|
||||
{
|
||||
if (textRuns.Count == 0)
|
||||
{
|
||||
return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties, fontManager);
|
||||
return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties);
|
||||
}
|
||||
|
||||
if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength))
|
||||
@@ -712,7 +699,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
switch (currentRun)
|
||||
{
|
||||
case ShapedTextRun:
|
||||
{
|
||||
{
|
||||
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Span);
|
||||
|
||||
while (lineBreaker.MoveNext(out var lineBreak))
|
||||
@@ -754,7 +741,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
break;
|
||||
}
|
||||
|
||||
while (lineBreaker.MoveNext(out lineBreak) && index < textRuns.Count)
|
||||
while (lineBreaker.MoveNext(out lineBreak))
|
||||
{
|
||||
currentPosition += lineBreak.PositionWrap;
|
||||
|
||||
@@ -780,6 +767,11 @@ namespace Avalonia.Media.TextFormatting
|
||||
currentPosition = currentLength + lineBreak.PositionWrap;
|
||||
}
|
||||
|
||||
if (currentPosition == 0 && measuredLength > 0)
|
||||
{
|
||||
currentPosition = measuredLength;
|
||||
}
|
||||
|
||||
breakFound = true;
|
||||
|
||||
break;
|
||||
@@ -819,13 +811,37 @@ namespace Avalonia.Media.TextFormatting
|
||||
|
||||
try
|
||||
{
|
||||
var textLineBreak = postSplitRuns?.Count > 0 ?
|
||||
new TextLineBreak(null, resolvedFlowDirection, postSplitRuns.ToArray()) :
|
||||
null;
|
||||
|
||||
if (textLineBreak is null && currentLineBreak?.TextEndOfLine != null)
|
||||
TextLineBreak? textLineBreak;
|
||||
if (postSplitRuns?.Count > 0)
|
||||
{
|
||||
textLineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine, resolvedFlowDirection);
|
||||
List<TextRun> remainingRuns;
|
||||
|
||||
// reuse the list as much as possible:
|
||||
// if canReuseTextRunList == true it's coming from previous remaining runs
|
||||
if (canReuseTextRunList)
|
||||
{
|
||||
remainingRuns = textRuns;
|
||||
remainingRuns.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingRuns = new List<TextRun>();
|
||||
}
|
||||
|
||||
for (var i = 0; i < postSplitRuns.Count; ++i)
|
||||
{
|
||||
remainingRuns.Add(postSplitRuns[i]);
|
||||
}
|
||||
|
||||
textLineBreak = new WrappingTextLineBreak(null, resolvedFlowDirection, remainingRuns);
|
||||
}
|
||||
else if (currentLineBreak?.TextEndOfLine is { } textEndOfLine)
|
||||
{
|
||||
textLineBreak = new TextLineBreak(textEndOfLine, resolvedFlowDirection);
|
||||
}
|
||||
else
|
||||
{
|
||||
textLineBreak = null;
|
||||
}
|
||||
|
||||
var textLine = new TextLineImpl(preSplitRuns.ToArray(), firstTextSourceIndex, measuredLength,
|
||||
@@ -833,6 +849,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
textLineBreak);
|
||||
|
||||
textLine.FinalizeLine();
|
||||
|
||||
return textLine;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -416,9 +416,11 @@ namespace Avalonia.Media.TextFormatting
|
||||
width = lineWidth;
|
||||
}
|
||||
|
||||
if (left > textLine.Start)
|
||||
var start = textLine.Start;
|
||||
|
||||
if (left > start)
|
||||
{
|
||||
left = textLine.Start;
|
||||
left = start;
|
||||
}
|
||||
|
||||
height += textLine.Height;
|
||||
@@ -427,12 +429,10 @@ namespace Avalonia.Media.TextFormatting
|
||||
private TextLine[] CreateTextLines()
|
||||
{
|
||||
var objectPool = FormattingObjectPool.Instance;
|
||||
var fontManager = FontManager.Current;
|
||||
|
||||
if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight))
|
||||
{
|
||||
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties,
|
||||
fontManager);
|
||||
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties);
|
||||
|
||||
Bounds = new Rect(0, 0, 0, textLine.Height);
|
||||
|
||||
@@ -461,7 +461,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
if (previousLine != null && previousLine.NewLineLength > 0)
|
||||
{
|
||||
var emptyTextLine = TextFormatterImpl.CreateEmptyTextLine(_textSourceLength, MaxWidth,
|
||||
_paragraphProperties, fontManager);
|
||||
_paragraphProperties);
|
||||
|
||||
textLines.Add(emptyTextLine);
|
||||
|
||||
@@ -504,7 +504,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
//Fulfill max lines constraint
|
||||
if (MaxLines > 0 && textLines.Count >= MaxLines)
|
||||
{
|
||||
if (textLine.TextLineBreak?.RemainingRuns is not null)
|
||||
if (textLine.TextLineBreak is { IsSplit: true })
|
||||
{
|
||||
textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width));
|
||||
}
|
||||
@@ -521,8 +521,7 @@ namespace Avalonia.Media.TextFormatting
|
||||
//Make sure the TextLayout always contains at least on empty line
|
||||
if (textLines.Count == 0)
|
||||
{
|
||||
var textLine =
|
||||
TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties, fontManager);
|
||||
var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties);
|
||||
|
||||
textLines.Add(textLine);
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Avalonia.Media.TextFormatting
|
||||
namespace Avalonia.Media.TextFormatting
|
||||
{
|
||||
public class TextLineBreak
|
||||
{
|
||||
public TextLineBreak(TextEndOfLine? textEndOfLine = null, FlowDirection flowDirection = FlowDirection.LeftToRight,
|
||||
IReadOnlyList<TextRun>? remainingRuns = null)
|
||||
public TextLineBreak(TextEndOfLine? textEndOfLine = null,
|
||||
FlowDirection flowDirection = FlowDirection.LeftToRight, bool isSplit = false)
|
||||
{
|
||||
TextEndOfLine = textEndOfLine;
|
||||
FlowDirection = flowDirection;
|
||||
RemainingRuns = remainingRuns;
|
||||
IsSplit = isSplit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -23,8 +21,9 @@ namespace Avalonia.Media.TextFormatting
|
||||
public FlowDirection FlowDirection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the remaining runs that were split up by the <see cref="TextFormatter"/> during the formatting process.
|
||||
/// Gets whether there were remaining runs after this line break,
|
||||
/// that were split up by the <see cref="TextFormatter"/> during the formatting process.
|
||||
/// </summary>
|
||||
public IReadOnlyList<TextRun>? RemainingRuns { get; }
|
||||
public bool IsSplit { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,9 +172,21 @@ namespace Avalonia.Media.TextFormatting
|
||||
|
||||
distance -= Start;
|
||||
|
||||
var firstRunIndex = 0;
|
||||
|
||||
if (_textRuns[firstRunIndex] is TextEndOfLine)
|
||||
{
|
||||
firstRunIndex++;
|
||||
}
|
||||
|
||||
if(firstRunIndex >= _textRuns.Length)
|
||||
{
|
||||
return new CharacterHit(FirstTextSourceIndex);
|
||||
}
|
||||
|
||||
if (distance <= 0)
|
||||
{
|
||||
var firstRun = _textRuns[0];
|
||||
var firstRun = _textRuns[firstRunIndex];
|
||||
|
||||
return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
|
||||
}
|
||||
@@ -1285,13 +1297,11 @@ namespace Avalonia.Media.TextFormatting
|
||||
{
|
||||
case ShapedTextRun textRun:
|
||||
{
|
||||
var properties = textRun.Properties;
|
||||
var textMetrics =
|
||||
new TextMetrics(properties.CachedGlyphTypeface, properties.FontRenderingEmSize);
|
||||
var textMetrics = textRun.TextMetrics;
|
||||
|
||||
if (fontRenderingEmSize < properties.FontRenderingEmSize)
|
||||
if (fontRenderingEmSize < textMetrics.FontRenderingEmSize)
|
||||
{
|
||||
fontRenderingEmSize = properties.FontRenderingEmSize;
|
||||
fontRenderingEmSize = textMetrics.FontRenderingEmSize;
|
||||
|
||||
if (ascent > textMetrics.Ascent)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Avalonia.Media.TextFormatting
|
||||
{
|
||||
/// <summary>Represents a line break that occurred due to wrapping.</summary>
|
||||
internal sealed class WrappingTextLineBreak : TextLineBreak
|
||||
{
|
||||
private List<TextRun>? _remainingRuns;
|
||||
|
||||
public WrappingTextLineBreak(TextEndOfLine? textEndOfLine, FlowDirection flowDirection,
|
||||
List<TextRun> remainingRuns)
|
||||
: base(textEndOfLine, flowDirection, isSplit: true)
|
||||
{
|
||||
Debug.Assert(remainingRuns.Count > 0);
|
||||
_remainingRuns = remainingRuns;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remaining runs from this line break, and clears them from this line break.
|
||||
/// </summary>
|
||||
/// <returns>A list of text runs.</returns>
|
||||
public List<TextRun>? AcquireRemainingRuns()
|
||||
{
|
||||
var remainingRuns = _remainingRuns;
|
||||
_remainingRuns = null;
|
||||
return remainingRuns;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ using System;
|
||||
namespace Avalonia.Metadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the ambient class/property
|
||||
/// Defines the ambient class/property
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = true)]
|
||||
public class AmbientAttribute : Attribute
|
||||
public sealed class AmbientAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Avalonia.Metadata
|
||||
/// Defines the property that contains the object's content in markup.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ContentAttribute : Attribute
|
||||
public sealed class ContentAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Avalonia.Metadata;
|
||||
/// Used on DataTemplate.DataType property so it can be inherited in compiled bindings inside of the template.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class DataTypeAttribute : Attribute
|
||||
public sealed class DataTypeAttribute : Attribute
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Avalonia.Metadata
|
||||
/// Indicates that the property depends on the value of another property in markup.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
|
||||
public class DependsOnAttribute : Attribute
|
||||
public sealed class DependsOnAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DependsOnAttribute"/> class.
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Avalonia.Metadata
|
||||
/// may be added to its API.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Interface)]
|
||||
public class NotClientImplementableAttribute : Attribute
|
||||
public sealed class NotClientImplementableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Avalonia.Metadata
|
||||
/// Defines the property that contains the object's content in markup.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class TemplateContentAttribute : Attribute
|
||||
public sealed class TemplateContentAttribute : Attribute
|
||||
{
|
||||
public Type? TemplateResultType { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Avalonia.Metadata
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class TrimSurroundingWhitespaceAttribute : Attribute
|
||||
public sealed class TrimSurroundingWhitespaceAttribute : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Avalonia.Metadata
|
||||
/// This API is unstable and is not covered by API compatibility guarantees between minor and
|
||||
/// patch releases.
|
||||
/// </summary>
|
||||
public class UnstableAttribute : Attribute
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class UnstableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System;
|
||||
namespace Avalonia.Metadata
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class UsableDuringInitializationAttribute : Attribute
|
||||
public sealed class UsableDuringInitializationAttribute : Attribute
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Avalonia.Metadata
|
||||
/// Indicates that a collection type should be processed as being whitespace significant by a XAML processor.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public class WhitespaceSignificantCollectionAttribute : Attribute
|
||||
public sealed class WhitespaceSignificantCollectionAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Avalonia.Metadata
|
||||
/// Maps an XML namespace to a CLR namespace for use in XAML.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public class XmlnsDefinitionAttribute : Attribute
|
||||
public sealed class XmlnsDefinitionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="XmlnsDefinitionAttribute"/> class.
|
||||
|
||||
@@ -23,5 +23,4 @@ public static class OptionalFeatureProviderExtensions
|
||||
rv = provider.TryGetFeature<T>();
|
||||
return rv != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia.Platform;
|
||||
|
||||
public struct PlatformGraphicsExternalImageProperties
|
||||
public record struct PlatformGraphicsExternalImageProperties
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
+1
-7
@@ -5,13 +5,7 @@ using Avalonia.Metadata;
|
||||
namespace Avalonia.Platform
|
||||
{
|
||||
[Unstable]
|
||||
public interface ITopLevelWithSystemNavigationManager
|
||||
{
|
||||
ISystemNavigationManager SystemNavigationManager { get; }
|
||||
}
|
||||
|
||||
[Unstable]
|
||||
public interface ISystemNavigationManager
|
||||
public interface ISystemNavigationManagerImpl
|
||||
{
|
||||
public event EventHandler<RoutedEventArgs>? BackRequested;
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Avalonia.Reactive;
|
||||
|
||||
internal class AnonymousObserver<T> : IObserver<T>
|
||||
/// <summary>
|
||||
/// Class to create an <see cref="IObserver{T}"/> instance from delegate-based implementations of the On* methods.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
|
||||
public class AnonymousObserver<T> : IObserver<T>
|
||||
{
|
||||
private static readonly Action<Exception> ThrowsOnError = ex => throw ex;
|
||||
private static readonly Action NoOpCompleted = () => { };
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Avalonia.Reactive
|
||||
/// usage. This class provides a more lightweight base for some internal observable types
|
||||
/// in the Avalonia framework.
|
||||
/// </remarks>
|
||||
public abstract class LightweightObservableBase<T> : IObservable<T>
|
||||
internal abstract class LightweightObservableBase<T> : IObservable<T>
|
||||
{
|
||||
private Exception? _error;
|
||||
private List<IObserver<T>>? _observers = new List<IObserver<T>>();
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Collections.Pooled;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Rendering.Composition.Drawing;
|
||||
using Avalonia.Rendering.Composition.Server;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
|
||||
@@ -38,6 +35,9 @@ public class CompositingRenderer : IRendererWithCompositor
|
||||
/// </summary>
|
||||
public bool RenderOnlyOnRenderThread { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public RendererDiagnostics Diagnostics { get; }
|
||||
|
||||
public CompositingRenderer(IRenderRoot root, Compositor compositor, Func<IEnumerable<object>> surfaces)
|
||||
{
|
||||
_root = root;
|
||||
@@ -46,20 +46,21 @@ public class CompositingRenderer : IRendererWithCompositor
|
||||
CompositionTarget = compositor.CreateCompositionTarget(surfaces);
|
||||
CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
|
||||
_update = Update;
|
||||
Diagnostics = new RendererDiagnostics();
|
||||
Diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawFps
|
||||
private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
get => CompositionTarget.DrawFps;
|
||||
set => CompositionTarget.DrawFps = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool DrawDirtyRects
|
||||
{
|
||||
get => CompositionTarget.DrawDirtyRects;
|
||||
set => CompositionTarget.DrawDirtyRects = value;
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(RendererDiagnostics.DebugOverlays):
|
||||
CompositionTarget.DebugOverlays = Diagnostics.DebugOverlays;
|
||||
break;
|
||||
case nameof(RendererDiagnostics.LastLayoutPassTiming):
|
||||
CompositionTarget.LastLayoutPassTiming = Diagnostics.LastLayoutPassTiming;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.Composition.Animations;
|
||||
using Avalonia.Rendering.Composition.Server;
|
||||
|
||||
@@ -15,7 +14,7 @@ public partial class Compositor
|
||||
/// <returns></returns>
|
||||
public CompositionTarget CreateCompositionTarget(Func<IEnumerable<object>> surfaces)
|
||||
{
|
||||
return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces));
|
||||
return new CompositionTarget(this, new ServerCompositionTarget(_server, surfaces, DiagnosticTextRenderer));
|
||||
}
|
||||
|
||||
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server));
|
||||
|
||||
@@ -35,11 +35,14 @@ namespace Avalonia.Rendering.Composition
|
||||
private Task? _pendingBatch;
|
||||
private readonly object _pendingBatchLock = new();
|
||||
private List<Action> _pendingServerCompositorJobs = new();
|
||||
private DiagnosticTextRenderer? _diagnosticTextRenderer;
|
||||
|
||||
internal IEasing DefaultEasing { get; }
|
||||
|
||||
private DiagnosticTextRenderer DiagnosticTextRenderer
|
||||
=> _diagnosticTextRenderer ??= new(Typeface.Default.GlyphTypeface, 12.0);
|
||||
|
||||
internal event Action? AfterCommit;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new compositor on a specified render loop that would use a particular GPU
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace Avalonia.Rendering.Composition.Expressions
|
||||
}
|
||||
}
|
||||
|
||||
internal class PrettyPrintStringAttribute : Attribute
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal sealed class PrettyPrintStringAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Rendering.Composition.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// A class used to render diagnostic strings (only!), with caching of ASCII glyph runs.
|
||||
/// </summary>
|
||||
internal sealed class DiagnosticTextRenderer
|
||||
{
|
||||
private const char FirstChar = (char)32;
|
||||
private const char LastChar = (char)126;
|
||||
|
||||
private readonly GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
|
||||
|
||||
public double GetMaxHeight()
|
||||
{
|
||||
var maxHeight = 0.0;
|
||||
|
||||
for (var c = FirstChar; c <= LastChar; c++)
|
||||
{
|
||||
var height = _runs[c - FirstChar].Size.Height;
|
||||
if (height > maxHeight)
|
||||
{
|
||||
maxHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
public DiagnosticTextRenderer(IGlyphTypeface typeface, double fontRenderingEmSize)
|
||||
{
|
||||
var chars = new char[LastChar - FirstChar + 1];
|
||||
for (var c = FirstChar; c <= LastChar; c++)
|
||||
{
|
||||
var index = c - FirstChar;
|
||||
chars[index] = c;
|
||||
var glyph = typeface.GetGlyph(c);
|
||||
_runs[index] = new GlyphRun(typeface, fontRenderingEmSize, chars.AsMemory(index, 1), new[] { glyph });
|
||||
}
|
||||
}
|
||||
|
||||
public Size MeasureAsciiText(ReadOnlySpan<char> text)
|
||||
{
|
||||
var width = 0.0;
|
||||
var height = 0.0;
|
||||
|
||||
foreach (var c in text)
|
||||
{
|
||||
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
|
||||
var run = _runs[effectiveChar - FirstChar];
|
||||
width += run.Size.Width;
|
||||
height = Math.Max(height, run.Size.Height);
|
||||
}
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
public void DrawAsciiText(IDrawingContextImpl context, ReadOnlySpan<char> text, IBrush foreground)
|
||||
{
|
||||
var offset = 0.0;
|
||||
var originalTransform = context.Transform;
|
||||
|
||||
foreach (var c in text)
|
||||
{
|
||||
var effectiveChar = c is >= FirstChar and <= LastChar ? c : ' ';
|
||||
var run = _runs[effectiveChar - FirstChar];
|
||||
context.Transform = originalTransform * Matrix.CreateTranslation(offset, 0.0);
|
||||
context.DrawGlyphRun(foreground, run.PlatformImpl);
|
||||
offset += run.Size.Width;
|
||||
}
|
||||
|
||||
context.Transform = originalTransform;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.TextFormatting;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Utilities;
|
||||
|
||||
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
|
||||
|
||||
@@ -17,26 +14,18 @@ namespace Avalonia.Rendering.Composition.Server;
|
||||
internal class FpsCounter
|
||||
{
|
||||
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
|
||||
private readonly DiagnosticTextRenderer _textRenderer;
|
||||
|
||||
private int _framesThisSecond;
|
||||
private int _totalFrames;
|
||||
private int _fps;
|
||||
private TimeSpan _lastFpsUpdate;
|
||||
const int FirstChar = 32;
|
||||
const int LastChar = 126;
|
||||
// ASCII chars
|
||||
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
|
||||
|
||||
public FpsCounter(IGlyphTypeface typeface)
|
||||
{
|
||||
for (var c = FirstChar; c <= LastChar; c++)
|
||||
{
|
||||
var s = new string((char)c, 1);
|
||||
var glyph = typeface.GetGlyph((uint)(s[0]));
|
||||
_runs[c - FirstChar] = new GlyphRun(typeface, 18, s.AsMemory(), new ushort[] { glyph });
|
||||
}
|
||||
}
|
||||
|
||||
public void FpsTick() => _framesThisSecond++;
|
||||
public FpsCounter(DiagnosticTextRenderer textRenderer)
|
||||
=> _textRenderer = textRenderer;
|
||||
|
||||
public void FpsTick()
|
||||
=> _framesThisSecond++;
|
||||
|
||||
public void RenderFps(IDrawingContextImpl context, string aux)
|
||||
{
|
||||
@@ -53,27 +42,24 @@ internal class FpsCounter
|
||||
_lastFpsUpdate = now;
|
||||
}
|
||||
|
||||
var fpsLine = FormattableString.Invariant($"Frame #{_totalFrames:00000000} FPS: {_fps:000} ") + aux;
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
foreach (var ch in fpsLine)
|
||||
{
|
||||
var run = _runs[ch - FirstChar];
|
||||
width += run.Size.Width;
|
||||
height = Math.Max(height, run.Size.Height);
|
||||
}
|
||||
#if NET6_0_OR_GREATER
|
||||
var fpsLine = string.Create(CultureInfo.InvariantCulture, $"Frame #{_totalFrames:00000000} FPS: {_fps:000} {aux}");
|
||||
#else
|
||||
var fpsLine = FormattableString.Invariant($"Frame #{_totalFrames:00000000} FPS: {_fps:000} {aux}");
|
||||
#endif
|
||||
|
||||
var rect = new Rect(0, 0, width + 3, height + 3);
|
||||
var size = _textRenderer.MeasureAsciiText(fpsLine.AsSpan());
|
||||
var rect = new Rect(0.0, 0.0, size.Width + 3.0, size.Height + 3.0);
|
||||
|
||||
context.DrawRectangle(Brushes.Black, null, rect);
|
||||
|
||||
double offset = 0;
|
||||
foreach (var ch in fpsLine)
|
||||
{
|
||||
var run = _runs[ch - FirstChar];
|
||||
context.Transform = Matrix.CreateTranslation(offset, 0);
|
||||
context.DrawGlyphRun(Brushes.White, run.PlatformImpl);
|
||||
offset += run.Size.Width;
|
||||
}
|
||||
_textRenderer.DrawAsciiText(context, fpsLine.AsSpan(), Brushes.White);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_framesThisSecond = 0;
|
||||
_totalFrames = 0;
|
||||
_fps = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Immutable;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Rendering.Composition.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a simple time graph for diagnostics purpose, used to show layout and render times.
|
||||
/// </summary>
|
||||
internal sealed class FrameTimeGraph
|
||||
{
|
||||
private const double HeaderPadding = 2.0;
|
||||
|
||||
private readonly IPlatformRenderInterface _renderInterface;
|
||||
private readonly ImmutableSolidColorBrush _borderBrush;
|
||||
private readonly ImmutablePen _graphPen;
|
||||
private readonly double[] _frameValues;
|
||||
private readonly Size _size;
|
||||
private readonly Size _headerSize;
|
||||
private readonly Size _graphSize;
|
||||
private readonly double _defaultMaxY;
|
||||
private readonly string _title;
|
||||
private readonly DiagnosticTextRenderer _textRenderer;
|
||||
|
||||
private int _startFrameIndex;
|
||||
private int _frameCount;
|
||||
|
||||
public Size Size
|
||||
=> _size;
|
||||
|
||||
public FrameTimeGraph(int maxFrames, Size size, double defaultMaxY, string title,
|
||||
DiagnosticTextRenderer textRenderer)
|
||||
{
|
||||
Debug.Assert(maxFrames >= 1);
|
||||
Debug.Assert(size.Width > 0.0);
|
||||
Debug.Assert(size.Height > 0.0);
|
||||
|
||||
_renderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
|
||||
_borderBrush = new ImmutableSolidColorBrush(0x80808080);
|
||||
_graphPen = new ImmutablePen(Brushes.Blue);
|
||||
_frameValues = new double[maxFrames];
|
||||
_size = size;
|
||||
_headerSize = new Size(size.Width, textRenderer.GetMaxHeight() + HeaderPadding * 2.0);
|
||||
_graphSize = new Size(size.Width, size.Height - _headerSize.Height);
|
||||
_defaultMaxY = defaultMaxY;
|
||||
_title = title;
|
||||
_textRenderer = textRenderer;
|
||||
}
|
||||
|
||||
public void AddFrameValue(double value)
|
||||
{
|
||||
if (_frameCount < _frameValues.Length)
|
||||
{
|
||||
_frameValues[_startFrameIndex + _frameCount] = value;
|
||||
++_frameCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
// overwrite oldest value
|
||||
_frameValues[_startFrameIndex] = value;
|
||||
if (++_startFrameIndex == _frameValues.Length)
|
||||
{
|
||||
_startFrameIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_startFrameIndex = 0;
|
||||
_frameCount = 0;
|
||||
}
|
||||
|
||||
public void Render(IDrawingContextImpl context)
|
||||
{
|
||||
var originalTransform = context.Transform;
|
||||
context.PushClip(new Rect(_size));
|
||||
|
||||
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_size)));
|
||||
context.DrawRectangle(_borderBrush, null, new RoundedRect(new Rect(_headerSize)));
|
||||
|
||||
context.Transform = originalTransform * Matrix.CreateTranslation(HeaderPadding, HeaderPadding);
|
||||
_textRenderer.DrawAsciiText(context, _title.AsSpan(), Brushes.Black);
|
||||
|
||||
if (_frameCount > 0)
|
||||
{
|
||||
var (min, avg, max) = GetYValues();
|
||||
|
||||
DrawLabelledValue(context, "Min", min, originalTransform, _headerSize.Width * 0.19);
|
||||
DrawLabelledValue(context, "Avg", avg, originalTransform, _headerSize.Width * 0.46);
|
||||
DrawLabelledValue(context, "Max", max, originalTransform, _headerSize.Width * 0.73);
|
||||
|
||||
context.Transform = originalTransform * Matrix.CreateTranslation(0.0, _headerSize.Height);
|
||||
context.DrawGeometry(null, _graphPen, BuildGraphGeometry(Math.Max(max, _defaultMaxY)));
|
||||
}
|
||||
|
||||
context.Transform = originalTransform;
|
||||
context.PopClip();
|
||||
}
|
||||
|
||||
private void DrawLabelledValue(IDrawingContextImpl context, string label, double value, in Matrix originalTransform,
|
||||
double left)
|
||||
{
|
||||
context.Transform = originalTransform * Matrix.CreateTranslation(left + HeaderPadding, HeaderPadding);
|
||||
|
||||
var brush = value <= _defaultMaxY ? Brushes.Black : Brushes.Red;
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
Span<char> buffer = stackalloc char[24];
|
||||
buffer.TryWrite(CultureInfo.InvariantCulture, $"{label}: {value,5:F2}ms", out var charsWritten);
|
||||
_textRenderer.DrawAsciiText(context, buffer.Slice(0, charsWritten), brush);
|
||||
#else
|
||||
var text = FormattableString.Invariant($"{label}: {value,5:F2}ms");
|
||||
_textRenderer.DrawAsciiText(context, text.AsSpan(), brush);
|
||||
#endif
|
||||
}
|
||||
|
||||
private IStreamGeometryImpl BuildGraphGeometry(double maxY)
|
||||
{
|
||||
Debug.Assert(_frameCount > 0);
|
||||
|
||||
var graphGeometry = _renderInterface.CreateStreamGeometry();
|
||||
using var geometryContext = graphGeometry.Open();
|
||||
|
||||
var xRatio = _graphSize.Width / _frameValues.Length;
|
||||
var yRatio = _graphSize.Height / maxY;
|
||||
|
||||
geometryContext.BeginFigure(new Point(0.0, _graphSize.Height - GetFrameValue(0) * yRatio), false);
|
||||
|
||||
for (var i = 1; i < _frameCount; ++i)
|
||||
{
|
||||
var x = Math.Round(i * xRatio);
|
||||
var y = _graphSize.Height - GetFrameValue(i) * yRatio;
|
||||
geometryContext.LineTo(new Point(x, y));
|
||||
}
|
||||
|
||||
geometryContext.EndFigure(false);
|
||||
return graphGeometry;
|
||||
}
|
||||
|
||||
private (double Min, double Average, double Max) GetYValues()
|
||||
{
|
||||
Debug.Assert(_frameCount > 0);
|
||||
|
||||
var min = double.MaxValue;
|
||||
var max = double.MinValue;
|
||||
var total = 0.0;
|
||||
|
||||
for (var i = 0; i < _frameCount; ++i)
|
||||
{
|
||||
var y = GetFrameValue(i);
|
||||
|
||||
total += y;
|
||||
|
||||
if (y < min)
|
||||
{
|
||||
min = y;
|
||||
}
|
||||
|
||||
if (y > max)
|
||||
{
|
||||
max = y;
|
||||
}
|
||||
}
|
||||
|
||||
return (min, total / _frameCount, max);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private double GetFrameValue(int frameOffset)
|
||||
=> _frameValues[(_startFrameIndex + frameOffset) % _frameValues.Length];
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
@@ -21,11 +21,12 @@ namespace Avalonia.Rendering.Composition.Server
|
||||
{
|
||||
private readonly ServerCompositor _compositor;
|
||||
private readonly Func<IEnumerable<object>> _surfaces;
|
||||
private readonly DiagnosticTextRenderer _diagnosticTextRenderer;
|
||||
private static long s_nextId = 1;
|
||||
public long Id { get; }
|
||||
public ulong Revision { get; private set; }
|
||||
private IRenderTarget? _renderTarget;
|
||||
private FpsCounter _fpsCounter = new FpsCounter(Typeface.Default.GlyphTypeface);
|
||||
private FpsCounter? _fpsCounter;
|
||||
private FrameTimeGraph? _renderTimeGraph;
|
||||
private FrameTimeGraph? _layoutTimeGraph;
|
||||
private Rect _dirtyRect;
|
||||
private Random _random = new();
|
||||
private Size _layerSize;
|
||||
@@ -35,18 +36,34 @@ namespace Avalonia.Rendering.Composition.Server
|
||||
private HashSet<ServerCompositionVisual> _attachedVisuals = new();
|
||||
private Queue<ServerCompositionVisual> _adornerUpdateQueue = new();
|
||||
|
||||
public long Id { get; }
|
||||
public ulong Revision { get; private set; }
|
||||
public ICompositionTargetDebugEvents? DebugEvents { get; set; }
|
||||
public ReadbackIndices Readback { get; } = new();
|
||||
public int RenderedVisuals { get; set; }
|
||||
|
||||
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces) :
|
||||
base(compositor)
|
||||
private FpsCounter FpsCounter
|
||||
=> _fpsCounter ??= new FpsCounter(_diagnosticTextRenderer);
|
||||
|
||||
private FrameTimeGraph LayoutTimeGraph
|
||||
=> _layoutTimeGraph ??= CreateTimeGraph("Layout");
|
||||
|
||||
private FrameTimeGraph RenderTimeGraph
|
||||
=> _renderTimeGraph ??= CreateTimeGraph("Render");
|
||||
|
||||
public ServerCompositionTarget(ServerCompositor compositor, Func<IEnumerable<object>> surfaces,
|
||||
DiagnosticTextRenderer diagnosticTextRenderer)
|
||||
: base(compositor)
|
||||
{
|
||||
_compositor = compositor;
|
||||
_surfaces = surfaces;
|
||||
_diagnosticTextRenderer = diagnosticTextRenderer;
|
||||
Id = Interlocked.Increment(ref s_nextId);
|
||||
}
|
||||
|
||||
private FrameTimeGraph CreateTimeGraph(string title)
|
||||
=> new(360, new Size(360.0, 64.0), 1000.0 / 60.0, title, _diagnosticTextRenderer);
|
||||
|
||||
partial void OnIsEnabledChanged()
|
||||
{
|
||||
if (IsEnabled)
|
||||
@@ -62,7 +79,33 @@ namespace Avalonia.Rendering.Composition.Server
|
||||
v.Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
partial void OnDebugOverlaysChanged()
|
||||
{
|
||||
if ((DebugOverlays & RendererDebugOverlays.Fps) == 0)
|
||||
{
|
||||
_fpsCounter?.Reset();
|
||||
}
|
||||
|
||||
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) == 0)
|
||||
{
|
||||
_layoutTimeGraph?.Reset();
|
||||
}
|
||||
|
||||
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) == 0)
|
||||
{
|
||||
_renderTimeGraph?.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnLastLayoutPassTimingChanged()
|
||||
{
|
||||
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
|
||||
{
|
||||
LayoutTimeGraph.AddFrameValue(LastLayoutPassTiming.Elapsed.TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
partial void DeserializeChangesExtra(BatchStreamReader c)
|
||||
{
|
||||
_redrawRequested = true;
|
||||
@@ -92,7 +135,10 @@ namespace Avalonia.Rendering.Composition.Server
|
||||
return;
|
||||
|
||||
Revision++;
|
||||
|
||||
|
||||
var captureTiming = (DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0;
|
||||
var startingTimestamp = captureTiming ? Stopwatch.GetTimestamp() : 0L;
|
||||
|
||||
// Update happens in a separate phase to extend dirty rect if needed
|
||||
Root.Update(this);
|
||||
|
||||
@@ -137,33 +183,69 @@ namespace Avalonia.Rendering.Composition.Server
|
||||
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
|
||||
new Rect(_layerSize),
|
||||
new Rect(Size), BitmapInterpolationMode.LowQuality);
|
||||
|
||||
|
||||
if (DrawDirtyRects)
|
||||
|
||||
if (DebugOverlays != RendererDebugOverlays.None)
|
||||
{
|
||||
targetContext.DrawRectangle(new ImmutableSolidColorBrush(
|
||||
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255),
|
||||
(byte)_random.Next(255)))
|
||||
, null, _dirtyRect);
|
||||
if (captureTiming)
|
||||
{
|
||||
var elapsed = StopwatchHelper.GetElapsedTime(startingTimestamp);
|
||||
RenderTimeGraph.AddFrameValue(elapsed.TotalMilliseconds);
|
||||
}
|
||||
|
||||
DrawOverlays(targetContext);
|
||||
}
|
||||
|
||||
if (DrawFps)
|
||||
{
|
||||
var nativeMem = ByteSizeHelper.ToString((ulong)(
|
||||
(Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
|
||||
Compositor.BatchMemoryPool.BufferSize), false);
|
||||
var managedMem = ByteSizeHelper.ToString((ulong)(
|
||||
(Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
|
||||
Compositor.BatchObjectPool.ArraySize *
|
||||
IntPtr.Size), false);
|
||||
_fpsCounter.RenderFps(targetContext, FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
|
||||
}
|
||||
RenderedVisuals = 0;
|
||||
|
||||
_dirtyRect = default;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOverlays(IDrawingContextImpl targetContext)
|
||||
{
|
||||
if ((DebugOverlays & RendererDebugOverlays.DirtyRects) != 0)
|
||||
{
|
||||
targetContext.DrawRectangle(
|
||||
new ImmutableSolidColorBrush(
|
||||
new Color(30, (byte)_random.Next(255), (byte)_random.Next(255), (byte)_random.Next(255))),
|
||||
null,
|
||||
_dirtyRect);
|
||||
}
|
||||
|
||||
if ((DebugOverlays & RendererDebugOverlays.Fps) != 0)
|
||||
{
|
||||
var nativeMem = ByteSizeHelper.ToString((ulong) (
|
||||
(Compositor.BatchMemoryPool.CurrentUsage + Compositor.BatchMemoryPool.CurrentPool) *
|
||||
Compositor.BatchMemoryPool.BufferSize), false);
|
||||
var managedMem = ByteSizeHelper.ToString((ulong) (
|
||||
(Compositor.BatchObjectPool.CurrentUsage + Compositor.BatchObjectPool.CurrentPool) *
|
||||
Compositor.BatchObjectPool.ArraySize *
|
||||
IntPtr.Size), false);
|
||||
FpsCounter.RenderFps(targetContext,
|
||||
FormattableString.Invariant($"M:{managedMem} / N:{nativeMem} R:{RenderedVisuals:0000}"));
|
||||
}
|
||||
|
||||
var top = 0.0;
|
||||
|
||||
void DrawTimeGraph(FrameTimeGraph graph)
|
||||
{
|
||||
top += 8.0;
|
||||
targetContext.Transform = Matrix.CreateTranslation(Size.Width - graph.Size.Width - 8.0, top);
|
||||
graph.Render(targetContext);
|
||||
top += graph.Size.Height;
|
||||
}
|
||||
|
||||
if ((DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0)
|
||||
{
|
||||
DrawTimeGraph(LayoutTimeGraph);
|
||||
}
|
||||
|
||||
if ((DebugOverlays & RendererDebugOverlays.RenderTimeGraph) != 0)
|
||||
{
|
||||
DrawTimeGraph(RenderTimeGraph);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect SnapToDevicePixels(Rect rect) => SnapToDevicePixels(rect, Scaling);
|
||||
|
||||
private static Rect SnapToDevicePixels(Rect rect, double scale)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.VisualTree;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Rendering.Composition;
|
||||
@@ -12,15 +11,9 @@ namespace Avalonia.Rendering
|
||||
public interface IRenderer : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the renderer should draw an FPS counter.
|
||||
/// Gets a value indicating whether the renderer should draw specific diagnostics.
|
||||
/// </summary>
|
||||
bool DrawFps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the renderer should draw a visual representation
|
||||
/// of its dirty rectangles.
|
||||
/// </summary>
|
||||
bool DrawDirtyRects { get; set; }
|
||||
RendererDiagnostics Diagnostics { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a portion of the scene has been invalidated.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Avalonia.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single layout pass timing.
|
||||
/// </summary>
|
||||
/// <param name="PassCounter">The number of the layout pass.</param>
|
||||
/// <param name="Elapsed">The elapsed time during the layout pass.</param>
|
||||
internal readonly record struct LayoutPassTiming(int PassCounter, TimeSpan Elapsed);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace Avalonia.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the various types of overlays that can be drawn by a renderer.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RendererDebugOverlays
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not draw any overlay.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Draw a FPS counter.
|
||||
/// </summary>
|
||||
Fps = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Draw invalidated rectangles each frame.
|
||||
/// </summary>
|
||||
DirtyRects = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Draw a graph of past layout times.
|
||||
/// </summary>
|
||||
LayoutTimeGraph = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Draw a graph of past render times.
|
||||
/// </summary>
|
||||
RenderTimeGraph = 1 << 3
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Avalonia.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages configurable diagnostics that can be displayed by a renderer.
|
||||
/// </summary>
|
||||
public class RendererDiagnostics : INotifyPropertyChanged
|
||||
{
|
||||
private RendererDebugOverlays _debugOverlays;
|
||||
private LayoutPassTiming _lastLayoutPassTiming;
|
||||
private PropertyChangedEventArgs? _debugOverlaysChangedEventArgs;
|
||||
private PropertyChangedEventArgs? _lastLayoutPassTimingChangedEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets which debug overlays are displayed by the renderer.
|
||||
/// </summary>
|
||||
public RendererDebugOverlays DebugOverlays
|
||||
{
|
||||
get => _debugOverlays;
|
||||
set
|
||||
{
|
||||
if (_debugOverlays != value)
|
||||
{
|
||||
_debugOverlays = value;
|
||||
OnPropertyChanged(_debugOverlaysChangedEventArgs ??= new(nameof(DebugOverlays)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last layout pass timing that the renderer may display.
|
||||
/// </summary>
|
||||
internal LayoutPassTiming LastLayoutPassTiming
|
||||
{
|
||||
get => _lastLayoutPassTiming;
|
||||
set
|
||||
{
|
||||
if (!_lastLayoutPassTiming.Equals(value))
|
||||
{
|
||||
_lastLayoutPassTiming = value;
|
||||
OnPropertyChanged(_lastLayoutPassTimingChangedEventArgs ??= new(nameof(LastLayoutPassTiming)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changes on the object.
|
||||
/// </summary>
|
||||
/// <param name="args">The property change details.</param>
|
||||
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
|
||||
=> PropertyChanged?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,23 @@ namespace Avalonia
|
||||
public static readonly StyledProperty<ControlTheme?> ThemeProperty =
|
||||
AvaloniaProperty.Register<StyledElement, ControlTheme?>(nameof(Theme));
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ActualThemeVariant"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
|
||||
AvaloniaProperty.Register<StyledElement, ThemeVariant>(
|
||||
nameof(ThemeVariant),
|
||||
inherits: true,
|
||||
defaultValue: ThemeVariant.Light);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the RequestedThemeVariant property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
|
||||
AvaloniaProperty.Register<StyledElement, ThemeVariant?>(
|
||||
nameof(ThemeVariant),
|
||||
defaultValue: ThemeVariant.Default);
|
||||
|
||||
private static readonly ControlTheme s_invalidTheme = new ControlTheme();
|
||||
private int _initCount;
|
||||
private string? _name;
|
||||
@@ -257,6 +274,15 @@ namespace Avalonia
|
||||
set => SetValue(ThemeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UI theme that is currently used by the element, which might be different than the <see cref="RequestedThemeVariantProperty"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned.
|
||||
/// Otherwise, current OS theme variant is returned.
|
||||
/// </returns>
|
||||
public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the styled element's logical children.
|
||||
/// </summary>
|
||||
@@ -439,11 +465,11 @@ namespace Avalonia
|
||||
void IResourceHost.NotifyHostedResourcesChanged(ResourcesChangedEventArgs e) => NotifyResourcesChanged(e);
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IResourceNode.TryGetResource(object key, out object? value)
|
||||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
|
||||
{
|
||||
value = null;
|
||||
return (_resources?.TryGetResource(key, out value) ?? false) ||
|
||||
(_styles?.TryGetResource(key, out value) ?? false);
|
||||
return (_resources?.TryGetResource(key, theme, out value) ?? false) ||
|
||||
(_styles?.TryGetResource(key, theme, out value) ?? false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -621,6 +647,13 @@ namespace Avalonia
|
||||
|
||||
if (change.Property == ThemeProperty)
|
||||
OnControlThemeChanged();
|
||||
else if (change.Property == RequestedThemeVariantProperty)
|
||||
{
|
||||
if (change.GetNewValue<ThemeVariant>() is {} themeVariant && themeVariant != ThemeVariant.Default)
|
||||
SetValue(ActualThemeVariantProperty, themeVariant);
|
||||
else
|
||||
ClearValue(ActualThemeVariantProperty);
|
||||
}
|
||||
}
|
||||
|
||||
private protected virtual void OnControlThemeChanged()
|
||||
@@ -658,7 +691,7 @@ namespace Avalonia
|
||||
{
|
||||
var theme = Theme;
|
||||
|
||||
// Explitly set Theme property takes precedence.
|
||||
// Explicitly set Theme property takes precedence.
|
||||
if (theme is not null)
|
||||
return theme;
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace Avalonia.Styling;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for an application host element with a root theme variant.
|
||||
/// </summary>
|
||||
[Unstable]
|
||||
public interface IGlobalThemeVariantProvider : IResourceHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the UI theme variant that is used by the control (and its child elements) for resource determination.
|
||||
/// </summary>
|
||||
ThemeVariant ActualThemeVariant { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the theme variant is changed on the element or an ancestor of the element.
|
||||
/// </summary>
|
||||
event EventHandler? ActualThemeVariantChanged;
|
||||
}
|
||||
@@ -74,16 +74,16 @@ namespace Avalonia.Styling
|
||||
|
||||
public event EventHandler? OwnerChanged;
|
||||
|
||||
public bool TryGetResource(object key, out object? result)
|
||||
public bool TryGetResource(object key, ThemeVariant? themeVariant, out object? result)
|
||||
{
|
||||
if (_resources is not null && _resources.TryGetResource(key, out result))
|
||||
if (_resources is not null && _resources.TryGetResource(key, themeVariant, out result))
|
||||
return true;
|
||||
|
||||
if (_children is not null)
|
||||
{
|
||||
for (var i = 0; i < _children.Count; ++i)
|
||||
{
|
||||
if (_children[i].TryGetResource(key, out result))
|
||||
if (_children[i].TryGetResource(key, themeVariant, out result))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,16 +115,16 @@ namespace Avalonia.Styling
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetResource(object key, out object? value)
|
||||
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
|
||||
{
|
||||
if (_resources != null && _resources.TryGetResource(key, out value))
|
||||
if (_resources != null && _resources.TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var i = Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (this[i].TryGetResource(key, out value))
|
||||
if (this[i].TryGetResource(key, theme, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Styling;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies a UI theme variant that should be used for the
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(ThemeVariantTypeConverter))]
|
||||
public sealed record ThemeVariant
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ThemeVariant"/>
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the theme variant by which variants are compared.</param>
|
||||
/// <param name="inheritVariant">Reference to a theme variant which should be used, if resource wasn't found for the requested variant.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if inheritVariant is a reference to the <see cref="ThemeVariant.Default"/> which is ambiguous value to inherit.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if key is null.</exception>
|
||||
public ThemeVariant(object key, ThemeVariant? inheritVariant)
|
||||
{
|
||||
Key = key ?? throw new ArgumentNullException(nameof(key));
|
||||
InheritVariant = inheritVariant;
|
||||
|
||||
if (inheritVariant == Default)
|
||||
{
|
||||
throw new ArgumentException("Inheriting default theme variant is not supported.", nameof(inheritVariant));
|
||||
}
|
||||
}
|
||||
|
||||
private ThemeVariant(object key)
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key of the theme variant by which variants are compared.
|
||||
/// </summary>
|
||||
public object Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a theme variant which should be used, if resource wasn't found for the requested variant.
|
||||
/// </summary>
|
||||
public ThemeVariant? InheritVariant { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Inherit theme variant from the parent. If set on Application, system theme is inherited.
|
||||
/// Using Default as the ResourceDictionary.Key marks this dictionary as a fallback in case the theme variant or resource key is not found in other theme dictionaries.
|
||||
/// </summary>
|
||||
public static ThemeVariant Default { get; } = new(nameof(Default));
|
||||
|
||||
/// <summary>
|
||||
/// Use the Light theme variant.
|
||||
/// </summary>
|
||||
public static ThemeVariant Light { get; } = new(nameof(Light));
|
||||
|
||||
/// <summary>
|
||||
/// Use the Dark theme variant.
|
||||
/// </summary>
|
||||
public static ThemeVariant Dark { get; } = new(nameof(Dark));
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Key.ToString() ?? $"ThemeVariant {{ Key = {Key} }}";
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Key.GetHashCode();
|
||||
}
|
||||
|
||||
public bool Equals(ThemeVariant? other)
|
||||
{
|
||||
return Key == other?.Key;
|
||||
}
|
||||
|
||||
public static explicit operator ThemeVariant(PlatformThemeVariant themeVariant)
|
||||
{
|
||||
return themeVariant switch
|
||||
{
|
||||
PlatformThemeVariant.Light => Light,
|
||||
PlatformThemeVariant.Dark => Dark,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static explicit operator PlatformThemeVariant?(ThemeVariant themeVariant)
|
||||
{
|
||||
if (themeVariant == Light)
|
||||
{
|
||||
return PlatformThemeVariant.Light;
|
||||
}
|
||||
else if (themeVariant == Dark)
|
||||
{
|
||||
return PlatformThemeVariant.Dark;
|
||||
}
|
||||
else if (themeVariant.InheritVariant is { } inheritVariant)
|
||||
{
|
||||
return (PlatformThemeVariant?)inheritVariant;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Avalonia.Styling;
|
||||
|
||||
public class ThemeVariantTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string);
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
nameof(ThemeVariant.Default) => ThemeVariant.Default,
|
||||
nameof(ThemeVariant.Light) => ThemeVariant.Light,
|
||||
nameof(ThemeVariant.Dark) => ThemeVariant.Dark,
|
||||
_ => throw new NotSupportedException("ThemeVariant type converter supports only build in variants. For custom variants please use x:Static markup extension.")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Avalonia.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Allows using <see cref="Stopwatch"/> as timestamps without allocating.
|
||||
/// </summary>
|
||||
/// <remarks>Equivalent to Stopwatch.GetElapsedTime in .NET 7.</remarks>
|
||||
internal static class StopwatchHelper
|
||||
{
|
||||
private static readonly double s_timestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
|
||||
public static TimeSpan GetElapsedTime(long startingTimestamp)
|
||||
=> GetElapsedTime(startingTimestamp, Stopwatch.GetTimestamp());
|
||||
|
||||
public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp)
|
||||
=> new((long)((endingTimestamp - startingTimestamp) * s_timestampToTicks));
|
||||
}
|
||||
@@ -39,8 +39,8 @@
|
||||
<Object Name="CompositionTarget" CustomServerCtor="true">
|
||||
<Property Name="Root" Type="CompositionVisual?"/>
|
||||
<Property Name="IsEnabled" Type="bool"/>
|
||||
<Property Name="DrawDirtyRects" Type="bool"/>
|
||||
<Property Name="DrawFps" Type="bool"/>
|
||||
<Property Name="DebugOverlays" Type="RendererDebugOverlays"/>
|
||||
<Property Name="LastLayoutPassTiming" Type="LayoutPassTiming" Internal="true"/>
|
||||
<Property Name="Scaling" Type="double"/>
|
||||
<Property Name="Size" Type="Size" />
|
||||
</Object>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
|
||||
<PackageId>Avalonia.Controls.ItemsRepeater</PackageId>
|
||||
<RootNamespace>Avalonia</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
|
||||
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\..\build\EmbedXaml.props" />
|
||||
<Import Project="..\..\build\BuildTargets.targets" />
|
||||
<!--<Import Project="..\..\build\ApiDiff.props" />-->
|
||||
<Import Project="..\..\build\NullableEnable.props" />
|
||||
<Import Project="..\..\build\TrimmingEnable.props" />
|
||||
<Import Project="..\..\build\DevAnalyzers.props" />
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user