Skip to content

Commit

Permalink
Fixed possible lost of viewport position in HtmlScrollPanePeer
Browse files Browse the repository at this point in the history
  • Loading branch information
salmonb committed Sep 12, 2023
1 parent bb4c886 commit a692c2b
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.webfx.kit.mapper.peers.javafxcontrols.gwt.html;

import dev.webfx.kit.util.properties.FXProperties;
import elemental2.dom.Element;
import elemental2.dom.HTMLElement;
import javafx.geometry.BoundingBox;
Expand Down Expand Up @@ -39,6 +40,12 @@ public void bind(N node, SceneRequester sceneRequester) {
setChildrenContainer(psContainer);
HtmlUtil.setChildren(getElement(), psContainer);
node.setOnChildrenLayout(HtmlScrollPanePeer.this::scheduleUpdate);
// The following listener is to reestablish the scroll position on scene change. For ex when the user 1) switches
// to another page through UI routing and then 2) come back, this node is removed from the scene graph at 1) =>
// scene = null until 2) => scene reestablished, but Perfect scrollbar lost its state when removed from the DOM.
// This listener will trigger a schedule update at 2) which will restore the perfect scrollbar state (scrollTop
// & scrollLeft will be reapplied).
FXProperties.runOnPropertiesChange(this::scheduleUpdate, node.sceneProperty());
}

private double scrollTop, scrollLeft;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public CanvasScenePeer(Scene scene) {
}

@Override
public void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<Node> childrenChange) {
public void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<? extends Node> childrenChange) {
scene.updateChildrenPeers(parent.getChildren());
requestCanvasRepaint();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface ScenePeer extends TKScene {

Scene getScene();

void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<Node> childrenChange);
void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<? extends Node> childrenChange);

NodePeer pickPeer(double sceneX, double sceneY);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,57 @@
*/
public abstract class Node implements INode, EventTarget, Styleable {

private final Property<Parent> parentProperty = new SimpleObjectProperty<>();

// This method is called when the node peer has been created, inserted to the DOM, and bound to this node (i.e.
// reacting to the properties changes to update the HTML mapping). The node may need to do something at this point.
void onNodePeerBound() { }

private final Property<Parent> parentProperty = new SimpleObjectProperty<>() {
protected void invalidated() {
// The child automatically inherits the scene from the parent (if the parent is null, the scene is set to
// null for this child)
Parent parent = getParent();
Scene newScene = parent == null ? null : parent.getScene();
setScene(newScene); // Note: the scene will be propagated to the possible children in the scene listener (see below)
}
};
@Override
public Property<Parent> parentProperty() {
return parentProperty;
}

private final ObjectProperty<Scene> scene = new SimpleObjectProperty<>() {
@Override
protected void invalidated() {
Scene newScene = getScene();
// Initialising the event dispatcher if not already done
if (newScene != null)
newScene.initializeInternalEventDispatcher();
// Propagating the scene to the children
if (Node.this instanceof Parent) {
for (Node child : ((Parent) Node.this).getChildren()) {
child.setScene(newScene);
}
}
// Also to the clip (if set)
Node clip = getClip();
if (clip != null)
clip.setScene(newScene);
}
};

public ObjectProperty<Scene> sceneProperty() {
return scene;
}

public Scene getScene() {
return scene.getValue();
}

public void setScene(Scene scene) {
this.scene.setValue(scene);
}

private final Property<Boolean> managedProperty = new SimpleObjectProperty<Boolean>(true) {
@Override
protected void invalidated() {
Expand All @@ -64,6 +109,7 @@ protected void invalidated() {
notifyManagedChanged();
}
};

@Override
public Property<Boolean> managedProperty() {
return managedProperty;
Expand Down Expand Up @@ -636,7 +682,7 @@ public ObservableList<Transform> getTransforms() {
private Translate layoutTranslateTransform;
private Scale scaleTransform;
private Rotate rotateTransform;

@Override
public List<Transform> getAllNodeTransforms() {
// We need to consider all transforms: I) those declared directly at the Node level via translateX/Y, rotateX/Y
Expand All @@ -648,8 +694,8 @@ public List<Transform> getAllNodeTransforms() {
double ltX = getTranslateX();
double ltY = getTranslateY();
//if (!(getParent() instanceof Group)) { // Breaks EnzoClocks, TallyCounter and SpaceFX demos
ltX += getLayoutX();
ltY += getLayoutY();
ltX += getLayoutX();
ltY += getLayoutY();
//}
if (ltX != 0 || ltY != 0) {
if (layoutTranslateTransform == null)
Expand Down Expand Up @@ -1288,25 +1334,6 @@ public void requestPeerFocus() {
peerFocusRequested = true;
}

private ObjectProperty<Scene> scene = new SimpleObjectProperty<>();

public ObjectProperty<Scene> sceneProperty() {
return scene;
}

public Scene getScene() {
return scene.getValue();
}

public void setScene(Scene scene) {
this.scene.setValue(scene);
Node clip = getClip();
if (clip != null)
clip.setScene(scene);
if (scene != null)
scene.initializeInternalEventDispatcher();
}

private LayoutMeasurable layoutMeasurable;

public LayoutMeasurable getLayoutMeasurable() {
Expand Down Expand Up @@ -1338,7 +1365,7 @@ protected void createLayoutMeasurable(NodePeer nodePeer) {
// Always creating a new LayoutMeasurable (even when nodePeer is valid) so that min/pref/max
// width/height user values are returned in priority whenever they have been set.
layoutMeasurable = new LayoutMeasurable() {
private LayoutMeasurable acceptedLayoutMeasurable = nodePeer instanceof LayoutMeasurable && shouldUseLayoutMeasurable() ? (LayoutMeasurable) nodePeer : null;
private final LayoutMeasurable acceptedLayoutMeasurable = nodePeer instanceof LayoutMeasurable && shouldUseLayoutMeasurable() ? (LayoutMeasurable) nodePeer : null;
{
if (nodePeer instanceof HasSizeChangedCallback)
UiScheduler.scheduleDeferred(() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.RectBounds;
import com.sun.javafx.geom.transform.BaseTransform;
import dev.webfx.kit.mapper.peers.javafxgraphics.markers.HasManagedProperty;
import dev.webfx.kit.util.properties.FXProperties;
import dev.webfx.kit.util.properties.ObservableLists;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.layout.LayoutFlags;
import javafx.scene.layout.PreferenceResizableNode;
import dev.webfx.kit.mapper.peers.javafxgraphics.markers.HasManagedProperty;
import dev.webfx.kit.util.properties.ObservableLists;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -23,40 +25,48 @@
public class Parent extends Node {

private final ObservableList<Node> children = FXCollections.observableArrayList();

{
children.addListener((ListChangeListener<Node>) c -> {
Scene scene = getScene();
children.addListener(this::onChildrenChanged);
}

private void onChildrenChanged(ListChangeListener.Change<? extends Node> c) {
// This listener has 2 main tasks: 1) propagate this parent change to the children & 2) ask the scene to update
// the peers structure (scene graph => DOM tree mapping).

// Note: the following sequence is the only one that works to preserve the HTML scrollbar state when removed and
// put back to the DOM (perfectscrollbar.js loses its state when removed from the DOM). See HtmlScrollPanePeer.

// First we propagate this parent to the children. This is half of the job for 1) as we also need to null the
// parent to the removed children, but it's important to not do it yet at this stage.
for (Node child : getChildren())
child.setParent(this);
// Then we do 2) i.e. call scene.updateParentAndChildrenPeers()
Scene scene = getScene(); // Of course scene needs to be non-null and the node peer of this parent needs to be created
if (scene != null && getNodePeer() != null) {
scene.updateParentAndChildrenPeers(this, c);
}
// We do the second part of the job for 1) here, which is to null the parent of the removed children.
if (c != null) {
c.reset(); // Because scene.updateParentAndChildrenPeers() already used it.
while (c.next()) {
if (c.wasAdded())
for (Node child : c.getAddedSubList()) {
setAndPropagateScene(child, scene);
child.setParent(this);
}
else if (c.wasRemoved())// Setting parent and scene to null for removed children
if (c.wasRemoved())// Setting parent and scene to null for removed children
for (Node child : c.getRemoved()) {
child.setParent(null);
setAndPropagateScene(child, null);
if (child.getParent() == Parent.this)
child.setParent(null);
}
}
managedChildChanged();
});
}

static void setAndPropagateScene(Node node, Scene scene) {
node.setScene(scene);
if (node instanceof Parent) {
for (Node child : ((Parent) node).getChildren()) {
//if (child.getScene() != scene) // Commented this optimisation as it prevents the Mandelbrot canvas thumbnail to get the scene back when returning to home page
setAndPropagateScene(child, scene);
/* Commented this optimization as it was causing problems in Mandelbrot demo: when going back to first page, 1 or 2 thumbnails had the scene set and not others
else // If already done for this child, assuming it's done for others
break;
*/
}
}
// Final detail, we need to call managedChildChanged()
managedChildChanged();
}

@Override
void onNodePeerBound() {
FXProperties.onPropertySet((ObservableValue) getProperties().get("skinProperty"),
skin -> onChildrenChanged(null), true);
}


Parent() {
}

Expand Down Expand Up @@ -636,8 +646,8 @@ private void recomputeBounds() {
? dirtyChildren : children,
dirtyChildrenCount))
*/
// failed to update cached bounds, recreate them
createCachedBounds(children);
// failed to update cached bounds, recreate them
createCachedBounds(children);
}

// Note: this marks the currently processed child in terms of transformed bounds. In rare situations like
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import dev.webfx.kit.mapper.peers.javafxgraphics.markers.HasHeightProperty;
import dev.webfx.kit.mapper.peers.javafxgraphics.markers.HasRootProperty;
import dev.webfx.kit.mapper.peers.javafxgraphics.markers.HasWidthProperty;
import dev.webfx.kit.util.properties.FXProperties;
import dev.webfx.kit.util.properties.ObservableLists;
import dev.webfx.platform.scheduler.Scheduled;
import dev.webfx.platform.uischeduler.AnimationFramePass;
Expand Down Expand Up @@ -185,7 +184,7 @@ protected void invalidated() {
createAndBindRootNodePeerAndChildren(root);
// Resetting the scene to null for the old root and its children
if (oldRoot != null && oldRoot.getScene() == Scene.this)
Parent.setAndPropagateScene(oldRoot, null);
oldRoot.setScene(null);
oldRoot = root;
}
};
Expand Down Expand Up @@ -652,11 +651,12 @@ private void keepParentAndChildrenPeersUpdated(Parent parent) {
// Setting the parent to all children
for (Node child : parent.getChildren())
child.setParent(parent);
updateParentAndChildrenPeers(parent, (ListChangeListener.Change<Node>) c);
// Note:
updateParentAndChildrenPeers(parent, c);
}, parent.getChildren());
}

private void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<Node> childrenChange) {
void updateParentAndChildrenPeers(Parent parent, ListChangeListener.Change<? extends Node> childrenChange) {
impl_getPeer().updateParentAndChildrenPeers(parent, childrenChange);
}

Expand Down Expand Up @@ -704,9 +704,7 @@ public NodePeer getOrCreateAndBindNodePeer(Node node) {
node.setNodePeer(nodePeer = createUnimplementedNodePeer(node)); // Displaying a "Unimplemented..." button instead
else { // Standard case (the node view was successfully created)
nodePeer.bind(node, sceneRequester);
if (node instanceof Parent)
FXProperties.onPropertySet((ObservableValue) node.getProperties().get("skinProperty"),
skin -> keepParentAndChildrenPeersUpdated((Parent) node), true);
node.onNodePeerBound();
}
node.callNodePeerHandlers();
}
Expand Down Expand Up @@ -1185,36 +1183,36 @@ public void changedSize(float w, float h) {
if (h != Scene.this.getHeight()) Scene.this.setHeight((double)h);
}

/*
@Override
public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY,
MouseButton button, boolean popupTrigger, boolean synthesized,
boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown,
boolean primaryDown, boolean middleDown, boolean secondaryDown)
{
MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button,
0, // click count will be adjusted by clickGenerator later anyway
shiftDown, controlDown, altDown, metaDown,
primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null);
impl_processMouseEvent(mouseEvent);
}
/*
@Override
public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY,
MouseButton button, boolean popupTrigger, boolean synthesized,
boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown,
boolean primaryDown, boolean middleDown, boolean secondaryDown)
{
MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button,
0, // click count will be adjusted by clickGenerator later anyway
shiftDown, controlDown, altDown, metaDown,
primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null);
impl_processMouseEvent(mouseEvent);
}
@Override
public void keyEvent(KeyEvent keyEvent)
{
impl_processKeyEvent(keyEvent);
}
@Override
public void keyEvent(KeyEvent keyEvent)
{
impl_processKeyEvent(keyEvent);
}
@Override
public void inputMethodEvent(EventType<InputMethodEvent> type,
ObservableList<InputMethodTextRun> composed, String committed,
int caretPosition)
{
InputMethodEvent inputMethodEvent = new InputMethodEvent(
type, composed, committed, caretPosition);
processInputMethodEvent(inputMethodEvent);
}
*/
@Override
public void inputMethodEvent(EventType<InputMethodEvent> type,
ObservableList<InputMethodTextRun> composed, String committed,
int caretPosition)
{
InputMethodEvent inputMethodEvent = new InputMethodEvent(
type, composed, committed, caretPosition);
processInputMethodEvent(inputMethodEvent);
}
*/
public void menuEvent(double x, double y, double xAbs, double yAbs,
boolean isKeyboardTrigger) {
Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger);
Expand Down
Loading

0 comments on commit a692c2b

Please sign in to comment.