...
First, the copyrightAngle is made styleable by having it extend one of the StyleableProperty classes
; in this case, StyleableDoubleProperty
. The code shown here follows the typical pattern. Notice that the only difference between a DoubleProperty
and the StyleableDoubleProperty
is the addition of the getCssMetaData
method which links the copyrightAngle property to its corresponding CssMetaData
instance.
Code Block |
---|
private DoubleProperty copyrightAngle = new StyleableDoubleProperty(45d) { /** Link this property with its CssMetaData */ @Override public CssMetaData getCssMetaData() { return COPYRIGHT_ANGLE; } @Override public Object getBean() { return Node.this; } @Override public String getName() { return "copyrightAngle"; } }; }; public final void setCopyrightAngle(double value) { copyrightAngle.set(value); } public final double getCopyrightAngle() { return copyrightAngle.get(); } public final DoubleProperty copyrightAngleProperty() { return copyrightAngle; } |
The CssMetaData
instance provides information about the CSS style and some methods that allow CSS to set the property's value. The convention is to instantiate the CssMetaData
as a singleton (note that this example code is not thread safe, but is sufficient). Here, the CSS property name is "-my-copyright-angle" and the initial value of 45 degrees with the default value of the copyrightAngle property.
Code Block |
---|
private static final CssMetaData<Watermark,Number> COPYRIGHT_ANGLE = new CssMetaData("-my-copyright-angle", 45d) { public abstract boolean isSettable(Watermark node) { return copyrightAngle == null || copyrightAngle.isBound() == false; } public abstract WritableValue<Number> getWritableValue(Watermark node) { return copyrightAngleProperty(); } }; |
In order for the CSS engine to know about the styleable properties of Watermark, the methods getClassCssMetaData()
and getCssMetaData()
need to be implemented. The first is a static method that returns the CssMetaData
of the Watermark class and of its super class; in Watermark's case, the super class is Control
. The second method returns the same value but is implemented as an instance method so that it is not necessary to use reflection to call getClassCssMetaData()
. These methods are called quite frequently; for efficiency, the List<CssMetaData> is only created once. The following code shows a typical implementation.
Code Block |
---|
private static final List<CssMetaData> CSS_META_DATA; static { final List<CssMetaData> metaData = new ArrayList<CssMetaData>(Control.getClassCssMetaData()); Collections.addAll(metaData, COPYRIGHT_ANGLE ); CSS_META_DATA = Collections.unmodifiableList(metaData); } public static List<CssMetaData> getClassCssMetaData() { return CSS_META_DATA; } @Override public List<CssMetaData> getCssMetaData() { return getClassCssMetaData(); } |
At this point, the copyrightAngle can be styled through CSS. For example, to display the copyright from lower left to upper right:
Code Block |
---|
.watermark { -my-copyright-angle: -45; }
|
...
The 'viewed' pseudo-class will be implemented as a BooleanProperty. Although BooleanProperty is most commonly used for pseudo-class state, any property type can be a pseudo-class, even a Styleable*Property. When the 'viewed' property changes value, the code needs to notify CSS that the state has changed. The place to do this is in the invalidated()
method of the property which calls the pseudoClassStateChanged
method, passing it then PseudoClass.State
of the pseudo-class that changed state. Again, this code follows the typical pattern of implementing a property. Note that a "simple" property could be used here, but the invalidated method would still need to be overridden. Since the anonymous class will be created anyway, the few bytes needed for the bean and name will be saved by using BooleanPropertyBase instead of SimpleBooleanProperty.
Code Block |
---|
public final void setViewed(boolean value) {
viewed.set(value);
}
public final boolean isViewed() {
return viewed.get();
}
public final BooleanProperty viewedProperty() {
return viewed;
}
private static final PseudoClass.State VIEWED_PSEUDO_CLASS = PseudoClass.getState("viewed");
private BooleanProperty viewed = new BooleanPropertyBase(false) {
@Override
protected void invalidated() {
pseudoClassStateChanged(VIEWED_PSEUDO_CLASS);
}
@Override
public Object getBean() {
return Node.this;
}
@Override
public String getName() {
return "viewed";
}
};
}
|
CSS calls getPseudoClassStates()
which returns the pseudo-class state of the node. The node's pseudo-class state should include the state from the node class and all its super classes. Therefore, the first thing getPseudoClassStates()
should do is call super.getPseudoClassStates()
.
Code Block |
---|
public PseudoClass.States getPseudoClassStates() {
PseudoClassStates pseudoClassStates = super.getPseudoClassStates();
if (isViewed()) pseudoClassStates = pseudoClassStates.addState(VIEWED_PSEUDO_CLASS);
return pseudoClassStates;
}
|
With this framework in place, the following style can be used to make the watermark more transparent if it has been viewed.
Code Block |
---|
.watermark:viewed { -fx-opacity: 30%; }
|
...
The getCssMetaData
method is useful for getting from a StyleableProperty
to the corresponding CssMetaData
, which is useful for tooling and unit testing. For example, one can get the CssMetaData of the fillProperty of a Rectangle:
Code Block |
---|
Rectangle rect = new Rectangle(50,50);
CssMetaData fillCssMetaData = ((StyleableProperty)rect.fillProperty()).getCssMetaData();
System.out.println("Use " + fillCssMetaData.getProperty() + " to style Rectangle fill");
|
...
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 the classes have a constructor taking a CssMetaData
arg and another taking a CssMetaData
arg and an initial value. For example, StyleableBooleanProperty
has the following constructors:
...
public class SImpleStyleableBooleanProperty extends SImpleBooleanProperty implements StyleableProperty<Boolean>
public class SImpleStyleableFloatProperty extends SImpleFloatProperty implements StyleableProperty<Float>
public class SImpleStyleableDoubleProperty extends SImpleDoubleProperty implements StyleableProperty<Double>
public class SImpleStyleableIntegerProperty extends SImpleIntegerProperty implements StyleableProperty<Integer>
public class SImpleStyleableLongProperty extends SImpleLongProperty implements StyleableProperty<Long>
public class SImpleStyleableStringProperty extends SImpleStringProperty implements StyleableProperty<String>
public class SImpleStyleableObjectProperty<T> extends SImpleObjectProperty<T> implements StyleableProperty<T>
Each of these simple classes have a constructor taking an Object which is the property bean and a String which is the property name in combination with a CssMetaData
arg and another taking a CssMetaData
arg and an initial value. For example, SimpleStyleableBooleanProperty
has the following constructors:
Code Block |
---|
/**
* The constructor of the {@code SimpleStyleableBooleanProperty}.
*/
public SimpleStyleableBooleanProperty(Object bean, String name, CssMetaData CssMetaData) {
super(bean, name);
this.CssMetaData = CssMetaData;
}
/**
* The constructor of the {@code SimpleStyleableBooleanProperty}.
*
* @param CssMetaData
* the {@code CssMetaData} that corresponds to this {@code StyleableProperty}
* @param initialValue
* the initial value of the wrapped {@code Object}
*/
public SimpleStyleableBooleanProperty(Object bean, String name, CssMetaData CssMetaData, boolean initialValue) {
super(bean, name, initialValue);
this.CssMetaData = CssMetaData;
}
/**
* The constructor of the {@code SimpleStyleableBooleanProperty}.
*/
public SimpleStyleableBooleanProperty(CssMetaData CssMetaData) {
super();
this.CssMetaData = CssMetaData;
}
/**
* The constructor of the {@code SimpleStyleableBooleanProperty}.
*
* @param CssMetaData
* the {@code CssMetaData} that corresponds to this {@code StyleableProperty}
* @param initialValue
* the initial value of the wrapped {@code Object}
*/
public SimpleStyleableBooleanProperty(CssMetaData CssMetaData, boolean initialValue) {
super(initialValue);
this.CssMetaData = CssMetaData;
}
|
Issues:
In the future, StyleableProperty
may need to incorporate support for attribute selectors and animations.
...
PseudoClass contains two inner classes, State and States TBD: bad naming?. PseudoClass.State is an immutable object that represents one pseudo-class state. PseudoClass.States is a mutable object that is returned from getPseudoClassStates()
and represents the summation of individual PseudoClass.State values that are in effect.
...
Typical usage is:
Code Block |
---|
private static final PseudoClassState MAGIC_PSEUDO_CLASS_STATE = PseudoClassState.getState("xyzzy");
@Override public PseudoClass.States getPseudoClassStates() {
PseudoClass.States states = super.getPseudoClassStates();
if (isMagic()) states.addState(MAGIC_PSEUDO_CLASS_STATE);
return states;
}
|
...
Code Block |
---|
package javafx.scene; public final class PseudoClass { /** * There is only one PseudoClass.State instance for a given pseudoClass. * @return The PseudoClass.State for the given pseudoClass. Will not return null. */ public static PseudoClass.State getState(String pseudoClass) { // Use pseudoClass as key to find State in a Map<String,State> // If not found, create it and add to the map. // return the PseudoClass.State } /** * @return A new PseudoClass.States instance */ public static PseudoClass.States createStatesInstance() { } public final class State { /** Cannot create an instance of State except through PseudoClass static methods */ private State() { } @Override public String toString() { // return the String that was used to create this PseudoClassState pseudo-class, e.g. "hover" } } public final class States { /** * Add the state to the current set of states. The result of the operation will be the union of * the existing set of states and the added state. * @param state The state to add */ public void addState(PseudoClass.State state) { } /** * Remove the state to the current states. The result of the operation will be the relative complement of * the removed state in the existing set of states. This implies that removing a state that is not in the * existing set of states had no effect. * @param state The state to remove */ public void removeState(PseudoClass.State state) { } /** * Reset the set of states to the empty set. */ public void clear() { } /** @return The list of PseudoClass.State that are represented by this States object */ public List<PseudoClass.State> getStates() { } /** Cannot create an instance of States except through PseudoClass static methods */ private States() { } } } |
...