package com.scrivenvar;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.skin.ScrollBarSkin;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.StackPane;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import javax.swing.*;
import static javafx.geometry.Orientation.VERTICAL;
public final class ScrollEventHandler implements EventHandler<Event> {
private final class MouseHandler implements EventHandler<MouseEvent> {
private final EventHandler<? super MouseEvent> mOldHandler;
private MouseHandler( final EventHandler<? super MouseEvent> oldHandler ) {
mOldHandler = oldHandler;
}
@Override
public void handle( final MouseEvent event ) {
ScrollEventHandler.this.handle( event );
mOldHandler.handle( event );
}
}
private final class ScrollHandler implements EventHandler<ScrollEvent> {
@Override
public void handle( final ScrollEvent event ) {
ScrollEventHandler.this.handle( event );
}
}
private final VirtualizedScrollPane<StyleClassedTextArea> mEditorScrollPane;
private final JScrollBar mPreviewScrollBar;
private final BooleanProperty mEnabled = new SimpleBooleanProperty();
public ScrollEventHandler(
final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane,
final JScrollBar previewScrollBar ) {
mEditorScrollPane = editorScrollPane;
mPreviewScrollBar = previewScrollBar;
mEditorScrollPane.addEventFilter( ScrollEvent.ANY, new ScrollHandler() );
final var thumb = getVerticalScrollBarThumb( mEditorScrollPane );
thumb.setOnMouseDragged( new MouseHandler( thumb.getOnMouseDragged() ) );
}
public BooleanProperty enabledProperty() {
return mEnabled;
}
@Override
public void handle( final Event event ) {
if( isEnabled() ) {
final var eScrollPane = getEditorScrollPane();
final int eScrollY =
eScrollPane.estimatedScrollYProperty().getValue().intValue();
final int eHeight = (int)
(eScrollPane.totalHeightEstimateProperty().getValue().intValue()
- eScrollPane.getHeight());
final double eRatio = eHeight > 0
? Math.min( Math.max( eScrollY / (float) eHeight, 0 ), 1 ) : 0;
final var pScrollBar = getPreviewScrollBar();
final var pHeight = pScrollBar.getMaximum() - pScrollBar.getHeight();
final var pScrollY = (int) (pHeight * eRatio);
pScrollBar.setValue( pScrollY );
pScrollBar.getParent().repaint();
}
}
private StackPane getVerticalScrollBarThumb(
final VirtualizedScrollPane<StyleClassedTextArea> pane ) {
final ScrollBar scrollBar = getVerticalScrollBar( pane );
final ScrollBarSkin skin = (ScrollBarSkin) (scrollBar.skinProperty().get());
for( final Node node : skin.getChildren() ) {
if( node.getStyleClass().contains( "thumb" ) ) {
return (StackPane) node;
}
}
throw new IllegalArgumentException( "No scroll bar skin found." );
}
private ScrollBar getVerticalScrollBar(
final VirtualizedScrollPane<StyleClassedTextArea> pane ) {
for( final Node node : pane.getChildrenUnmodifiable() ) {
if( node instanceof ScrollBar ) {
final ScrollBar scrollBar = (ScrollBar) node;
if( scrollBar.getOrientation() == VERTICAL ) {
return scrollBar;
}
}
}
throw new IllegalArgumentException( "No vertical scroll pane found." );
}
private boolean isEnabled() {
return mEnabled.get();
}
private VirtualizedScrollPane<StyleClassedTextArea> getEditorScrollPane() {
return mEditorScrollPane;
}
private JScrollBar getPreviewScrollBar() {
return mPreviewScrollBar;
}
}