Open maxkatz6 opened 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:
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).
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
Note, that icons should be somewhat compatible with exportable menus. So we need a way to rasterize those as PNG on the UI thread.
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.
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.
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.
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 Template
s for these icons, to ensure that the icons themselves could be made reusable. I find that DrawingGroup
s offer less flexibility and are more difficult and annoying to create than Template
s.
Thus I feel that having the option to use Template
s for icons is a good thing, however I'm not sure that requiring the use of Template
s 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:
0
would look stupid in 99% of use cases, but setting it to anything else seemed...somehow wrong.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.
@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.
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.
@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.
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.
@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?
@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.
@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 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?
@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?
Might be interesting https://github.com/dotnet/wpf/issues/8647 I still should have a branch somewhere.
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:
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: