javafx.scene.control.SkinBase Maven / Gradle / Ivy
/*
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.control;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler;
import com.sun.javafx.scene.control.ListenerHelper;
/**
* Base implementation class for defining the visual representation of user
* interface controls by defining a scene graph of nodes to represent the
* {@link Skin skin}.
* A user interface control is abstracted behind the {@link Skinnable} interface.
*
* @param the type of the control
* @since JavaFX 8.0
*/
public abstract class SkinBase implements Skin {
static {
// must be the first code to execute
ListenerHelper.setAccessor((skin) -> skin.listenerHelper());
}
/* *************************************************************************
* *
* Private fields *
* *
**************************************************************************/
/**
* The {@code Control} that is referencing this Skin. There is a
* one-to-one relationship between a {@code Skin} and a {@code Control}.
* When a {@code Skin} is set on a {@code Control}, this variable is
* automatically updated.
*/
private C control;
/**
* A local field that directly refers to the children list inside the Control.
*/
private ObservableList children;
/**
* This is part of the workaround introduced during delomboking. We probably will
* want to adjust the way listeners are added rather than continuing to use this
* map (although it doesn't really do much harm).
*
* TODO remove after migration to ListenerHelper
*/
private LambdaMultiplePropertyChangeListenerHandler lambdaChangeListenerHandler;
private ListenerHelper listenerHelper;
/* *************************************************************************
* *
* Event Handlers / Listeners *
* *
**************************************************************************/
/**
* Mouse handler used for consuming all mouse events (preventing them
* from bubbling up to parent)
*/
private static final EventHandler mouseEventConsumer = event -> {
/*
** we used to consume mouse wheel rotations here,
** be we've switched to ScrollEvents, and only consume those which we use.
** See RT-13995 & RT-14480
*/
event.consume();
};
/* *************************************************************************
* *
* Constructor *
* *
**************************************************************************/
/**
* Constructor for all SkinBase instances.
*
* @param control The control for which this Skin should attach to.
*/
protected SkinBase(final C control) {
if (control == null) {
throw new IllegalArgumentException("Cannot pass null for control");
}
// Update the control and behavior
this.control = control;
this.children = control.getControlChildren();
// Default behavior for controls is to consume all mouse events
consumeMouseEvents(true);
}
/* *************************************************************************
* *
* Public API (from Skin) *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override public final C getSkinnable() {
return control;
}
/** {@inheritDoc} */
@Override public final Node getNode() {
return control;
}
/** {@inheritDoc} */
@Override public void dispose() {
// control.removeEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler);
// unhook listeners
if (lambdaChangeListenerHandler != null) {
lambdaChangeListenerHandler.dispose();
}
if (listenerHelper != null) {
listenerHelper.disconnect();
}
this.control = null;
}
/* *************************************************************************
* *
* Public API *
* *
**************************************************************************/
/**
* Returns the children of the skin.
* @return the children of the skin
*/
public final ObservableList getChildren() {
return children;
}
/**
* Called during the layout pass of the scenegraph.
* @param contentX the x position
* @param contentY the y position
* @param contentWidth the width
* @param contentHeight the height
*/
protected void layoutChildren(final double contentX, final double contentY,
final double contentWidth, final double contentHeight) {
// By default simply sizes all managed children to fit within the space provided
for (int i=0, max=children.size(); i observable, Consumer> operation) {
if (lambdaChangeListenerHandler == null) {
lambdaChangeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler();
}
lambdaChangeListenerHandler.registerChangeListener(observable, operation);
}
/**
* Unregisters all operations that have been registered using
* {@link #registerChangeListener(ObservableValue, Consumer)}
* for the given {@code observable}. Does nothing if {@code observable} is {@code null}.
*
* @param observable the observable for which the registered operations should be removed,
* may be {@code null}
* @return a composed consumer representing all previously registered operations, or
* {@code null} if none have been registered or the observable is {@code null}
* @since 9
*/
protected final Consumer> unregisterChangeListeners(ObservableValue> observable) {
if (lambdaChangeListenerHandler == null) {
return null;
}
return lambdaChangeListenerHandler.unregisterChangeListeners(observable);
}
/**
* Registers an operation to perform when the given {@code observable} sends an invalidation event.
* Does nothing if either {@code observable} or {@code operation} are {@code null}.
* If multiple operations are registered on the same observable, they will be performed in the
* order in which they were registered.
*
* @param observable the observable to observe for invalidation events, may be {@code null}
* @param operation the operation to perform when the observable sends an invalidation event,
* may be {@code null}
* @since 17
*/
protected final void registerInvalidationListener(Observable observable, Consumer operation) {
if (lambdaChangeListenerHandler == null) {
lambdaChangeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler();
}
lambdaChangeListenerHandler.registerInvalidationListener(observable, operation);
}
/**
* Unregisters all operations that have been registered using
* {@link #registerInvalidationListener(Observable, Consumer)}
* for the given {@code observable}. Does nothing if {@code observable} is {@code null}.
*
* @param observable the observable for which the registered operations should be removed,
* may be {@code null}
* @return a composed consumer representing all previously registered operations, or
* {@code null} if none have been registered or the observable is {@code null}
* @since 17
*/
protected final Consumer unregisterInvalidationListeners(Observable observable) {
if (lambdaChangeListenerHandler == null) {
return null;
}
return lambdaChangeListenerHandler.unregisterInvalidationListeners(observable);
}
/**
* Registers an operation to perform when the given {@code observableList} sends a list change event.
* Does nothing if either {@code observableList} or {@code operation} are {@code null}.
* If multiple operations are registered on the same observable list, they will be performed in the
* order in which they were registered.
*
* @param observableList the observableList to observe for list change events, may be {@code null}
* @param operation the operation to perform when the observableList sends a list change event,
* may be {@code null}
* @since 17
*/
protected final void registerListChangeListener(ObservableList> observableList, Consumer> operation) {
if (lambdaChangeListenerHandler == null) {
lambdaChangeListenerHandler = new LambdaMultiplePropertyChangeListenerHandler();
}
lambdaChangeListenerHandler.registerListChangeListener(observableList, operation);
}
/**
* Unregisters all operations that have been registered using
* {@link #registerListChangeListener(ObservableList, Consumer)}
* for the given {@code observableList}. Does nothing if {@code observableList} is {@code null}.
*
* @param observableList the observableList for which the registered operations should be removed,
* may be {@code null}
* @return a composed consumer representing all previously registered operations, or
* {@code null} if none have been registered or the observableList is {@code null}
* @since 17
*/
protected final Consumer> unregisterListChangeListeners(ObservableList> observableList) {
if (lambdaChangeListenerHandler == null) {
return null;
}
return lambdaChangeListenerHandler.unregisterListChangeListeners(observableList);
}
/* *************************************************************************
* *
* Public Layout-related API *
* *
**************************************************************************/
/**
* Computes the minimum allowable width of the Skin, based on the provided
* height.
*
* @param height The height of the Skin, in case this value might dictate
* the minimum width.
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return A double representing the minimum width of this Skin.
*/
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double minX = 0;
double maxX = 0;
boolean firstManagedChild = true;
for (int i = 0; i < children.size(); i++) {
Node node = children.get(i);
if (node.isManaged()) {
final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
if (!firstManagedChild) { // branch prediction favors most often used condition
minX = Math.min(minX, x);
maxX = Math.max(maxX, x + node.minWidth(-1));
} else {
minX = x;
maxX = x + node.minWidth(-1);
firstManagedChild = false;
}
}
}
double minWidth = maxX - minX;
return leftInset + minWidth + rightInset;
}
/**
* Computes the minimum allowable height of the Skin, based on the provided
* width.
*
* @param width The width of the Skin, in case this value might dictate
* the minimum height.
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return A double representing the minimum height of this Skin.
*/
protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
double minY = 0;
double maxY = 0;
boolean firstManagedChild = true;
for (int i = 0; i < children.size(); i++) {
Node node = children.get(i);
if (node.isManaged()) {
final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
if (!firstManagedChild) { // branch prediction favors most often used condition
minY = Math.min(minY, y);
maxY = Math.max(maxY, y + node.minHeight(-1));
} else {
minY = y;
maxY = y + node.minHeight(-1);
firstManagedChild = false;
}
}
}
double minHeight = maxY - minY;
return topInset + minHeight + bottomInset;
}
/**
* Computes the maximum allowable width of the Skin, based on the provided
* height.
*
* @param height The height of the Skin, in case this value might dictate
* the maximum width.
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return A double representing the maximum width of this Skin.
*/
protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return Double.MAX_VALUE;
}
/**
* Computes the maximum allowable height of the Skin, based on the provided
* width.
*
* @param width The width of the Skin, in case this value might dictate
* the maximum height.
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return A double representing the maximum height of this Skin.
*/
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return Double.MAX_VALUE;
}
// PENDING_DOC_REVIEW
/**
* Calculates the preferred width of this {@code SkinBase}. The default
* implementation calculates this width as the width of the area occupied
* by its managed children when they are positioned at their
* current positions at their preferred widths.
*
* @param height the height that should be used if preferred width depends on it
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return the calculated preferred width
*/
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
double minX = 0;
double maxX = 0;
boolean firstManagedChild = true;
for (int i = 0; i < children.size(); i++) {
Node node = children.get(i);
if (node.isManaged()) {
final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
if (!firstManagedChild) { // branch prediction favors most often used condition
minX = Math.min(minX, x);
maxX = Math.max(maxX, x + node.prefWidth(-1));
} else {
minX = x;
maxX = x + node.prefWidth(-1);
firstManagedChild = false;
}
}
}
return maxX - minX;
}
// PENDING_DOC_REVIEW
/**
* Calculates the preferred height of this {@code SkinBase}. The default
* implementation calculates this height as the height of the area occupied
* by its managed children when they are positioned at their current
* positions at their preferred heights.
*
* @param width the width that should be used if preferred height depends on it
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return the calculated preferred height
*/
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
double minY = 0;
double maxY = 0;
boolean firstManagedChild = true;
for (int i = 0; i < children.size(); i++) {
Node node = children.get(i);
if (node.isManaged()) {
final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
if (!firstManagedChild) { // branch prediction favors most often used condition
minY = Math.min(minY, y);
maxY = Math.max(maxY, y + node.prefHeight(-1));
} else {
minY = y;
maxY = y + node.prefHeight(-1);
firstManagedChild = false;
}
}
}
return maxY - minY;
}
/**
* Calculates the baseline offset based on the first managed child. If there
* is no such child, returns {@link Node#getBaselineOffset()}.
*
* @param topInset the pixel snapped top inset
* @param rightInset the pixel snapped right inset
* @param bottomInset the pixel snapped bottom inset
* @param leftInset the pixel snapped left inset
* @return baseline offset
*/
protected double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
int size = children.size();
for (int i = 0; i < size; ++i) {
Node child = children.get(i);
if (child.isManaged()) {
double offset = child.getBaselineOffset();
if (offset == Node.BASELINE_OFFSET_SAME_AS_HEIGHT) {
continue;
}
return child.getLayoutBounds().getMinY() + child.getLayoutY() + offset;
}
}
return Node.BASELINE_OFFSET_SAME_AS_HEIGHT;
}
/* *************************************************************************
* *
* (Mostly ugly) Skin -> Control forwarding API *
* *
**************************************************************************/
/**
* Utility method to get the top inset which includes padding and border
* inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true.
*
* @return Rounded up insets top
*/
protected double snappedTopInset() {
return control.snappedTopInset();
}
/**
* Utility method to get the bottom inset which includes padding and border
* inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true.
*
* @return Rounded up insets bottom
*/
protected double snappedBottomInset() {
return control.snappedBottomInset();
}
/**
* Utility method to get the left inset which includes padding and border
* inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true.
*
* @return Rounded up insets left
*/
protected double snappedLeftInset() {
return control.snappedLeftInset();
}
/**
* Utility method to get the right inset which includes padding and border
* inset. Then snapped to whole pixels if getSkinnable().isSnapToPixel() is true.
*
* @return Rounded up insets right
*/
protected double snappedRightInset() {
return control.snappedRightInset();
}
/**
* If {@code getSkinnable().isSnapToPixel()} is false, this method
* returns the same value, else it tries to return a value rounded to
* the nearest pixel, but since there is no indication if the value is
* a vertical or horizontal measurement then it may be snapped to the
* wrong pixel size metric on screens with different horizontal and
* vertical scales.
* @param value the space value to be snapped
* @return value rounded to nearest pixel
* @deprecated replaced by {@code snapSpaceX()} and {@code snapSpaceY()}
*/
@Deprecated(since="9")
protected double snapSpace(double value) {
return control.snapSpaceX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapSpaceX(double) snapSpaceX()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapSpaceX(value)}.
* @param value the space value to be snapped
* @return value rounded to nearest pixel
* @see Region#snapSpaceX(double)
* @since 9
*/
protected double snapSpaceX(double value) {
return control.snapSpaceX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapSpaceY(double) snapSpaceY()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapSpaceY(value)}.
* @param value the space value to be snapped
* @return value rounded to nearest pixel
* @see Region#snapSpaceY(double)
* @since 9
*/
protected double snapSpaceY(double value) {
return control.snapSpaceY(value);
}
/**
* If {@code getSkinnable().isSnapToPixel()} is false, this method
* returns the same value, else it tries to return a value ceiled to
* the nearest pixel, but since there is no indication if the value is
* a vertical or horizontal measurement then it may be snapped to the
* wrong pixel size metric on screens with different horizontal and
* vertical scales.
* @param value the size value to be snapped
* @return value ceiled to nearest pixel
* @deprecated replaced by {@code snapSizeX()} and {@code snapSizeY()}
*/
@Deprecated(since="9")
protected double snapSize(double value) {
return control.snapSizeX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapSizeX(double) snapSizeX()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapSizeX(value)}.
* @param value the size value to be snapped
* @return value ceiled to nearest pixel
* @see Region#snapSizeX(double)
* @since 9
*/
protected double snapSizeX(double value) {
return control.snapSizeX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapSizeY(double) snapSizeY()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapSizeY(value)}.
* @param value the size value to be snapped
* @return value ceiled to nearest pixel
* @see Region#snapSizeY(double)
* @since 9
*/
protected double snapSizeY(double value) {
return control.snapSizeY(value);
}
/**
* If {@code getSkinnable().isSnapToPixel()} is false, this method
* returns the same value, else it tries to return a value rounded to
* the nearest pixel, but since there is no indication if the value is
* a vertical or horizontal measurement then it may be snapped to the
* wrong pixel size metric on screens with different horizontal and
* vertical scales.
* @param value the position value to be snapped
* @return value rounded to nearest pixel
* @deprecated replaced by {@code snapPositionX()} and {@code snapPositionY()}
*/
@Deprecated(since="9")
protected double snapPosition(double value) {
return control.snapPositionX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapPositionX(double) snapPositionX()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapPositionX(value)}.
* @param value the position value to be snapped
* @return value rounded to nearest pixel
* @see Region#snapPositionX(double)
* @since 9
*/
protected double snapPositionX(double value) {
return control.snapPositionX(value);
}
/**
* Convenience method for accessing the
* {@link Region#snapPositionY(double) snapPositionY()}
* method on the skinnable.
* It is equivalent to calling
* {@code getSkinnable().snapPositionY(value)}.
* @param value the position value to be snapped
* @return value rounded to nearest pixel
* @see Region#snapPositionY(double)
* @since 9
*/
protected double snapPositionY(double value) {
return control.snapPositionY(value);
}
/**
* Utility method which positions the child within an area of this
* skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* This function does not resize the node and uses the node's layout bounds
* width and height to determine how it should be positioned within the area.
*
* If the vertical alignment is {@code VPos.BASELINE} then it
* will position the node so that its own baseline aligns with the passed in
* {@code baselineOffset}, otherwise the baseline parameter is ignored.
*
* If {@code snapToPixel} is {@code true} for this skin, then the x/y position
* values will be rounded to their nearest pixel boundaries.
*
* @param child the child being positioned within this skin
* @param areaX the horizontal offset of the layout area relative to this skin
* @param areaY the vertical offset of the layout area relative to this skin
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
*/
protected void positionInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight, double areaBaselineOffset,
HPos halignment, VPos valignment) {
positionInArea(child, areaX, areaY, areaWidth, areaHeight,
areaBaselineOffset, Insets.EMPTY, halignment, valignment);
}
/**
* Utility method which positions the child within an area of this
* skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* This function does not resize the node and uses the node's layout bounds
* width and height to determine how it should be positioned within the area.
*
* If the vertical alignment is {@code VPos.BASELINE} then it
* will position the node so that its own baseline aligns with the passed in
* {@code baselineOffset}, otherwise the baseline parameter is ignored.
*
* If {@code snapToPixel} is {@code true} for this skin, then the x/y position
* values will be rounded to their nearest pixel boundaries.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* @param child the child being positioned within this skin
* @param areaX the horizontal offset of the layout area relative to this skin
* @param areaY the vertical offset of the layout area relative to this skin
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
* @since JavaFX 8.0
*/
protected void positionInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight, double areaBaselineOffset,
Insets margin, HPos halignment, VPos valignment) {
Region.positionInArea(child, areaX, areaY, areaWidth, areaHeight,
areaBaselineOffset, margin, halignment, valignment,
control.isSnapToPixel());
}
/**
* Utility method which lays out the child within an area of this
* skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will resize it to fill the specified
* area unless the node's maximum size prevents it. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first to the area's width (up to the child's max width limit) and then pass
* that value to compute the child's height. If child's contentBias is vertical,
* then it will set its height to the area height (up to child's max height limit)
* and pass that height to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this skin
* @param areaX the horizontal offset of the layout area relative to this skin
* @param areaY the vertical offset of the layout area relative to this skin
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
HPos halignment, VPos valignment) {
layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
Insets.EMPTY, true, true, halignment, valignment);
}
/**
* Utility method which lays out the child within an area of this
* skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will resize it to fill the specified
* area unless the node's maximum size prevents it. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first to the area's width (up to the child's max width limit) and then pass
* that value to compute the child's height. If child's contentBias is vertical,
* then it will set its height to the area height (up to child's max height limit)
* and pass that height to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this skin
* @param areaX the horizontal offset of the layout area relative to this skin
* @param areaY the vertical offset of the layout area relative to this skin
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
Insets margin,
HPos halignment, VPos valignment) {
layoutInArea(child, areaX, areaY, areaWidth, areaHeight, areaBaselineOffset,
margin, true, true, halignment, valignment);
}
/**
* Utility method which lays out the child within an area of this
* skin defined by {@code areaX}, {@code areaY}, {@code areaWidth} x {@code areaHeight},
* with a baseline offset relative to that area.
*
* If the child is resizable, this method will use {@code fillWidth} and {@code fillHeight}
* to determine whether to resize it to fill the area or keep the child at its
* preferred dimension. If fillWidth/fillHeight are true, then this method
* will only resize the child up to its max size limits. If the node's maximum
* size preference is less than the area size, the maximum size will be used.
* If node's maximum is greater than the area size, then the node will be
* resized to fit within the area, unless its minimum size prevents it.
*
* If the child has a non-null contentBias, then this method will use it when
* resizing the child. If the contentBias is horizontal, it will set its width
* first and then pass that value to compute the child's height. If child's
* contentBias is vertical, then it will set its height first
* and pass that value to compute the child's width. If the child's contentBias
* is null, then it's width and height have no dependencies on each other.
*
* If the child is not resizable (Shape, Group, etc) then it will only be
* positioned and not resized.
*
* If the child's resulting size differs from the area's size (either
* because it was not resizable or it's sizing preferences prevented it), then
* this function will align the node relative to the area using horizontal and
* vertical alignment values.
* If valignment is {@code VPos.BASELINE} then the node's baseline will be aligned
* with the area baseline offset parameter, otherwise the baseline parameter
* is ignored.
*
* If {@code margin} is non-null, then that space will be allocated around the
* child within the layout area. margin may be null.
*
* If {@code snapToPixel} is {@code true} for this skin, then the resulting x,y
* values will be rounded to their nearest pixel boundaries and the
* width/height values will be ceiled to the next pixel boundary.
*
* @param child the child being positioned within this skin
* @param areaX the horizontal offset of the layout area relative to this skin
* @param areaY the vertical offset of the layout area relative to this skin
* @param areaWidth the width of the layout area
* @param areaHeight the height of the layout area
* @param areaBaselineOffset the baseline offset to be used if VPos is BASELINE
* @param margin the margin of space to be allocated around the child
* @param fillWidth whether or not the child should be resized to fill the area width or kept to its preferred width
* @param fillHeight whether or not the child should e resized to fill the area height or kept to its preferred height
* @param halignment the horizontal alignment for the child within the area
* @param valignment the vertical alignment for the child within the area
*/
protected void layoutInArea(Node child, double areaX, double areaY,
double areaWidth, double areaHeight,
double areaBaselineOffset,
Insets margin, boolean fillWidth, boolean fillHeight,
HPos halignment, VPos valignment) {
Region.layoutInArea(child, areaX, areaY, areaWidth, areaHeight,
areaBaselineOffset, margin, fillWidth, fillHeight, halignment,
valignment, control.isSnapToPixel());
}
/* *************************************************************************
* *
* Private Implementation *
* *
**************************************************************************/
/* ************************************************************************
* *
* Specialization of CSS handling code *
* *
**************************************************************************/
private static class StyleableProperties {
private static final List> STYLEABLES;
static {
STYLEABLES = Collections.unmodifiableList(Control.getClassCssMetaData());
}
}
/**
* Returns the CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
* @return the CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses
*/
public static List> getClassCssMetaData() {
return SkinBase.StyleableProperties.STYLEABLES;
}
/**
* This method should delegate to {@link Node#getClassCssMetaData()} so that
* a Node's CssMetaData can be accessed without the need for reflection.
* @return The CssMetaData associated with this node, which may include the
* CssMetaData of its superclasses.
*/
public List> getCssMetaData() {
return getClassCssMetaData();
}
/**
* Used to specify that a pseudo-class of this Node has changed. If the
* pseudo-class is used in a CSS selector that matches this Node, CSS will
* be reapplied. Typically, this method is called from the {@code invalidated}
* method of a property that is used as a pseudo-class. For example:
*
*
* private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state");
*
* BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) {
*
* {@literal @}Override public void invalidated() {
* pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get());
* }
*
* {@literal @}Override public Object getBean() {
* return MyControl.this;
* }
*
* {@literal @}Override public String getName() {
* return "myPseudoClassState";
* }
* };
*
*
* @see Node#pseudoClassStateChanged
* @param pseudoClass the pseudo-class that has changed state
* @param active whether or not the state is active
* @since JavaFX 8.0
*/
public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) {
Control ctl = getSkinnable();
if (ctl != null) {
ctl.pseudoClassStateChanged(pseudoClass, active);
}
}
/* *************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
/**
* This method is called by the assistive technology to request
* the value for an attribute.
*
* This method is commonly overridden by subclasses to implement
* attributes that are required for a specific role.
* If a particular attribute is not handled, the superclass implementation
* must be called.
*
*
* @param attribute the requested attribute
* @param parameters optional list of parameters
* @return the value for the requested attribute
*
* @see AccessibleAttribute
* @see Node#queryAccessibleAttribute
*
* @since JavaFX 8u40
*/
protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
return null;
}
/**
* This method is called by the assistive technology to request the action
* indicated by the argument should be executed.
*
* This method is commonly overridden by subclasses to implement
* action that are required for a specific role.
* If a particular action is not handled, the superclass implementation
* must be called.
*
*
* @param action the action to execute
* @param parameters optional list of parameters
*
* @see AccessibleAction
* @see Node#executeAccessibleAction
*
* @since JavaFX 8u40
*/
protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
}
/* *************************************************************************
* *
* Testing-only API *
* *
**************************************************************************/
}