Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG?] scrollVisibleBool and alwaysTensileBool ignored #2525

Closed
jsfan3 opened this issue Aug 19, 2018 · 22 comments
Closed

[BUG?] scrollVisibleBool and alwaysTensileBool ignored #2525

jsfan3 opened this issue Aug 19, 2018 · 22 comments
Assignees
Milestone

Comments

@jsfan3
Copy link
Contributor

jsfan3 commented Aug 19, 2018

In a project that targets Javascript I want that the scrollbars are always visibile with a decent width (if there is a scrollable content) and I don't want the "always tensile" effect of iOS. Or, at least, I want to be able to choose when to show scroll bars. The problem is that the theme constant scrollVisibleBool and alwaysTensileBool are ignored. I suppose that this issue is not specific of the Javascript port because it happens also in the Simulator (tried with iPhone and iPad skins).

I tried to add these constants in the CSS, but there is no effect:

#Constants {
    [...]
    scrollVisibleBool: true;
    alwaysTensileBool: false;
}

I also tried .setScroolVisibile(true) on some container with scrollable content, without any effect.

As last resource, I tried this approch in the init(), without effect:

UIManager.getInstance().setLookAndFeel(new DefaultLookAndFeel(UIManager.getInstance()) {
            @Override
            public void bind(Component cmp) {
                if (cmp instanceof Container) {
                    cmp.setScrollVisible(true);
                }
            }
        });

For this issue, a very simple test case can be:

Form hi = new Form("Hi World", BoxLayout.y());
        for (int i = 0; i < 100; i++) {
            hi.add(new Label("Hi World"));
        }
        hi.show();

with this CSS:

#Constants {
    includeNativeBool: true;
    scrollVisibleBool: true;
    alwaysTensileBool: false;
}

I used the standard updated Codename One libs, without compiling against the Codename One sources.

A final consideration: if the browser (because I target to Javascript) is used from a touch screen device, the default of no scrool bar is ok, but if it's used from a standard browser of a computer desktop (that means "no touch screen" and the use of the mouse) the user expects to see the scroolbars and to be able to use both the scrool wheel and the scrool bars... moreover, the alwaysTensileBool in a desktop browser is very strange.

@codenameone
Copy link
Collaborator

I wanted to add a desktop specific package as I discussed here: https://www.codenameone.com/blog/scrollbars-tooltips.html but it didn't come to fruition.

Did you add styling for the scrollbar after defining that?

The problem is that scrollbars are often styled to be invisible so this code might help: https://www.codenameone.com/blog/permanent-sidemenu-getAllStyles-scrollbar-and-more.html you can probably use isDesktop() which should return correctly in JavaScript too.

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 20, 2018

Thank you.
No, I didn't added any style to the scroolbar and I don't know how to style it.

I tried the code you suggested in https://www.codenameone.com/blog/permanent-sidemenu-getAllStyles-scrollbar-and-more.html, but it doesn't work correctly neither in the Simulator or in the Javascript built. It's easier to show you what it produces instead of describing the problems:
https://cloud.codenameone.com/build/getData?m=result&i=55d73358-aa80-4dfd-9330-5060c9e8f135&b=09b0d1ed-553e-46b4-afdb-8fda673f8d1d&n=preview2209505260667210790.html
Please try to scroll that linked page both with the scroll wheel and with the scroolbar.

This is the code I used:

package cool.teammate.apps.test;

import static com.codename1.ui.CN.*;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Label;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.FontImage;
import com.codename1.ui.Image;
import com.codename1.ui.Slider;
import com.codename1.ui.events.DataChangedListener;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.layouts.BorderLayout;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename
 * One</a> for the purpose of building native mobile applications using Java.
 */
public class TestCase {

    private Form current;
    private Resources theme;

    public void init(Object context) {
        // use two network threads instead of one
        updateNetworkThreadCount(2);

        theme = UIManager.initFirstTheme("/theme");

        // Enable Toolbar on all Forms by default
        Toolbar.setGlobalToolbar(true);

        // Pro only feature
        Log.bindCrashProtection(true);

        addNetworkErrorListener(err -> {
            // prevent the event from propagating
            err.consume();
            if (err.getError() != null) {
                Log.e(err.getError());
            }
            Log.sendLogAsync();
            Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
        });
    }

    public void start() {
        if (current != null) {
            current.show();
            return;
        }
        Form hi = new Form("Hi World", BoxLayout.y());
        Container cnt = new Container(BoxLayout.y());
        for (int i = 0; i < 100; i++) {
            cnt.add(new Label("Text Line " + i));
        }
        Container scrollableCnt = makeScrollable(cnt, FontImage.createMaterial(FontImage.MATERIAL_REORDER, "Label", 5));
        hi.add(scrollableCnt);
        hi.show();
    }

    public void stop() {
        current = getCurrentForm();
        if (current instanceof Dialog) {
            ((Dialog) current).dispose();
            current = getCurrentForm();
        }
    }

    public void destroy() {
    }

    // https://www.codenameone.com/blog/permanent-sidemenu-getAllStyles-scrollbar-and-more.html
    private Container makeScrollable(final Component scrollable, Image thumb) {
        scrollable.setScrollVisible(false);
        final Slider scroll = new Slider();
        scroll.setThumbImage(thumb);
        Container sc = new Container(new BorderLayout());
        sc.addComponent(BorderLayout.CENTER, scrollable);
        sc.addComponent(BorderLayout.EAST, scroll);
        scroll.setVertical(true);
        scroll.setMinValue(0);
        scroll.setEditable(true);
        scroll.setMaxValue(scrollable.getScrollDimension().getHeight());
        scroll.setProgress(scroll.getMaxValue());
        final boolean[] lock = new boolean[1];
        scroll.addDataChangedListener(new DataChangedListener() {
            public void dataChanged(int type, int index) {
                if (!lock[0]) {
                    lock[0] = true;
                    scrollable.scrollRectToVisible(0, scroll.getMaxValue() - index, 5, scrollable.getHeight(), scrollable);
                    lock[0] = false;
                }
            }
        });
        scrollable.addScrollListener(new ScrollListener() {
            public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
                if (!lock[0]) {
                    lock[0] = true;
                    scroll.setProgress(scroll.getMaxValue() - scrollY);
                    lock[0] = false;
                }
            }
        });
        return sc;
    }

}

with this theme.css:

#Constants {
    includeNativeBool: true;
    scrollVisibleBool: true;
    alwaysTensileBool: false;
}

Moreover, this is only half of the problem: the other part is the impossible to disable alwaysTensileBool

@codenameone
Copy link
Collaborator

This is newer code that does the same thing from the GUI builder/settings app:

    public static Component makeScrollable(final Component scrollable) {
        if(!Display.getInstance().isDesktop()) {
            return scrollable;
        }
        if (!(scrollable instanceof Container)) {
            return scrollable;
        }
        ScrollBar scroll = new ScrollBar((Container)scrollable, ScrollBar.Y_AXIS);
        Container sc = BorderLayout.center(scrollable).
                add(BorderLayout.EAST, scroll);
        $(sc).selectAllStyles().setBgColor(0xffffff).setBgTransparency(255);
        return sc;
    }

Notice that in the simulator it should only work on the desktop skin. I'll assign this to 6.0 as 5.0 is winding down.

@shannah I think we need a desktop package that includes better support for scrolling and related API's.

@codenameone codenameone added this to the Version 6.0 milestone Aug 20, 2018
@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 20, 2018

I'm sorry, I didn't get where to put the new code, primarily because the ScrollBar class is not present in the Codename One API.

@codenameone
Copy link
Collaborator

Damn, I missed the fact that this isn't public. Anyway as a stopgap measure this is the class. Hopefully it doesn't have problematic dependencies:

public class ScrollBar extends Container implements ScrollListener {
    private final Container target;
    private final DragHandle dragHandle;
    
    
    private int orientation;
    public static final int X_AXIS=0;
    public static final int Y_AXIS=1;
    
    public ScrollBar(Container target, int orientation) {
        super(new LayeredLayout());
        setScrollableX(false);
        setScrollableY(false);
        this.orientation = orientation;
        this.target = target;
        this.target.addScrollListener(this);
        $(this).selectAllStyles()
                .setBorder(Border.createCompoundBorder(Border.createEmpty(), Border.createEmpty(), Border.createLineBorder(1, 0xcccccc), Border.createLineBorder(1, 0xcccccc)))
                .setBgColor(0xeaeaea)
                .setBgTransparency(128)
                .setPadding(0);
        
        dragHandle = new DragHandle();
        add(dragHandle);
        LayeredLayout ll = (LayeredLayout)getLayout();
        if (orientation == X_AXIS) {
            ll.setInsets(dragHandle, "0px auto 0px "+getInsetForScroll(target.getScrollX())+"px");
        } else {
            ll.setInsets(dragHandle, getInsetForScroll(target.getScrollY())+"px 0.25mm auto 0.25mm");
        }
    }

    int pressedX, pressedY;
    
    @Override
    public void pointerPressed(int x, int y) {
        super.pointerPressed(x, y); 
        pressedX = x;
        pressedY = y;
        if (!dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * (target.getLayoutHeight() - target.getHeight()) / 
                    ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (target.getScrollY() > targetY) {
                int newScrollY = target.getScrollY() - target.getHeight();
                if (newScrollY < 0) {
                    newScrollY = 0;
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            } else if (target.getScrollY() < targetY) {
                int newScrollY = target.getScrollY() + target.getHeight();
                if (newScrollY > target.getLayoutHeight() - target.getHeight()) {
                    newScrollY = target.getLayoutHeight() - target.getHeight();
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            }
        }
    }

    @Override
    public void pointerDragged(int x, int y) {
        super.pointerDragged(x, y);
        if (!dragHandle.inDrag && !dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (targetY < 0) {
                targetY = 0;
            }
            if (targetY > target.getLayoutHeight() - target.getHeight()) {
                targetY = target.getLayoutHeight() - target.getHeight();
            }
            target.scrollRectToVisible(target.getScrollX(), targetY, target.getWidth(), target.getHeight(), target);
        }
    }

    

    
    
    
    @Override
    protected Dimension calcPreferredSize() {
        switch (orientation) {
            case X_AXIS:
                return new Dimension(target.getPreferredW(), px(2));
            default:
                int prefH = target.getPreferredH();
                return new Dimension(px(2), prefH);
        }
        
    }

    int lastTargetWidth, lastTargetLayoutWidth, lastTargetHeight, lastTargetLayoutHeight;
    @Override
    public void revalidate() {
        if (lastTargetWidth != target.getWidth() 
                || lastTargetHeight != target.getHeight() 
                || lastTargetLayoutHeight != target.getLayoutHeight() 
                || lastTargetLayoutWidth != target.getLayoutWidth()) {
            lastTargetWidth = target.getWidth();
            lastTargetHeight = target.getHeight();
            lastTargetLayoutHeight = target.getLayoutHeight();
            lastTargetLayoutWidth = target.getLayoutWidth();
            forceRevalidate();
            return;
        }
        super.revalidate();
    }

    
    
    
    @Override
    public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
        if (dragHandle.inDrag) {
            // We already updated ourselves
            return;
        }
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            cnst.left().setPixels(getInsetForScroll(scrollX));
        } else {
            
            int y = getInsetForScroll(scrollY);
            if (y != cnst.top().getCurrentValuePx()) {
                cnst.top().setPixels(y);
                //System.out.println("Revalidating");
                //forceRevalidate();
                revalidate();
            }
        }
        //revalidate();
    }
    
    private int getInsetForScroll(int scroll) {
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            return (int)Math.round(scroll * getInnerWidth() / (float)target.getLayoutWidth());
            
        } else {
            int out = (int)Math.round(scroll * getInnerHeight() / (float)target.getLayoutHeight());
            return Math.min(out, getInnerHeight() - dragHandle.getOuterPreferredH());
        }
    }
    /*
    private int convertScrollBarXToTargetX(int x) {
        return (int)Math.round(x * target.getLayoutWidth() / (float)getWidth());
    }
    
    private int convertScrollBarYToTargetY(int y) {
        return (int)Math.round((y + getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight()));
    }
    
    private int convertTargetXToScrollBarX(int x) {
        return (int)Math.round(x * getWidth() / (float)target.getLayoutWidth());
    }
    
    private int convertTargetYToScrollBarY(int y) {
        return Math.max(0,
                Math.min(getInnerHeight() - dragHandle.getOuterPreferredH(),
                
                (int)Math.round(y * (getInnerHeight() - dragHandle.getOuterPreferredH()) / (float)target.getLayoutHeight())
                ));
    }
    */
    private class DragHandle extends Button {
        int startOuterY, startOuterX;
        int draggedX, draggedY, pressedX, pressedY;
        int targetPressedScrollX;
        int targetPressedScrollY;
        boolean inDrag;
        LayeredLayoutConstraint pressedConstraint;
        
        
        DragHandle() {
            RoundRectBorder border = RoundRectBorder.create()
                    .bezierCorners(false)
                    .cornerRadius(0.75f);
                    
            $(this).selectAllStyles().setBgColor(0xcccccc).setBgTransparency(200).setBorder(border)
                    .setPadding(0).setMargin(0);
            this.setDraggable(true);
            
        }

        @Override
        protected boolean isStickyDrag() {
            return true;
        }

        @Override
        protected Image getDragImage() {
            return null;
        }

        @Override
        protected void drawDraggedImage(Graphics g, Image img, int x, int y) {
            
        }

        @Override
        protected int getDragRegionStatus(int x, int y) {
            return orientation == X_AXIS ? Component.DRAG_REGION_IMMEDIATELY_DRAG_X : Component.DRAG_REGION_IMMEDIATELY_DRAG_Y;
        }

        @Override
        protected void dragFinished(int x, int y) {
            super.dragFinished(x, y); //To change body of generated methods, choose Tools | Templates.
            inDrag = false;
        }

        
        
        
        @Override
        public void pointerPressed(int x, int y) {
            super.pointerPressed(x, y);
            inDrag = true;
            targetPressedScrollX = target.getScrollX();
            targetPressedScrollY = target.getScrollY();
            startOuterY = dragHandle.getOuterY();
            startOuterX = dragHandle.getOuterX();
            pressedX = x;
            pressedY = y;
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            pressedConstraint = ll.getOrCreateConstraint(this).copy();
            pointerDragged(x, y); // so drag starts instantly
        }

        @Override
        public void pointerDragged(int x, int y) {
            super.pointerDragged(x, y); 
            setVisible(true);
            draggedX = x;
            draggedY = y;
            
            int deltaX = draggedX - pressedX;
            int deltaY = draggedY - pressedY;
            
            if (startOuterY + deltaY < ScrollBar.this.getStyle().getPaddingTop()) {
                deltaY = ScrollBar.this.getStyle().getPaddingTop() -startOuterY;
            }
            if (startOuterX + deltaX < ScrollBar.this.getStyle().getPaddingLeft(false)) {
                deltaX = ScrollBar.this.getStyle().getPaddingLeft(false)-startOuterX;
            }
            
            if (startOuterY + dragHandle.getOuterPreferredH() + deltaY > ScrollBar.this.getInnerHeight()) {
                deltaY = ScrollBar.this.getInnerHeight() - dragHandle.getOuterPreferredH() - startOuterY;
            }
            
            if (deltaY == 0 && orientation == Y_AXIS) {
                return;
            }
            
            if (deltaX == 0 && orientation == X_AXIS) {
                return;
            }
            if (pressedConstraint == null) {
                return;
            }
            LayeredLayoutConstraint cnst = pressedConstraint.copy();
            switch (orientation) {
                case X_AXIS:
                    cnst.left().translatePixels(deltaX, true, ScrollBar.this);
                    break;
                default:
                    cnst.top().translatePixels(deltaY, true, ScrollBar.this);
                    
                    
            }
            
            
            
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            cnst.copyTo(ll.getOrCreateConstraint(this));
            ScrollBar.this.revalidate();
            
            // Set the target's scroll position
            switch (orientation) {
                case X_AXIS:
                    //target.scrollRectToVisible(targetPressedScrollX + convertScrollBarXToTargetX(deltaX), targetPressedScrollY, target.getLayoutWidth(), target.getLayoutHeight(), target);
                    break;
                default:
                    int targetDeltaY = Math.round(deltaY * (target.getLayoutHeight() - target.getHeight())/((float)ScrollBar.this.getInnerHeight()-getOuterPreferredH()));
                    int targetY = targetPressedScrollY + targetDeltaY;
                    if (targetY + target.getHeight() > target.getLayoutHeight()) {
                        targetY = target.getLayoutHeight() - target.getHeight();
                    }
                    target.scrollRectToVisible(targetPressedScrollX, targetY , target.getWidth(), target.getHeight(), target);
            }
            
        }
        
        
        
        
        
        @Override
        protected Dimension calcPreferredSize() {
            int lHeight = target.getLayoutHeight();
            int iHeight = target.getHeight();
            if (iHeight == 0 || lHeight == 0) {
                //it hasn't been laid out yet.. lets wait and revalidate later
                Timer timer = new Timer();
                
                timer.schedule(new TimerTask() {
                    
                    @Override
                    public void run() {
                        $(()->{
                           DragHandle.this.setShouldCalcPreferredSize(true);
                           ScrollBar.this.revalidate(); 
                        });
                    }
                }, 30);
                
                return new Dimension(0, 0);
            }
            
            if (orientation == X_AXIS) {
                int h = Math.max(px(4), ScrollBar.this.getInnerHeight());
                if (target.getLayoutWidth() == target.getWidth() || !target.isScrollableX()) {
                    return new Dimension(0,0);
                }
                int w = (int)Math.round(ScrollBar.this.getInnerWidth() * ((float)target.getWidth()/target.getLayoutWidth()));
                return new Dimension(w, h);
            } else {
                int w = ScrollBar.this.getInnerWidth();
                if (target.getLayoutHeight() == target.getHeight() || !GBAccessor.scrollableYFlag(target)) {
                    return new Dimension(0,0);
                }
                int h = (int)Math.round(ScrollBar.this.getInnerHeight() * ((float)target.getHeight()/target.getLayoutHeight()));
                return new Dimension(w, h);
            }
        }
        
    }
    private int px(double mm) {
        return Display.getInstance().convertToPixels((float) mm);
    }
}

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 20, 2018

Thank you. The only external dependency is in this line, about the static method GBAccessor.scrollableYFlag:

if (target.getLayoutHeight() == target.getHeight() || !GBAccessor.scrollableYFlag(target)) {

@codenameone
Copy link
Collaborator

I think you can just remove this but @shannah correct me if I'm wrong

@shannah
Copy link
Collaborator

shannah commented Aug 20, 2018

I seem to recall it being necessary. You can get it almost working correctly by changing !GBAccessor.scrollableYFlag(target) to target.isScrollableY()

I'm trying to decide whether we should expose the scrollableYFlag() method, or to expose the information in some other way.. E.g. overloading isScrollableY().

The difference is that isScrollableY() will return a different value depending on the state of the UI (e.g. if the component is scrollable, but it currently doesn't need to scroll, then isScrollableY() will return false).

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 20, 2018

Thank you both, however the result in a test case is not fine with or without target.isScrollableY()

With:
https://cloud.codenameone.com/build/getData?m=result&i=3ac70bd6-fcfa-4b5a-811b-757f63bc1971&b=580aefb3-a086-4e71-93cc-cfcd92a57c5c&n=preview8623044740253416625.html

Without:
https://cloud.codenameone.com/build/getData?m=result&i=62cf91a5-c4e2-442d-9ec1-80ab7138e7a9&b=a5e4c646-a58e-4563-8cd6-b817e8ca8b74&n=preview497646722569798695.html

The scroolbar doesn't seem usable in both cases: it seems a vertical line of the same color (without an inner small line like in the browser scrool bar) that occupies all the height space, so there is no indication of the scrolling.

Please also note that the scrolling with the mouse wheel is quite fine on Chrome, but it's quite impossible with Firefox (because the page scrolls very slowly). Tested on Linux. Can you reproduce this problem with the two links in this comment?

So, at the moment there are three issues: I'm not able to add a scroolbar, I'm not able to disable the alwaysTensile effect, and scrolling with mouse wheel seems problematic with Firefox. I hope for a solution for all :)

@shannah
Copy link
Collaborator

shannah commented Aug 20, 2018

Please post the code for your test case.

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 20, 2018

The test case is composed by these three files:

theme.css:

#Constants {
    includeNativeBool: true;
    scrollVisibleBool: true;
    alwaysTensileBool: false;
}

MyApplication.java:

import static com.codename1.ui.CN.*;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose 
 * of building native mobile applications using Java.
 */
public class MyApplication {

    private Form current;
    private Resources theme;

    public void init(Object context) {
        // use two network threads instead of one
        updateNetworkThreadCount(2);

        theme = UIManager.initFirstTheme("/theme");

        // Enable Toolbar on all Forms by default
        Toolbar.setGlobalToolbar(true);

        // Pro only feature
        Log.bindCrashProtection(true);

        addNetworkErrorListener(err -> {
            // prevent the event from propagating
            err.consume();
            if(err.getError() != null) {
                Log.e(err.getError());
            }
            Log.sendLogAsync();
            Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
        });        
    }
    
    public void start() {
        if (current != null) {
            current.show();
            return;
        }
        Form hi = new Form("Hi World", BoxLayout.y());
        Container cnt = new Container(BoxLayout.y());
        for (int i = 0; i < 100; i++) {
            cnt.add(new Label("Text Line " + i));
        }
        Component scrollableCnt = makeScrollable(cnt);
        hi.add(scrollableCnt);
        hi.show();
    }

    public void stop() {
        current = getCurrentForm();
        if(current instanceof Dialog) {
            ((Dialog)current).dispose();
            current = getCurrentForm();
        }
    }
    
    public void destroy() {
    }
    
    public static Component makeScrollable(final Component scrollable) {
        if(!Display.getInstance().isDesktop()) {
            return scrollable;
        }
        if (!(scrollable instanceof Container)) {
            return scrollable;
        }
        ScrollBar scroll = new ScrollBar((Container)scrollable, ScrollBar.Y_AXIS);
        Container sc = BorderLayout.center(scrollable).
                add(BorderLayout.EAST, scroll);
        $(sc).selectAllStyles().setBgColor(0xffffff).setBgTransparency(255);
        return sc;
    }

}

ScrollBar.java:

import com.codename1.ui.Button;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.LayeredLayout.LayeredLayoutConstraint;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.RoundRectBorder;
import java.util.Timer;
import java.util.TimerTask;

/**
 * https://github.com/codenameone/CodenameOne/issues/2525
 */
public class ScrollBar extends Container implements ScrollListener {
    private final Container target;
    private final DragHandle dragHandle;
    
    
    private int orientation;
    public static final int X_AXIS=0;
    public static final int Y_AXIS=1;
    
    public ScrollBar(Container target, int orientation) {
        super(new LayeredLayout());
        setScrollableX(false);
        setScrollableY(false);
        this.orientation = orientation;
        this.target = target;
        this.target.addScrollListener(this);
        $(this).selectAllStyles()
                .setBorder(Border.createCompoundBorder(Border.createEmpty(), Border.createEmpty(), Border.createLineBorder(1, 0xcccccc), Border.createLineBorder(1, 0xcccccc)))
                .setBgColor(0xeaeaea)
                .setBgTransparency(128)
                .setPadding(0);
        
        dragHandle = new DragHandle();
        add(dragHandle);
        LayeredLayout ll = (LayeredLayout)getLayout();
        if (orientation == X_AXIS) {
            ll.setInsets(dragHandle, "0px auto 0px "+getInsetForScroll(target.getScrollX())+"px");
        } else {
            ll.setInsets(dragHandle, getInsetForScroll(target.getScrollY())+"px 0.25mm auto 0.25mm");
        }
    }

    int pressedX, pressedY;
    
    @Override
    public void pointerPressed(int x, int y) {
        super.pointerPressed(x, y); 
        pressedX = x;
        pressedY = y;
        if (!dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * (target.getLayoutHeight() - target.getHeight()) / 
                    ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (target.getScrollY() > targetY) {
                int newScrollY = target.getScrollY() - target.getHeight();
                if (newScrollY < 0) {
                    newScrollY = 0;
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            } else if (target.getScrollY() < targetY) {
                int newScrollY = target.getScrollY() + target.getHeight();
                if (newScrollY > target.getLayoutHeight() - target.getHeight()) {
                    newScrollY = target.getLayoutHeight() - target.getHeight();
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            }
        }
    }

    @Override
    public void pointerDragged(int x, int y) {
        super.pointerDragged(x, y);
        if (!dragHandle.inDrag && !dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (targetY < 0) {
                targetY = 0;
            }
            if (targetY > target.getLayoutHeight() - target.getHeight()) {
                targetY = target.getLayoutHeight() - target.getHeight();
            }
            target.scrollRectToVisible(target.getScrollX(), targetY, target.getWidth(), target.getHeight(), target);
        }
    }

    

    
    
    
    @Override
    protected Dimension calcPreferredSize() {
        switch (orientation) {
            case X_AXIS:
                return new Dimension(target.getPreferredW(), px(2));
            default:
                int prefH = target.getPreferredH();
                return new Dimension(px(2), prefH);
        }
        
    }

    int lastTargetWidth, lastTargetLayoutWidth, lastTargetHeight, lastTargetLayoutHeight;
    @Override
    public void revalidate() {
        if (lastTargetWidth != target.getWidth() 
                || lastTargetHeight != target.getHeight() 
                || lastTargetLayoutHeight != target.getLayoutHeight() 
                || lastTargetLayoutWidth != target.getLayoutWidth()) {
            lastTargetWidth = target.getWidth();
            lastTargetHeight = target.getHeight();
            lastTargetLayoutHeight = target.getLayoutHeight();
            lastTargetLayoutWidth = target.getLayoutWidth();
            forceRevalidate();
            return;
        }
        super.revalidate();
    }

    
    
    
    @Override
    public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
        if (dragHandle.inDrag) {
            // We already updated ourselves
            return;
        }
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            cnst.left().setPixels(getInsetForScroll(scrollX));
        } else {
            
            int y = getInsetForScroll(scrollY);
            if (y != cnst.top().getCurrentValuePx()) {
                cnst.top().setPixels(y);
                //System.out.println("Revalidating");
                //forceRevalidate();
                revalidate();
            }
        }
        //revalidate();
    }
    
    private int getInsetForScroll(int scroll) {
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            return (int)Math.round(scroll * getInnerWidth() / (float)target.getLayoutWidth());
            
        } else {
            int out = (int)Math.round(scroll * getInnerHeight() / (float)target.getLayoutHeight());
            return Math.min(out, getInnerHeight() - dragHandle.getOuterPreferredH());
        }
    }
    /*
    private int convertScrollBarXToTargetX(int x) {
        return (int)Math.round(x * target.getLayoutWidth() / (float)getWidth());
    }
    
    private int convertScrollBarYToTargetY(int y) {
        return (int)Math.round((y + getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight()));
    }
    
    private int convertTargetXToScrollBarX(int x) {
        return (int)Math.round(x * getWidth() / (float)target.getLayoutWidth());
    }
    
    private int convertTargetYToScrollBarY(int y) {
        return Math.max(0,
                Math.min(getInnerHeight() - dragHandle.getOuterPreferredH(),
                
                (int)Math.round(y * (getInnerHeight() - dragHandle.getOuterPreferredH()) / (float)target.getLayoutHeight())
                ));
    }
    */
    private class DragHandle extends Button {
        int startOuterY, startOuterX;
        int draggedX, draggedY, pressedX, pressedY;
        int targetPressedScrollX;
        int targetPressedScrollY;
        boolean inDrag;
        LayeredLayoutConstraint pressedConstraint;
        
        
        DragHandle() {
            RoundRectBorder border = RoundRectBorder.create()
                    .bezierCorners(false)
                    .cornerRadius(0.75f);
                    
            $(this).selectAllStyles().setBgColor(0xcccccc).setBgTransparency(200).setBorder(border)
                    .setPadding(0).setMargin(0);
            this.setDraggable(true);
            
        }

        @Override
        protected boolean isStickyDrag() {
            return true;
        }

        @Override
        protected Image getDragImage() {
            return null;
        }

        @Override
        protected void drawDraggedImage(Graphics g, Image img, int x, int y) {
            
        }

        @Override
        protected int getDragRegionStatus(int x, int y) {
            return orientation == X_AXIS ? Component.DRAG_REGION_IMMEDIATELY_DRAG_X : Component.DRAG_REGION_IMMEDIATELY_DRAG_Y;
        }

        @Override
        protected void dragFinished(int x, int y) {
            super.dragFinished(x, y); //To change body of generated methods, choose Tools | Templates.
            inDrag = false;
        }

        
        
        
        @Override
        public void pointerPressed(int x, int y) {
            super.pointerPressed(x, y);
            inDrag = true;
            targetPressedScrollX = target.getScrollX();
            targetPressedScrollY = target.getScrollY();
            startOuterY = dragHandle.getOuterY();
            startOuterX = dragHandle.getOuterX();
            pressedX = x;
            pressedY = y;
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            pressedConstraint = ll.getOrCreateConstraint(this).copy();
            pointerDragged(x, y); // so drag starts instantly
        }

        @Override
        public void pointerDragged(int x, int y) {
            super.pointerDragged(x, y); 
            setVisible(true);
            draggedX = x;
            draggedY = y;
            
            int deltaX = draggedX - pressedX;
            int deltaY = draggedY - pressedY;
            
            if (startOuterY + deltaY < ScrollBar.this.getStyle().getPaddingTop()) {
                deltaY = ScrollBar.this.getStyle().getPaddingTop() -startOuterY;
            }
            if (startOuterX + deltaX < ScrollBar.this.getStyle().getPaddingLeft(false)) {
                deltaX = ScrollBar.this.getStyle().getPaddingLeft(false)-startOuterX;
            }
            
            if (startOuterY + dragHandle.getOuterPreferredH() + deltaY > ScrollBar.this.getInnerHeight()) {
                deltaY = ScrollBar.this.getInnerHeight() - dragHandle.getOuterPreferredH() - startOuterY;
            }
            
            if (deltaY == 0 && orientation == Y_AXIS) {
                return;
            }
            
            if (deltaX == 0 && orientation == X_AXIS) {
                return;
            }
            if (pressedConstraint == null) {
                return;
            }
            LayeredLayoutConstraint cnst = pressedConstraint.copy();
            switch (orientation) {
                case X_AXIS:
                    cnst.left().translatePixels(deltaX, true, ScrollBar.this);
                    break;
                default:
                    cnst.top().translatePixels(deltaY, true, ScrollBar.this);
                    
                    
            }
            
            
            
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            cnst.copyTo(ll.getOrCreateConstraint(this));
            ScrollBar.this.revalidate();
            
            // Set the target's scroll position
            switch (orientation) {
                case X_AXIS:
                    //target.scrollRectToVisible(targetPressedScrollX + convertScrollBarXToTargetX(deltaX), targetPressedScrollY, target.getLayoutWidth(), target.getLayoutHeight(), target);
                    break;
                default:
                    int targetDeltaY = Math.round(deltaY * (target.getLayoutHeight() - target.getHeight())/((float)ScrollBar.this.getInnerHeight()-getOuterPreferredH()));
                    int targetY = targetPressedScrollY + targetDeltaY;
                    if (targetY + target.getHeight() > target.getLayoutHeight()) {
                        targetY = target.getLayoutHeight() - target.getHeight();
                    }
                    target.scrollRectToVisible(targetPressedScrollX, targetY , target.getWidth(), target.getHeight(), target);
            }
            
        }
        
        
        
        
        
        @Override
        protected Dimension calcPreferredSize() {
            int lHeight = target.getLayoutHeight();
            int iHeight = target.getHeight();
            if (iHeight == 0 || lHeight == 0) {
                //it hasn't been laid out yet.. lets wait and revalidate later
                Timer timer = new Timer();
                
                timer.schedule(new TimerTask() {
                    
                    @Override
                    public void run() {
                        $(()->{
                           DragHandle.this.setShouldCalcPreferredSize(true);
                           ScrollBar.this.revalidate(); 
                        });
                    }
                }, 30);
                
                return new Dimension(0, 0);
            }
            
            if (orientation == X_AXIS) {
                int h = Math.max(px(4), ScrollBar.this.getInnerHeight());
                if (target.getLayoutWidth() == target.getWidth() || !target.isScrollableX()) {
                    return new Dimension(0,0);
                }
                int w = (int)Math.round(ScrollBar.this.getInnerWidth() * ((float)target.getWidth()/target.getLayoutWidth()));
                return new Dimension(w, h);
            } else {
                int w = ScrollBar.this.getInnerWidth();
                if (target.getLayoutHeight() == target.getHeight() ) { // || target.isScrollableY()
                    return new Dimension(0,0);
                }
                int h = (int)Math.round(ScrollBar.this.getInnerHeight() * ((float)target.getHeight()/target.getLayoutHeight()));
                return new Dimension(w, h);
            }
        }
        
    }
    private int px(double mm) {
        return Display.getInstance().convertToPixels((float) mm);
    }
}

@shannah
Copy link
Collaborator

shannah commented Aug 20, 2018

It could be because of your nested scrollables. Change the form layout to BorderLayout, then add the scrollable container into its center. That will probably fix it.

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 21, 2018

Thank you very much,
after your suggest I got a decent result in Chrome (tested on Linux and Windows):
https://cloud.codenameone.com/build/getData?m=result&i=86835002-c214-4808-96b2-e32e264d9e36&b=f152f98f-0e5d-4ab7-9ec7-13f4ab6d6ae8&n=preview5754994989708290924.html

But it still works badly on Firefox (tested on Linux and Windows).

I also noted that in the following line of ScrollBar.java:
if (target.getLayoutHeight() == target.getHeight()) {
I cannot add target.isScrollableY() in the following way:
if (target.getLayoutHeight() == target.getHeight() || target.isScrollableY()) {
otherwise the scrollbar stops to work.

So, at the moment, the result in Chrome is satisfying. About Firefox, the "too much slow scrolling" with the mouse wheel happens also without the scrollbar... and if we add the scrool bar it is however "difficult to move".
I didn't do any test yet with Safari or Microsoft Edge, because I haven't them in my machine.

This is the new code of the test case after the last changes:

MyApplication.java:

package cool.teammate.apps.test;


import static com.codename1.ui.CN.*;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.FontImage;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;

/**
 * This file was generated by <a href="https://www.codenameone.com/">Codename One</a> for the purpose 
 * of building native mobile applications using Java.
 */
public class MyApplication {

    private Form current;
    private Resources theme;

    public void init(Object context) {
        // use two network threads instead of one
        updateNetworkThreadCount(2);

        theme = UIManager.initFirstTheme("/theme");

        // Enable Toolbar on all Forms by default
        Toolbar.setGlobalToolbar(true);

        // Pro only feature
        Log.bindCrashProtection(true);

        addNetworkErrorListener(err -> {
            // prevent the event from propagating
            err.consume();
            if(err.getError() != null) {
                Log.e(err.getError());
            }
            Log.sendLogAsync();
            Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
        });        
    }
    
    public void start() {
        if (current != null) {
            current.show();
            return;
        }
        Form hi = new Form("Hi World", new BorderLayout(BorderLayout.CENTER_BEHAVIOR_SCALE));
        Container cnt = new Container(BoxLayout.y());
        cnt.setScrollableY(true);
        for (int i = 0; i < 100; i++) {
            cnt.add(new Label("Text Line " + i));
        }
        Component scrollableCnt = makeScrollable(cnt);
        hi.add(BorderLayout.CENTER, scrollableCnt);
        hi.show();
    }

    public void stop() {
        current = getCurrentForm();
        if(current instanceof Dialog) {
            ((Dialog)current).dispose();
            current = getCurrentForm();
        }
    }
    
    public void destroy() {
    }
    
    public static Component makeScrollable(final Component scrollable) {
        if(!Display.getInstance().isDesktop()) {
            return scrollable;
        }
        if (!(scrollable instanceof Container)) {
            return scrollable;
        }
        ScrollBar scroll = new ScrollBar((Container)scrollable, ScrollBar.Y_AXIS);
        Container sc = BorderLayout.center(scrollable).
                add(BorderLayout.EAST, scroll);
        $(sc).selectAllStyles().setBgColor(0xffffff).setBgTransparency(255);
        return sc;
    }

}

ScrollBar.java:

package cool.teammate.apps.test;

import com.codename1.ui.Button;
import com.codename1.ui.Component;
import static com.codename1.ui.ComponentSelector.$;
import com.codename1.ui.Container;
import com.codename1.ui.Display;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.layouts.LayeredLayout;
import com.codename1.ui.layouts.LayeredLayout.LayeredLayoutConstraint;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.RoundRectBorder;
import java.util.Timer;
import java.util.TimerTask;

/**
 * https://github.com/codenameone/CodenameOne/issues/2525
 */
public class ScrollBar extends Container implements ScrollListener {
    private final Container target;
    private final DragHandle dragHandle;
    
    
    private int orientation;
    public static final int X_AXIS=0;
    public static final int Y_AXIS=1;
    
    public ScrollBar(Container target, int orientation) {
        super(new LayeredLayout());
        setScrollableX(false);
        setScrollableY(false);
        this.orientation = orientation;
        this.target = target;
        this.target.addScrollListener(this);
        $(this).selectAllStyles()
                .setBorder(Border.createCompoundBorder(Border.createEmpty(), Border.createEmpty(), Border.createLineBorder(1, 0xcccccc), Border.createLineBorder(1, 0xcccccc)))
                .setBgColor(0xeaeaea)
                .setBgTransparency(128)
                .setPadding(0);
        
        dragHandle = new DragHandle();
        add(dragHandle);
        LayeredLayout ll = (LayeredLayout)getLayout();
        if (orientation == X_AXIS) {
            ll.setInsets(dragHandle, "0px auto 0px "+getInsetForScroll(target.getScrollX())+"px");
        } else {
            ll.setInsets(dragHandle, getInsetForScroll(target.getScrollY())+"px 0.25mm auto 0.25mm");
        }
    }

    int pressedX, pressedY;
    
    @Override
    public void pointerPressed(int x, int y) {
        super.pointerPressed(x, y); 
        pressedX = x;
        pressedY = y;
        if (!dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * (target.getLayoutHeight() - target.getHeight()) / 
                    ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (target.getScrollY() > targetY) {
                int newScrollY = target.getScrollY() - target.getHeight();
                if (newScrollY < 0) {
                    newScrollY = 0;
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            } else if (target.getScrollY() < targetY) {
                int newScrollY = target.getScrollY() + target.getHeight();
                if (newScrollY > target.getLayoutHeight() - target.getHeight()) {
                    newScrollY = target.getLayoutHeight() - target.getHeight();
                }
                target.scrollRectToVisible(target.getScrollX(), newScrollY, target.getWidth(), target.getHeight(), target);
            }
        }
    }

    @Override
    public void pointerDragged(int x, int y) {
        super.pointerDragged(x, y);
        if (!dragHandle.inDrag && !dragHandle.contains(x, y)) {
            int relY = y - getAbsoluteY();
            int targetY = (int)Math.round((relY - getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight() - dragHandle.getOuterPreferredH()));
            if (targetY < 0) {
                targetY = 0;
            }
            if (targetY > target.getLayoutHeight() - target.getHeight()) {
                targetY = target.getLayoutHeight() - target.getHeight();
            }
            target.scrollRectToVisible(target.getScrollX(), targetY, target.getWidth(), target.getHeight(), target);
        }
    }

    

    
    
    
    @Override
    protected Dimension calcPreferredSize() {
        switch (orientation) {
            case X_AXIS:
                return new Dimension(target.getPreferredW(), px(2));
            default:
                int prefH = target.getPreferredH();
                return new Dimension(px(2), prefH);
        }
        
    }

    int lastTargetWidth, lastTargetLayoutWidth, lastTargetHeight, lastTargetLayoutHeight;
    @Override
    public void revalidate() {
        if (lastTargetWidth != target.getWidth() 
                || lastTargetHeight != target.getHeight() 
                || lastTargetLayoutHeight != target.getLayoutHeight() 
                || lastTargetLayoutWidth != target.getLayoutWidth()) {
            lastTargetWidth = target.getWidth();
            lastTargetHeight = target.getHeight();
            lastTargetLayoutHeight = target.getLayoutHeight();
            lastTargetLayoutWidth = target.getLayoutWidth();
            forceRevalidate();
            return;
        }
        super.revalidate();
    }

    
    
    
    @Override
    public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
        if (dragHandle.inDrag) {
            // We already updated ourselves
            return;
        }
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            cnst.left().setPixels(getInsetForScroll(scrollX));
        } else {
            
            int y = getInsetForScroll(scrollY);
            if (y != cnst.top().getCurrentValuePx()) {
                cnst.top().setPixels(y);
                //System.out.println("Revalidating");
                //forceRevalidate();
                revalidate();
            }
        }
        //revalidate();
    }
    
    private int getInsetForScroll(int scroll) {
        LayeredLayout ll = (LayeredLayout)getLayout();
        LayeredLayoutConstraint cnst = ll.getOrCreateConstraint(dragHandle);
        if (orientation == X_AXIS) {
            return (int)Math.round(scroll * getInnerWidth() / (float)target.getLayoutWidth());
            
        } else {
            int out = (int)Math.round(scroll * getInnerHeight() / (float)target.getLayoutHeight());
            return Math.min(out, getInnerHeight() - dragHandle.getOuterPreferredH());
        }
    }
    /*
    private int convertScrollBarXToTargetX(int x) {
        return (int)Math.round(x * target.getLayoutWidth() / (float)getWidth());
    }
    
    private int convertScrollBarYToTargetY(int y) {
        return (int)Math.round((y + getStyle().getPaddingTop()) * target.getLayoutHeight() / ((float)getInnerHeight()));
    }
    
    private int convertTargetXToScrollBarX(int x) {
        return (int)Math.round(x * getWidth() / (float)target.getLayoutWidth());
    }
    
    private int convertTargetYToScrollBarY(int y) {
        return Math.max(0,
                Math.min(getInnerHeight() - dragHandle.getOuterPreferredH(),
                
                (int)Math.round(y * (getInnerHeight() - dragHandle.getOuterPreferredH()) / (float)target.getLayoutHeight())
                ));
    }
    */
    private class DragHandle extends Button {
        int startOuterY, startOuterX;
        int draggedX, draggedY, pressedX, pressedY;
        int targetPressedScrollX;
        int targetPressedScrollY;
        boolean inDrag;
        LayeredLayoutConstraint pressedConstraint;
        
        
        DragHandle() {
            RoundRectBorder border = RoundRectBorder.create()
                    .bezierCorners(false)
                    .cornerRadius(0.75f);
                    
            $(this).selectAllStyles().setBgColor(0xcccccc).setBgTransparency(200).setBorder(border)
                    .setPadding(0).setMargin(0);
            this.setDraggable(true);
            
        }

        @Override
        protected boolean isStickyDrag() {
            return true;
        }

        @Override
        protected Image getDragImage() {
            return null;
        }

        @Override
        protected void drawDraggedImage(Graphics g, Image img, int x, int y) {
            
        }

        @Override
        protected int getDragRegionStatus(int x, int y) {
            return orientation == X_AXIS ? Component.DRAG_REGION_IMMEDIATELY_DRAG_X : Component.DRAG_REGION_IMMEDIATELY_DRAG_Y;
        }

        @Override
        protected void dragFinished(int x, int y) {
            super.dragFinished(x, y); //To change body of generated methods, choose Tools | Templates.
            inDrag = false;
        }

        
        
        
        @Override
        public void pointerPressed(int x, int y) {
            super.pointerPressed(x, y);
            inDrag = true;
            targetPressedScrollX = target.getScrollX();
            targetPressedScrollY = target.getScrollY();
            startOuterY = dragHandle.getOuterY();
            startOuterX = dragHandle.getOuterX();
            pressedX = x;
            pressedY = y;
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            pressedConstraint = ll.getOrCreateConstraint(this).copy();
            pointerDragged(x, y); // so drag starts instantly
        }

        @Override
        public void pointerDragged(int x, int y) {
            super.pointerDragged(x, y); 
            setVisible(true);
            draggedX = x;
            draggedY = y;
            
            int deltaX = draggedX - pressedX;
            int deltaY = draggedY - pressedY;
            
            if (startOuterY + deltaY < ScrollBar.this.getStyle().getPaddingTop()) {
                deltaY = ScrollBar.this.getStyle().getPaddingTop() -startOuterY;
            }
            if (startOuterX + deltaX < ScrollBar.this.getStyle().getPaddingLeft(false)) {
                deltaX = ScrollBar.this.getStyle().getPaddingLeft(false)-startOuterX;
            }
            
            if (startOuterY + dragHandle.getOuterPreferredH() + deltaY > ScrollBar.this.getInnerHeight()) {
                deltaY = ScrollBar.this.getInnerHeight() - dragHandle.getOuterPreferredH() - startOuterY;
            }
            
            if (deltaY == 0 && orientation == Y_AXIS) {
                return;
            }
            
            if (deltaX == 0 && orientation == X_AXIS) {
                return;
            }
            if (pressedConstraint == null) {
                return;
            }
            LayeredLayoutConstraint cnst = pressedConstraint.copy();
            switch (orientation) {
                case X_AXIS:
                    cnst.left().translatePixels(deltaX, true, ScrollBar.this);
                    break;
                default:
                    cnst.top().translatePixels(deltaY, true, ScrollBar.this);
                    
                    
            }
            
            
            
            LayeredLayout ll = (LayeredLayout)ScrollBar.this.getLayout();
            cnst.copyTo(ll.getOrCreateConstraint(this));
            ScrollBar.this.revalidate();
            
            // Set the target's scroll position
            switch (orientation) {
                case X_AXIS:
                    //target.scrollRectToVisible(targetPressedScrollX + convertScrollBarXToTargetX(deltaX), targetPressedScrollY, target.getLayoutWidth(), target.getLayoutHeight(), target);
                    break;
                default:
                    int targetDeltaY = Math.round(deltaY * (target.getLayoutHeight() - target.getHeight())/((float)ScrollBar.this.getInnerHeight()-getOuterPreferredH()));
                    int targetY = targetPressedScrollY + targetDeltaY;
                    if (targetY + target.getHeight() > target.getLayoutHeight()) {
                        targetY = target.getLayoutHeight() - target.getHeight();
                    }
                    target.scrollRectToVisible(targetPressedScrollX, targetY , target.getWidth(), target.getHeight(), target);
            }
            
        }
        
        
        
        
        
        @Override
        protected Dimension calcPreferredSize() {
            int lHeight = target.getLayoutHeight();
            int iHeight = target.getHeight();
            if (iHeight == 0 || lHeight == 0) {
                //it hasn't been laid out yet.. lets wait and revalidate later
                Timer timer = new Timer();
                
                timer.schedule(new TimerTask() {
                    
                    @Override
                    public void run() {
                        $(()->{
                           DragHandle.this.setShouldCalcPreferredSize(true);
                           ScrollBar.this.revalidate(); 
                        });
                    }
                }, 30);
                
                return new Dimension(0, 0);
            }
            
            if (orientation == X_AXIS) {
                int h = Math.max(px(4), ScrollBar.this.getInnerHeight());
                if (target.getLayoutWidth() == target.getWidth() || !target.isScrollableX()) {
                    return new Dimension(0,0);
                }
                int w = (int)Math.round(ScrollBar.this.getInnerWidth() * ((float)target.getWidth()/target.getLayoutWidth()));
                return new Dimension(w, h);
            } else {
                int w = ScrollBar.this.getInnerWidth();
                if (target.getLayoutHeight() == target.getHeight()) { //  || target.isScrollableY()
                    return new Dimension(0,0);
                }
                int h = (int)Math.round(ScrollBar.this.getInnerHeight() * ((float)target.getHeight()/target.getLayoutHeight()));
                return new Dimension(w, h);
            }
        }
        
    }
    private int px(double mm) {
        return Display.getInstance().convertToPixels((float) mm);
    }
}

test.css:

#Constants {
    includeNativeBool: true;
    scrollVisibleBool: true;
    alwaysTensileBool: false;
}

@shannah
Copy link
Collaborator

shannah commented Aug 21, 2018

First observation. Firefox on Mac works fine.
Firefox on Windows 10 exhibits terrible scrolling. Looking into it.

@shannah
Copy link
Collaborator

shannah commented Aug 21, 2018

I have made a few changes the scrolling issue in Firefox. Tested on FF Windows 10. They will be available in the next server update.

@shannah shannah closed this as completed Aug 21, 2018
@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 21, 2018

Thank you very much :)

Does I have to file another issue for the "alwaysTensileBool" impossible to deactivate, already reported from the starting of this discussion?

@shannah
Copy link
Collaborator

shannah commented Aug 21, 2018

You need to set both alwaysTensileBool=false and tensileDragBool=false

(Actually I'm not sure if the former is even necessary, but tensileDragBool=false is necessary)

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 21, 2018

Thank you, setting both alwaysTensileBool=false and tensileDragBool=false the issue is solved!

@shannah
Copy link
Collaborator

shannah commented Aug 21, 2018

Note: Upon further testing of my latest changes, there still may be performance issues in Firefox on Windows. It's finicky. (It seems to work better with developer tools opened, than with it closed - which is very strange). On my Surface Pro, the scrolling in Firefox is sporadically choppy and slow. It works well on all other platforms (FF/OSX, FF/Linux, Chrome/OSX, Chrome/Linux, Chrome/Windows, Edge/Windows) all scroll smoothly and consistently.

Running FF/Windows 10 on VirtualBox on my Ubuntu machine seems to work OK so it might just be my Surface.

The issue seems similar to react-dnd/react-dnd#1000 in the sense that it seems to randomly lose mouse events (they never get delivered).

When the update is out on Friday, please try it out and see if it fixes it for you.

@shannah shannah reopened this Aug 21, 2018
@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 21, 2018

@shannah Ok. Thank you for your effort, on Friday I'll do the test.

@jsfan3
Copy link
Contributor Author

jsfan3 commented Aug 24, 2018

Today I tested the scrolling on Firefox and Chrome on Linux. It seems fine. At the moment, I cannot test my project ported to Javascript on different operating systems, I'll do that in a next step.

About the performance issues, yes, I have performance issue on Firefox (I haven't them on Chrome), but I cannot know if they are related to the scrolling or not. I suppose not. The performance penality, in my case, means that the browser reacts as more slowly to the button clicks (to add or cancel sorted rows) as more data I have in a scrollable container containing a Table binded to List<PropertyBusinessObject>. Chrome seems to work better with big Tables than Firefox, however I don't know the cause.

@shannah
Copy link
Collaborator

shannah commented Oct 10, 2018

Closing this issue. If firefox performance continues to be a problem, let's open a new issue for that - hopefully with a test case that we can try to optimize.

@shannah shannah closed this as completed Oct 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants