/******************************************************************************* * 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 static com.badlogic.gdx.scenes.scene2d.actions.Actions.*; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.Stage; 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.utils.Pools; /** * A select box (aka a drop-down list) allows a user to choose one of a number of values from a list. When inactive, the * selected value is displayed. When activated, it shows the list of values that may be selected. * <p> * {@link ChangeEvent} is fired when the selectbox selection changes. * <p> * The preferred size of the select box is determined by the maximum text bounds of the items and the size of the * {@link SelectBoxStyle#background}. * * @author mzechner * @author Nathan Sweet */ public class SelectBox extends Widget { SelectBoxStyle style; String[] items; int selectedIndex = 0; private final TextBounds bounds = new TextBounds(); final Vector2 screenCoords = new Vector2(); SelectList list; private float prefWidth, prefHeight; private ClickListener clickListener; public SelectBox(Object[] items, Skin skin) { this(items, skin.get(SelectBoxStyle.class)); } public SelectBox(Object[] items, Skin skin, String styleName) { this(items, skin.get(styleName, SelectBoxStyle.class)); } public SelectBox(Object[] items, SelectBoxStyle style) { setStyle(style); setItems(items); setWidth(getPrefWidth()); setHeight(getPrefHeight()); addListener(clickListener = new ClickListener() { public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { if (pointer == 0 && button != 0) return false; if (list != null && list.getParent() != null) { hideList(); return true; } Stage stage = getStage(); Vector2 stageCoords = Vector2.tmp; stage.screenToStageCoordinates(stageCoords.set(screenCoords.x, screenCoords.y)); list = new SelectList(stageCoords.x, stageCoords.y); stage.addActor(list); return true; } }); } public void setStyle(SelectBoxStyle style) { if (style == null) throw new IllegalArgumentException("style cannot be null."); this.style = style; if (items != null) setItems(items); else invalidateHierarchy(); } /** * Returns the select box's style. Modifying the returned style may not have an effect until * {@link #setStyle(SelectBoxStyle)} is called. */ public SelectBoxStyle getStyle() { return style; } public void setItems(Object[] objects) { if (objects == null) throw new IllegalArgumentException("items cannot be null."); if (!(objects instanceof String[])) { String[] strings = new String[objects.length]; for (int i = 0, n = objects.length; i < n; i++) strings[i] = String.valueOf(objects[i]); objects = strings; } this.items = (String[]) objects; selectedIndex = 0; Drawable bg = style.background; BitmapFont font = style.font; prefHeight = Math.max(bg.getTopHeight() + bg.getBottomHeight() + font.getCapHeight() - font.getDescent() * 2, bg.getMinHeight()); float max = 0; for (int i = 0; i < items.length; i++) max = Math.max(font.getBounds(items[i]).width, max); prefWidth = bg.getLeftWidth() + bg.getRightWidth() + max; prefWidth = Math.max(prefWidth, max + style.listBackground.getLeftWidth() + style.listBackground.getRightWidth() + 2 * style.itemSpacing); invalidateHierarchy(); } @Override public void draw(SpriteBatch batch, float parentAlpha) { Drawable background; if (list != null && list.getParent() != null && style.backgroundOpen != null) background = style.backgroundOpen; else if (clickListener.isOver() && style.backgroundOver != null) background = style.backgroundOver; else background = style.background; final BitmapFont font = style.font; final Color fontColor = style.fontColor; Color color = getColor(); float x = getX(); float y = getY(); float width = getWidth(); float height = getHeight(); batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); background.draw(batch, x, y, width, height); if (items.length > 0) { float availableWidth = width - background.getLeftWidth() - background.getRightWidth(); int numGlyphs = font.computeVisibleGlyphs(items[selectedIndex], 0, items[selectedIndex].length(), availableWidth); bounds.set(font.getBounds(items[selectedIndex])); height -= background.getBottomHeight() + background.getTopHeight(); float textY = (int) (height / 2 + background.getBottomHeight() + bounds.height / 2); font.setColor(fontColor.r, fontColor.g, fontColor.b, fontColor.a * parentAlpha); font.draw(batch, items[selectedIndex], x + background.getLeftWidth(), y + textY, 0, numGlyphs); } // calculate screen coords where list should be displayed getStage().toScreenCoordinates(screenCoords.set(x, y), batch.getTransformMatrix()); } /** * Sets the selected item via it's index * * @param selection * the selection index */ public void setSelection(int selection) { this.selectedIndex = selection; } public void setSelection(String item) { for (int i = 0; i < items.length; i++) { if (items[i].equals(item)) { selectedIndex = i; } } } /** @return the index of the current selection. The top item has an index of 0 */ public int getSelectionIndex() { return selectedIndex; } /** @return the string of the currently selected item */ public String getSelection() { return items[selectedIndex]; } public float getPrefWidth() { return prefWidth; } public float getPrefHeight() { return prefHeight; } public void hideList() { if (list.getParent() == null) return; list.addAction(sequence(fadeOut(0.15f, Interpolation.fade), removeActor())); } class SelectList extends Actor { Vector2 oldScreenCoords = new Vector2(); float itemHeight; float textOffsetX, textOffsetY; int listSelectedIndex = SelectBox.this.selectedIndex; InputListener stageListener = new InputListener() { public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { if (pointer == 0 && button != 0) return false; stageToLocalCoordinates(Vector2.tmp); x = Vector2.tmp.x; y = Vector2.tmp.y; if (x > 0 && x < getWidth() && y > 0 && y < getHeight()) { listSelectedIndex = (int) ((getHeight() - y) / itemHeight); listSelectedIndex = Math.max(0, listSelectedIndex); listSelectedIndex = Math.min(items.length - 1, listSelectedIndex); selectedIndex = listSelectedIndex; if (items.length > 0) { ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class); SelectBox.this.fire(changeEvent); Pools.free(changeEvent); } } return true; } public void touchUp(InputEvent event, float x, float y, int pointer, int button) { hideList(); event.getStage().removeCaptureListener(stageListener); } public boolean mouseMoved(InputEvent event, float x, float y) { stageToLocalCoordinates(Vector2.tmp); x = Vector2.tmp.x; y = Vector2.tmp.y; if (x > 0 && x < getWidth() && y > 0 && y < getHeight()) { listSelectedIndex = (int) ((getHeight() - style.listBackground.getTopHeight() - y) / itemHeight); listSelectedIndex = Math.max(0, listSelectedIndex); listSelectedIndex = Math.min(items.length - 1, listSelectedIndex); } return true; } }; public SelectList(float x, float y) { setBounds(x, 0, SelectBox.this.getWidth(), 100); this.oldScreenCoords.set(screenCoords); layout(); Stage stage = SelectBox.this.getStage(); float height = getHeight(); if (y - height < 0 && y + SelectBox.this.getHeight() + height < stage.getCamera().viewportHeight) setY(y + SelectBox.this.getHeight()); else setY(y - height); stage.addCaptureListener(stageListener); getColor().a = 0; addAction(fadeIn(0.3f, Interpolation.fade)); } private void layout() { final BitmapFont font = style.font; final Drawable listSelection = style.listSelection; final Drawable listBackground = style.listBackground; itemHeight = font.getCapHeight() + -font.getDescent() * 2 + style.itemSpacing; itemHeight += listSelection.getTopHeight() + listSelection.getBottomHeight(); textOffsetX = listSelection.getLeftWidth() + style.itemSpacing; textOffsetY = listSelection.getTopHeight() + -font.getDescent() + style.itemSpacing / 2; setWidth(SelectBox.this.getWidth()); setHeight(items.length * itemHeight + listBackground.getTopHeight() + listBackground.getBottomHeight()); } @Override public void draw(SpriteBatch batch, float parentAlpha) { final Drawable listBackground = style.listBackground; final Drawable listSelection = style.listSelection; final BitmapFont font = style.font; final Color fontColor = style.fontColor; float x = getX(); float y = getY(); float width = getWidth(); float height = getHeight(); Color color = getColor(); batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); listBackground.draw(batch, x, y, width, height); width -= listBackground.getLeftWidth() + listBackground.getRightWidth(); x += listBackground.getLeftWidth(); float posY = height - listBackground.getTopHeight(); for (int i = 0; i < items.length; i++) { if (listSelectedIndex == i) { listSelection.draw(batch, x, y + posY - itemHeight, width, itemHeight); } font.setColor(fontColor.r, fontColor.g, fontColor.b, color.a * fontColor.a * parentAlpha); font.draw(batch, items[i], x + textOffsetX, y + posY - textOffsetY); posY -= itemHeight; } } @Override public Actor hit(float x, float y, boolean touchable) { return this; } public void act(float delta) { super.act(delta); if (screenCoords.x != oldScreenCoords.x || screenCoords.y != oldScreenCoords.y) hideList(); } } /** * The style for a select box, see {@link SelectBox}. * * @author mzechner * @author Nathan Sweet */ static public class SelectBoxStyle { public Drawable background; /** Optional. */ public Drawable backgroundOver, backgroundOpen; public Drawable listBackground; public Drawable listSelection; public BitmapFont font; public Color fontColor = new Color(1, 1, 1, 1); public float itemSpacing = 10; public SelectBoxStyle() { } public SelectBoxStyle(BitmapFont font, Color fontColor, Drawable background, Drawable listBackground, Drawable listSelection) { this.background = background; this.listBackground = listBackground; this.listSelection = listSelection; this.font = font; this.fontColor.set(fontColor); } public SelectBoxStyle(SelectBoxStyle style) { this.background = style.background; this.listBackground = style.listBackground; this.listSelection = style.listSelection; this.font = style.font; this.fontColor.set(style.fontColor); } } }