Package com.badlogic.gdx.scenes.scene2d.ui

Source Code of com.badlogic.gdx.scenes.scene2d.ui.ScrollPane

/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/

package com.badlogic.gdx.scenes.scene2d.ui;

import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Layout;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
import com.badlogic.gdx.scenes.scene2d.ui.utils.ScissorStack;

/** A special container that allows scrolling over its children.
*
* <h2>Functionality</h2> A ScrollPane can embed any {@link Actor} (and {@link Widget} or {@link Table} for that matter) and
* provide scrolling functionality in case the embedded Actor is bigger than the scroll pane itself. The scroll pane will
* automatically decide whether it needs a vertical and/or horizontal scroll handle based on the contained Actor's size with
* respect to the scroll pane's own size.</p>
*
* <b>Note: do not use any of the {@link #addActor(Actor)} or {@link #removeActor(Actor)} methods with this class! The embedded
* widget is specified at construction time or via {@link #setWidget(Actor)}.</b> * <h2>Layout</h2> The (preferred) width and
* height of a scroll pane is determined by the size passed to its constructor. The contained Actor will be positioned in such a
* way that it's top left corner will coincide with the scroll pane's corner when the vertical and horizontal scroll handles are
* at their minimum position.</p>
*
*
* <h2>Style</h2> A ScrollPane is a {@link Group} (note the comment in the functionality section!) that conditionally displays
* horizontal and vertical scroll bars and handles as well as the embedded Actor, clipped to the available area inside of the
* scroll pane. The scroll bars are {@link NinePatch} instances, the scroll handles are {@link NinePatch} instances as well. In
* addition a background {@link NinePatch} is displayed behind the embedded Actor. The style is defined via a
* {@link ScrollPaneStyle} instance, which can be either done programmatically or via a {@link Skin}.</p>
*
* The height of the horizontal scroll bar and handle is constant and takes on the maximum {@link NinePatch#getTotalHeight()}
* value of the involed NinePatch instances. The width is determined automatically based on the size of the scroll pane.</p>
*
* The width of the vertical scroll bar and handle is constant and takes on the maximum {@link NinePatch#getTotalWidth()} value of
* the involed NinePatch instances. The height is determined automatically based on the size of the scroll pane.</p>
*
*
* A ScrollPane's style definition in a skin XML file should look like this:
*
* <pre>
* {@code
* <scrollpane name="default"
*             background="backgroundPatch"
*             hScroll="horizontalScrollBarPatch"
*             hScrollKnob="horizontalScrollHandlePatch"
*             vScroll="verticalScrollBarPatch"
*             vScrollKnob="verticalScrollBarHandle"/>
* }
* </pre>
*
* <ul>
* <li>The <code>name</code> attribute defines the name of the style which you can later use with
* {@link Skin#newScrollPane(String, Stage, Actor, int, int, String)}.</li>
* <li>The <code>background</code> attribute references a {@link NinePatch} by name, to be used as the scroll pane's background</li>
* <li>The <code>hScroll</code> attribute references a {@link NinePatch} by name, to be used as the scroll pane's horizontal
* scroll bar.</li>
* <li>The <code>hScrollKnow</code> attribute references a {@link NinePatch} by name, to be used as the scroll pane's horizontal
* scroll handle.</li>
* <li>The <code>vScroll</code> attribute references a {@link NinePatch} by name, to be used as the scroll pane's vertical scroll
* bar.</li>
* <li>The <code>vScrollKnow</code> attribute references a {@link NinePatch} by name, to be used as the scroll pane's vertical
* scroll handle..</li>
* </ul>
*
* @author mzechner */
public class ScrollPane extends Group implements Layout {
  final ScrollPaneStyle style;
  Actor widget;
  Stage stage;

  Rectangle hScrollBounds = new Rectangle();
  Rectangle vScrollBounds = new Rectangle();
  Rectangle hScrollKnobBounds = new Rectangle();
  Rectangle vScrollKnobBounds = new Rectangle();
  Rectangle widgetAreaBounds = new Rectangle();
  Rectangle scissorBounds = new Rectangle();

  float hScrollAmount = 0;
  float vScrollAmount = 0;
  boolean hasHScroll = false;
  boolean hasVScroll = false;
  boolean touchScrollH = false;
  boolean touchScrollV = false;
  Vector2 lastPoint = new Vector2();

  public ScrollPane (Actor widget, Stage stage, Skin skin) {
    this(widget, stage, skin.getStyle(ScrollPaneStyle.class), null);
  }

  public ScrollPane (Actor widget, Stage stage, ScrollPaneStyle style) {
    this(widget, stage, style, null);
  }

  public ScrollPane (Actor widget, Stage stage, ScrollPaneStyle style, String name) {
    super(name);
    this.widget = widget;
    this.stage = stage;
    this.style = style;
    addActor(widget);
    layout();
  }

  Vector3 tmp = new Vector3();

  private void calculateBoundsAndPositions (Matrix4 batchTransform) {
    final NinePatch background = style.background;
    final NinePatch hScrollKnob = style.hScrollKnob;
    final NinePatch vScrollKnob = style.vScrollKnob;

    // Get available space size by subtracting background's padded area.
    float areaWidth = width - background.getLeftWidth() - background.getRightWidth();
    float areaHeight = height - background.getTopHeight() - background.getBottomHeight();

    // Get widget's desired width.
    float widgetWidth, widgetHeight;
    if (widget instanceof Layout) {
      Layout layout = (Layout)widget;
      widgetWidth = layout.getPrefWidth();
      widgetHeight = layout.getPrefHeight();
    } else {
      widgetWidth = widget.width;
      widgetHeight = widget.height;
    }

    // Figure out if we need horizontal/vertical scrollbars,
    hasHScroll = false;
    hasVScroll = false;
    if (widgetWidth > areaWidth) hasHScroll = true;
    if (widgetHeight > areaHeight) hasVScroll = true;

    // Check again, now taking into account the area that's taken up by any enabled scrollbars.
    if (hasVScroll && (widgetWidth > areaWidth - vScrollKnob.getTotalWidth())) {
      hasHScroll = true;
      areaWidth -= vScrollKnob.getTotalWidth();
    }
    if (hasHScroll && (widgetHeight > areaHeight - hScrollKnob.getTotalHeight())) {
      hasVScroll = true;
      areaHeight -= hScrollKnob.getTotalHeight();
    }

    // If the widget is smaller than the available space, make it take up the available space.
    widgetWidth = Math.max(areaWidth, widgetWidth);
    widgetHeight = Math.max(areaHeight, widgetHeight);
    if (widget.width != widgetWidth || widget.height != widgetHeight) {
      widget.width = widgetWidth;
      widget.height = widgetHeight;
      if (widget instanceof Layout) {
        Layout layout = (Layout)widget;
        layout.invalidate();
        layout.layout();
      }
    }

    // Set the bounds and scroll knob sizes if scrollbars are needed.
    if (hasHScroll) {
      hScrollBounds.set(background.getLeftWidth(), background.getBottomHeight(), areaWidth, hScrollKnob.getTotalHeight());
      hScrollKnobBounds.width = Math.max(hScrollKnob.getTotalWidth(), (int)(hScrollBounds.width * areaWidth / widget.width));
      hScrollKnobBounds.height = hScrollKnob.getTotalHeight();

      hScrollKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hScrollKnobBounds.width) * hScrollAmount);
      hScrollKnobBounds.y = hScrollBounds.y;
    }

    if (hasVScroll) {
      vScrollBounds.set(width - background.getRightWidth() - vScrollKnob.getTotalWidth(), height - background.getTopHeight()
        - areaHeight, vScrollKnob.getTotalWidth(), areaHeight);
      vScrollKnobBounds.width = vScrollKnob.getTotalWidth();
      vScrollKnobBounds.height = Math.max(vScrollKnob.getTotalHeight(),
        (int)(vScrollBounds.height * areaHeight / widget.height));
      vScrollKnobBounds.x = vScrollBounds.x;
      vScrollKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vScrollKnobBounds.height) * (1 - vScrollAmount));
    }

    // Set the widget area bounds.
    widgetAreaBounds.set(background.getLeftWidth(), background.getBottomHeight()
      + (hasHScroll ? hScrollKnob.getTotalHeight() : 0), areaWidth, areaHeight);

    // Calculate the widgets offset depending on the scroll state and available widget area.
    widget.y = widgetAreaBounds.y - (!hasVScroll ? (int)(widget.height - areaHeight) : 0)
      - (hasVScroll ? (int)((widget.height - areaHeight) * (1 - vScrollAmount)) : 0);
    widget.x = widgetAreaBounds.x - (hasHScroll ? (int)((widget.width - areaWidth) * hScrollAmount) : 0);

    // Caculate the scissor bounds based on the batch transform, the available widget area and the camera transform. We need to
    // project those to screen coordinates for OpenGL ES to consume.
    ScissorStack.calculateScissors(stage.getCamera(), batchTransform, widgetAreaBounds, scissorBounds);
  }

  @Override
  public void draw (SpriteBatch batch, float parentAlpha) {
    final NinePatch background = style.background;
    final NinePatch hScrollKnob = style.hScrollKnob;
    final NinePatch hScroll = style.hScroll;
    final NinePatch vScrollKnob = style.vScrollKnob;
    final NinePatch vScroll = style.vScroll;

    // Setup transform for this group.
    applyTransform(batch);

    // Calculate the bounds for the scrollbars, the widget area and the scissor area.
    calculateBoundsAndPositions(batch.getTransformMatrix());

    // Draw the background ninepatch.
    batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
    background.draw(batch, 0, 0, width, height);
    batch.flush();

    // Enable scissors for widget area and draw the widget.
    ScissorStack.pushScissors(scissorBounds);
    drawChildren(batch, parentAlpha);
    ScissorStack.popScissors();

    // Render scrollbars and knobs on top.
    batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
    if (hasHScroll) {
      hScroll.draw(batch, hScrollBounds.x, hScrollBounds.y, hScrollBounds.width, hScrollBounds.height);
      hScrollKnob.draw(batch, hScrollKnobBounds.x, hScrollKnobBounds.y, hScrollKnobBounds.width, hScrollKnobBounds.height);
    }
    if (hasVScroll) {
      vScroll.draw(batch, vScrollBounds.x, vScrollBounds.y, vScrollBounds.width, vScrollBounds.height);
      vScrollKnob.draw(batch, vScrollKnobBounds.x, vScrollKnobBounds.y, vScrollKnobBounds.width, vScrollKnobBounds.height);
    }

    resetTransform(batch);
  }

  /** Defines a scroll pane's style, see {@link ScrollPane}.
   * @author mzechner */
  public static class ScrollPaneStyle {
    public NinePatch background;
    public NinePatch hScroll;
    public NinePatch hScrollKnob;
    public NinePatch vScroll;
    public NinePatch vScrollKnob;

    public ScrollPaneStyle () {
    }

    public ScrollPaneStyle (NinePatch backgroundPatch, NinePatch hScroll, NinePatch hScrollKnob, NinePatch vScroll,
      NinePatch vScrollKnob) {
      this.background = backgroundPatch;
      this.hScroll = hScroll;
      this.hScrollKnob = hScrollKnob;
      this.vScroll = vScroll;
      this.vScrollKnob = vScrollKnob;
    }
  }

  @Override
  public void layout () {
  }

  @Override
  public void invalidate () {
  }

  @Override
  public float getPrefWidth () {
    return 150;
  }

  @Override
  public float getPrefHeight () {
    return 150;
  }

  public float getMinWidth () {
    return 0;
  }

  public float getMinHeight () {
    return 0;
  }

  public float getMaxWidth () {
    return 0;
  }

  public float getMaxHeight () {
    return 0;
  }

  float handlePos = 0;

  @Override
  public boolean touchDown (float x, float y, int pointer) {
    if (pointer != 0) return false;

    if (hasHScroll && hScrollBounds.contains(x, y)) {
      if (hScrollKnobBounds.contains(x, y)) {
        lastPoint.set(x, y);
        handlePos = hScrollKnobBounds.x;
        touchScrollH = true;
      } else {
        if (x < hScrollKnobBounds.x) {
          hScrollAmount = Math.max(0, hScrollAmount - 0.1f);
        } else {
          hScrollAmount = Math.min(1, hScrollAmount + 0.1f);
        }
      }
      return true;
    } else if (hasVScroll && vScrollBounds.contains(x, y)) {
      if (vScrollKnobBounds.contains(x, y)) {
        lastPoint.set(x, y);
        handlePos = vScrollKnobBounds.y;
        touchScrollV = true;
      } else {
        if (y < vScrollKnobBounds.y) {
          vScrollAmount = Math.min(1, vScrollAmount + 0.1f);
        } else {
          vScrollAmount = Math.max(0, vScrollAmount - 0.1f);
        }
      }
      return true;
    } else if (widgetAreaBounds.contains(x, y)) {
      return super.touchDown(x, y, pointer);
    } else
      return false;
  }

  @Override
  public void touchUp (float x, float y, int pointer) {
    if (touchScrollH || touchScrollV) {
      touchScrollH = false;
      touchScrollV = false;
      return;
    }
    if (focusedActor[pointer] != null) super.touchUp(x, y, pointer);
  }

  @Override
  public void touchDragged (float x, float y, int pointer) {
    if (touchScrollH) {
      float delta = x - lastPoint.x;
      float scrollH = handlePos + delta;
      handlePos = scrollH;
      scrollH = Math.max(hScrollBounds.x, scrollH);
      scrollH = Math.min(hScrollBounds.x + hScrollBounds.width - hScrollKnobBounds.width, scrollH);
      hScrollAmount = (scrollH - hScrollBounds.x) / (hScrollBounds.width - hScrollKnobBounds.width);
      lastPoint.set(x, y);
    } else if (touchScrollV) {
      float delta = y - lastPoint.y;
      float scrollV = handlePos + delta;
      handlePos = scrollV;
      scrollV = Math.max(vScrollBounds.y, scrollV);
      scrollV = Math.min(vScrollBounds.y + vScrollBounds.height - vScrollKnobBounds.height, scrollV);
      vScrollAmount = 1 - ((scrollV - vScrollBounds.y) / (vScrollBounds.height - vScrollKnobBounds.height));
      lastPoint.set(x, y);
    } else
      super.touchDragged(x, y, pointer);
  }

  @Override
  public Actor hit (float x, float y) {
    return x > 0 && x < width && y > 0 && y < height ? this : null;
  }

  public void setVScrollAmount (float vScrollAmount) {
    this.vScrollAmount = vScrollAmount;
  }

  public void setHScrollAmount (float hScrollAmount) {
    this.hScrollAmount = hScrollAmount;
  }

  /** Sets the {@link Actor} embedded in this scroll pane.
   * @param widget the Actor */
  public void setWidget (Actor widget) {
    if (widget == null) throw new IllegalArgumentException("widget must not be null");
    this.removeActor(this.widget);
    this.widget = widget;
    this.addActor(widget);
    invalidate();
  }
}
TOP

Related Classes of com.badlogic.gdx.scenes.scene2d.ui.ScrollPane

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');