-
Enhancement
-
Resolution: Unresolved
-
P4
-
None
-
None
This is split off from JDK-8290040.
This proposal would introduce a new property on Node which is true when the Node is currently shown on screen in any way (although it may be covered, invisible or off screen). More formally, it would be true when the current node is part of a Scene, which is attached to a Window that is currently showing.
Such a property already is used by ProgressIndicatorSkin to enable animations only when it would make sense to do so, and by PopupWindow to only show the popup when its owner is showing.
This property is also incredibly useful for user code:
It can be used to active and deactivate animations on demand. A running animation will keep a reference to its Node, so it is very important to disable it when a Node is no longer shown in case it is no longer referenced. Equally important is that the animation starts running again if such a Node becomes visible again.
It can be used to register and unregister listeners on demand, allowing listeners to be pro-actively removed when Nodes are about to be cleaned up (or to be re-added if this was not the case).
Use case examples:
label.textProperty().bind(
model.textProperty().when(label::shownProperty)
);
The above binds a label's text property to a longer lived model, but only when the label is in active use. Normally, this would make the label unable to be cleaned up due to the model referring it, but because the shown property will become false, the reference is removed using the `when` construct.
node.shownProperty().addListener((obs, old, shown) -> {
if (shown) timeline.playFromStart();
else timeline.stop();
});
The above example uses the shownProperty to ensure that an animation is either playing or stopped at the exact right moment. Disabling the animation when a node is no longer shown ensures there are no memory leaks.
# Impact on Node
The addition of another property on a class like Node is always a trade off. There are a few things that can be done to minimize this impact.
First, the proposed property would only be created when needed, like most other properties on Node. Second, the property will be using the lazy properties provided by the fluent bindings, which means they will not register any listeners unless in actual use. This is to avoid an overabundance of listeners (one for each Node) on Scene and Window (see the regression that was fixed in https://github.com/openjdk/jfx/pull/185).
If another field in `Node` is considered unreasonable for this use case, we could consider putting this property in the properties map, using a key that can't be duplicated by user code (see USER_DATA_KEY), or we could consider introducing a new map specifically for this purpose and perhaps move many existing rarely used properties to this map as well (like those under MiscProperties). If the addition of a single property is considered "too much" then for the same reason, moving many existing rarely used properties should be considered a "big win".
Candidates for fields that are rarely used are: id, styleClass, style, blendMode, pickOnBounds, nodeOrientation and labeledBy.
# Alternatives
A helper class could be provided that creates the shown property in a static way, as all the required information to create it is already available and public.
This should be available in a very easy to reach place, a possible suggestion is:
public class Nodes {
public static ObservableValue<Boolean> showing(Node node) {
return node.sceneProperty()
.flatMap(Scene::windowProperty)
.flatMap(Window::showingProperty)
.orElse(false);
}
}
This would however be less discoverable than having the property on Node.
This proposal would introduce a new property on Node which is true when the Node is currently shown on screen in any way (although it may be covered, invisible or off screen). More formally, it would be true when the current node is part of a Scene, which is attached to a Window that is currently showing.
Such a property already is used by ProgressIndicatorSkin to enable animations only when it would make sense to do so, and by PopupWindow to only show the popup when its owner is showing.
This property is also incredibly useful for user code:
It can be used to active and deactivate animations on demand. A running animation will keep a reference to its Node, so it is very important to disable it when a Node is no longer shown in case it is no longer referenced. Equally important is that the animation starts running again if such a Node becomes visible again.
It can be used to register and unregister listeners on demand, allowing listeners to be pro-actively removed when Nodes are about to be cleaned up (or to be re-added if this was not the case).
Use case examples:
label.textProperty().bind(
model.textProperty().when(label::shownProperty)
);
The above binds a label's text property to a longer lived model, but only when the label is in active use. Normally, this would make the label unable to be cleaned up due to the model referring it, but because the shown property will become false, the reference is removed using the `when` construct.
node.shownProperty().addListener((obs, old, shown) -> {
if (shown) timeline.playFromStart();
else timeline.stop();
});
The above example uses the shownProperty to ensure that an animation is either playing or stopped at the exact right moment. Disabling the animation when a node is no longer shown ensures there are no memory leaks.
# Impact on Node
The addition of another property on a class like Node is always a trade off. There are a few things that can be done to minimize this impact.
First, the proposed property would only be created when needed, like most other properties on Node. Second, the property will be using the lazy properties provided by the fluent bindings, which means they will not register any listeners unless in actual use. This is to avoid an overabundance of listeners (one for each Node) on Scene and Window (see the regression that was fixed in https://github.com/openjdk/jfx/pull/185).
If another field in `Node` is considered unreasonable for this use case, we could consider putting this property in the properties map, using a key that can't be duplicated by user code (see USER_DATA_KEY), or we could consider introducing a new map specifically for this purpose and perhaps move many existing rarely used properties to this map as well (like those under MiscProperties). If the addition of a single property is considered "too much" then for the same reason, moving many existing rarely used properties should be considered a "big win".
Candidates for fields that are rarely used are: id, styleClass, style, blendMode, pickOnBounds, nodeOrientation and labeledBy.
# Alternatives
A helper class could be provided that creates the shown property in a static way, as all the required information to create it is already available and public.
This should be available in a very easy to reach place, a possible suggestion is:
public class Nodes {
public static ObservableValue<Boolean> showing(Node node) {
return node.sceneProperty()
.flatMap(Scene::windowProperty)
.flatMap(Window::showingProperty)
.orElse(false);
}
}
This would however be less discoverable than having the property on Node.