Giter Site home page Giter Site logo

Smooth scrolling about flowless HOT 8 CLOSED

fxmisc avatar fxmisc commented on May 29, 2024 3
Smooth scrolling

from flowless.

Comments (8)

palexdev avatar palexdev commented on May 29, 2024 1

@Jugen Hello, sorry for the late response.
I can confirm that now the smooth scrolling works, however there still seems to be some issues:

  1. The DRAG_DETECTED handler is not working. So if the view is scrolling, and you click on the scrollbar it doesn't stop scrolling.
  2. There's some sort of "jump" when the scrolling starts which should not happen
2021-04-11.12-41-49_Trim.mp4

EDIT: I was wondering if I could fix it by changing the type of handler... Yep, everything works now
The code now is:

    public static void setSmoothScrolling(VirtualFlow<?, ?> flow, Var<Double> scrollDirection) {
        final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
        final double[] pushes = {1};
        final double[] derivatives = new double[frictions.length];

        Timeline timeline = new Timeline();
        final EventHandler<MouseEvent> dragHandler = event -> {
            System.out.println("STOP!");
            timeline.stop();
        };

        final EventHandler<ScrollEvent> scrollHandler = event -> {
            if (event.getEventType() == ScrollEvent.SCROLL) {
                System.out.println("Smooth Scrolling");
                int direction = event.getDeltaY() > 0 ? -1 : 1;
                for (int i = 0; i < pushes.length; i++) {
                    derivatives[i] += direction * pushes[i];
                }
                if (timeline.getStatus() == Animation.Status.STOPPED) {
                    timeline.play();
                }
                event.consume();
            }
        };

        if (flow.getParent() != null) {
            flow.getParent().addEventFilter(MouseEvent.DRAG_DETECTED, dragHandler);
        }
       flow.parentProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue != null) {
                oldValue.removeEventFilter(MouseEvent.DRAG_DETECTED, dragHandler);
            }
            if (newValue != null) {
                newValue.addEventFilter(MouseEvent.DRAG_DETECTED, dragHandler);
            }
        });
        flow.addEventFilter(MouseEvent.DRAG_DETECTED, dragHandler);
        flow.addEventFilter(ScrollEvent.ANY, scrollHandler);

        timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
            for (int i = 0; i < derivatives.length; i++) {
                derivatives[i] *= frictions[i];
            }
            for (int i = 1; i < derivatives.length; i++) {
                derivatives[i] += derivatives[i - 1];
            }
            double dy = derivatives[derivatives.length - 1];

            scrollDirection.setValue(scrollDirection.getValue() + dy);

            if (Math.abs(dy) < 0.001) {
                timeline.stop();
            }
        }));
        timeline.setCycleCount(Animation.INDEFINITE);
    }

I changed the handlers to filters and everything seems to work good.
The only thing I'm going to change is making this method private and create a new public method that accepts a list view as a parameter because the virtual flow is in the skin class.
This method will probably look like this:

    public static void setSmoothScrolling(AbstractMFXFlowlessListView<?, ?, ?> listView) {
        if (listView.getScene() != null) {
            VirtualFlow<?, ?> flow = (VirtualFlow<?, ?>) listView.lookup(".virtual-flow");
            setSmoothScrolling(flow, flow.estimatedScrollYProperty());
        } else {
            listView.skinProperty().addListener(new ChangeListener<>() {
                @Override
                public void changed(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) {
                    if (newValue != null) {
                        VirtualFlow<?, ?> flow = (VirtualFlow<?, ?>) listView.lookup(".virtual-flow");
                        setSmoothScrolling(flow, flow.estimatedScrollYProperty());
                        listView.skinProperty().removeListener(this);
                    }
                }
            });
        }
    }

from flowless.

JordanMartinez avatar JordanMartinez commented on May 29, 2024

Now that I've documented the code in #33, I don't know if your Val.animate idea is feasible. In my understanding, the viewport lays out its content first, and then checks whether there is unused space. If there is, it may relayout that content a second time. Since the scroll values are estimates calculated from the averages of the displayed cells' nodes, I'd guess that these jerky value changes are a result of these averages being recalculated when the displayed nodes are repositioned or nodes are added/removed.

Instead, I'd suggest the code does something similar to the RichTexfFX approach of suspending a value when the view is being updated and resuming it when its finished.

from flowless.

JordanMartinez avatar JordanMartinez commented on May 29, 2024

Can you give a standalone test that demonstrates this jerkiness? I'm assuming the solution would be to wrap the layoutChildren and any show()-related methods in a Suspendables.combine(estimatedScrollX, estimatedScrollY).suspendWhile() block

from flowless.

palexdev avatar palexdev commented on May 29, 2024

Still no support for smooth scrolling?
I tried to make it work with the same strategy I use for JavaFX's scroll panes, see here: MaterialFX ScrollPane, but it won't work

from flowless.

Jugen avatar Jugen commented on May 29, 2024

What happens when you try your method on a VirtualFlow ?
Can you provide a demo with both a ScrollPane and a VirtualFlow with the same content that show/compares the behavior you want ?

from flowless.

palexdev avatar palexdev commented on May 29, 2024
2021-03-21_14-07-36.mp4

As you can see the handler in never executed with the VirtualizedScrollPane.
I had to modify the code a bit though because VirtualizedScrollPane doesn't have vvalue and hvalue properties.
I had to use reflection and change the smooth scroll code to:

    private static void customScrolling(MFXVirtualizedScrollPane<?> scrollPane, DoubleProperty scrollDirection, Function<Bounds, Double> sizeFunc) {
        final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
        final double[] pushes = {1};
        final double[] derivatives = new double[frictions.length];

        Timeline timeline = new Timeline();
        final EventHandler<MouseEvent> dragHandler = event -> timeline.stop();
        final EventHandler<ScrollEvent> scrollHandler = event -> {
            System.out.println("Smooth Scrolling");
            if (event.getEventType() == ScrollEvent.SCROLL) {
                int direction = event.getDeltaY() > 0 ? -1 : 1;
                for (int i = 0; i < pushes.length; i++) {
                    derivatives[i] += direction * pushes[i];
                }
                if (timeline.getStatus() == Animation.Status.STOPPED) {
                    timeline.play();
                }
                event.consume();
            }
        };
        if (scrollPane.getContent().getParent() != null) {
            scrollPane.getContent().getParent().addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
            scrollPane.getContent().getParent().addEventHandler(ScrollEvent.ANY, scrollHandler);
        }
        scrollPane.getContent().parentProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue != null) {
                oldValue.removeEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
                oldValue.removeEventHandler(ScrollEvent.ANY, scrollHandler);
            }
            if (newValue != null) {
                newValue.addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
                newValue.addEventHandler(ScrollEvent.ANY, scrollHandler);
            }
        });
        timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
            for (int i = 0; i < derivatives.length; i++) {
                derivatives[i] *= frictions[i];
            }
            for (int i = 1; i < derivatives.length; i++) {
                derivatives[i] += derivatives[i - 1];
            }
            double dy = derivatives[derivatives.length - 1];
            double size = sizeFunc.apply(scrollPane.getContent().getLayoutBounds());
            scrollDirection.set(Math.min(Math.max(scrollDirection.get() + dy / size, 0), 1));
            if (Math.abs(dy) < 0.001) {
                timeline.stop();
            }
        }));
        timeline.setCycleCount(Animation.INDEFINITE);
    }
    
    public static void smoothVScrolling(MFXVirtualizedScrollPane<?> scrollPane) {
        try {
            Field vvalue = VirtualizedScrollPane.class.getDeclaredField("vBarValue");
            vvalue.setAccessible(true);
            Var<Double> obj = (Var<Double>) vvalue.get(scrollPane);
            DoubleProperty doubleProperty = new SimpleDoubleProperty();
            doubleProperty.bind(obj);
            customScrolling(scrollPane, doubleProperty, Bounds::getHeight);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

from flowless.

Jugen avatar Jugen commented on May 29, 2024

Here you go, try this slightly modified version of the code you provided:

// Changed method signature to use VirtualFlow and its estimated scroll property directly
private static void customScrolling( VirtualFlow<?,?> flowPane, Var<Double> scrollDirection ) {
    final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
    final double[] pushes = {1};
    final double[] derivatives = new double[frictions.length];

    Timeline timeline = new Timeline();
    final EventHandler<MouseEvent> dragHandler = event -> timeline.stop();
    final EventHandler<ScrollEvent> scrollHandler = event -> {
        if (event.getEventType() == ScrollEvent.SCROLL) {
            System.out.println("Smooth Scrolling");
            int direction = event.getDeltaY() > 0 ? -1 : 1;
            for (int i = 0; i < pushes.length; i++) {
                derivatives[i] += direction * pushes[i];
            }
            if (timeline.getStatus() == Animation.Status.STOPPED) {
                timeline.play();
            }
            event.consume();
        }
    };

    // Changed this to get ScrollEvents to work, "Smooth Scrolling" prints now :-)
    flowPane.addEventHandler(MouseEvent.DRAG_DETECTED, dragHandler);
    flowPane.addEventHandler(ScrollEvent.ANY, scrollHandler);

    timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), (event) -> {
        for (int i = 0; i < derivatives.length; i++) {
            derivatives[i] *= frictions[i];
        }
        for (int i = 1; i < derivatives.length; i++) {
            derivatives[i] += derivatives[i - 1];
        }
        double dy = derivatives[derivatives.length - 1];

        // Changed this as values aren't between 0 & 1
        scrollDirection.setValue(scrollDirection.getValue() + dy);

        if (Math.abs(dy) < 0.001) {
            timeline.stop();
        }
    }));
    timeline.setCycleCount(Animation.INDEFINITE);
}

from flowless.

Jugen avatar Jugen commented on May 29, 2024

Sorry the method signature can be reduced to:

private static void customScrolling( VirtualFlow<?,?> flowPane, Var<Double> scrollDirection )

which then is invoked with:

customScrolling( virtualFlow, virtualFlow.estimatedScrollYProperty() );

from flowless.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.