AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
24.54k stars 2.12k forks source link

Build-in support for Icons #4087

Open maxkatz6 opened 4 years ago

maxkatz6 commented 4 years ago

If we want to create some templated menu that should also support icons, or some kind of NavigationView with icons, we would need to provide some way to customize/define those icons.

Problem:

  1. There is no any way to define abstract Icon property on standard/custom control, that could have any icon type and source. (Must)
  2. Make API expandable for third-party libraries. For example, there should be possible to create custom FontAwesomeIcon type delivered from Avalonia's abstract Icon type. (Should)
  3. There is no way to share specific icon of unknown type (bitmap/font...) as resource, that could be reused between controls. (Could)

Current solution:

Use either Image, Viewbox + Path, TextBlock + specific font. It isn't possible to provide developer-friendly way to change icon with a property. Upd: actually, it is possible with Control or object, which could be used inside of ContentPresenter.

Possible icon types:

maxkatz6 commented 4 years ago

Actually it is inspired with UWP icons. But I am not sure, if it should be used as reference, because its API is quite messy.

There are two common ways to do the think:

  1. Use classes inherited from IconElement which could be rendered as visual element. If we have property "IconElement Icon { get; set; }", then we can write following xaml:

    <CustomControl.Icon>
    <SymbolIcon Symbol="Refresh" />
    </CustomControl.Icon>

    And in CustomControl,xaml:

    <ContentPresenter Content="{TemplatedBinding Icon}" />

    With that approach everything looks good until we need to share icon between controls (we can't share visual element between parents).

  2. Use metadata-like classes inherited from IconSource, which could not be rendered, but could be used in multiple places using special visual control IconSourceElement (which in fact inherits IconElement from # 1). So code will looks differently with "IconSource Icon { get; set; }":

    <CustomControl.Icon>
    <SymbolIconSource Symbol="Refresh" />
    </CustomControl.Icon>

    And in CustomControl,xaml:

    <IconSourceElement IconSource="{TemplatedBinding Icon}" />
    <!-- And we can add second one -->
    <IconSourceElement IconSource="{TemplatedBinding Icon}" />

Apart from "API dualism" there are another disadvantages with inconsistency: https://github.com/microsoft/microsoft-ui-xaml/issues/1494

kekekeks commented 4 years ago

Note, that icons should be somewhat compatible with exportable menus. So we need a way to rasterize those as PNG on the UI thread.

MarchingCube commented 4 years ago

Perhaps we can require icon classes to implement a method to obtain a png. I guess in most of the vector cases you can render the control itself and return the result bitmap.

In my project we have a custom icon presenter which is very similar to symbol icons. For databinding purposes you generally want to use some kind of resource identifier and then resolve geometry in the control itself.

kekekeks commented 4 years ago

most of the vector cases

Those might be using bitmap-based brushes. We kinda want to have bitmaps to live in GPU memory, so rendering such icons will either require locking the GPU context or have to be async.

maxkatz6 commented 3 years ago

Xamarin.Forms also has something similar - https://github.com/IeuanWalker/Xamarin.Forms.Breadcrumb#separator-customization Control has ImageSource abstract property, and developer can set FileImageSource or UriImageSource or FontImageSource, that are build in XF.

Interesting that ImageSource property is used in Image class, so you can set FontImageSource in the image.

Splitwirez commented 3 years ago

Figure I may as well add my two cents on this...for my Mechanism for Avalonia library, I implemented an icon property for all controls...I decided to use Templates for these icons, to ensure that the icons themselves could be made reusable. I find that DrawingGroups offer less flexibility and are more difficult and annoying to create than Templates.

Thus I feel that having the option to use Templates for icons is a good thing, however I'm not sure that requiring the use of Templates for icons is such a good idea. Some apps use icons in, for example, .PNG or .SVG format, and it would hardly surprise me if someone has even tried using .ICO icons in their app's UI. Is it possible to somehow create one property that could accept either a Template or* a resource URI (or whatever those avares:// things are)? If so, this might just be the way to go as far as I'm concerned.

In addition to this, I also toyed with implementing an IconGap property (of type double), which would allow the app developer to externally control how much space lies between the icon and the control's other content (if it has any). This seemed like a good idea on paper at first, but I quickly realized two big problems with it:

  1. What should the default value be? 0 would look stupid in 99% of use cases, but setting it to anything else seemed...somehow wrong.
  2. ...awkwardly, I've somehow managed to forget what the second problem was while typing this message out. I'll edit this message to add it if and/or when I remember what it was.

Unfortunately, since Mechanism is only a library which augments Avalonia with some new controls and such, I was forced to implement this as an Attached Property, which is...not ideal, as far as I'm concerned.

That same inability to add non-attached properties to existing controls also prevented me from doing something which I strongly believe should be done for a built-in implementation of icons: HasContent and HasIcon properties (of type bool), the values of which would effectively represent Content != null and Icon != null respectively. Instead of properties, these could alternatively take the form of pseudoclasses, e.g. :hasContent and :hasIcon or something like that (not sure about the capitalization). This would allow styles to determine if the control has content and/or an icon, which is useful for determining whether or not to add extra space between the content and the icon, and could potentially have other less obvious uses as well.


Another thing I'd wondered about is which controls should have this property. Besides the obvious answer of "all controls that have a Content property", here are a few contenders that came to mind:

a) Controls that inherit from Button b) HeaderedContentControl, HeaderedItemsControl and other similarly..."headered" controls c) ListBoxItem, TabItem, and other such "item" controls d) Some combination of the above

Thoughts?


Side note: If and/or when a decision is made on this topic, I'd be happy to take a crack at implementing it.

maxkatz6 commented 3 years ago

@Splitwirez I have implementation based on icons from UWP - https://github.com/AvaloniaUI/Avalonia/tree/feature/icons

In master branch we already have IconElement and PathIcon. I have added FontIcon/FontIconSource, ImageIcon/ImageIconSource (for now I ignored BitmapIcon from UWP, because I don't see any advantages in it comparing new ImageIcon, see ImageIcon specs) and PathIconSource.

PathIcon/FontIcon/ImageIcon are simple templated controls, that can't be reused neither in UWP nor in Avalonia. And FontIconSource/ImageIconSource/PathIconSource exist specifically for cases when it's expected from API to accept only reusable icons, these types are simple dependency objects with its specific properties and IconElementTemplate.

IconSource approach similar to your Template approach, but with more specific API, that doesn't allows anything, and in same type it's possible to create TypeConverter that will convert "avares://" paths to the ImageIcon or ImageIconSource.

And unlikely UWP implementation, in my branch it's possible to create new IconElement/IconSource pairs (for example, some specific types for FontAwesome icons, that shouldn't be included in the Avalonia repo, but must be possible to implement as third party library). In UWP implementation there is no IconElementTemplate, but only hardcoded switch-case for well known icon types.

In addition to this, I also toyed with implementing an IconGap

What difference between IconGap and Margin? If it's related to the content near icon, probably it shouldn't be included in the Icon type itself.

Besides the obvious answer of "all controls that have a Content property"

It's not so obvious for me. Why all controls with Content property should have an icon? I.e. in case of Button you should be able to put your icon inside of Content property, or combine icon with another content with StackPanel (or any other panel). There might be some extended controls like UWP.AppBarButton that has Icon and Label properties (instead of single Content). Looking on current Avalonia's controls set, I have concerns with MenuItem, that already has "object Icon" and allows any controls to be included. Moreover, it can't be just replaced with IconElement type (ignoring breaking changes), because in its current implementation it's the only single way to implement toggle/radio MenuItem using that Icon property. In other worlds it will require some additional work on MenuItem before.

maxkatz6 commented 3 years ago

Also it would be nice to have AnimatedIcon in the future, see specs and implementation from WinUI repo. But before that we need working Lottie in the avalonia.

maxkatz6 commented 3 years ago

@kekekeks about rendering these icons to achieve bitmap source usable with native menus. From my vision I see only one way with using ImageIconSource.Source if it's a image icon, or rendering that icon as a control to the bitmap otherwise. Do you have any thought how we can avoid "requiring locking the GPU context" or to make it async?

Upd: not to mention, it's probably never be possible to make animated icon work with native menus, unless using its first frame as an image.

robloo commented 3 years ago

So a summary of reasons Icon (at least FontIcon, PathIcon, BitmapIcon etc.) are needed in my opinion:

SymbolIcon as @maxkatz6 stated elsewhere is better left to 3rd parties libraries for several reasons: package size, font licensing, variations, keeping up-to-date, etc.

Splitwirez commented 3 years ago

@robloo About SymbolIcon...if that's referring to doing font icon-type stuff, I feel like it might be beneficial to have a built-in SymbolIcon, which...isn't tied to a specific font or anything like that - that way, Avalonia still doesn't have to deal with font licensing, but third-party devs won't be at risk of ending up producing a bunch of different implementations that are a chore to switch between. Just imagine, being able to set the icon font in a Style or Binding or something like that...thoughts?

robloo commented 3 years ago

@Splitwirez Yes, SymbolIcon (as implemented in UWP) is very similar to a FontIcon. However, you do not manually specify the glyph as a Unicode value. Instead, you specify the symbol directly as an enum value. (https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.symbolicon.symbol?view=winrt-20348).

SymbolIcon is not nearly as general-purpose as FontIcon and requires a large enum of pre-defined symbols corresponding to a symbol font (as in UWP). I don't think it's a good idea to bring all of this into Avalonia considering Avalonia does not have a standard set of symbols or a symbols font like Windows does. It's better to leave it up to 3rd parties who may implement this differently using LineAwesome, the newly public fluent icons, or even the Uno Platforms Segoe UI equivalent font.

Your point where it would be a good idea to abstract this in Avalonia: Avalonia would have pre-defined symbols for zoom_in, add, remove, etc... would be nice. However, again, that would require some default implementation of these symbols in Avalonia itself and the above mentioned issues.

SymbolIcon is also something that could be added in the future without undoing anything discussed here. It is probably better to wait on this no matter how you look at it.

robloo commented 2 years ago

@Splitwirez FluentAvalonia is updating the SymbolIcon to support what we discussed here and then some. It has a lot of symbols (505 total). It might be good to try that out for your use cases and give feedback over on the other repo. If it becomes widely used perhaps SymbolIcon could come into Avalonia itself based on FluentAvalonia's implementation. Glyphs themselves are fully open source and so is the new font used to implement them.

Splitwirez commented 2 years ago

@Splitwirez FluentAvalonia is updating the SymbolIcon to support what we discussed here and then some. It has a lot of symbols (505 total). It might be good to try that out for your use cases and give feedback over on the other repo. If it becomes widely used perhaps SymbolIcon could come into Avalonia itself based on FluentAvalonia's implementation. Glyphs themselves are fully open source and so is the new font used to implement them.

Oh nice, thanks for the heads-up.

So uh...is that on the https://github.com/amwx/FluentAvalonia/tree/IconElement-fixes branch, or is there somewhere else I should be looking?

robloo commented 2 years ago

@Splitwirez

So uh...is that on the https://github.com/amwx/FluentAvalonia/tree/IconElement-fixes branch, or is there somewhere else I should be looking?

https://github.com/amwx/FluentAvalonia/pull/47

maxkatz6 commented 5 months ago

Might be interesting https://github.com/dotnet/wpf/issues/8647 I still should have a branch somewhere.