Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build-in support for Icons #4087

Open
maxkatz6 opened this issue Jun 7, 2020 · 16 comments
Open

Build-in support for Icons #4087

maxkatz6 opened this issue Jun 7, 2020 · 16 comments
Labels

Comments

@maxkatz6
Copy link
Member

maxkatz6 commented Jun 7, 2020

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:

  • AvaloniaIcon / IconBase / IconSource / IconElement - base class for all icons, that will be used as property type for control properties.
  • BitmapIcon / BitmapIconSource - specific icon type with UriSource/Source property to render raster image as icon.
  • FontIcon - specific icon with Glyph (or Text?) and FontFamily properties. Probably should has similar to TextBlock set of properties.
  • PathIcon - specific icon with Data property.
  • (optional) SymbolIcon - specific icon with Symbol enum property. Could be used to display well known icons. Probably it shouldn't be build-in, but allowed to implement using FontIcon/PathIcon as third party library (using FontAwesome for example).
  • SvgIcon - svg is not supported in Avalonia, but third party lib could implement it with Svg.Skia.Avalonia
@maxkatz6
Copy link
Member Author

maxkatz6 commented Jun 7, 2020

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).

  1. 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: microsoft/microsoft-ui-xaml#1494

@kekekeks
Copy link
Member

kekekeks commented Jun 7, 2020

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
Copy link
Collaborator

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
Copy link
Member

kekekeks commented Jun 7, 2020

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
Copy link
Member Author

maxkatz6 commented Aug 17, 2020

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
Copy link
Contributor

Splitwirez commented Jan 21, 2021

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
Copy link
Member Author

@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
Copy link
Member Author

maxkatz6 commented Jan 23, 2021

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
Copy link
Member Author

maxkatz6 commented Jan 23, 2021

@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
Copy link
Contributor

robloo commented Jun 3, 2021

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

  • Improved compatiblity with WinUI
  • Forces a sorely needed standardization in Avalonia: "Right now we have NativeMenuItem.Icon that is a Bitmap. We have Window.Icon that is also a Bitmap....We also have MenuItem.Icon that is an object." This will help in the future with other controls such as CommandBar as well if they ever come over from WinUI.
  • Provides a clear distinction in the API where Icon's are required. Avalonia, needs to be aware of 'icons' separately from 'bitmaps'. Icons are a subset of Bitmaps but for very specific usages: like menus and indication UI elements. Conceptually these are different things when authorizing controls or UI's. Additionally, Icons are also usually vector graphics and automatically size/scale to design requirements.
  • Can be used to automatically rasterize vector icons in the needed scale and resolution for native menu integration. Perhaps Icons should be drawing themselves as "Basic Control" to solve the export issue similar to @Gillibald and @amwx suggestion.
  • Improves developer speed (once you get the hang of it, this is an improvement in UWP over WPF)

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
Copy link
Contributor

@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
Copy link
Contributor

robloo commented Jun 3, 2021

@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
Copy link
Contributor

robloo commented Oct 18, 2021

@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
Copy link
Contributor

@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
Copy link
Contributor

robloo commented Oct 20, 2021

@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?

amwx/FluentAvalonia#47

@maxkatz6
Copy link
Member Author

maxkatz6 commented Jan 10, 2024

Might be interesting dotnet/wpf#8647
I still should have a branch somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants