CSS on javaFX
Daniel Zwolenski
zonski at googlemail.com
Wed Apr 18 00:39:05 PDT 2012
Hi Richard,
Some thoughts (I'm in a remote outback town, on a temperamental connection, on my phone so apologies for the dodgy pseudo code and looseness of this email).
There are two parts to CSS. The first are the 'attributes' which are just a bunch of settings - a kind of 'display model'. The second is the whole selector/class/specifier stuff that is used to derive the applicable attributes for each Control.
I'm not a fan of CSS selectors and the 'cascading' part of it all. I'd personally prefer this whole part to go away. It is way too loose, fragile and error prone for my liking. One man's trash is another man's treasure though, and what I dislike, for its looseness, others seem to love for it's dynamicness.
So assuming CSS selectors are here to stay, I'd prefer to see an option C that focused less on Control styles and more on an Object Model for the Stylesheets themselves. A kind of DOM for the Stylesheet that we can read and manipulate.
So to set a Stylesheet on a scene we would do:
Stylesheet sheet = Stylesheet.load("mystyles.css");
scene.getStylesheets().add(sheet);
And the 'Stylesheet' would allow us to iterate over its styles (and variables, etc):
List<Style> styles = sheet.getStyles();
for(style : styles) {
print(style.getSelectorPath()):
}
Ideally the Style instances would be strongly typed concrete classes, which had specific attributes for each style type, eg LabelStyle would have a getFont/setFont, etc. Unfortunately, CSS is untyped so I don't think this would be possible so then a 'Style' would have to be a Map of values, with enums as keys:
style.getAtrributes().get(StyleAttribute.font);
The values could be just of type Object and we cast to what we expect (eg Font, Color, etc), or we could have a StyleValue class that wrapped these (or even an interface that Color, Font, etc implemented). To implement variables we just have a StyleReference class that extends/implements StyleValue and references the value itself (which is then of type StyleValue as well).
Obviously as well as being able to query the Styles we would be able to edit them at runtime, add new ones, remove them, etc and we would expect the scene to update accordingly (or at worst update after a Scene.reapplyStyles or similar).
Now when we get to a specific control there are two things we have to work with. The first is the Control's own style map, ie those things specifically set on the control directly, not via a Stylesheet. For me this would just be another Style class instance attached to the Control, so instead of doing this:
label.setStyle("-fx-font: arial 16pt");
We could do:
label.getStyleAttributes().put(StyleAttribute.font, new Font("Arial", 16);
This affects only that Control's style though. The Control itself however may have styles derived from its scene's Stylesheets so the other thing we need is a way to query this. Firstly at an individual attribute level:
StyleValue value = StyleHelper.lookup(StyleAttribute.font, mylabel);
Which searches all the applicable styles and finds the relevant font attribute, whether it's from a Stylesheet or the Control's personal map, etc.
But also it would be useful to find all the Style maps that are applicable to a control:
List<Style> styles = StyleHelper.find(myLabel);
And:
List<Style> styles = StyleHelper.find(".customclass");
(some complexities with all the above with pseudo classes but at a surface level I don't think they would be insurmountable)
For nothing else but debugging (working out which attributes were being applied and which styles were being selected) this above stuff would have saved me hours on my last project. It would be useful actually if StyleValue had a link back to its Style, which had a link back to its Stylesheet so you could work backwards up the tree.
Debugging aside it would allow coders much more control. We could build error styles, highlight styles in code and apply them, we could dynamically change fonts and background colors for the whole system or single buttons, etc.
Tooling people could query styles and copy them, etc. I'd envisage a tidy little styling tool that let you build styles and manipulate them at runtime, showing which style classes were having affect, and where, etc. man that would have been useful!
In any case the developer is back in the logic of the styling game and can write code to do what they want, whether that is for good or for evil.
That's my thinking on it anyway. Its very possible I'm simplifying something as I'm not intimate with the CSS implementation details. I admit I haven't had a chance to work through the skin/CSS code in openjfx yet, which might give me more insight, but as a pure consumer of the API, the above sort of thing would let me do what I want to do.
Cheers,
Dan
On 16/04/2012, at 10:36 PM, Richard Bair <richard.bair at oracle.com> wrote:
> Hi Pedro,
>
> I think some of what you are asking for we simply cannot provide without making our Skin implementations public API. Let me explain.
>
> One of the key design principles for the JavaFX UI controls was a clear and strong separation between the View and the Model of the control. There is a design tension there where, for convenience, people want more visual state available on the Control class, but for flexibility, it is a bad choice to do so. For example, the Swing JButton specified different icons for different states that the button might be in, as public API on the JButton class. Carried to its logical extreme you would have literally hundreds of such APIs.
>
> But even more so, a single control might have radically different views. One use case we used to talk about was a slider. In the normal case there is a track and a knob and you slide the knob within the track. However what if I were doing an application for a Chiropractor, and I wanted the slider to be curved in the shape of the back and the knob to be a vertebra? Or perhaps I will want my slider to be represented as a spinning knob as in A/V equipment, where there isn't a track at all?
>
> We have also noted that a ToggleButton and RadioButton are both actually the same control, but with different skins. The TabPane and the "dot pagination" control (like you might see on an iPhone home screen) are actually the same control, just with different visual representation.
>
> So we wanted to make sure we kept the View/Model separation as strict as possible. In some places we fudged it, such as the "font" on a Label. Some things like the "graphic text gap", "content display" and so forth also made compromises. Whenever such state is added to the control, it restricts the range of choices available to a Skin provider. All skin implementations of a Label (that are meant for a wider audience than just yourself!) have to deal with all the various content display settings and alignment and so forth. In the most strict sense those variables shouldn't have been on our Labeled, but should have been part of the skin and set via CSS (or some other mechanism for styling the skins). So we did make compromises.
>
> So when you are styling things from CSS, sometimes you are styling the control, and sometimes the Skin. And of course from CSS you can also specify the skin!
>
> Our default skin implementations are all designed to be styled from CSS. So here are two options for being able to set the style on a skin from Java code. One is type-safe, one is not.
>
> Option A: Make the Skin APIs public and specify the skin manually
>
> In this option, we would move our default skin implementations to javafx.scene.control.skin.caspian, for example. When you wanted to change the background fills of a button, you could do something like this:
>
> Button button = new Button("Hi");
> ButtonSkin skin = new ButtonSkin(button);
> button.setSkin(skin);
> skin.getBackgrounds().addAll(...);
>
> This approach you can do today, except that ButtonSkin isn't public API so it can (and most likely will) change in incompatible ways as time goes on and the ability to set backgrounds / borders / etc is functionality in Region that doesn't have public API yet. But from an architectural perspective this is doable today. This is the only real way to have what you ultimately want -- the type-safe code friendly way to style things. Now, some designer can come along and still override your settings from CSS, so later on if you assume that this skin is actually set up the way you think it is and somebody has styled it differently from CSS it will break, but that's actually intended behavior -- you the developer might be off working on some other project and some poor designer has to spruce up the UI. So best practice would be not to make assumptions on the view (even what skin is being used!). So if you follow this pattern the best practice would be to set it up and then afterwards make no assumption as to what it actually looks like. But that's up to you, if you make such assumptions, then somebody cannot come later and style it without potentially breaking the app. That's your choice.
>
> Option B: Modify CSS Styles from Java
>
> This option is not type-safe, in fact it is just like in HTML where from JavaScript you can modify the CSS style. We might do it a little differently but the idea is the same. For example:
>
> Button button = new Button("Hi");
> Style style = button.getStyle();
> style.backgrounds.addAll(...);
>
> Or something like that. Such that you can hang any known CSS variables on the style for a single node, and then have some string based implementation for styles that we don't know about (since our CSS allows you to create new "variables" in the CSS just by declaring them). So for example:
>
> style.put("-my-variable-name", "25pt");
> style.fontSize("-my-variable-name");
>
> Or whatnot. This API has not yet been designed, I'm just making this up as I go. But you get the idea -- there would be some kind of API for dealing with common things in a type-safe way (that also allows us to avoid having to do conversions and such) but also some fallback onto essentially a string-based CSS syntax that we can parse and deal with for those things that we cannot anticipate and provide predefined API for.
>
> I'm not sure which of these alternatives is something that you prefer, but I'm guessing it is more Option A. The problem with Option A is that it introduces a *lot* of new public API and so we need to be careful about it and it takes time to vet it. Once released, we live with it forever. Of course anybody could take our existing skins and spruce them up, put them in another package, license as GPL (with classpath extension) and publish their own set of Skins which allow you to set everything via a public API, you don't have to wait for us to do it and include it in the platform.
>
> Cheers
> Richard
>
>
> On Apr 15, 2012, at 10:03 AM, Pedro Duque Vieira wrote:
>
>> I should have elaborated more on that sentence.
>> Yes CSS is not a "programming language" nor is it intended to be, it tries
>> to be a language to describe the visual part of interfaces separating the
>> structure of a document (HTML) from its appearance (the CSS part). Problem
>> is that for more elaborated interfaces, the looks start to have a logic,
>> which CSS fails to provide. That's when HTML web developers start to use
>> javascript, toggling HTML elements class's,
>>
>> I think CSS is a reasonable language to describe visual interfaces, but I
>> think we can do much better. We have to remind ourselves of its origins, it
>> appeared in the context of HTML to solve problems caused by its model, it
>> didn't come up as a language on its own.
>>
>> One thing we can not naturally do today, that we would be able to do if we
>> had an object model of the css properties is animating those properties. We
>> will be able to do it eventually when CSS3 animations come to JavaFX, but
>> on the meantime we can't, at least not on a natural, straight forward way.
>>
>> In conclusion I think CSS should not THE way to describe visual interfaces
>> on JavaFX, rather I think it should be one more way of doing it, that is,
>> we should also be able to accomplish the same by using Java or some other
>> language without having to parse CSS strings.
>>
>>
>> On Sun, Apr 15, 2012 at 2:15 PM, Stephen Winnall <steve at winnall.ch> wrote:
>>
>>> On 14 Apr 2012, at 02:20, Pedro Duque Vieira wrote:
>>>
>>>
>>> CSS isn't even a language it lacks lots of common language constructs.
>>>
>>>
>>> That depends on what you understand as a "language".
>>>
>>> I would agree that CSS is not a programming language, but it is not
>>> intended to be. Nor is XML for that matter. Both CSS and XML are data
>>> description languages and - as such - capture the state of the described
>>> data at the point where the CSS or XML is loaded (by
>>> Scene.getStylesheets.add() or FXMLLoader.load() etc).
>>>
>>> The provision of "common language constructs" is what scripting languages
>>> provide. We have enough of those already without adding scripting
>>> constructs to CSS and XML. In the context of JavaFX, it may make more sense
>>> to use Java itself.
>>>
>>> I agree that Zonski's proposal is interesting. In the XML world there is
>>> data binding, which allows you to create mapping from XML schemas to Java
>>> Beans. Something similar for CSS would be very useful.
>>>
>>> Steve
>>>
>>
>>
>>
>> --
>> Pedro Duque Vieira
>
More information about the openjfx-dev
mailing list