New ScrollPanel control

I’ve added a new ScrollPanel control as the original was never really intended for users to use directly and only had vertical scrolling.

The new version is easy to implement and:

  • Has both horizontal and vertical scrolling
  • Setting vertical wrap disables horizontal scrolling if you need
  • Allows for child ScrollPanels (nested clipping works-- single level deep for now)
  • Has comprehensive methods for getting just about any info you need about the current state of the scrollable area
  • Has methods for scrollToTop, Bottom, Left & Right
  • Works for both text and Elements together or separately
  • Can dynamically update the scrollbar sizes with a single method call

Anyways, It should be available with tonight’s update… or you can grab it from the repo now that it is finished.

On a side note, I may be updating how clipping is handled, giving you the ability to have as many separate clipping layers as you need for any given control. The clipping will likely be as automatic as it is now, but could potentially be something you need to manage if creating complex nesting situations that require resize+clipping from multiple parent elements (and by manage I mean add/remove the clipping layer… the rest is done internally).

The general idea worked as I have it implemented now (2 layers) for the ScrollPanel, but there doesn’t really seem to be a reason not to update this to handle infinite clipping layers and I’ve already seen from many people decent use cases for why this would be beneficial.

Sooo… um… that’s all for now… /wave

1 Like

Eh… two small updates for this… one that I think @rockfire will be interested if noone else.

  1. Added a simple scroll content paging mechanism. Just call setUseContentPaging(true) and it will hide/show (remove from scene/add to scene) content based on whether or not it is within the visual area of the ScrollPanel.
  2. Added an onScrollContentHook(ScrollDirection direction) method you can override if you need to execute code based on the user scrolling content.

That’s pretty much it.

Thanks so much for this. Sorry for the delay in reply, I’ve had my head stuck in a terrain editor for what seems like an eternity. Integrating these improvements into the Table control will be a welcome relief.

I will post here when it is done.

RR

1 Like
@rockfire said: Thanks so much for this. Sorry for the delay in reply, I've had my head stuck in a terrain editor for what seems like an eternity. Integrating these improvements into the Table control will be a welcome relief.

I will post here when it is done.

RR

Let me know as you need things updated or changed, as it’s an initial implementation that seems to work pretty well so far, but… still

Well I’ve spent a few hours with it, and it’s look pretty good so far. The code is way saner than ScrollArea, so good job there :slight_smile:

I thought I’d start off small, to try and get used to the component. So, I decided to create a version of this that works with my layout stuff. This went well, and I have something workable. I had a tiny issue with the horizontal bar not hiding when it wasn’t needed (because I was using vertical wrap), but this may have been something else. I’ll get back to you on this one.

Next was an attempt to use it TextElement. This is where things didn’t work out perfectly, although I am pretty sure it’s TextElement at fault.

[java]
// The text element
TextElement text = new TextElement(screen, Vector2f.ZERO, new Vector2f(380, 380),assetManager.loadFont(screen.getStyle(“Font”).getString(“defaultFont”))) {
@Override
public void onUpdate(float tpf) {
}

        @Override
        public void onEffectStart() {
        }

        @Override
        public void onEffectStop() {
        }
    };
    text.setFontSize(screen.getStyle("Common").getFloat("fontSize"));
    
    // This just loads up  a large amount of text
    try {
        text.setText(IOUtils.toString(getClass().getResource("/META-INF/help.txt")).replace("\n", "<br/>"));
    } catch (Exception ex) {
        text.setText("Failed to load help. " + ex.getMessage());
    }
    text.setTextWrap(LineWrapMode.Word);

    // The scroller
    ScrollPanel scroller = new ScrollPanel(screen, new Vector2f(10, 10), new Vector2f(380, 380));
    scroller.addScrollableContent(text);
    scroller.setUseVerticalWrap(true);
    
    // These have no effect. No clipping, no way of padding?
    text.setClipPadding(8);
    text.setTextPadding(8);
    text.setControlClippingLayer(scroller);

// text.setClippingLayer(scroller);

    // A window, just because
    Panel window = new Panel(screen, new Vector2f(100, 100), new Vector2f(400, 400));
    window.addChild(scroller);

    // Add window to screen (causes a layout)  
    screen.addElement(window);
    window.show();

[/java]

This has two main problems. Firstly the text can’t be inset, and so overlaps the scrollpanels borders. Secondly, there appears to be no clipping taking place.

Is TextElement known to have clipping problems?

I’ll start work on the Table upgrade tomorrow, I can’t seen anything obvious that would stop it being converted.

@rockfire said: Well I've spent a few hours with it, and it's look pretty good so far. The code is way saner than ScrollArea, so good job there :)

I thought I’d start off small, to try and get used to the component. So, I decided to create a version of this that works with my layout stuff. This went well, and I have something workable. I had a tiny issue with the horizontal bar not hiding when it wasn’t needed (because I was using vertical wrap), but this may have been something else. I’ll get back to you on this one.

Next was an attempt to use it TextElement. This is where things didn’t work out perfectly, although I am pretty sure it’s TextElement at fault.

[java]
// The text element
TextElement text = new TextElement(screen, Vector2f.ZERO, new Vector2f(380, 380),assetManager.loadFont(screen.getStyle(“Font”).getString(“defaultFont”))) {
@Override
public void onUpdate(float tpf) {
}

        @Override
        public void onEffectStart() {
        }

        @Override
        public void onEffectStop() {
        }
    };
    text.setFontSize(screen.getStyle("Common").getFloat("fontSize"));
    
    // This just loads up  a large amount of text
    try {
        text.setText(IOUtils.toString(getClass().getResource("/META-INF/help.txt")).replace("

", “<br/>”));
} catch (Exception ex) {
text.setText("Failed to load help. " + ex.getMessage());
}
text.setTextWrap(LineWrapMode.Word);

    // The scroller
    ScrollPanel scroller = new ScrollPanel(screen, new Vector2f(10, 10), new Vector2f(380, 380));
    scroller.addScrollableContent(text);
    scroller.setUseVerticalWrap(true);
    
    // These have no effect. No clipping, no way of padding?
    text.setClipPadding(8);
    text.setTextPadding(8);
    text.setControlClippingLayer(scroller);

// text.setClippingLayer(scroller);

    // A window, just because
    Panel window = new Panel(screen, new Vector2f(100, 100), new Vector2f(400, 400));
    window.addChild(scroller);

    // Add window to screen (causes a layout)  
    screen.addElement(window);
    window.show();

[/java]

This has two main problems. Firstly the text can’t be inset, and so overlaps the scrollpanels borders. Secondly, there appears to be no clipping taking place.

Is TextElement known to have clipping problems?

I’ll start work on the Table upgrade tomorrow, I can’t seen anything obvious that would stop it being converted.

It is likely TextElement, as I still haven’t finished it up. It works really well with NoWrap for things like Labels, etc. I’ll hopefully be summing both of these up within the next few days as I start writing the tests for using these together (This will help determine how content padding will be handled, as well as force me to actually finish Word and Character Wrapping for TextElement)

Oops… forgot to answer the clipping thing. It does have clipping, however I never tested it past setting the clipping to itself. So, there is still likely some issues with this as well.

I played with AnimText text a little and came up with the follow which seems to fix the clipping :-

[java]
@Override
public void controlResizeHook() {
if (this.getIsResizable()) {
animText.setPositionY(getHeight()-animText.getLineHeight());
animText.setBounds(getDimensions());
switch (textWrap) {
case Character:
animText.wrapTextToCharacter(getWidth());
break;
case Word:
animText.wrapTextToWord(getWidth());
break;
}
setTextAlign(textAlign);
setTextVAlign(textVAlign);

                    /* Commented out this vvvv 
                    
		animText.getMaterial().setVector4("Clipping", getClippingBounds());
	//	animText.getMaterial().setBoolean("UseClipping", true);
		animText.update(0);
                    */
	}
            
            // And replaced with this (resizable is false for me)
            animText.getMaterial().setVector4("Clipping", getClippingBounds());
            animText.getMaterial().setBoolean("UseClipping", true);
            if(animText.getLineDisplay().getParent() != null) {
                animText.getLineDisplay().getMaterial().setVector4("Clipping", getClippingBounds());
                animText.getLineDisplay().getMaterial().setBoolean("UseClipping", true);
            }
            animText.update(0);
}

[/java]

Hopefully that can give you some clues for a more permanent fix.

I also have a request for ScrollPanel. Would it be possible to give HScrollBar it’s own set of styles (“ScrollArea#HScrollBar”)? It currently reuses VScrollBar’s. This is fine for the default skin, but I use a fancy track image that looks pretty bad when squashed then stretched :wink:

EDIT: I should say this doesn’t address the padding of course.

RR

@rockfire said: I played with AnimText text a little and came up with the follow which seems to fix the clipping :-

[java]
@Override
public void controlResizeHook() {
if (this.getIsResizable()) {
animText.setPositionY(getHeight()-animText.getLineHeight());
animText.setBounds(getDimensions());
switch (textWrap) {
case Character:
animText.wrapTextToCharacter(getWidth());
break;
case Word:
animText.wrapTextToWord(getWidth());
break;
}
setTextAlign(textAlign);
setTextVAlign(textVAlign);

                    /* Commented out this vvvv 
                    
		animText.getMaterial().setVector4("Clipping", getClippingBounds());
	//	animText.getMaterial().setBoolean("UseClipping", true);
		animText.update(0);
                    */
	}
            
            // And replaced with this (resizable is false for me)
            animText.getMaterial().setVector4("Clipping", getClippingBounds());
            animText.getMaterial().setBoolean("UseClipping", true);
            if(animText.getLineDisplay().getParent() != null) {
                animText.getLineDisplay().getMaterial().setVector4("Clipping", getClippingBounds());
                animText.getLineDisplay().getMaterial().setBoolean("UseClipping", true);
            }
            animText.update(0);
}

[/java]

Hopefully that can give you some clues for a more permanent fix.

I also have a request for ScrollPanel. Would it be possible to give HScrollBar it’s own set of styles (“ScrollArea#HScrollBar”)? It currently reuses VScrollBar’s. This is fine for the default skin, but I use a fancy track image that looks pretty bad when squashed then stretched :wink:

EDIT: I should say this doesn’t address the padding of course.

RR

Thank you for this! It should be super helpful in getting this finished up. And yes about the style info. It was on the list of things to do and I just haven’t yet.

Btw, did you update the plugin (or you local copy of the repo) and have you seen any picking issues since? I can’t repro the problem here any more and haven’t seen it once since the fix, however, I’d love to hear from other people that is definitely fixed the issue. This one has been killing me for a loooooong time.

Thanks! I look forward to style updates, I have worked around it for now.

And yup, I updated last night. I haven’t had chance to try it in my game yet (I am in the middle of cough refactoring - things are a bit bessy). But, I was working with a smaller tonegodgui app. that seemed better in a couple of places that were problematic before. However, after a few minutes EVERYTHING stopped accepting clicks except for getting focus on some text fields. Now, this may have been something completely unrelated. I will keep an eye on it, and let you know.

1 Like

Thanks for the new horizontal styles, they work good.

Two little things I have noticed relating to behavior when scrollbars appear and disappear …

  1. You have some content that is ‘high’ and so is showing a vertical scrollbar. You then resize, shrinking the scrollpanes container horizontally. The horizontal bar won’t appear until the vertical bars width is completely covering the content, i.e. the test for whether to display the horizontal bar doesn’t take into account whether the vertical bar is already showing.

The following fixes this …

[java]

if (!verticalWrap) {
// take vertical bar visibility into account when testing if horizontal should show
if (getWidth() - ((vShow || vScrollBar.getIsVisible() ) && !vHide ? scrollSize : 0) < scrollableArea.getWidth()) {
if (innerBounds.getHeight() == getHeight())
hResize = true;
if (!hScrollBar.getIsVisible())
hShow = true;
hDir = false;

[/java]

  1. When the horizontal bar shows, if you are already scrolled to bottom, the horizontal bar will cover the bottom of the content. If you are not already scrolled to the bottom, it correctly adjusts the scroll position. I’m not sure sure about the fix for this one.

It’s something to do with this bit …

[java]
if (!verticalWrap) {
if (hShow) {
hScrollBar.show();
scrollableArea.setY(scrollableArea.getY()-scrollSize);
}
else if (hHide) {
hScrollBar.hide();
scrollableArea.setY(scrollableArea.getY()+scrollSize);
}
}

[/java]

I think this just needs to only adjust if not already at the bottom. However, I couldn’t seem to find an accurate way of determining if the content is scrolled to the bottom. Any change of an “isAtBottom()” (and isAtTop()) method?

RR

@rockfire said: Thanks for the new horizontal styles, they work good.

Two little things I have noticed relating to behavior when scrollbars appear and disappear …

  1. You have some content that is ‘high’ and so is showing a vertical scrollbar. You then resize, shrinking the scrollpanes container horizontally. The horizontal bar won’t appear until the vertical bars width is completely covering the content, i.e. the test for whether to display the horizontal bar doesn’t take into account whether the vertical bar is already showing.

The following fixes this …

[java]

if (!verticalWrap) {
// take vertical bar visibility into account when testing if horizontal should show
if (getWidth() - ((vShow || vScrollBar.getIsVisible() ) && !vHide ? scrollSize : 0) < scrollableArea.getWidth()) {
if (innerBounds.getHeight() == getHeight())
hResize = true;
if (!hScrollBar.getIsVisible())
hShow = true;
hDir = false;

[/java]

  1. When the horizontal bar shows, if you are already scrolled to bottom, the horizontal bar will cover the bottom of the content. If you are not already scrolled to the bottom, it correctly adjusts the scroll position. I’m not sure sure about the fix for this one.

It’s something to do with this bit …

[java]
if (!verticalWrap) {
if (hShow) {
hScrollBar.show();
scrollableArea.setY(scrollableArea.getY()-scrollSize);
}
else if (hHide) {
hScrollBar.hide();
scrollableArea.setY(scrollableArea.getY()+scrollSize);
}
}

[/java]

I think this just needs to only adjust if not already at the bottom. However, I couldn’t seem to find an accurate way of determining if the content is scrolled to the bottom. Any change of an “isAtBottom()” (and isAtTop()) method?

RR

Yay! Thanks for spending time with this… the ScrollPanel has/had no concept of content padding yet and the reshape function apparently wasn’t account for the current scroll width (though… I honestly thought I had done this at least… apparently not).

I’ll get these changes implemented today.

EDIT: And yep… I’ll proved methods for checking against top/bottom/left/right as part of the utility methods already in the class.

Great, thanks as ever. I was wondering about the padding too, glad you have that in hand too.

Can you think of any reason why ScrollPanel might eat mouse button events? I have a class whose signature looks like …

[java]
public abstract class SelectArea extends ScrollPanel implements MouseMovementListener, MouseButtonListener, TabFocusListener, KeyboardListener {
// …
}
[/java]

The only way i’ve found is to turn off mouse events on “innerBounds” (a ScrollPanelBounds), but this of course breaks the mouse wheel scrolling. The weird thing is, this doesn’t implement MouseButtonListener, so i’m not sure why it’s not getting the event bubbled up.

[java]
public class ScrollPanelBounds extends Element implements MouseWheelListener, TouchListener, FlingListener {
}
[/java]

setIgnoreMouse seems a bit black & white. I presume its not possible to selectively disable events for a particular control?

@rockfire said: Can you think of any reason why ScrollPanel might eat mouse button events? I have a class whose signature looks like ..

[java]
public abstract class SelectArea extends ScrollPanel implements MouseMovementListener, MouseButtonListener, TabFocusListener, KeyboardListener {
// …
}
[/java]

The only way i’ve found is to turn off mouse events on “innerBounds” (a ScrollPanelBounds), but this of course breaks the mouse wheel scrolling. The weird thing is, this doesn’t implement MouseButtonListener, so i’m not sure why it’s not getting the event bubbled up.

[java]
public class ScrollPanelBounds extends Element implements MouseWheelListener, TouchListener, FlingListener {
}
[/java]

setIgnoreMouse seems a bit black & white. I presume its not possible to selectively disable events for a particular control?

Two part answer.

  1. I’ll try and track down the actual cause. Think I just heard about this in another situation… so I’d like to know the actualy cause.
  2. setIgnoreMouse needs to be split by listener. The current method will disable all, but you should be able to decide which of these you specifically need to ignore. Biggest use case is still allowing wheel scrolling when elements that do not use mouse wheel are blocking the inner bounds area…

@rockfire
I haven’t been able to look into the specific issue yet, however, I just committed the changes that splits all event types for get/set ignore methods. Here is a little bit more specifics:

NOTE: Mouse wheel events are ignored by default. Screen was updated to propagate events based on event type checks. So when getEventElement is called… it now takes x, y and EventCheckType… this calls a centralized methods that checks for the specific ignore, blah blah.

SUPER SUPER AWESOME NOTE: Scroll area/panel now can use mouse wheel scrolling no matter what is added to them.

[java]
public void setIgnoreMouse(boolean ignoreMouse); // Ignores all mouse events
public boolean getIgnoreMouse();
public void setIgnoreMouseButtons(boolean ignoreMouseButtons); // Ignore left/right mouse button events
public boolean getIgnoreMouseButtons();
public void setIgnoreMouseLeftButton(boolean ignoreMouseLeftButton); // Ignores left mouse button
public boolean getIgnoreMouseLeftButton();
public void setIgnoreMouseRightButton(boolean ignoreMouseRightButton); // Ignores right mouse button
public boolean getIgnoreMouseRightButton();
public void setIgnoreMouseFocus(boolean ignoreMouseFocus); // Ignores mouse focus
public boolean getIgnoreMouseFocus();
public void setIgnoreMouseWheel(boolean ignoreMouseWheel); // Ignores all mouse wheel events
public boolean getIgnoreMouseWheel();
public void setIgnoreMouseWheelClick(boolean ignoreMouseWheelClick); // Ignores mouse wheel click events
public boolean getIgnoreMouseWheelClick();
public void setIgnoreMouseWheelMove(boolean ignoreMouseWheelMove); // Ignores mouse wheel move events
public boolean getIgnoreMouseWheelMove();
public void setIgnoreTouchEvents(boolean ignoreTouchEvents); // Ignores all touch events
public boolean getIgnoreTouchEvents();
public void setIgnoreTouch(boolean ignoreTouch); // Ignores Touch up and down events
public boolean getIgnoreTouch();
public void setIgnoreTouchMove(boolean ignoreTouchMove); // Ignores touch move events
public boolean getIgnoreTouchMove();
public void setIgnoreFling(boolean ignoreFling); // Ignore fling events
public boolean getIgnoreFling();
[/java]

Oh… also note… I have a little bit of cleanup to do with this… so ignore hightlights and button focus effects. I commited the current changes so you could make sure this was what you were picturing. I’ll get the needed changes added next.

EDIT: Ignore this… fixed and committed.

Here is the results with the ScrollPanel:

[video]http://youtu.be/rIrASxE7Qcg[/video]

1 Like

Brilliant, this worked perfectly!

PS: setUseContentPaging(), how cool is that. I’ve nearly finished converting Table to use ScrollPanel, and tried this out. Big tables are now are actually usable and I didn’t have to lift a finger :slight_smile:

1 Like