Introduction
CSS is an integral part of JavaFX but heretofore has been implemented exclusively in private API within, primarily, the com.sun.javafx.css package. Adding a styleable property in JavaFX is fairly straight-forward and exposing certain elements of the internal API will facilitate the development of custom UI controls in the open source environment. This document outlines this public API.
Design Goals
The primary goal is to create an API that allows CSS styling to be applied to a JavaFX property. The API should support the following javafx.beans.property types: BooleanProperty, FloatProperty, DoubleProperty, IntegerProperty, LongProperty, and StringProperty. Support for ObjectProperty is desired but would, possibly, require additional hooks into the parser or public API to convert a parsed value to the parameterized type which may not be feasible at this time. However, the ability to style an ObjectProperty with a parameterized type such as Insets or Paint is essential.
A secondary goal is to make the API such that an IDEs whould be able to automatically generate much of the necessary code. It is not a goal, however, to create the IDE boilerplate.
While it is possible to have classes other than instances of Node be styleable through CSS, this support requires an additional interface which exists in private implementation. Including this interface in the public API is not a goal.
The current implementation of CSS in JavaFX is only a small portion of the W3C standards and is only partially compliant with those standards. It is beyond the scope of this API implementation to rectify differences between the W3C standards and the current JavaFX implementation.
Use Case
Say a contributor wished to develop a Watermark control which could overlay a region and present a widget for obtaining copyright information. The Watermark API has a StringProperty cornerProperty that controls which corner of the region the widget is to be displayed. The contributor wishes to make this cornerProperty styleable through CSS.
Architecture
There are two main pieces to the architecture. First is a StyleablePropertyMetaData whose value can be represented syntactically in a .css file. A StyleablePropertyMetaData encapsulates the CSS property name, the type into which the string value is converted, and the default value of the property. Second is the JavaFX property to which the parsed StyleablePropertyMetaData value applies. Any JavaFX property that supports this styling is a StyleableProperty. A StyleableProperty also incorporates additional logic to ensure that values set by the user through calls to set methods are not overridden by styles in a user agent stylesheet.
There is a one-to-one correspondence between a StyleablePropertyMetaData and a StyleableProperty. A StyleablePropertyMetaData is scoped to a class whereas a StyleableProperty is an attribute of a class instance. Typically, a node will assume the CssMetaData of its ancestors. During CSS processing, the CSS engine iterates over a List<StyleablePropertyMetaData> (particular to the node) and looks up the parsed value of each StyleablePropertyMetaData in turn. If the StyleablePropertyMetaData has a parsed value, the parsed value is converted to the type of the StyleableProperty and the StyleableProperty is set.
Making a property styleable, then, consist of:
1. defining the javafx.beans.property as a StyleableProperty
2. creating a corresponding StyleablePropertyMetaData
3. ensuring the StyleablePropertyMetaData is returned in the List<StyleablePropertyMetaData>
API
StyleableProperty
StyleableProperty is an interface that is implemented by various classes that extend from javafx.beans.property properties; for example,
public class StyleableBooleanProperty extends BooleanPropertyBase implements StyleableProperty<Boolean>
.
interface StyleableProperty<T> {
/**
* This method is called from CSS code to set the value of the property.
*/
void applyStyle(Origin origin, T value);
/**
* Tells the origin of the value of the property. This is needed to
* determine whether or not CSS can override the value.
*/
Origin getOrigin();
/**
* Reflect back the StyleablePropertyMetaData that corresponds to this
* <code>javafx.beans.property.StyleableProperty</code>
*/
StyleablePropertyMetaData getStyleablePropertyMetaData();
}
In implementation, the applyStyle method delegates to the set method of the javafx.beans.property property. The getStyleablePropertyMetaData method is useful for getting from a StyleableProperty to the corresponding StyleablePropertyMetaData which is useful for tooling and unit testing.
Example:
private StringProperty corner = new StyleableStringProperty(CORNER, "lower-right")
public void setCorner(String corner)
public String getCorner()
public final StringProperty cornerProperty()
Implementing classes:
public class StyleableBooleanProperty extends BooleanPropertyBase implements StyleableProperty<Boolean>
public class StyleableFloatProperty extends FloatPropertyBase implements StyleableProperty<Float>
public class StyleableDoubleProperty extends DoublePropertyBase implements StyleableProperty<Double>
public class StyleableIntegerProperty extends IntegerPropertyBase implements StyleableProperty<Integer>
public class StyleableLongProperty extends LongPropertyBase implements StyleableProperty<Long>
public class StyleableStringProperty extends StringPropertyBase implements StyleableProperty<String>
public class StyleableObjectProperty<T> extends ObjectPropertyBase<T> implements StyleableProperty<T>
Each of these classes have a constructor taking a StyleablePropertyMetaData arg and another taking a StyleablePropertyMetaData arg and an initial value. For example, StyleableBooleanProperty has the following constructors:
/**
* The constructor of the
.
*/
public StyleableBooleanProperty(StyleablePropertyMetaData StyleablePropertyMetaData)
/**
* The constructor of the
.
*
* @param StyleablePropertyMetaData
* the
that corresponds to this
* @param initialValue
* the initial value of the wrapped
*/
public StyleableBooleanProperty(StyleablePropertyMetaData StyleablePropertyMetaData, boolean initialValue)
Issues:
In the future, StyleableProperty may need to incorporate support for attribute selectors and animations.
StyleablePropertyMetaData
StyleablePropertyMetaData encapsulates the data needed to lookup a value and apply that value to a StyleableProperty. This includes the CSS property name, the default property value, and a link back to the corresponding StyleableProperty. The class is abstract and it is necessary to implement two methods which are invoked from the CSS engine:
/**
* Check to see if the corresponding property on the given node is
* settable. This method is called before any styles are looked up for the
* given property. It is abstract so that the code can check if the property
* is settable without expanding the property. Generally, the property is
* settable if it is not null or is not bound.
*
* @param node The node on which the property value is being set
* @return true if the property can be set.
*/
public abstract boolean isSettable(N node);
/**
* Return the corresponding <code>javafx.beans.value.WriteableValue</code> for
* the given Node. Note that calling this method will cause the property
* to be expanded.
* @param node
* @return
*/
public abstract WritableValue<V> getWritableValue(N node);
The StyleableProperty is required to be a WritableValue. WritableValue is the interface which has the setValue method. If it isn't possible to setValue on the property, then it cannot be styled from CSS. The implementation is fairly consistent throughout the existing code, typically:
public abstract boolean isSettable(Watermark node)
public abstract WritableValue<String> getWritableValue(Watermark node)
Note that isSettable is implemented in a way that does not cause the expansion of the property. The isSettable check is performed before the CSS value is looked up. If the property is not settable, then there is no need to do the lookup. The getWritableValue method is invoked only if there is a CSS value to apply. Thus, if the property is not settable or there is no CSS value, the property is not expanded.
The CSS engine does not call setValue directly on the WritableValue. Rather, the code calls a set method on the StyleablePropertyMetaData which, in turn, calls applyStyle on the StyleableProperty. This level of indirection allows a StyleablePropertyMetaData to intercept the value before the calculated style value is applied (in other words, before set is called on the corresponding property). This is used primarily for Number based properties where the parameterized type is Number but the actual type might be Integer and so the value's intValue() method needs to be invoked.
The parameterization of StyleablePropertyMetaData is that it requires a Node since Node is the visible element of the scene graph. The V parameter is the type of the property's value. The corner property of Watermark would be declared as StyleablePropertyMetaData<Watermark, String>.
Also in the API are a handful of convenience methods. Should these methods belong here or in some other class?
public abstract class StyleablePropertyMetaData<N extends Node, V> {
/**
* Check to see if the corresponding property on the given node is
* settable. This method is called before any styles are looked up for the
* given property. It is abstract so that the code can check if the property
* is settable without expanding the property. Generally, the property is
* settable if it is not null or is not bound.
*
* @param node The node on which the property value is being set
* @return true if the property can be set.
*/
public abstract boolean isSettable(N node);
/**
* Return the corresponding <code>javafx.beans.value.WriteableValue</code> for
* the given Node. Note that calling this method will cause the property
* to be expanded.
* @param node
* @return
*/
public abstract WritableValue<V> getWritableValue(N node);
/**
* Set the value of the corresponding property on the given Node.
* @param node The node on which the property value is being set
* @param value The value to which the property is set
*/
public void set(N node, V value, Origin origin)
/**
* Return the StyleablePropertyMetaData associated with the given WritableValue. Will
* return null if the WritableValue is not a StyleableProperty.
*/
public static StyleablePropertyMetaData getStyleablePropertyMetaData(WritableValue writableValue)
/**
* Return the Stylesheet.Origin associated with the given WritableValue. Will
* return null if the WritableValue is not a StyleableProperty.
*/
public static Origin getOrigin(WritableValue writableValue)
private final String property;
/**
* @return the CSS property name
*/
public final String getProperty()
private final V initialValue;
/**
* The initial value of a StyleablePropertyMetaData corresponds to the default
* value of the WritableValue in code.
* For example, the default value of Shape.fill is Color.BLACK and the
* initialValue of Shape.CssMetaData.FILL is also Color.BLACK.
* <p>
* There may be exceptions to this, however. The initialValue may depend
* on the state of the Node. A ScrollBar has a default orientation of
* horizontal. If the ScrollBar is vertical, however, this method should
* return Orientation.VERTICAL. Otherwise, a vertical ScrollBar would be
* incorrectly set to a horizontal ScrollBar when the initial value is
* applied.
* @return The initial value of the property, possibly null
*/
public V getInitialValue(N node)
private final List<StyleablePropertyMetaData> subProperties;
/**
* The sub-properties refers to the constituent properties of this property,
* if any. For example, "-fx-font-weight" is sub-property of "-fx-font".
*/
public final List<StyleablePropertyMetaData> getSubProperties()
private final boolean inherits;
/**
* If true, the value of this property is the same as
* the parent's computed value of this property.
* @default false
* @see <a href="http://www.w3.org/TR/css3-cascade/#inheritance">CSS Inheritance</a>
*/
public final boolean isInherits()
/**
* Construct a StyleablePropertyMetaData with the given parameters and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
* @param inherits true if this property uses CSS inheritance
* @param subProperties the sub-properties of this property. For example,
* the -fx-font property has the sub-properties -fx-font-family,
* -fx-font-size, -fx-font-weight, and -fx-font-style.
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue,
boolean inherits,
final List<StyleablePropertyMetaData> subProperties)
/**
* Construct a StyleablePropertyMetaData with the given parameters and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
* @param inherits true if this property uses CSS inheritance
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue,
boolean inherits)
/**
* Construct a StyleablePropertyMetaData with the given parameters, inherit set to
* false and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue)
/**
* Construct a StyleablePropertyMetaData with the given parameters, initialValue is
* null, inherit is set to false, and no sub-properties.
* @param property the CSS property
*/
protected StyleablePropertyMetaData(
final String property)
}
Issues:
One major aspect of the private interface has been omitted from the public interface and that is the notion of a Conveter. A Converter takes a value from the parser (a ParsedValue) and converts it to the corresponding java type. So, for example, a ColorConverter would convert a ParsedValue representing a Color into a Color. If possible, the parser performs the conversion. But this is not always possible such as when converting an em size to an absolute pixel value. The Converter was omitted because including it in the API would result in dragging much more of the private implementation than is desired. ParsedValue, for instance, is (in my opinion) not suitable for public API in its current state because it is tightly coupled with the parser and the Converters. I'm not sure, however, that this public API can exist without a Converter member, but I believe I can infer the Converter from the WritableValue. Alternatively, some type indicator could be used or a set of CSS*Property classes (e.g., CSSBooleanProperty) could be created.
Origin
/**
* Enumeration of the possible source or origin of a stylesheet and styles.
*/
public enum Origin {
/** The stylesheet is a user-agent stylesheet */
USER_AGENT,
/** The value of a property was set by the user through a call to a set method */
USER,
/** The stylesheet is an external file */
AUTHOR,
/** The style is from the Node via setStyle */
INLINE
}
Pseudoclass
In the current implementation, the pattern for handling pseudo-class state is to override the invalidated method of the property in order to invoke impl_pseudoClassStateChanged which sets a flag indicating that the node needs updating by CSS. When CSS processes the update, it gets the node's current pseudo-class state by calling impl_getPseudoClassState(). Each node that has pseudo-class state overrides impl_getPseudoClassState. A typical example from ButtonBase, where ARMED_PSEUDOCLASS_STATE is a bit mask:
@Override public long impl_getPseudoClassState()
The issue with promoting this implementation to public API is that it is susceptable to coding errors. One might forget to call super.impl_getPsuedoClassState() or might use the wrong bit mask, either accidentally or purposfully. It should be simple enough to codify this pattern in a PseudoClass class, but this approach is inadequate since some pseudo-class states are implemented in read-only properties (Node hover, for example), some are boolean, others are not boolean, and some are also styleable properties(ScrollBar orientation, for example).
Also, at some point attribute selectors will need to be implemented, so attribute selectors need to be considered now to ensure the API will not have to change to support them. One way that attribute selectors can be handled is by overriding the setValue method of the property such that, if the property (via getName()) is used in an attribute selector and the value would cause selection, then a CSS update is triggered. For example:
@Override public void setValue(Orientation orientation) {
super.setValue(orientation);
if (isUsedAsAttributeSelector(getName() && isValueCausesSelection(orientation))
}
This approach would require the developer decide at design time which properties could be used in an attribute selector. It would be less restrictive to allow any property to be used in an attribute selector. Imagine selecting a rule when the value of the rotate property is 45. Clearly the system would incur unacceptable overhead if every property were to implement such a method. Further, there would need to be a new class for each property class for this to be implemented in a set of classes that override the javafx.beans.property classes.
It seems better, then, to add a listener to a property that is used in an attribute selector. The downside here is the potential for memory leaks and wether it be necessary to have a listener for every node in the scene graph. These concerns are mitigated by the fact that every node has a StyleHelper, which is a bit of code that does the style lookup, calculates the effective value, and sets the value on the property. The StyleHelper knows what styles apply to a node. Further, it knows what pseudo-classes are present in the matching selectors. Therefore, it is possible for the StyleHelper to add a listener to the corresponding property. Since the StyleManager is final in Node, there would be no leak.
There would only need to be one implementation of the listener which would check if the new value causes selection and, if so, notifies the node to update. StyleHelper itself could keep track of the node's pseudo-class state eliminating the need to call getPseudoClassState.
For the developer, then, simply using a property in the .css file as a pseudo-class would cause it to be used as such and there would be no additional implementation required.
Issues:
One thing I haven't looked at is how to get from the attribute name to the property. If the parser sees [rotate=45], how does StyleHelper find rotateProperty in the node?
For the orientation example, there is horizontal and vertical which don't map to property names. Really the selector should have [orientation=horizontal] rather than :horizontal.
Additional public API.
Node
List<StyleablePropertyMetaData> getStyleablePropertyMetaData()
This method returns a List of supported CssMetaData. The list should include not only the properties of this node, but the properties of the node's super-class(es) as well. This method is called frequently and, by convention, it returns a static list. Since this static list may be used by other node classes, the convention is to provide a public static method that returns the static list: public static List<StyleablePropertyMetaData> getClassStyleablePropertyMetaData(). The getStyleablePropertyMetaData() method is used by the CSS engine to avoid reflection. A typical implementation would be:
private static class CssMetaData {
private static final StyleablePropertyMetaData<Watermark, String> CORNER = new StyleablePropertyMetaData<Watermark, String>("-my-corner", "lower-right") {
public abstract boolean isSettable(Watermark node)
public abstract WritableValue<String> getWritableValue(Watermark node)
}
private static final List<StyleablePropertyMetaData> DATA;
static
}
public static List<StyleablePropertyMetaData> getClassStyleablePropertyMetaData() {
return CssMetaData.DATA;
}
public static List<StyleablePropertyMetaData> getStyleablePropertyMetaDataMetaData() {
return getClassStyleablePropertyMetaData();
}
Introduction
CSS is an integral part of JavaFX but heretofore has been implemented exclusively in private API within, primarily, the com.sun.javafx.css package. Adding a styleable property in JavaFX is fairly straight-forward and exposing certain elements of the internal API will facilitate the development of custom UI controls in the open source environment. This document outlines this public API.
Design Goals
The primary goal is to create an API that allows CSS styling to be applied to a JavaFX property. The API should support the following javafx.beans.property types: BooleanProperty, FloatProperty, DoubleProperty, IntegerProperty, LongProperty, and StringProperty. Support for ObjectProperty is desired but would, possibly, require additional hooks into the parser or public API to convert a parsed value to the parameterized type which may not be feasible at this time. However, the ability to style an ObjectProperty with a parameterized type such as Insets or Paint is essential.
A secondary goal is to make the API such that an IDEs whould be able to automatically generate much of the necessary code. It is not a goal, however, to create the IDE boilerplate.
While it is possible to have classes other than instances of Node be styleable through CSS, this support requires an additional interface which exists in private implementation. Including this interface in the public API is not a goal.
The current implementation of CSS in JavaFX is only a small portion of the W3C standards and is only partially compliant with those standards. It is beyond the scope of this API implementation to rectify differences between the W3C standards and the current JavaFX implementation.
Use Case
Say a contributor wished to develop a Watermark control which could overlay a region and present a widget for obtaining copyright information. The Watermark API has a StringProperty cornerProperty that controls which corner of the region the widget is to be displayed. The contributor wishes to make this cornerProperty styleable through CSS.
Architecture
There are two main pieces to the architecture. First is a StyleablePropertyMetaData whose value can be represented syntactically in a .css file. A StyleablePropertyMetaData encapsulates the CSS property name, the type into which the string value is converted, and the default value of the property. Second is the JavaFX property to which the parsed StyleablePropertyMetaData value applies. Any JavaFX property that supports this styling is a StyleableProperty. A StyleableProperty also incorporates additional logic to ensure that values set by the user through calls to set methods are not overridden by styles in a user agent stylesheet.
There is a one-to-one correspondence between a StyleablePropertyMetaData and a StyleableProperty. A StyleablePropertyMetaData is scoped to a class whereas a StyleableProperty is an attribute of a class instance. Typically, a node will assume the CssMetaData of its ancestors. During CSS processing, the CSS engine iterates over a List<StyleablePropertyMetaData> (particular to the node) and looks up the parsed value of each StyleablePropertyMetaData in turn. If the StyleablePropertyMetaData has a parsed value, the parsed value is converted to the type of the StyleableProperty and the StyleableProperty is set.
Making a property styleable, then, consist of:
1. defining the javafx.beans.property as a StyleableProperty
2. creating a corresponding StyleablePropertyMetaData
3. ensuring the StyleablePropertyMetaData is returned in the List<StyleablePropertyMetaData>
API
StyleableProperty
StyleableProperty is an interface that is implemented by various classes that extend from javafx.beans.property properties; for example,
public class StyleableBooleanProperty extends BooleanPropertyBase implements StyleableProperty<Boolean>
.
interface StyleableProperty<T>
In implementation, the applyStyle method delegates to the set method of the javafx.beans.property property. The getStyleablePropertyMetaData method is useful for getting from a StyleableProperty to the corresponding StyleablePropertyMetaData which is useful for tooling and unit testing.
Example:
private StringProperty corner = new StyleableStringProperty(CORNER, "lower-right")
public void setCorner(String corner)
public String getCorner()
public final StringProperty cornerProperty()
Implementing classes:
public class StyleableBooleanProperty extends BooleanPropertyBase implements StyleableProperty<Boolean>
public class StyleableFloatProperty extends FloatPropertyBase implements StyleableProperty<Float>
public class StyleableDoubleProperty extends DoublePropertyBase implements StyleableProperty<Double>
public class StyleableIntegerProperty extends IntegerPropertyBase implements StyleableProperty<Integer>
public class StyleableLongProperty extends LongPropertyBase implements StyleableProperty<Long>
public class StyleableStringProperty extends StringPropertyBase implements StyleableProperty<String>
public class StyleableObjectProperty<T> extends ObjectPropertyBase<T> implements StyleableProperty<T>
Each of these classes have a constructor taking a StyleablePropertyMetaData arg and another taking a StyleablePropertyMetaData arg and an initial value. For example, StyleableBooleanProperty has the following constructors:
/**
* The constructor of the
.
*/
public StyleableBooleanProperty(StyleablePropertyMetaData StyleablePropertyMetaData)
/**
* The constructor of the
.
*
* @param StyleablePropertyMetaData
* the
that corresponds to this
* @param initialValue
* the initial value of the wrapped
*/
public StyleableBooleanProperty(StyleablePropertyMetaData StyleablePropertyMetaData, boolean initialValue)
Issues:
In the future, StyleableProperty may need to incorporate support for attribute selectors and animations.
StyleablePropertyMetaData
StyleablePropertyMetaData encapsulates the data needed to lookup a value and apply that value to a StyleableProperty. This includes the CSS property name, the default property value, and a link back to the corresponding StyleableProperty. The class is abstract and it is necessary to implement two methods which are invoked from the CSS engine:
/**
* Check to see if the corresponding property on the given node is
* settable. This method is called before any styles are looked up for the
* given property. It is abstract so that the code can check if the property
* is settable without expanding the property. Generally, the property is
* settable if it is not null or is not bound.
*
* @param node The node on which the property value is being set
* @return true if the property can be set.
*/
public abstract boolean isSettable(N node);
/**
* Return the corresponding <code>javafx.beans.value.WriteableValue</code> for
* the given Node. Note that calling this method will cause the property
* to be expanded.
* @param node
* @return
*/
public abstract WritableValue<V> getWritableValue(N node);
The StyleableProperty is required to be a WritableValue. WritableValue is the interface which has the setValue method. If it isn't possible to setValue on the property, then it cannot be styled from CSS. The implementation is fairly consistent throughout the existing code, typically:
public abstract boolean isSettable(Watermark node)
public abstract WritableValue<String> getWritableValue(Watermark node)
Note that isSettable is implemented in a way that does not cause the expansion of the property. The isSettable check is performed before the CSS value is looked up. If the property is not settable, then there is no need to do the lookup. The getWritableValue method is invoked only if there is a CSS value to apply. Thus, if the property is not settable or there is no CSS value, the property is not expanded.
The CSS engine does not call setValue directly on the WritableValue. Rather, the code calls a set method on the StyleablePropertyMetaData which, in turn, calls applyStyle on the StyleableProperty. This level of indirection allows a StyleablePropertyMetaData to intercept the value before the calculated style value is applied (in other words, before set is called on the corresponding property). This is used primarily for Number based properties where the parameterized type is Number but the actual type might be Integer and so the value's intValue() method needs to be invoked.
The parameterization of StyleablePropertyMetaData is that it requires a Node since Node is the visible element of the scene graph. The V parameter is the type of the property's value. The corner property of Watermark would be declared as StyleablePropertyMetaData<Watermark, String>.
Also in the API are a handful of convenience methods. Should these methods belong here or in some other class?
public abstract class StyleablePropertyMetaData<N extends Node, V> {
/**
* Check to see if the corresponding property on the given node is
* settable. This method is called before any styles are looked up for the
* given property. It is abstract so that the code can check if the property
* is settable without expanding the property. Generally, the property is
* settable if it is not null or is not bound.
*
* @param node The node on which the property value is being set
* @return true if the property can be set.
*/
public abstract boolean isSettable(N node);
/**
* Return the corresponding <code>javafx.beans.value.WriteableValue</code> for
* the given Node. Note that calling this method will cause the property
* to be expanded.
* @param node
* @return
*/
public abstract WritableValue<V> getWritableValue(N node);
/**
* Set the value of the corresponding property on the given Node.
* @param node The node on which the property value is being set
* @param value The value to which the property is set
*/
public void set(N node, V value, Origin origin)
/**
* Return the StyleablePropertyMetaData associated with the given WritableValue. Will
* return null if the WritableValue is not a StyleableProperty.
*/
public static StyleablePropertyMetaData getStyleablePropertyMetaData(WritableValue writableValue)
/**
* Return the Stylesheet.Origin associated with the given WritableValue. Will
* return null if the WritableValue is not a StyleableProperty.
*/
public static Origin getOrigin(WritableValue writableValue)
private final String property;
/**
* @return the CSS property name
*/
public final String getProperty()
private final V initialValue;
/**
* The initial value of a StyleablePropertyMetaData corresponds to the default
* value of the WritableValue in code.
* For example, the default value of Shape.fill is Color.BLACK and the
* initialValue of Shape.CssMetaData.FILL is also Color.BLACK.
* <p>
* There may be exceptions to this, however. The initialValue may depend
* on the state of the Node. A ScrollBar has a default orientation of
* horizontal. If the ScrollBar is vertical, however, this method should
* return Orientation.VERTICAL. Otherwise, a vertical ScrollBar would be
* incorrectly set to a horizontal ScrollBar when the initial value is
* applied.
* @return The initial value of the property, possibly null
*/
public V getInitialValue(N node)
private final List<StyleablePropertyMetaData> subProperties;
/**
* The sub-properties refers to the constituent properties of this property,
* if any. For example, "-fx-font-weight" is sub-property of "-fx-font".
*/
public final List<StyleablePropertyMetaData> getSubProperties()
private final boolean inherits;
/**
* If true, the value of this property is the same as
* the parent's computed value of this property.
* @default false
* @see <a href="http://www.w3.org/TR/css3-cascade/#inheritance">CSS Inheritance</a>
*/
public final boolean isInherits()
/**
* Construct a StyleablePropertyMetaData with the given parameters and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
* @param inherits true if this property uses CSS inheritance
* @param subProperties the sub-properties of this property. For example,
* the -fx-font property has the sub-properties -fx-font-family,
* -fx-font-size, -fx-font-weight, and -fx-font-style.
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue,
boolean inherits,
final List<StyleablePropertyMetaData> subProperties)
/**
* Construct a StyleablePropertyMetaData with the given parameters and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
* @param inherits true if this property uses CSS inheritance
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue,
boolean inherits)
/**
* Construct a StyleablePropertyMetaData with the given parameters, inherit set to
* false and no sub-properties.
* @param property the CSS property
* @param initalValue the default value of the corresponding property which may be null
*/
protected StyleablePropertyMetaData(
final String property,
final V initialValue)
/**
* Construct a StyleablePropertyMetaData with the given parameters, initialValue is
* null, inherit is set to false, and no sub-properties.
* @param property the CSS property
*/
protected StyleablePropertyMetaData(
final String property)
}
Issues:
One major aspect of the private interface has been omitted from the public interface and that is the notion of a Conveter. A Converter takes a value from the parser (a ParsedValue) and converts it to the corresponding java type. So, for example, a ColorConverter would convert a ParsedValue representing a Color into a Color. If possible, the parser performs the conversion. But this is not always possible such as when converting an em size to an absolute pixel value. The Converter was omitted because including it in the API would result in dragging much more of the private implementation than is desired. ParsedValue, for instance, is (in my opinion) not suitable for public API in its current state because it is tightly coupled with the parser and the Converters. I'm not sure, however, that this public API can exist without a Converter member, but I believe I can infer the Converter from the WritableValue. Alternatively, some type indicator could be used or a set of CSS*Property classes (e.g., CSSBooleanProperty) could be created.
Origin
/**
* Enumeration of the possible source or origin of a stylesheet and styles.
*/
public enum Origin
Pseudoclass
In the current implementation, the pattern for handling pseudo-class state is to override the invalidated method of the property in order to invoke impl_pseudoClassStateChanged which sets a flag indicating that the node needs updating by CSS. When CSS processes the update, it gets the node's current pseudo-class state by calling impl_getPseudoClassState(). Each node that has pseudo-class state overrides impl_getPseudoClassState. A typical example from ButtonBase, where ARMED_PSEUDOCLASS_STATE is a bit mask:
@Override public long impl_getPseudoClassState()
The issue with promoting this implementation to public API is that it is susceptable to coding errors. One might forget to call super.impl_getPsuedoClassState() or might use the wrong bit mask, either accidentally or purposfully. It should be simple enough to codify this pattern in a PseudoClass class, but this approach is inadequate since some pseudo-class states are implemented in read-only properties (Node hover, for example), some are boolean, others are not boolean, and some are also styleable properties(ScrollBar orientation, for example).
Also, at some point attribute selectors will need to be implemented, so attribute selectors need to be considered now to ensure the API will not have to change to support them. One way that attribute selectors can be handled is by overriding the setValue method of the property such that, if the property (via getName()) is used in an attribute selector and the value would cause selection, then a CSS update is triggered. For example:
@Override public void setValue(Orientation orientation) {
super.setValue(orientation);
if (isUsedAsAttributeSelector(getName() && isValueCausesSelection(orientation))
}
This approach would require the developer decide at design time which properties could be used in an attribute selector. It would be less restrictive to allow any property to be used in an attribute selector. Imagine selecting a rule when the value of the rotate property is 45. Clearly the system would incur unacceptable overhead if every property were to implement such a method. Further, there would need to be a new class for each property class for this to be implemented in a set of classes that override the javafx.beans.property classes.
It seems better, then, to add a listener to a property that is used in an attribute selector. The downside here is the potential for memory leaks and wether it be necessary to have a listener for every node in the scene graph. These concerns are mitigated by the fact that every node has a StyleHelper, which is a bit of code that does the style lookup, calculates the effective value, and sets the value on the property. The StyleHelper knows what styles apply to a node. Further, it knows what pseudo-classes are present in the matching selectors. Therefore, it is possible for the StyleHelper to add a listener to the corresponding property. Since the StyleManager is final in Node, there would be no leak.
There would only need to be one implementation of the listener which would check if the new value causes selection and, if so, notifies the node to update. StyleHelper itself could keep track of the node's pseudo-class state eliminating the need to call getPseudoClassState.
For the developer, then, simply using a property in the .css file as a pseudo-class would cause it to be used as such and there would be no additional implementation required.
Issues:
One thing I haven't looked at is how to get from the attribute name to the property. If the parser sees [rotate=45], how does StyleHelper find rotateProperty in the node?
For the orientation example, there is horizontal and vertical which don't map to property names. Really the selector should have [orientation=horizontal] rather than :horizontal.
Additional public API.
Node
List<StyleablePropertyMetaData> getStyleablePropertyMetaData()
This method returns a List of supported CssMetaData. The list should include not only the properties of this node, but the properties of the node's super-class(es) as well. This method is called frequently and, by convention, it returns a static list. Since this static list may be used by other node classes, the convention is to provide a public static method that returns the static list: public static List<StyleablePropertyMetaData> getClassStyleablePropertyMetaData(). The getStyleablePropertyMetaData() method is used by the CSS engine to avoid reflection. A typical implementation would be:
private static class CssMetaData {
private static final StyleablePropertyMetaData<Watermark, String> CORNER = new StyleablePropertyMetaData<Watermark, String>("-my-corner", "lower-right") {
public abstract boolean isSettable(Watermark node)
public abstract WritableValue<String> getWritableValue(Watermark node)
}
private static final List<StyleablePropertyMetaData> DATA;
static
}
public static List<StyleablePropertyMetaData> getClassStyleablePropertyMetaData()
public static List<StyleablePropertyMetaData> getStyleablePropertyMetaDataMetaData()