Skip to content

Commit

Permalink
Merge pull request #36 from kadampabookings/prod
Browse files Browse the repository at this point in the history
August work
  • Loading branch information
salmonb authored Aug 30, 2024
2 parents fcefa8f + 1e54bc5 commit 18be277
Show file tree
Hide file tree
Showing 32 changed files with 646 additions and 148 deletions.
6 changes: 6 additions & 0 deletions webfx-kit/webfx-kit-javafxbase-emul/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-util</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.sun.javafx.event;

import dev.webfx.platform.util.tuples.Pair;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -229,15 +232,21 @@ private static void validateEventFilter(
public interface EventSourcesListener {
void onEventSource(EventType<?> eventType, Object eventSource);
}
private static EventSourcesListener eventSourcesListener;
private static EventSourcesListener EVENT_SOURCES_LISTENER;
private static final List<Pair<EventType<?>, Object>> PENDING_NOTIFY_EVENTS = new ArrayList<>();

public static void setEventSourcesListener(EventSourcesListener eventSourcesListener) {
EventHandlerManager.eventSourcesListener = eventSourcesListener;
private static void notifyEventSourcesListener(EventType<?> eventType, Object eventSource) {
if (EVENT_SOURCES_LISTENER != null) {
EVENT_SOURCES_LISTENER.onEventSource(eventType, eventSource);
} else {
PENDING_NOTIFY_EVENTS.add(new Pair<>(eventType, eventSource));
}
}

private static void notifyEventSourcesListener(EventType<?> eventType, Object eventSource) {
if (eventSourcesListener != null)
eventSourcesListener.onEventSource(eventType, eventSource);
public static void setEventSourcesListener(EventSourcesListener eventSourcesListener) {
EVENT_SOURCES_LISTENER = eventSourcesListener;
PENDING_NOTIFY_EVENTS.forEach(pair -> eventSourcesListener.onEventSource(pair.get1(), pair.get2()));
PENDING_NOTIFY_EVENTS.clear();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,45 @@ public class ScrollPane extends Control {
private Runnable onChildrenLayout;

public ScrollPane() {
// The purpose of this code is to register the mouse handler so it captures focus on mouse click
new BehaviorSkinBase<ScrollPane, ScrollPaneBehavior>(this, new ScrollPaneBehavior(this)) {};
// The purpose of this code is to register the mouse handler, so it captures focus on mouse click
new BehaviorSkinBase<>(this, new ScrollPaneBehavior(this)) {};
}

public ScrollPane(Node content) {
this();
setContent(content);
}

public boolean shouldUseLayoutMeasurable() {
return false;
}

@Override protected double computeMinWidth(double height) {
return 0;
}

@Override protected double computePrefWidth(double height) {
Node content = getContent();
return content == null ? 0 : content.prefWidth(height);
}

@Override protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}

@Override protected double computeMinHeight(double width) {
return 0;
}

@Override protected double computePrefHeight(double width) {
Node content = getContent();
return content == null ? 0 : content.prefHeight(width);
}

@Override protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}

@Override
protected void sceneToLocal(com.sun.javafx.geom.Point2D pt) {
super.sceneToLocal(pt);
Expand All @@ -40,7 +70,7 @@ protected void localToScene(com.sun.javafx.geom.Point2D pt) {
super.localToScene(pt);
}

private Property<ScrollBarPolicy> hbarPolicyProperty = new SimpleObjectProperty<>(ScrollBarPolicy.AS_NEEDED);
private final Property<ScrollBarPolicy> hbarPolicyProperty = new SimpleObjectProperty<>(ScrollBarPolicy.AS_NEEDED);

public Property<ScrollBarPolicy> hbarPolicyProperty() {
return hbarPolicyProperty;
Expand All @@ -54,7 +84,7 @@ public ScrollBarPolicy getHbarPolicy() {
return hbarPolicyProperty.getValue();
}

private Property<ScrollBarPolicy> vbarPolicyProperty = new SimpleObjectProperty<>(ScrollBarPolicy.AS_NEEDED);
private final Property<ScrollBarPolicy> vbarPolicyProperty = new SimpleObjectProperty<>(ScrollBarPolicy.AS_NEEDED);

public Property<ScrollBarPolicy> vbarPolicyProperty() {
return vbarPolicyProperty;
Expand Down
12 changes: 12 additions & 0 deletions webfx-kit/webfx-kit-javafxcontrols-peers-gwt-j2cl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-os</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-scheduler</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-uischeduler</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,40 @@
import dev.webfx.kit.mapper.peers.javafxcontrols.base.ScrollPanePeerMixin;
import dev.webfx.kit.mapper.peers.javafxgraphics.SceneRequester;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.html.HtmlRegionPeer;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.html.layoutmeasurable.HtmlLayoutMeasurable;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.shared.HtmlSvgNodePeer;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.util.HtmlUtil;
import dev.webfx.kit.util.properties.FXProperties;
import dev.webfx.platform.os.OperatingSystem;
import dev.webfx.platform.scheduler.Scheduled;
import dev.webfx.platform.uischeduler.UiScheduler;
import elemental2.dom.AddEventListenerOptions;
import elemental2.dom.Element;
import elemental2.dom.HTMLElement;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.TouchEvent;
import jsinterop.base.Js;
import jsinterop.base.JsPropertyMap;

import java.util.function.Consumer;

/**
* @author Bruno Salmon
*/
public final class HtmlScrollPanePeer
<N extends ScrollPane, NB extends ScrollPanePeerBase<N, NB, NM>, NM extends ScrollPanePeerMixin<N, NB, NM>>
<N extends ScrollPane, NB extends ScrollPanePeerBase<N, NB, NM>, NM extends ScrollPanePeerMixin<N, NB, NM>>

extends HtmlRegionPeer<N, NB, NM>
implements ScrollPanePeerMixin<N, NB, NM> {

private static final boolean USE_CSS = OperatingSystem.isMobile(); // much smoother experience on mobiles but requires passive mode
private static final boolean USE_PERFECT_SCROLLBAR = !USE_CSS; // the perfect scrollbars looks better on desktops than in CSS mode

extends HtmlRegionPeer<N, NB, NM>
implements ScrollPanePeerMixin<N, NB, NM>, HtmlLayoutMeasurable {
private double scrollTop, scrollLeft;
private boolean syncing;
private boolean cssScrollDetected;

public HtmlScrollPanePeer() {
this((NB) new ScrollPanePeerBase(), HtmlUtil.createElement("fx-scrollpane"));
Expand All @@ -37,22 +50,83 @@ public HtmlScrollPanePeer(NB base, HTMLElement element) {
@Override
public void bind(N node, SceneRequester sceneRequester) {
super.bind(node, sceneRequester);
// Important: perfect scrollbar expect a standard HTML container (won't work with fx-scrollpane)
HTMLElement psContainer = HtmlUtil.setStyle(HtmlUtil.createDivElement(), "position: absolute; width: 100%; height: 100%");
setChildrenContainer(psContainer);
HtmlUtil.setChildren(getElement(), psContainer);
node.setOnChildrenLayout(HtmlScrollPanePeer.this::scheduleUpdate);
HTMLElement element = getElement();
if (USE_PERFECT_SCROLLBAR) {
// Important: perfect scrollbar expect a standard HTML container (won't work with fx-scrollpane)
HTMLElement psContainer = HtmlUtil.setStyle(HtmlUtil.createDivElement(), "position: absolute; width: 100%; height: 100%");
setChildrenContainer(psContainer);
HtmlUtil.setChildren(element, psContainer);
} else { // CSS mode which relies on the overflow-x & overflow-y values
// Important: the CSS mode will react to touch events only if the Scene touch events thar are passed to
// JavaFX originates from an event listener in passive mode, which is not the case by default with webfx,
// because the passive mode doesn't let us stop the propagation which is a feature that the application code
// requests when calling event.consume(). But it's very likely ok to disable this feature during a touch
// scroll to enjoy the smoothness of the scroll in passive mode on mobiles.
node.addEventHandler(TouchEvent.ANY, e -> {
// The purpose of this handler is to detect any touch event that targets the scroll pane while still in
// standard mode (non-passive). By chance, this detection happens just before it is passed to the scene,
// and it's the only and last opportunity to activate the passive mode. Without catching this event
// and switching to passive mode, the CSS overflow scrollbars wouldn't react to the touch events.
if (!HtmlSvgNodePeer.isScrolling()) {
HtmlSvgNodePeer.setScrolling(true);
// It's also important to detect the end of this touch scroll to go back to the standard mode, so we
// set up a periodic timer for that.
UiScheduler.schedulePeriodicInAnimationFrame(100, new Consumer<>() {
private long lastMillis = System.currentTimeMillis();
private double lastScrollTop = element.scrollTop, lastScrollLeft = element.scrollLeft;
private double scrollTopInertia, scrollLeftInertia;
@Override
public void accept(Scheduled scheduled) {
// We check if the scroll has become stationary since last call
double deltaTop = element.scrollTop - lastScrollTop;
double deltaLeft = element.scrollLeft - lastScrollLeft;
boolean stationary = deltaTop == 0 && deltaLeft == 0 && !cssScrollDetected; // also considering any scroll event
// Note:
long nowMillis = System.currentTimeMillis();
long delayMillis = nowMillis - lastMillis;
// Skipping suspicious false stop detection for 2s
if (stationary && delayMillis < 2000 && (Math.abs(scrollTopInertia) > 0.05 || Math.abs(scrollLeftInertia) > 0.05)) {
return;
}
scrollTopInertia = deltaTop / delayMillis;
scrollLeftInertia = deltaLeft / delayMillis;
// if since the last period no scroll events have been generated and the element looks stationary
if (stationary) {
// we consider it's the end of the touch scroll and go back to the standard mode
HtmlSvgNodePeer.setScrolling(false);
scheduled.cancel(); // we can stop this periodic check
} else {
lastScrollLeft = element.scrollLeft;
lastScrollTop = element.scrollTop;
lastMillis = nowMillis;
cssScrollDetected = false;
}
}
});
}
});
AddEventListenerOptions passiveOption = AddEventListenerOptions.create();
passiveOption.setPassive(true);
// We intercept the JS scroll events to update the ScrollPane position in JavaFX when the html one changes
element.addEventListener("scroll", e -> {
if (element.scrollTop != scrollTop)
setScrollTop(element.scrollTop);
if (element.scrollLeft != scrollLeft)
setScrollLeft(element.scrollLeft);
// Also if this happens during a touch scroll, we report the detection of the scroll and reschedule the
// css scroll end detector.
cssScrollDetected = true;
}, passiveOption);
}
node.setOnChildrenLayout(HtmlScrollPanePeer.this::scheduleUiUpdate);
// 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());
FXProperties.runOnPropertiesChange(this::scheduleUiUpdate, node.sceneProperty());
}

private double scrollTop, scrollLeft;
private boolean syncing;

private void setScrollTop(double scrollTop) {
this.scrollTop = scrollTop;
vSyncModelFromUi();
Expand Down Expand Up @@ -141,22 +215,28 @@ private void syncUiFromModel(boolean horizontal, boolean vertical) {
scrollTop = 0;
}
}
scheduleUpdate();
scheduleUiUpdate();
syncing = false;
}

private boolean pending, psInitialized;
private void scheduleUpdate() {
private void scheduleUiUpdate() {
if (!pending) {
pending = true;
UiScheduler.scheduleDeferred(() -> {
Element psContainer = getChildrenContainer();
if (!psInitialized) {
N node = getNode();
callPerfectScrollbarInitialize(psContainer, node.getHbarPolicy() == ScrollPane.ScrollBarPolicy.NEVER, node.getvbarPolicy() == ScrollPane.ScrollBarPolicy.NEVER);
psInitialized = true;
if (USE_PERFECT_SCROLLBAR) {
Element psContainer = getChildrenContainer();
if (!psInitialized) {
N node = getNode();
callPerfectScrollbarInitialize(psContainer, node.getHbarPolicy() == ScrollPane.ScrollBarPolicy.NEVER, node.getvbarPolicy() == ScrollPane.ScrollBarPolicy.NEVER);
psInitialized = true;
}
callPerfectScrollbarUpdate(psContainer);
} else {
HTMLElement element = getElement();
element.scrollTop = scrollTop;
element.scrollLeft = scrollLeft;
}
callPerfectScrollbarUpdate(psContainer);
pending = false;
});
}
Expand All @@ -167,7 +247,7 @@ private void callPerfectScrollbarInitialize(Element psContainer, boolean suppres
Js.asPropertyMap(psContainer).set("ps", ps);
psContainer.addEventListener("ps-scroll-x", e -> setScrollLeft(psContainer.scrollLeft));
psContainer.addEventListener("ps-scroll-y", e -> setScrollTop(psContainer.scrollTop));
};
}

private void callPerfectScrollbarUpdate(Element psContainer) {
psContainer.scrollLeft = scrollLeft;
Expand All @@ -188,12 +268,34 @@ public void updateHeight(Number height) {
vSyncUiFromModel();
}

private String scrollbarPolicyToOverflow(ScrollPane.ScrollBarPolicy scrollBarPolicy) {
if (scrollBarPolicy != null) {
switch (scrollBarPolicy) {
case ALWAYS:
return "scroll";
case AS_NEEDED:
return "auto";
case NEVER:
return "hidden";
}
}
return "hidden";
}

@Override
public void updateHbarPolicy(ScrollPane.ScrollBarPolicy hbarPolicy) {
if (USE_CSS) {
setElementStyleAttribute("overflow-x", scrollbarPolicyToOverflow(hbarPolicy));
}
// Note: with PerfectScrollbar, This value is considered during initialisation only
}

@Override
public void updateVbarPolicy(ScrollPane.ScrollBarPolicy vbarPolicy) {
if (USE_CSS) {
setElementStyleAttribute("overflow-y", scrollbarPolicyToOverflow(vbarPolicy));
}
// Note: with PerfectScrollbar, This value is considered during initialisation only
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/***** Global variables *****/
:root {
--fx-border-color: #c0c0c0;
--fx-border-radius: 5px;
--fx-border-style: solid;
--fx-border-width: 1px;
--fx-border-color-focus: #0096D6;
--fx-textfield-background: white;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public String getName() {
private static boolean warned;
@Override
public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
NodePeer nodePeer = getNodePeer();
NodePeer nodePeer = getOrCreateAndBindNodePeer();
if (nodePeer instanceof LayoutMeasurable) {
Bounds layoutBounds = ((LayoutMeasurable) nodePeer).getLayoutBounds();
return new BoxBounds((float) layoutBounds.getMinX(), (float) layoutBounds.getMinY(), 0, (float) layoutBounds.getMaxX(), (float) layoutBounds.getMaxY(), 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,8 @@ public DoubleProperty strokeDashOffsetProperty() {
public ObservableList<Double> getStrokeDashArray() {
return getStrokeDashArray;
}

public static Shape subtract(final Shape shape1, final Shape shape2) {
return new SubtractShape(shape1, shape2);
}
}
Loading

0 comments on commit 18be277

Please sign in to comment.