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

Source Code of com.badlogic.gdx.scenes.scene2d.ui.Tree$TreeStyle

/*******************************************************************************
* 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.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pools;

/** A tree widget where each node has an icon, actor, and child nodes.
* <p>
* The preferred size of the tree is determined by the preferred size of the actors for the expanded nodes.
* <p>
* {@link ChangeEvent} is fired when the selected node changes.
* @author Nathan Sweet */
public class Tree extends WidgetGroup {
  static boolean isMac = System.getProperty("os.name").contains("Mac");

  TreeStyle style;
  final Array<Node> rootNodes = new Array();
  final Array<Node> selectedNodes = new Array();
  float ySpacing = 4, iconSpacingLeft = 2, iconSpacingRight = 2, padding = 0, indentSpacing;
  private float leftColumnWidth, prefWidth, prefHeight;
  private boolean sizeInvalid = true;
  boolean multiSelect = true;
  boolean toggleSelect = true;
  private Node foundNode;
  Node overNode;
  private ClickListener clickListener;

  public Tree (Skin skin) {
    this(skin.get(TreeStyle.class));
  }

  public Tree (Skin skin, String styleName) {
    this(skin.get(styleName, TreeStyle.class));
  }

  public Tree (TreeStyle style) {
    setStyle(style);
    initialize();
  }

  private void initialize () {
    addListener(clickListener = new ClickListener() {
      private boolean isCtrlPressed () {
        if (isMac)
          return Gdx.input.isKeyPressed(Keys.SYM);
        else
          return Gdx.input.isKeyPressed(Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed(Keys.CONTROL_RIGHT);
      }

      public void clicked (InputEvent event, float x, float y) {
        Node node = getNodeAt(y);
        if (node == null) return;
        if (node != getNodeAt(getTouchDownY())) return;
        if (multiSelect && selectedNodes.size > 0
          && (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Keys.SHIFT_RIGHT))) {
          // Select range (shift/ctrl).
          float low = selectedNodes.first().actor.getY();
          float high = node.actor.getY();
          if (!isCtrlPressed()) selectedNodes.clear();
          if (low > high)
            selectNodes(rootNodes, high, low);
          else
            selectNodes(rootNodes, low, high);
          fireChangeEvent();
          return;
        }
        if (!multiSelect || !isCtrlPressed()) {
          if (node.children.size > 0) {
            // Toggle expanded.
            float rowX = node.actor.getX();
            if (node.icon != null) rowX -= iconSpacingRight + node.icon.getMinWidth();
            if (x < rowX) {
              node.setExpanded(!node.expanded);
              return;
            }
          }
          if (!node.isSelectable()) return;
          boolean unselect = toggleSelect && selectedNodes.size == 1 && selectedNodes.contains(node, true);
          selectedNodes.clear();
          if (unselect) {
            fireChangeEvent();
            return;
          }
        } else if (!node.isSelectable()) //
          return;
        // Select single (ctrl).
        if (!selectedNodes.removeValue(node, true)) selectedNodes.add(node);
        fireChangeEvent();
      }

      public boolean mouseMoved (InputEvent event, float x, float y) {
        setOverNode(getNodeAt(y));
        return false;
      }

      public void exit (InputEvent event, float x, float y, int pointer, Actor toActor) {
        super.exit(event, x, y, pointer, toActor);
        if (toActor == null || !toActor.isDescendantOf(Tree.this)) setOverNode(null);
      }
    });
  }

  public void setStyle (TreeStyle style) {
    this.style = style;
    indentSpacing = Math.max(style.plus.getMinWidth(), style.minus.getMinWidth()) + iconSpacingLeft;
  }

  public void add (Node node) {
    insert(rootNodes.size, node);
  }

  public void insert (int index, Node node) {
    remove(node);
    node.parent = null;
    rootNodes.insert(index, node);
    node.addToTree(this);
    invalidateHierarchy();
  }

  public void remove (Node node) {
    if (node.parent != null) {
      node.parent.remove(node);
      return;
    }
    rootNodes.removeValue(node, true);
    node.removeFromTree(this);
    invalidateHierarchy();
  }

  /** Removes all tree nodes. */
  public void clearChildren () {
    super.clearChildren();
    rootNodes.clear();
    selectedNodes.clear();
    setOverNode(null);
    fireChangeEvent();
  }

  void fireChangeEvent () {
    ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
    fire(changeEvent);
    Pools.free(changeEvent);
  }

  public Array<Node> getNodes () {
    return rootNodes;
  }

  public void invalidate () {
    super.invalidate();
    sizeInvalid = true;
  }

  private void computeSize () {
    sizeInvalid = false;
    prefWidth = style.plus.getMinWidth();
    prefWidth = Math.max(prefWidth, style.minus.getMinWidth());
    prefHeight = getHeight();
    leftColumnWidth = 0;
    computeSize(rootNodes, indentSpacing);
    leftColumnWidth += iconSpacingLeft + padding;
    prefWidth += leftColumnWidth + padding;
    prefHeight = getHeight() - prefHeight;
  }

  private void computeSize (Array<Node> nodes, float indent) {
    float ySpacing = this.ySpacing;
    float spacing = iconSpacingLeft + iconSpacingRight;
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      float rowWidth = indent + iconSpacingRight;
      Actor actor = node.actor;
      if (actor instanceof Layout) {
        Layout layout = (Layout)actor;
        rowWidth += layout.getPrefWidth();
        node.height = layout.getPrefHeight();
        layout.pack();
      } else {
        rowWidth += actor.getWidth();
        node.height = actor.getHeight();
      }
      if (node.icon != null) {
        rowWidth += spacing + node.icon.getMinWidth();
        node.height = Math.max(node.height, node.icon.getMinHeight());
      }
      prefWidth = Math.max(prefWidth, rowWidth);
      prefHeight -= node.height + ySpacing;
      if (node.expanded) computeSize(node.children, indent + indentSpacing);
    }
  }

  public void layout () {
    if (sizeInvalid) computeSize();
    layout(rootNodes, leftColumnWidth + indentSpacing + iconSpacingRight, getHeight() - ySpacing / 2);
  }

  private float layout (Array<Node> nodes, float indent, float y) {
    float ySpacing = this.ySpacing;
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      Actor actor = node.actor;
      float x = indent;
      if (node.icon != null) x += node.icon.getMinWidth();
      y -= node.height;
      node.actor.setPosition(x, y);
      y -= ySpacing;
      if (node.expanded) y = layout(node.children, indent + indentSpacing, y);
    }
    return y;
  }

  public void draw (SpriteBatch batch, float parentAlpha) {
    Color color = getColor();
    if (style.background != null) {
      batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
      style.background.draw(batch, getX(), getY(), getWidth(), getHeight());
      batch.setColor(Color.WHITE);
    }
    draw(batch, rootNodes, leftColumnWidth);
    super.draw(batch, parentAlpha); // Draw actors.
  }

  /** Draws selection, icons, and expand icons. */
  private void draw (SpriteBatch batch, Array<Node> nodes, float indent) {
    Drawable plus = style.plus, minus = style.minus;
    float x = getX(), y = getY();
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      Actor actor = node.actor;

      if (selectedNodes.contains(node, true) && style.selection != null) {
        style.selection.draw(batch, x, y + actor.getY() - ySpacing / 2, getWidth(), node.height + ySpacing);
      } else if (node == overNode && style.over != null) {
        style.over.draw(batch, x, y + actor.getY() - ySpacing / 2, getWidth(), node.height + ySpacing);
      }

      if (node.icon != null) {
        float iconY = actor.getY() + Math.round((node.height - node.icon.getMinHeight()) / 2);
        batch.setColor(actor.getColor());
        node.icon.draw(batch, x + node.actor.getX() - iconSpacingRight - node.icon.getMinWidth(), y + iconY,
          node.icon.getMinWidth(), node.icon.getMinHeight());
        batch.setColor(Color.WHITE);
      }

      if (node.children.size == 0) continue;

      Drawable expandIcon = node.expanded ? minus : plus;
      float iconY = actor.getY() + Math.round((node.height - expandIcon.getMinHeight()) / 2);
      expandIcon.draw(batch, x + indent - iconSpacingLeft, y + iconY, expandIcon.getMinWidth(), expandIcon.getMinHeight());
      if (node.expanded) draw(batch, node.children, indent + indentSpacing);
    }
  }

  /** @return May be null. */
  public Node getNodeAt (float y) {
    foundNode = null;
    getNodeAt(rootNodes, y, getHeight());
    return foundNode;
  }

  private float getNodeAt (Array<Node> nodes, float y, float rowY) {
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      if (y >= rowY - node.height - ySpacing && y < rowY) {
        foundNode = node;
        return -1;
      }
      rowY -= node.height + ySpacing;
      if (node.expanded) {
        rowY = getNodeAt(node.children, y, rowY);
        if (rowY == -1) return -1;
      }
    }
    return rowY;
  }

  void selectNodes (Array<Node> nodes, float low, float high) {
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      if (node.actor.getY() < low) break;
      if (!node.isSelectable()) continue;
      if (node.actor.getY() <= high && !selectedNodes.contains(node, true)) selectedNodes.add(node);
      if (node.expanded) selectNodes(node.children, low, high);
    }
  }

  public Array<Node> getSelection () {
    return selectedNodes;
  }

  public void setSelection (Node node) {
    selectedNodes.clear();
    if (node != null) selectedNodes.add(node);
    fireChangeEvent();
  }

  public void setSelection (Array<Node> nodes) {
    selectedNodes.clear();
    selectedNodes.addAll(nodes);
    fireChangeEvent();
  }

  public void addSelection (Node node) {
    if (node == null) return;
    selectedNodes.add(node);
    fireChangeEvent();
  }

  public void clearSelection () {
    selectedNodes.clear();
    fireChangeEvent();
  }

  public TreeStyle getStyle () {
    return style;
  }

  public Array<Node> getRootNodes () {
    return rootNodes;
  }

  public Node getOverNode () {
    return overNode;
  }

  public void setOverNode (Node overNode) {
    this.overNode = overNode;
  }

  /** Sets the amount of horizontal space between the nodes and the left/right edges of the tree. */
  public void setPadding (float padding) {
    this.padding = padding;
  }

  /** Sets the amount of vertical space between nodes. */
  public void setYSpacing (float ySpacing) {
    this.ySpacing = ySpacing;
  }

  /** Sets the amount of horizontal space between the node actors and icons. */
  public void setIconSpacing (float left, float right) {
    this.iconSpacingLeft = left;
    this.iconSpacingRight = right;
  }

  public float getPrefWidth () {
    if (sizeInvalid) computeSize();
    return prefWidth;
  }

  public float getPrefHeight () {
    if (sizeInvalid) computeSize();
    return prefHeight;
  }

  public void findExpandedObjects (Array objects) {
    findExpandedObjects(rootNodes, objects);
  }

  public void restoreExpandedObjects (Array objects) {
    for (int i = 0, n = objects.size; i < n; i++) {
      Node node = findNode(objects.get(i));
      if (node != null) {
        node.setExpanded(true);
        node.expandTo();
      }
    }
  }

  static boolean findExpandedObjects (Array<Node> nodes, Array objects) {
    boolean expanded = false;
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      if (node.expanded && !findExpandedObjects(node.children, objects)) objects.add(node.object);
    }
    return expanded;
  }

  /** Returns the node with the specified object, or null. */
  public Node findNode (Object object) {
    if (object == null) throw new IllegalArgumentException("object cannot be null.");
    return findNode(rootNodes, object);
  }

  static Node findNode (Array<Node> nodes, Object object) {
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      if (object.equals(node.object)) return node;
    }
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      Node found = findNode(node.children, object);
      if (found != null) return found;
    }
    return null;
  }

  public void collapseAll () {
    collapseAll(rootNodes);
  }

  static void collapseAll (Array<Node> nodes) {
    for (int i = 0, n = nodes.size; i < n; i++) {
      Node node = nodes.get(i);
      node.setExpanded(false);
      collapseAll(node.children);
    }
  }

  public void expandAll () {
    expandAll(rootNodes);
  }

  static void expandAll (Array<Node> nodes) {
    for (int i = 0, n = nodes.size; i < n; i++)
      nodes.get(i).expandAll();
  }

  /** Returns the click listener the tree uses for clicking on nodes and the over node. */
  public ClickListener getClickListener () {
    return clickListener;
  }

  public void setMultiSelect (boolean multiSelect) {
    this.multiSelect = multiSelect;
  }

  public void setToggleSelect (boolean toggleSelect) {
    this.toggleSelect = toggleSelect;
  }

  static public class Node {
    Actor actor;
    Node parent;
    final Array<Node> children = new Array(0);
    boolean selectable = true;
    boolean expanded;
    Drawable icon;
    float height;
    Object object;

    public Node (Actor actor) {
      if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
      this.actor = actor;
    }

    public void setExpanded (boolean expanded) {
      if (expanded == this.expanded) return;
      this.expanded = expanded;
      if (children.size == 0) return;
      Tree tree = getTree();
      if (tree == null) return;
      if (expanded) {
        for (int i = 0, n = children.size; i < n; i++)
          children.get(i).addToTree(tree);
      } else {
        for (int i = 0, n = children.size; i < n; i++)
          children.get(i).removeFromTree(tree);
      }
      tree.invalidateHierarchy();
    }

    /** Called to add the actor to the tree when the node's parent is expanded. */
    protected void addToTree (Tree tree) {
      tree.addActor(actor);
      if (!expanded) return;
      for (int i = 0, n = children.size; i < n; i++)
        children.get(i).addToTree(tree);
    }

    /** Called to remove the actor from the tree when the node's parent is collapsed. */
    protected void removeFromTree (Tree tree) {
      tree.removeActor(actor);
      if (!expanded) return;
      for (int i = 0, n = children.size; i < n; i++)
        children.get(i).removeFromTree(tree);
    }

    public void add (Node node) {
      insert(children.size, node);
    }

    public void addAll (Array<Node> nodes) {
      for (int i = 0, n = nodes.size; i < n; i++)
        insert(children.size, nodes.get(i));
    }

    public void insert (int index, Node node) {
      node.parent = this;
      children.insert(index, node);
      updateChildren();
    }

    public void remove () {
      Tree tree = getTree();
      if (tree != null)
        tree.remove(this);
      else if (parent != null) //
        parent.remove(this);
    }

    public void remove (Node node) {
      children.removeValue(node, true);
      if (!expanded) return;
      Tree tree = getTree();
      if (tree == null) return;
      node.removeFromTree(tree);
      if (children.size == 0) expanded = false;
    }

    public void removeAll () {
      Tree tree = getTree();
      if (tree != null) {
        for (int i = 0, n = children.size; i < n; i++)
          children.get(i).removeFromTree(tree);
      }
      children.clear();
    }

    /** Returns the tree this node is currently in, or null. */
    public Tree getTree () {
      Group parent = actor.getParent();
      if (!(parent instanceof Tree)) return null;
      return (Tree)parent;
    }

    public Actor getActor () {
      return actor;
    }

    public boolean isExpanded () {
      return expanded;
    }

    /** If the children order is changed, {@link #updateChildren()} must be called. */
    public Array<Node> getChildren () {
      return children;
    }

    public void updateChildren () {
      if (!expanded) return;
      Tree tree = getTree();
      if (tree == null) return;
      for (int i = 0, n = children.size; i < n; i++)
        children.get(i).addToTree(tree);
    }

    /** @return May be null. */
    public Node getParent () {
      return parent;
    }

    /** Sets an icon that will be drawn to the left of the actor. */
    public void setIcon (Drawable icon) {
      this.icon = icon;
    }

    public Object getObject () {
      return object;
    }

    /** Sets an application specific object for this node. */
    public void setObject (Object object) {
      this.object = object;
    }

    public Drawable getIcon () {
      return icon;
    }

    /** Returns this node or the child node with the specified object, or null. */
    public Node findNode (Object object) {
      if (object == null) throw new IllegalArgumentException("object cannot be null.");
      if (object.equals(this.object)) return this;
      return Tree.findNode(children, object);
    }

    /** Collapses all nodes under and including this node. */
    public void collapseAll () {
      setExpanded(false);
      Tree.collapseAll(children);
    }

    /** Expands all nodes under and including this node. */
    public void expandAll () {
      setExpanded(true);
      if (children.size > 0) Tree.expandAll(children);
    }

    /** Expands all parent nodes of this node. */
    public void expandTo () {
      Node node = parent;
      while (node != null) {
        node.setExpanded(true);
        node = node.parent;
      }
    }

    public boolean isSelectable () {
      return selectable;
    }

    public void setSelectable (boolean selectable) {
      this.selectable = selectable;
    }

    public void findExpandedObjects (Array objects) {
      if (expanded && !Tree.findExpandedObjects(children, objects)) objects.add(object);
    }

    public void restoreExpandedObjects (Array objects) {
      for (int i = 0, n = objects.size; i < n; i++) {
        Node node = findNode(objects.get(i));
        if (node != null) {
          node.setExpanded(true);
          node.expandTo();
        }
      }
    }
  }

  /** The style for a {@link Tree}.
   * @author Nathan Sweet */
  static public class TreeStyle {
    public Drawable plus, minus;
    /** Optional. */
    public Drawable over, selection, background;

    public TreeStyle () {
    }

    public TreeStyle (Drawable plus, Drawable minus, Drawable selection) {
      this.plus = plus;
      this.minus = minus;
      this.selection = selection;
    }

    public TreeStyle (TreeStyle style) {
      this.plus = style.plus;
      this.minus = style.minus;
      this.selection = style.selection;
    }
  }
}
TOP

Related Classes of com.badlogic.gdx.scenes.scene2d.ui.Tree$TreeStyle

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.