FluentAvalonia icon indicating copy to clipboard operation
FluentAvalonia copied to clipboard

Soften Button Border Brushes

Open robloo opened this issue 2 years ago • 7 comments

Describe the bug

Fluent v2 uses Acrylic Brushes for various borders -- including Button. Avalonia does not currently support Acrylic. Therefore, my assumption is FluentAvalonia uses the fallback values.

This results in button borders that are "too sharp" in dark theme (likely light as well).

To fix this, I would like to pre-blend the button border brush with the background brush taking into account the acrylic transparency as if it was there. This pre-blended color value would be the new border brush value. This means it would differ from WinUI numerically but look more visually similar (softening the button edges from a design standpoint).

Screenshots

WinUI (using UWP, WinUI 2 Library, Windows 10 Pro):

image

FluentAvalonia:

image

Desktop/Platform (please complete the following information):

  • OS: [e.g. Windows (11/10), linux (distro), osx, wasm, etc]
  • FluentAvalonia Version [e.g. 1.0.0, or latest]
  • Avalonia Version [e.g. 0.10.10]

Additional context

I will fix this once direction is agreed.

robloo avatar Dec 27 '22 23:12 robloo

Actually, Buttons don't use an acrylic brush. IIRC in Fluent v2, Acrylic brushes are only used in overlays (FlyoutPresenter, e.g.).

ButtonBorderBrush maps to ControlElevationBorderBrush which is a LinearGradientBrush composed of ControlStrokeColorSecondary and ControlStrokeColorDefault colors

I think the actual issue here is the use of BackgroundSizing=InnerBorderEdge in WinUI, which isn't available in Avalonia. A quick test modifying the template to place the ContentPresenter in FABorder with the background sizing set results in this, which looks much better. image

amwx avatar Dec 28 '22 21:12 amwx

Yea, sorry, I should have looked at the XAML and instead just made a guess. What you found makes sense though and the TestButton looks perfect. The good news is this is a lot "cleaner" to fix than customizing a bunch of colors.

How do you want to go about this change? Would you like me to open a PR switching to FABorder in all related control templates? This affects ComboBox, CalendarDatePicker, etc. as well.

I haven't looked at your FABorder implementation but I'm assuming it's fine for use in places like this.

robloo avatar Dec 29 '22 02:12 robloo

I think the best solution to this is to get BackgroundSizing added upstream to Border and ContentPresenter (and maybe TemplatedControl, which would match all the UWP equivalents). I'd rather not add the FABorder to all controls (that need it) because: 1- While I don't really care about diverging templates from WinUI, I do prefer to keep things as simple as possible and keep the VisualTree as compressed as possible. And since this is a minor visual issue (at least to me), I don't view the extra template item worth it 2- FABorder isn't perfect in how it renders, particularly when there's a corner radius involved:

image

This is an extreme example with the contrasting colors (and zoomed in) and is a bit more subtle with the softer fluent colors, but highlights the slightly incorrect radii that doesn't match between the border and background geometry. An official implementation would need to fix this (and since geometry and me don't get along, it's not something I'm looking to do)

amwx avatar Dec 29 '22 03:12 amwx

I think the best solution to this is to get BackgroundSizing added upstream

I've been thinking about this for a few months. Unfortunatley I don't think it's straightforward at all.

  • In Windows, I think GDI and direct X just handles this behind the scenes. There is no need do anything but pass the properties down into the renderer.
  • In Skia itself, this is NOT supported. Strokes are drawn on the edge of the shape itself. This means half the stroke extends past the shape and half the stroke is inside the shape. It's right on the middle. Fundamentally, THIS is the issue. I have no idea how Skia gets away with this since the shape bounds (width/height) I don't think take into account the stroke width... I have to look into that more.
  • Since this isn't in Skia, how can it be added to Avalonia? Well, we have to do SEPARATE geometries for the shape fill (background) and then the shape stroke (border). These are complex geometries now taking into account different thicknesses and corner radius.
  • Since we have to use complex geometry to get this to work... performance is a big question. This is low level how all of Avalonia is composed so if we stop using rectangles for everything and now have geometries I have to think performance is going to be noticeably worse.
  • Now we have the composition renderer and there was an attempt to fix a few things with corner radius by moving calculations down into the composition system. That still has issues https://github.com/AvaloniaUI/Avalonia/pull/9488. There may be more tricks used there so I need to look at that code some more.

Bottom line, as you know, this is not an easy fix and goes pretty deep. If it was easier I'm sure it would have been done by now. I will open an issue and start poking around with things upstream though. This has to get solved sometime and 11.0 is really the best time.

robloo avatar Dec 29 '22 14:12 robloo

  • In Skia itself, this is NOT supported. Strokes are drawn on the edge of the shape itself. This means half the stroke extends past the shape and half the stroke is inside the shape. It's right on the middle. Fundamentally, THIS is the issue. I have no idea how Skia gets away with this since the shape bounds (width/height) I don't think take into account the stroke width... I have to look into that more.

Yes, that's is the issue - specifically its that Skia allows a combined drawing operation - which makes it very easy to overlook the possible issues of not adjusting the geometry between the fill & draw.

  • In Windows, I think GDI and direct X just handles this behind the scenes. There is no need do anything but pass the properties down into the renderer.

Direct2D (and I believe GDI too) doesn't do "combined" draw calls like Skia does, you have to render the stroke and fill with 2 separate draw calls:

m_d2dContext.FillRectangle(&rect, pbrush);
m_d2dContext.DrawRectangle(&rect, pbrush);

However, if you don't adjust the geometry after Fill, you end up getting the Skia default equivalent: image

  • Since this isn't in Skia, how can it be added to Avalonia? Well, we have to do SEPARATE geometries for the shape fill (background) and then the shape stroke (border). These are complex geometries now taking into account different thicknesses and corner radius.

BorderRenderHelper class in Avalonia uses the complex render path (2 geometries) already if:

  • Non-uniform border thickness is detected - as that's not supported in any drawing API afaik
  • Non-uniform corner radii, if the platform doesn't support that

For the non-uniform corner radii: Skia's RoundedRectangle class supports 4 unique corners. In DirectX, D2D1_ROUNDED_RECT only supports RadiusX and RadiusY which means even in DirectX, complex geometry is needed for the 4 corners. So, even WinUI has to be using 2 geometries for this case, since Direct2D is what's under the hood of the compositor.

The other issue is that because of this, Avalonia's border rendering is different whether the simple or complex path is taken - the simple path uses Skia's default and the complex path is equivalent to InnerBorderSizing (I think).

Obviously there's a perf impact in taking the complex render path, but I don't see anyway around this. With all the transparency in modern UI themes, drawing a stroke "centered" is obviously not the right approach. We'll probably need some benchmarks in a "complex-ish" UI to see how bad this would be, particularly on non-desktop devices Avalonia supports.

amwx avatar Dec 29 '22 21:12 amwx

I wonder if this issue also explains this artifact: image (left - FluentAvalonia, right - Windows Settings)

Leon99 avatar Jul 26 '23 09:07 Leon99

@Leon99 Do you still your issue above with the v2.1.0 previews?

robloo avatar Apr 13 '24 20:04 robloo