diff --git a/Avalonia.sln b/Avalonia.sln index 0bae725d33..107c55aa6f 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -264,13 +264,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Generators.Tests", "tests\Avalonia.Generators.Tests\Avalonia.Generators.Tests.csproj", "{4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{2999D79E-3C20-4A90-B651-CA7E0AC92D35}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{2999D79E-3C20-4A90-B651-CA7E0AC92D35}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Browser", "samples\MobileSandbox.Browser\MobileSandbox.Browser.csproj", "{43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -607,10 +609,6 @@ Global {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU - {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -639,14 +637,14 @@ Global {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU - {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.Build.0 = Release|Any CPU {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU + {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED976634-B118-43F8-8B26-0279C7A7044F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED976634-B118-43F8-8B26-0279C7A7044F}.Release|Any CPU.Build.0 = Release|Any CPU {4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -659,6 +657,10 @@ Global {F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU {F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3}.Release|Any CPU.Build.0 = Release|Any CPU + {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -728,9 +730,6 @@ Global {C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} - {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7} - {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7} - {F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7} {DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098} {F8928267-688E-4A51-989C-612A72446D33} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -738,11 +737,12 @@ Global {22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098} {4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7} {F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7} + {ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7} {4B8EBBEB-A1AD-49EC-8B69-B93ED15BFA64} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {2999D79E-3C20-4A90-B651-CA7E0AC92D35} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {F83FC908-A4E3-40DE-B4CF-A4BA1E92CDB3} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {43FCC14E-EEBE-44B3-BCBC-F1C537EECBF8} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 3f313ead1b..b8373cf609 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 85c159467b..5249a4fb41 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -18,9 +18,16 @@ namespace ControlCatalog.NetCore { static class Program { + private static bool s_useFramebuffer; + [STAThread] static int Main(string[] args) { + if (args.Contains("--fbdev")) + { + s_useFramebuffer = true; + } + if (args.Contains("--wait-for-attach")) { Console.WriteLine("Attach debugger and use 'Set next statement'"); @@ -42,10 +49,10 @@ namespace ControlCatalog.NetCore return scaling; return 1; } - if (args.Contains("--fbdev")) + if (s_useFramebuffer) { - SilenceConsole(); - return builder.StartLinuxFbDev(args, scaling: GetScaling()); + SilenceConsole(); + return builder.StartLinuxFbDev(args, scaling: GetScaling()); } else if (args.Contains("--vnc")) { @@ -128,10 +135,13 @@ namespace ControlCatalog.NetCore .WithInterFont() .AfterSetup(builder => { - builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + if (!s_useFramebuffer) { - StartupScreenIndex = 1, - }); + builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + { + StartupScreenIndex = 1, + }); + } EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin() : OperatingSystem.IsMacOS() ? new EmbedSampleMac() diff --git a/samples/MobileSandbox.Browser/Logo.svg b/samples/MobileSandbox.Browser/Logo.svg new file mode 100644 index 0000000000..9685a23af1 --- /dev/null +++ b/samples/MobileSandbox.Browser/Logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj b/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj new file mode 100644 index 0000000000..001fe94c7f --- /dev/null +++ b/samples/MobileSandbox.Browser/MobileSandbox.Browser.csproj @@ -0,0 +1,43 @@ + + + net7.0 + browser-wasm + main.js + Exe + true + true + true + -sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0 + + + + true + true + full + true + true + -O2 + -O2 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MobileSandbox.Browser/Program.cs b/samples/MobileSandbox.Browser/Program.cs new file mode 100644 index 0000000000..cdd986d6e8 --- /dev/null +++ b/samples/MobileSandbox.Browser/Program.cs @@ -0,0 +1,19 @@ +using System.Runtime.Versioning; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Browser; +using MobileSandbox; + +[assembly:SupportedOSPlatform("browser")] + +internal partial class Program +{ + public static async Task Main(string[] args) + { + await BuildAvaloniaApp() + .StartBrowserAppAsync("out"); + } + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); +} diff --git a/samples/MobileSandbox.Browser/Properties/launchSettings.json b/samples/MobileSandbox.Browser/Properties/launchSettings.json new file mode 100644 index 0000000000..66234a3211 --- /dev/null +++ b/samples/MobileSandbox.Browser/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "MobileSandbox.Browser": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:65312;http://localhost:65313;" + } + } +} diff --git a/samples/MobileSandbox.Browser/Roots.xml b/samples/MobileSandbox.Browser/Roots.xml new file mode 100644 index 0000000000..5255ae3ef8 --- /dev/null +++ b/samples/MobileSandbox.Browser/Roots.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/samples/MobileSandbox.Browser/app.css b/samples/MobileSandbox.Browser/app.css new file mode 100644 index 0000000000..0e6ab12461 --- /dev/null +++ b/samples/MobileSandbox.Browser/app.css @@ -0,0 +1,56 @@ +:root { + --sat: env(safe-area-inset-top); + --sar: env(safe-area-inset-right); + --sab: env(safe-area-inset-bottom); + --sal: env(safe-area-inset-left); +} + +#out { + height: 100vh; + width: 100vw +} + +#avalonia-splash { + position: relative; + height: 100%; + width: 100%; + color: whitesmoke; + background: #171C2C; + font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} + +#avalonia-splash a{ + color: whitesmoke; + text-decoration: none; +} + +.center { + display: flex; + justify-content: center; + height: 250px; +} + +.splash-close { + animation: slide 0.5s linear 1s forwards; +} + +@keyframes slide { + 0% { + top: 0%; + } + + 50% { + opacity: 80%; + } + + 100% { + top: 100%; + overflow: hidden; + opacity: 0; + display: none; + visibility: collapse; + } +} diff --git a/samples/MobileSandbox.Browser/favicon.ico b/samples/MobileSandbox.Browser/favicon.ico new file mode 100644 index 0000000000..da8d49ff9b Binary files /dev/null and b/samples/MobileSandbox.Browser/favicon.ico differ diff --git a/samples/MobileSandbox.Browser/index.html b/samples/MobileSandbox.Browser/index.html new file mode 100644 index 0000000000..32ab8628fb --- /dev/null +++ b/samples/MobileSandbox.Browser/index.html @@ -0,0 +1,31 @@ + + + + + + + Mobile Sandbox + + + + + + + + + +
+
+
+

Powered by

+ + Avalonia Logo + Avalonia + +
+
+
+ + + + diff --git a/samples/MobileSandbox.Browser/main.js b/samples/MobileSandbox.Browser/main.js new file mode 100644 index 0000000000..9d90db8bd2 --- /dev/null +++ b/samples/MobileSandbox.Browser/main.js @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet } from './dotnet.js' + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const dotnetRuntime = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +const config = dotnetRuntime.getConfig(); + +await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]); diff --git a/samples/MobileSandbox.Browser/runtimeconfig.template.json b/samples/MobileSandbox.Browser/runtimeconfig.template.json new file mode 100644 index 0000000000..8f0557352c --- /dev/null +++ b/samples/MobileSandbox.Browser/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 0c22213d33..b3f41eb420 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -334,7 +334,7 @@ namespace Avalonia /// . /// /// The type of the property change sender. - /// /// The type of the property.. + /// The type of the property. /// The property changed observable. /// /// The method to call. The parameters are the sender and the event args. diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 8868e966f0..04c444f441 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -51,7 +51,7 @@ namespace Avalonia.Input pointer.Capture(hit); } - var target = pointer.Captured ?? args.Root; + var target = pointer.Captured ?? args.InputHitTestResult ?? args.Root; var gestureTarget = pointer.CapturedGestureRecognizer?.Target; var updateKind = args.Type.ToUpdateKind(); var keyModifier = args.InputModifiers.ToKeyModifiers(); diff --git a/src/Avalonia.Base/Media/EllipseGeometry.cs b/src/Avalonia.Base/Media/EllipseGeometry.cs index 8211855324..84d74e888e 100644 --- a/src/Avalonia.Base/Media/EllipseGeometry.cs +++ b/src/Avalonia.Base/Media/EllipseGeometry.cs @@ -56,6 +56,10 @@ namespace Avalonia.Media /// /// Gets or sets a rect that defines the bounds of the ellipse. /// + /// + /// When set, this takes priority over the other properties that define an + /// ellipse using a center point and X/Y-axis radii. + /// public Rect Rect { get => GetValue(RectProperty); @@ -65,6 +69,10 @@ namespace Avalonia.Media /// /// Gets or sets a double that defines the radius in the X-axis of the ellipse. /// + /// + /// In order for this property to be used, must not be set + /// (equal to the default value). + /// public double RadiusX { get => GetValue(RadiusXProperty); @@ -74,6 +82,10 @@ namespace Avalonia.Media /// /// Gets or sets a double that defines the radius in the Y-axis of the ellipse. /// + /// + /// In order for this property to be used, must not be set + /// (equal to the default value). + /// public double RadiusY { get => GetValue(RadiusYProperty); @@ -83,6 +95,10 @@ namespace Avalonia.Media /// /// Gets or sets a point that defines the center of the ellipse. /// + /// + /// In order for this property to be used, must not be set + /// (equal to the default value). + /// public Point Center { get => GetValue(CenterProperty); @@ -92,7 +108,30 @@ namespace Avalonia.Media /// public override Geometry Clone() { - return new EllipseGeometry(Rect); + // Note that the ellipse properties are used in two modes: + // + // 1. Rect-only Mode: + // Directly set the rectangle bounds the ellipse will fill + // + // 2. Center + Radii Mode: + // Set a center-point and then X/Y-axis radii that are used to + // calculate the rectangle bounds the ellipse will fill. + // This is the only mode supported by WPF. + // + // Rendering the ellipse will only ever use one of these two modes + // based on if the Rect property is set (not equal to default). + // + // This means it would normally be fine to copy ONLY the Rect property + // when it is set. However, while it would render the same, it isn't + // a true clone. We want to include all the properties here regardless + // of the rendering mode that will eventually be used. + return new EllipseGeometry() + { + Rect = Rect, + RadiusX = RadiusX, + RadiusY = RadiusY, + Center = Center, + }; } /// diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 350d8817f1..0f70386424 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -643,12 +643,13 @@ namespace Avalonia.Media lastCluster = _glyphInfos[_glyphInfos.Count - 1].GlyphCluster; } + var isReversed = firstCluster > lastCluster; + if (!IsLeftToRight) { (lastCluster, firstCluster) = (firstCluster, lastCluster); } - var isReversed = firstCluster > lastCluster; var height = GlyphTypeface.Metrics.LineSpacing * Scale; var widthIncludingTrailingWhitespace = 0d; @@ -766,15 +767,13 @@ namespace Avalonia.Media if (!charactersSpan.IsEmpty) { - var characterIndex = 0; + var characterIndex = charactersSpan.Length - 1; for (var i = 0; i < _glyphInfos.Count; i++) { var currentCluster = _glyphInfos[i].GlyphCluster; var codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out var characterLength); - characterIndex += characterLength; - if (!codepoint.IsWhiteSpace) { break; @@ -784,9 +783,9 @@ namespace Avalonia.Media var j = i; - while (j - 1 >= 0) + while (j + 1 < _glyphInfos.Count) { - var nextCluster = _glyphInfos[--j].GlyphCluster; + var nextCluster = _glyphInfos[++j].GlyphCluster; if (currentCluster == nextCluster) { @@ -798,6 +797,8 @@ namespace Avalonia.Media break; } + characterIndex -= clusterLength; + if (codepoint.IsBreakChar) { newLineLength += clusterLength; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index a609800fb8..d2198a2cbf 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -684,7 +684,9 @@ namespace Avalonia.Media.TextFormatting var textRuns = new TextRun[] { new ShapedTextRun(shapedBuffer, properties) }; var line = new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection); + line.FinalizeLine(); + return line; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 4ccb3f6a37..b6b6d11a49 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -128,7 +128,7 @@ namespace Avalonia.Media.TextFormatting /// /// Gets the text spacing. /// - public double LetterSpacing => _paragraphProperties.LetterSpacing; + public double LetterSpacing => _paragraphProperties.LetterSpacing; /// /// Gets the text lines. @@ -271,11 +271,13 @@ namespace Avalonia.Media.TextFormatting var currentY = 0.0; - foreach (var textLine in _textLines) + for (var i = 0; i < _textLines.Length; i++) { + var textLine = _textLines[i]; + var end = textLine.FirstTextSourceIndex + textLine.Length; - if (end <= textPosition && end < _textSourceLength) + if (end <= textPosition && i + 1 < _textLines.Length) { currentY += textLine.Height; @@ -511,7 +513,7 @@ namespace Avalonia.Media.TextFormatting { var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); - UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, + UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); return new TextLine[] { textLine }; @@ -638,13 +640,13 @@ namespace Avalonia.Media.TextFormatting } private void UpdateMetrics( - TextLine currentLine, - ref double lineStartOfLongestLine, - ref Point origin, - ref bool first, + TextLine currentLine, + ref double lineStartOfLongestLine, + ref Point origin, + ref bool first, ref double accBlackBoxLeft, - ref double accBlackBoxTop, - ref double accBlackBoxRight, + ref double accBlackBoxTop, + ref double accBlackBoxRight, ref double accBlackBoxBottom) { var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index c2ec78e187..ca31d9a6d0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -371,14 +371,16 @@ namespace Avalonia.Media.TextFormatting IndexedTextRun currentIndexedRun = _indexedTextRuns[i]; - while(currentIndexedRun.TextSourceCharacterIndex != currentPosition) + while (currentIndexedRun.TextSourceCharacterIndex != currentPosition) { - if(i + 1 < _indexedTextRuns.Count) + if (i + 1 == _indexedTextRuns.Count) { - i++; - - currentIndexedRun = _indexedTextRuns[i]; + break; } + + i++; + + currentIndexedRun = _indexedTextRuns[i]; } return currentIndexedRun; @@ -430,7 +432,7 @@ namespace Avalonia.Media.TextFormatting if (currentTextRun == null) { - return 0; + return Start; } var directionalWidth = 0.0; @@ -584,6 +586,8 @@ namespace Avalonia.Media.TextFormatting var currentPosition = FirstTextSourceIndex; var remainingLength = textLength; + TextBounds? lastBounds = null; + static FlowDirection GetDirection(TextRun textRun, FlowDirection currentDirection) { if (textRun is ShapedTextRun shapedTextRun) @@ -604,12 +608,14 @@ namespace Avalonia.Media.TextFormatting while (currentIndexedRun.TextSourceCharacterIndex != currentPosition) { - if (i + 1 < _indexedTextRuns.Count) + if (i + 1 == _indexedTextRuns.Count) { - i++; - - currentIndexedRun = _indexedTextRuns[i]; + break; } + + i++; + + currentIndexedRun = _indexedTextRuns[i]; } return currentIndexedRun; @@ -632,6 +638,40 @@ namespace Avalonia.Media.TextFormatting return distance; } + bool TryMergeWithLastBounds(TextBounds currentBounds, TextBounds lastBounds) + { + if (currentBounds.FlowDirection != lastBounds.FlowDirection) + { + return false; + } + + if (currentBounds.Rectangle.Left == lastBounds.Rectangle.Right) + { + foreach (var runBounds in currentBounds.TextRunBounds) + { + lastBounds.TextRunBounds.Add(runBounds); + } + + lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle); + + return true; + } + + if (currentBounds.Rectangle.Right == lastBounds.Rectangle.Left) + { + for (int i = 0; i < currentBounds.TextRunBounds.Count; i++) + { + lastBounds.TextRunBounds.Insert(i, currentBounds.TextRunBounds[i]); + } + + lastBounds.Rectangle = lastBounds.Rectangle.Union(currentBounds.Rectangle); + + return true; + } + + return false; + } + while (remainingLength > 0 && currentPosition < FirstTextSourceIndex + Length) { var currentIndexedRun = FindIndexedRun(); @@ -667,67 +707,21 @@ namespace Avalonia.Media.TextFormatting directionalWidth = currentDrawable.Size.Width; } - if (currentTextRun is not TextEndOfLine) - { - if (currentDirection == FlowDirection.LeftToRight) - { - // Find consecutive runs of same direction - for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) - { - var nextRun = _textRuns[lastRunIndex + 1]; - - var nextDirection = GetDirection(nextRun, currentDirection); - - if (currentDirection != nextDirection) - { - break; - } - - if (nextRun is DrawableTextRun nextDrawable) - { - directionalWidth += nextDrawable.Size.Width; - } - } - } - else - { - // Find consecutive runs of same direction - for (; firstRunIndex - 1 > 0; firstRunIndex--) - { - var previousRun = _textRuns[firstRunIndex - 1]; - - var previousDirection = GetDirection(previousRun, currentDirection); - - if (currentDirection != previousDirection) - { - break; - } - - if (previousRun is DrawableTextRun previousDrawable) - { - directionalWidth += previousDrawable.Size.Width; - - currentX -= previousDrawable.Size.Width; - } - } - } - } - int coveredLength; - TextBounds? textBounds; + TextBounds? currentBounds; switch (currentDirection) { case FlowDirection.RightToLeft: { - textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, + currentBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, currentPosition, remainingLength, out coveredLength, out currentPosition); break; } default: { - textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, + currentBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, currentPosition, remainingLength, out coveredLength, out currentPosition); break; @@ -736,7 +730,18 @@ namespace Avalonia.Media.TextFormatting if (coveredLength > 0) { - result.Add(textBounds); + if (lastBounds != null && TryMergeWithLastBounds(currentBounds, lastBounds)) + { + currentBounds = lastBounds; + + result[result.Count - 1] = currentBounds; + } + else + { + result.Add(currentBounds); + } + + lastBounds = currentBounds; remainingLength -= coveredLength; } @@ -997,14 +1002,14 @@ namespace Avalonia.Media.TextFormatting public void FinalizeLine() { + _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex); + _textLineMetrics = CreateLineMetrics(); if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine) { _textLineBreak = new TextLineBreak(textEndOfLine); - } - - _indexedTextRuns = BidiReorderer.Instance.BidiReorder(_textRuns, _paragraphProperties.FlowDirection, FirstTextSourceIndex); + } } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 3406432ce7..f418d4e14a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -687,7 +687,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// This method resolves the sos and eos values for the run /// and adds the run to the list - /// /// + /// /// The index of the start of the run (in x9 removed units) /// The length of the run (in x9 removed units) /// The level of the run diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 6f62c3be1d..57fedb3d69 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -18,7 +18,7 @@ namespace Avalonia.Platform /// Creates an ellipse geometry implementation. /// /// The bounds of the ellipse. - /// An ellipse geometry.. + /// An ellipse geometry. IGeometryImpl CreateEllipseGeometry(Rect rect); /// diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs index 55aac6f3fa..76323eb900 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs @@ -10,16 +10,19 @@ internal static class StorageProviderHelpers { public static IStorageItem? TryCreateBclStorageItem(string path) { - var directory = new DirectoryInfo(path); - if (directory.Exists) + if (!string.IsNullOrWhiteSpace(path)) { - return new BclStorageFolder(directory); - } - - var file = new FileInfo(path); - if (file.Exists) - { - return new BclStorageFile(file); + var directory = new DirectoryInfo(path); + if (directory.Exists) + { + return new BclStorageFolder(directory); + } + + var file = new FileInfo(path); + if (file.Exists) + { + return new BclStorageFile(file); + } } return null; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs index 9c6c78c1ad..da24b3812a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs @@ -49,22 +49,31 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp public void UpdateWithAutomaticSync(CompositionImportedGpuImage image) { - PerformSanityChecks(image); - Update(image.Image.SnapshotWithAutomaticSync(), image.Context); + using (Compositor.RenderInterface.EnsureCurrent()) + { + PerformSanityChecks(image); + Update(image.Image.SnapshotWithAutomaticSync(), image.Context); + } } public void UpdateWithKeyedMutex(CompositionImportedGpuImage image, uint acquireIndex, uint releaseIndex) { - PerformSanityChecks(image); - Update(image.Image.SnapshotWithKeyedMutex(acquireIndex, releaseIndex), image.Context); + using (Compositor.RenderInterface.EnsureCurrent()) + { + PerformSanityChecks(image); + Update(image.Image.SnapshotWithKeyedMutex(acquireIndex, releaseIndex), image.Context); + } } public void UpdateWithSemaphores(CompositionImportedGpuImage image, CompositionImportedGpuSemaphore wait, CompositionImportedGpuSemaphore signal) { - PerformSanityChecks(image); - if (!wait.IsUsable || !signal.IsUsable) - throw new PlatformGraphicsContextLostException(); - Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context); + using (Compositor.RenderInterface.EnsureCurrent()) + { + PerformSanityChecks(image); + if (!wait.IsUsable || !signal.IsUsable) + throw new PlatformGraphicsContextLostException(); + Update(image.Image.SnapshotWithSemaphores(wait.Semaphore, signal.Semaphore), image.Context); + } } public void Dispose() diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 7672bcb14d..8f1aa1cb49 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -128,7 +128,9 @@ namespace Avalonia.Rendering.Composition.Server if (_renderTarget?.IsCorrupted == true) { - _renderTarget!.Dispose(); + _layer?.Dispose(); + _layer = null; + _renderTarget.Dispose(); _renderTarget = null; _redrawRequested = true; } @@ -157,14 +159,15 @@ namespace Avalonia.Rendering.Composition.Server _redrawRequested = false; using (var targetContext = _renderTarget.CreateDrawingContext()) { - var layerSize = Size * Scaling; + var size = Size; + var layerSize = size * Scaling; if (layerSize != _layerSize || _layer == null || _layer.IsCorrupted) { _layer?.Dispose(); _layer = null; - _layer = targetContext.CreateLayer(Size); + _layer = targetContext.CreateLayer(size); _layerSize = layerSize; - _dirtyRect = new Rect(0, 0, layerSize.Width, layerSize.Height); + _dirtyRect = new Rect(0, 0, size.Width, size.Height); } if (_dirtyRect.Width != 0 || _dirtyRect.Height != 0) @@ -185,7 +188,7 @@ namespace Avalonia.Rendering.Composition.Server else targetContext.DrawBitmap(_layer, 1, new Rect(_layerSize), - new Rect(Size)); + new Rect(size)); if (DebugOverlays != RendererDebugOverlays.None) { diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 3a82bf02e0..7dbb0872f5 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -306,7 +306,7 @@ namespace Avalonia.Utilities /// if the value could not be converted. /// /// The value to convert. - /// The type to convert to.. + /// The type to convert to. /// The culture to use. /// A value of . [RequiresUnreferencedCode(TrimmingMessages.TypeConversionRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index aaf272c6d2..4700081e6b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -266,26 +266,26 @@ namespace Avalonia.Controls MidnightBlue9 = 0xFF1C2833, MidnightBlue10 = 0xFF17202A, - Pomegranate = Pomegranate3, - Alizarin = Alizarin3, - Amethyst = Amethyst3, - Wisteria = Wisteria3, - BelizeHole = BelizeHole3, - PeterRiver = PeterRiver3, - Turquoise = Turquoise3, - GreenSea = GreenSea3, - Nephritis = Nephritis3, - Emerald = Emerald3, - Sunflower = Sunflower3, - Orange = Orange3, - Carrot = Carrot3, - Pumpkin = Pumpkin3, - Clouds = Clouds3, - Silver = Silver3, - Concrete = Concrete3, - Asbestos = Asbestos3, - WetAsphalt = WetAsphalt3, - MidnightBlue = MidnightBlue3, + Pomegranate = Pomegranate6, + Alizarin = Alizarin6, + Amethyst = Amethyst6, + Wisteria = Wisteria6, + BelizeHole = BelizeHole6, + PeterRiver = PeterRiver6, + Turquoise = Turquoise6, + GreenSea = GreenSea6, + Nephritis = Nephritis6, + Emerald = Emerald6, + Sunflower = Sunflower6, + Orange = Orange6, + Carrot = Carrot6, + Pumpkin = Pumpkin6, + Clouds = Clouds6, + Silver = Silver6, + Concrete = Concrete6, + Asbestos = Asbestos6, + WetAsphalt = WetAsphalt6, + MidnightBlue = MidnightBlue6, }; // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 6683346eeb..deb0dfb6dd 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -45,7 +45,6 @@ namespace Avalonia.Controls.Primitives private bool _updatingColor = false; private bool _updatingHsvColor = false; - private bool _coercedInitialColor = false; private bool _isPointerPressed = false; private bool _shouldShowLargeSelection = false; private List _hsvValues = new List(); @@ -622,7 +621,7 @@ namespace Avalonia.Controls.Primitives // that no color has been selected by the user. Note that #00000000 is different than // #00FFFFFF (Transparent). // - // In this situation, the first time the user clicks on the spectrum the third + // In this situation, whenever the user clicks on the spectrum, the third // component and alpha component will remain zero. This is because the spectrum only // controls two components at any given time. // @@ -633,16 +632,19 @@ namespace Avalonia.Controls.Primitives // though the desired value is simply full color. // // To work around this usability issue with an initial #00000000 color, the selected - // color is coerced (only the first time) into a color with maximum third component - // value and maximum alpha. This can only happen once and only if those two components - // are already zero. + // color is coerced into a color with maximum third component value and maximum alpha. + // This can only happen here in the spectrum if those two components are already zero. + // + // In the past this coercion was restricted to occur only one time. However, when + // ColorPicker controls are re-used or recycled #00000000 can be set multiple times. + // Each time needs this special logic for usability so now anytime the color is + // changed on the spectrum this logic will run. // // Also note this is NOT currently done for #00FFFFFF (Transparent) but based on // further usability study that case may need to be handled here as well. Right now // Transparent is treated as a normal color value with the alpha intentionally set // to zero so the alpha slider must still be adjusted after the spectrum. - if (!_coercedInitialColor && - IsLoaded) + if (IsLoaded) { bool isAlphaComponentZero = (alpha == 0.0); bool isThirdComponentZero = false; @@ -691,8 +693,6 @@ namespace Avalonia.Controls.Primitives newHsv.H = 360.0; break; } - - _coercedInitialColor = true; } } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 96d072260c..d28c0969c4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -1103,6 +1103,16 @@ namespace Avalonia.Controls get; set; } + + /// + /// Gets or sets an object associated with this column. + /// + public object Tag + { + get; + set; + } + /// /// Holds a Comparer to use for sorting, if not using the default. /// diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs index 76b56f3a11..f9542c883a 100644 --- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs @@ -6,6 +6,7 @@ namespace Avalonia.Controls.Primitives { public class OverlayLayer : Canvas { + protected override bool BypassFlowDirectionPolicies => true; public Size AvailableSize { get; private set; } public static OverlayLayer? GetOverlayLayer(Visual visual) { diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 0c9bb89caa..4029782772 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -216,7 +216,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// /// If the adjusted position also ends up being constrained, the resulting position of the /// FlipX adjustment will be the one before the adjustment. - /// /// + /// FlipX = 4, /// diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 57d709ba94..ea420c7c45 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -668,17 +668,7 @@ namespace Avalonia.Controls if (HasComplexContent) { - if (_textRuns != null) - { - foreach (var textRun in _textRuns) - { - if (textRun is EmbeddedControlRun controlRun && - controlRun.Control is Control control) - { - VisualChildren.Remove(control); - } - } - } + VisualChildren.Clear(); var textRuns = new List(); diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 7bf8d3bb68..56a5999f29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -84,6 +84,11 @@ namespace Avalonia.Controls /// /// Gets or sets a value indicating whether to automatically scroll to newly selected items. /// + /// + /// This property is of limited use with as it will only scroll + /// to realized items. To scroll to a non-expanded item, you need to ensure that its + /// ancestors are expanded. + /// public bool AutoScrollToSelectedItem { get => GetValue(AutoScrollToSelectedItemProperty); @@ -353,9 +358,13 @@ namespace Avalonia.Controls SelectedItemsAdded(e.NewItems!.Cast().ToArray()); - if (AutoScrollToSelectedItem) + var selectedItem = SelectedItem; + + if (AutoScrollToSelectedItem && + selectedItem is not null && + e.NewItems![0] == selectedItem) { - var container = ContainerFromItem(e.NewItems![0]!); + var container = TreeContainerFromItem(selectedItem); container?.BringIntoView(); } @@ -531,6 +540,12 @@ namespace Avalonia.Controls // The IsSelected property is not set on the container: update the container // selection based on the current selection as understood by this control. MarkContainerSelected(container, SelectedItems.Contains(item)); + + // If the newly realized container is the selected container, scroll to it after layout. + if (AutoScrollToSelectedItem && SelectedItem == item) + { + Dispatcher.UIThread.Post(container.BringIntoView, DispatcherPriority.Loaded); + } } /// diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index eff190c39e..c248116614 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Text; using Avalonia.Controls; +using Avalonia.Controls.Embedding.Offscreen; using Avalonia.Controls.Platform; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -13,6 +14,9 @@ namespace Avalonia.DesignerSupport public class DesignWindowLoader { public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath) + => LoadDesignerWindow(xaml, assemblyPath, xamlFileProjectPath, 1.0); + + public static Window LoadDesignerWindow(string xaml, string assemblyPath, string xamlFileProjectPath, double renderScaling) { Window window; Control control; @@ -96,6 +100,9 @@ namespace Avalonia.DesignerSupport window = new Window() {Content = (Control)control}; } + if (window.PlatformImpl is OffscreenTopLevelImplBase offscreenImpl) + offscreenImpl.RenderScaling = renderScaling; + Design.ApplyDesignModeProperties(window, control); if (!window.IsSet(Window.SizeToContentProperty)) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index b6c0c3ae3d..9463224b99 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,8 +67,8 @@ namespace Avalonia.DesignerSupport.Remote { _transport.Send(new RequestViewportResizeMessage { - Width = clientSize.Width, - Height = clientSize.Height + Width = Math.Ceiling(clientSize.Width * RenderScaling), + Height = Math.Ceiling(clientSize.Height * RenderScaling) }); ClientSize = clientSize; RenderAndSendFrameIfNeeded(); diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 313063269b..6a6bc8c746 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -1,13 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Net; using System.Reflection; using System.Threading; -using System.Xml; using Avalonia.Controls; using Avalonia.DesignerSupport.Remote.HtmlTransport; -using Avalonia.Input; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Viewport; @@ -20,6 +17,7 @@ namespace Avalonia.DesignerSupport.Remote private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats; private static ClientViewportAllocatedMessage s_viewportAllocatedMessage; private static ClientRenderInfoMessage s_renderInfoMessage; + private static double s_lastRenderScaling = 1.0; private static IAvaloniaRemoteTransportConnection s_transport; class CommandLineArgs @@ -226,6 +224,9 @@ namespace Avalonia.DesignerSupport.Remote } if (obj is UpdateXamlMessage xaml) { + if (s_currentWindow is not null) + s_lastRenderScaling = s_currentWindow.RenderScaling; + try { s_currentWindow?.Close(); @@ -237,7 +238,7 @@ namespace Avalonia.DesignerSupport.Remote s_currentWindow = null; try { - s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, xaml.XamlFileProjectPath); + s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, xaml.XamlFileProjectPath, s_lastRenderScaling); s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()}); } catch (Exception e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml new file mode 100644 index 0000000000..7162be888f --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs new file mode 100644 index 0000000000..268e42cff3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs @@ -0,0 +1,121 @@ +using System; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using Avalonia.Media.Immutable; + +namespace Avalonia.Diagnostics.Controls +{ + [TemplatePart("PART_ClearButton", typeof(Button))] + partial class BrushEditor : TemplatedControl + { + private readonly EventHandler clearHandler; + private Button? _clearButton = default; + private readonly ColorView _colorView = new() + { + HexInputAlphaPosition = AlphaComponentPosition.Leading, // Always match XAML + }; + + public BrushEditor() + { + FlyoutBase.SetAttachedFlyout(this, new Flyout { Content = _colorView }); + _colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor); + clearHandler = (s, e) => Brush = default; + } + + protected override Type StyleKeyOverride => typeof(BrushEditor); + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + if (_clearButton is not null) + { + _clearButton.Click -= clearHandler; + } + _clearButton = e.NameScope.Find