The trouble with Skins
Richard Bair
richard.bair at oracle.com
Mon Mar 23 18:07:22 UTC 2015
tl;dr; I lean toward keeping the Control API as view-agnostic as possible, but where view details become essential to the operation of the control, then define the Control to always include those specific view details.
============================================================================
Very interesting discussion, I’m enjoying reading through it. The following is a bit of background as to where my head was when we were designing this.
In Swing we had a similar concept to the Skins, they were the various UI delegates. In theory they allowed you to change the look of a Swing component (and they did) — but there were all kinds of “view specific” details that leaked into it, and also in the Swing components. With JavaFX, I wanted to go down more of a purist route, where the Skin would literally do all the UI (view) and the control would be the controller. OK, the Control was a Node because it made more sense conceptually to put a Button into a scene graph than to create a Button, associate it with a view, put the View into the scene graph, and somewhere stash the controller so you could get it back later. And for something like a Button, it works well.
Almost as soon as we’d gone down this road we got into the various problems you guys have been discussion around scroll bars and view positions. If you’re using the standard UI, then you kind of want to have access to these properties so you can get the UX right. However if we add these properties to ListView, then it would go down the road of saying “ListView displays a list of items and has a scroll bar or at least a scroll position and you have convenience API for making certain items visible, etc”.
Another very common complaint was about how to go about knowing the size of a cell for a specific list view item. You’d really want an API on ListView that said “getCell(item)” and then you got the cell, at least assuming it was visible, otherwise since it is virtualized it doesn’t make a whole lot of sense. But anyway, if you don’t know whether the skin even does virtualization, then how can you define these semantics in a way that is broadly consistent and usable?
Basically it comes down to having the API on the controller (Control) and restricting what kinds of skins can be created per control or having users cast the skin to a concrete type and work with the Skin directly for such things. If we were a dynamic language we could be dirty and munge the two together into a single thing (dynamic properties), but with its own set of trade-offs (you have to know what kind of skin you’re dealing with, and if you get it wrong, your app breaks).
As time went on we ended up adding more view-specific functionality to the controls (even adding view state via the CSS background API etc!!). I don’t know what the right answer is for this design question, but where we were heading was a place where you would have different UI controls for different “classes” of controls. So a ListView with scrollbars is our ListView, and a PagingListView or something would probably be different. They could have a common base class if we designed it such, otherwise they would just have those similarities that are consistent between the two classes of control.
That is why I find this actually quite appropriate:
> So Skins prevent us from getting visual details of the Control (such
> as scroll position, position of item on screen, ...), because it is
> Skin-specific, but at the same time they fail to customize the look &
> feel, because visual presentation leaks into the Control anyway.
This is the design balance! If you get it wrong, you get exactly what you’re saying here.
In the end, I lean toward keeping the Control API as view-agnostic as possible, but where view details become essential to the operation of the control, then define the Control to always include those specific view details.
More information about the openjfx-dev
mailing list