mgarin / weblaf

WebLaF is a fully open-source Look & Feel and component library written in pure Java for cross-platform desktop Swing applications.
http://weblookandfeel.com
GNU General Public License v3.0
1.13k stars 234 forks source link

XML style from String #584

Closed husker-dev closed 4 years ago

husker-dev commented 4 years ago

I'm creating WebLaF style editor and I don't know how to apply skin to component without creating .xml file. How can I do it?

mgarin commented 4 years ago

All styles that are read from XML in runtime are stored in SkinInfo data objects contained in each separate XmlSkin or XmlSkinExtension instance.

SkinInfo is the main class that contains both raw and compiled ComponentStyle objects. Raw ones are exactly the barebone styles read from XML files "as is" (except that includes are all compiled into a single list of styles in the order they were provided in XML). Compiled ones contain all information required for the final rendering of components - so they already contain all information from styles they extend or override.

Compillation of loaded raw styles occurs first time someone requests any ComponentStyle from the SkinInfo instance, so it has somewhat lazy initialization.

Classes for reference:

I also have some major changes for those classes locally, but mostly unrelated to how styles are loaded. So complexity is not going anywhere yet, just some internal refactoring and improvements.


So, back to the question:

how to apply skin to component without creating .xml file

Unfortunately it isn't possible right now because of the complexity of current implementation - there is no simple way to add more styles on top of loaded ones because of possible errors it might cause (and will cause in some specific cases).

As one of examples - overriding existing style have to force a complete skin & extensions reload, otherwise it will simply not function correctly. There is no "nice" way to do that in the current implementation.

Another example - adding new style requires it to be compiled separately, but using existing styles as dependencies which is currently implemented through extensions which can be loaded in runtime, but this is a one-time thing with no rollback options - once styles with specific identifiers are loaded from the extension and compiled into the main skin they cannot be removed or replaced by another extension. So while that works perfectly fine for extensions - it won't work for a style editor application as you will most probably try loading the same style multiple times and it will be rejected by the skin since it already has style with that identifier.

So as it currently stands - any change in styles in runtime requires at least a full recompile of all styles from the skin and its extensions (which takes a lot of time) and there is no public API available to do it either.


That being said - you might have noticed that there is a working StyleEditor component/frame available in weblaf-ui module and it actually allows editing styles in runtime.

That thing is using a hack I've made specifically for StyleEditor years ago:

Basically this XStream converter written for SkinInfo class allows providing custom XML text for particular includes ( SkinInfoConverter.addCustomResource ( ... ) ) and uses them whenever some skin is loaded. It doesn't check what type of skin is loaded, whether include actually fits it, whether there are any other issues with structure or not etc. It's just a dirty hack for temporary StyleEditor functioning, nothing more.

So StyleEditor simply uses that hack for providing edited text of specific includes from the skin in runtime and reloads skin completely to display updates. It is a very slow and inefficient approach and it has a lot of drawbacks, so I didn't update it a lot since it was first added in the library for testing purposes.

I already do have plans for the StyleEditor for the v1.2.12 update though: #540

This will most probably include revamping the way styles can be loaded and a lot of internal improvements to make styles API actually useful outside of the internal tools, so my general recommendation is to wait until that particular enhancement is out.

I am currently wrapping up some big changes for v1.2.11 and it will most probably be released this or next week and I'll be moving onto v1.2.12 at that point. So while that particular issue (#540) is probably not going to be implemented first, it is something I will be working on in the near future and there I will be able to provide a better API for runtime style manipulations along with it.

mgarin commented 4 years ago

TL;DR: There is no public API to do what you asked currently and there are a lot of issues with implementing it, so it can't be done easily. That being said - I do have plans to add it in near future, specifically for creating a better implementation of StyleEditor included in WebLaF library, so stay tuned for updates in #540

husker-dev commented 4 years ago

You wrote that you used it in your StyleEditor

SkinInfoConverter.addCustomResource ( ... )

But what then does it?

SkinInfo skinInfo = XmlUtils.fromXML(text);
StyleManager.setSkin(new XmlSkin(skinInfo));

Why can't I just put skin in XmlUtils.fromXML(text)?

mgarin commented 4 years ago

You can put your skin there for sure, but it needs to be complete in that case. Complete means that it must have ALL styles that each skin have to support to be valid - meaning you need to do either of two things:

  1. Provide ALL styles directly in that text - which is quite unreasonable as there are hundreds of styles that are required by default, you can check StyleId class to see the full list. Although you don't necessarily need all of them - once you encounter any component that asks non-existing style you will receive an exception.

  2. Have include of existing skin that is available in runtime (like WebLightSkin) to cover all required styles right away. Then add any custom styles in the very same text, for instance something like this should work (but not guaranteed):

    
    <skin xmlns="http://weblookandfeel.com/XmlSkin">
    
    <!-- Skin settings -->
    <id>example.skin</id>
    <class>some.existing.Class</class>
    <supportedSystems>all</supportedSystems>
    
    <!-- Including WebLaF default skin, will use its style as a base -->
    <include nearClass="com.alee.skin.light.WebLightSkin">resources/web-light-skin.xml</include>
    
    <!-- Custom styles here -->
    ...


First big problem here - you need that `some.existing.Class` to exist in runtime. It can be whatever, but it will be used for locating includes that don't have `nearClass` attributes and for skin icon. That means there is basically no good way to load a multi-file skin from text in runtime without hacking with `SkinInfoConverter.addCustomResource ( ... )` and even then there are some caveats.
mgarin commented 4 years ago

So the reason my answer was

there is no public API to do what you asked currently

Isn't because there really isn't any way at all, but because I cannot guarantee it will work properly - most probably it won't and you will encounter some issues along the way, sooner or later.

Basically current API was not really meant to load skins from text, but only from files within your application JAR.

husker-dev commented 4 years ago

Perhaps this is not very related to the question, but <include nearClass="com.alee.skin.light.WebLightSkin"> in current WebLaF version should be <include nearClass="com.alee.skin.web.WebSkin">.

Also this issue is in Style introduction in Wiki (https://github.com/mgarin/weblaf/wiki/Styling-introduction)

mgarin commented 4 years ago

It's actually already adjusted (in that wiki article) for v1.2.11 as the update is coming this week and WebLightSkin is already available in SNAPSHOT builds.

Was doing it in advance as I was pushing some big changes recently and writing this article about IconManager: https://github.com/mgarin/weblaf/wiki/How-to-use-IconManager Otherwise I'm pretty sure I would've forgotten it later on with all the stuff that needs to be done.

But yes, in the previous update you would've needed this line:

<include nearClass="com.alee.skin.web.WebSkin">resources/skin.xml</include>

to include the default light skin.

mgarin commented 4 years ago

I'll close this as I guess the original question was answered.

I'm also working on the style loading fixes & performance improvements - those will be coming in v1.2.12 at the end of this month, but there are separate opened issues for that.