All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kotcrab.vis.ui.widget.PopupMenu Maven / Gradle / Ivy

There is a newer version: 1.5.3
Show newest version
/*
 * Copyright 2014-2017 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.kotcrab.vis.ui.widget;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.SnapshotArray;
import com.kotcrab.vis.ui.Sizes;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.util.ActorUtils;

/**
 * Standard popup menu that can be displayed anywhere on stage. Menu is automatically removed when user clicked outside menu,
 * or clicked menu item. For proper behaviour menu should be displayed in touchUp event. If you want to display
 * menu from touchDown you have to call event.stop() otherwise menu will by immediately closed.
 * 

* If you want to add right click menu to actor you can use getDefaultInputListener() to get premade default listener. *

* Since 1.0.2 arrow keys can be used to navigate menu hierarchy. * @author Kotcrab */ public class PopupMenu extends Table { private static final Vector2 tmpVector = new Vector2(); private Sizes sizes; private PopupMenuStyle style; private PopupMenuListener listener; private InputListener stageListener; private InputListener sharedMenuItemInputListener; private ChangeListener sharedMenuItemChangeListener; private InputListener defaultInputListener; /** The parent sub-menu, that this popup menu belongs to or null if this sub menu is root */ private PopupMenu parentSubMenu; /** The current sub-menu, set by MenuItem */ private PopupMenu activeSubMenu; private MenuItem activeItem; public PopupMenu () { this("default"); } public PopupMenu (String styleName) { this(VisUI.getSkin().get(styleName, PopupMenuStyle.class)); } public PopupMenu (PopupMenuStyle style) { this(VisUI.getSizes(), style); } public PopupMenu (Sizes sizes, PopupMenuStyle style) { this.sizes = sizes; this.style = style; setTouchable(Touchable.enabled); pad(0); setBackground(style.background); createListeners(); } /** * Removes every instance of {@link PopupMenu} form {@link Stage} actors. *

* Generally called from {@link ApplicationListener#resize(int, int)} to remove menus on resize event. */ public static void removeEveryMenu (Stage stage) { for (Actor actor : stage.getActors()) { if (actor instanceof PopupMenu) { PopupMenu menu = (PopupMenu) actor; menu.removeHierarchy(); } } } private void createListeners () { stageListener = new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { if (getRootMenu().subMenuStructureContains(x, y) == false) { remove(); } return true; } @Override public boolean keyDown (InputEvent event, int keycode) { SnapshotArray children = getChildren(); if (children.size == 0 || activeSubMenu != null) return false; if (keycode == Input.Keys.DOWN) { selectNextItem(); return true; } if (keycode == Input.Keys.UP) { selectPreviousItem(); return true; } if (activeItem == null) return false; if (keycode == Input.Keys.LEFT && activeItem.containerMenu.parentSubMenu != null) { activeItem.containerMenu.parentSubMenu.setActiveSubMenu(null); return true; } if (keycode == Input.Keys.RIGHT && activeItem.getSubMenu() != null) { activeItem.showSubMenu(); activeSubMenu.selectNextItem(); return true; } if (keycode == Input.Keys.ENTER) { activeItem.fireChangeEvent(); return true; } return false; } }; sharedMenuItemInputListener = new InputListener() { @Override public void enter (InputEvent event, float x, float y, int pointer, Actor fromActor) { if (pointer == -1 && event.getListenerActor() instanceof MenuItem) { MenuItem item = (MenuItem) event.getListenerActor(); if (item.isDisabled() == false) { setActiveItem(item, false); } } } @Override public void exit (InputEvent event, float x, float y, int pointer, Actor toActor) { if (pointer == -1 && event.getListenerActor() instanceof MenuItem) { if (activeSubMenu != null) return; MenuItem item = (MenuItem) event.getListenerActor(); if (item == activeItem) { setActiveItem(null, false); } } } }; sharedMenuItemChangeListener = new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { if (event.isStopped() == false) removeHierarchy(); } }; } private PopupMenu getRootMenu () { if (parentSubMenu != null) return parentSubMenu.getRootMenu(); return this; } private boolean subMenuStructureContains (float x, float y) { if (contains(x, y)) return true; if (activeSubMenu != null) return activeSubMenu.subMenuStructureContains(x, y); return false; } private void removeHierarchy () { if (activeItem != null && activeItem.containerMenu.parentSubMenu != null) { activeItem.containerMenu.parentSubMenu.removeHierarchy(); } remove(); } private void selectNextItem () { SnapshotArray children = getChildren(); if (!hasSelectableMenuItems()) return; int startIndex = activeItem == null ? 0 : children.indexOf(activeItem, true) + 1; for (int i = startIndex; ; i++) { if (i >= children.size) i = 0; Actor actor = children.get(i); if (actor instanceof MenuItem && ((MenuItem) actor).isDisabled() == false) { setActiveItem((MenuItem) actor, true); break; } } } private void selectPreviousItem () { SnapshotArray children = getChildren(); if (!hasSelectableMenuItems()) return; int startIndex = activeItem == null ? children.size - 1 : children.indexOf(activeItem, true) - 1; for (int i = startIndex; ; i--) { if (i <= -1) i = children.size - 1; Actor actor = children.get(i); if (actor instanceof MenuItem && ((MenuItem) actor).isDisabled() == false) { setActiveItem((MenuItem) actor, true); break; } } } private boolean hasSelectableMenuItems () { SnapshotArray children = getChildren(); for (Actor actor : children) { if (actor instanceof MenuItem && ((MenuItem) actor).isDisabled() == false) return true; } return false; } @Override public Cell add (T actor) { if (actor instanceof MenuItem) { throw new IllegalArgumentException("MenuItems can be only added to PopupMenu by using addItem(MenuItem) method"); } return super.add(actor); } public void addItem (MenuItem item) { super.add(item).fillX().expandX().row(); pack(); item.addListener(sharedMenuItemChangeListener); item.addListener(sharedMenuItemInputListener); } public void addSeparator () { add(new Separator("menu")).padTop(2).padBottom(2).fill().expand().row(); } /** * Returns input listener that can be added to scene2d actor. When right mouse button is pressed on that actor, * menu will be displayed */ public InputListener getDefaultInputListener () { return getDefaultInputListener(Buttons.RIGHT); } /** * Returns input listener that can be added to scene2d actor. When mouse button is pressed on that actor, * menu will be displayed * @param mouseButton from {@link Buttons} */ public InputListener getDefaultInputListener (final int mouseButton) { if (defaultInputListener == null) { defaultInputListener = new InputListener() { @Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { return true; } @Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { if (event.getButton() == mouseButton) showMenu(event.getStage(), event.getStageX(), event.getStageY()); } }; } return defaultInputListener; } @Override public void draw (Batch batch, float parentAlpha) { super.draw(batch, parentAlpha); if (style.border != null) style.border.draw(batch, getX(), getY(), getWidth(), getHeight()); } /** * Shows menu as given stage coordinates * @param stage stage instance that this menu is being added to * @param x stage x position * @param y stage y position */ public void showMenu (Stage stage, float x, float y) { setPosition(x, y - getHeight()); if (stage.getHeight() - getY() > stage.getHeight()) setY(getY() + getHeight()); ActorUtils.keepWithinStage(stage, this); stage.addActor(this); } /** * Shows menu below (or above if not enough space) given actor. * @param stage stage instance that this menu is being added to * @param actor used to get calculate menu position in stage, menu will be displayed above or below it */ public void showMenu (Stage stage, Actor actor) { Vector2 pos = actor.localToStageCoordinates(tmpVector.setZero()); float menuY; if (pos.y - getHeight() <= 0) { menuY = pos.y + actor.getHeight() + getHeight() - sizes.borderSize; } else { menuY = pos.y + sizes.borderSize; } showMenu(stage, pos.x, menuY); } public boolean contains (float x, float y) { return getX() < x && getX() + getWidth() > x && getY() < y && getY() + getHeight() > y; } /** Called by framework, when PopupMenu is added to MenuItem as submenu */ void setActiveSubMenu (PopupMenu newSubMenu) { if (activeSubMenu == newSubMenu) return; if (activeSubMenu != null) activeSubMenu.remove(); activeSubMenu = newSubMenu; if (newSubMenu != null) { newSubMenu.setParentMenu(this); } } @Override protected void setStage (Stage stage) { super.setStage(stage); if (stage != null) stage.addListener(stageListener); } @Override public boolean remove () { if (getStage() != null) getStage().removeListener(stageListener); if (activeSubMenu != null) activeSubMenu.remove(); setActiveItem(null, false); parentSubMenu = null; activeSubMenu = null; return super.remove(); } void setActiveItem (MenuItem newItem, boolean keyboardChange) { activeItem = newItem; if (listener != null) listener.activeItemChanged(newItem, keyboardChange); } public MenuItem getActiveItem () { return activeItem; } void setParentMenu (PopupMenu parentSubMenu) { this.parentSubMenu = parentSubMenu; } public PopupMenuListener getListener () { return listener; } public void setListener (PopupMenuListener listener) { this.listener = listener; } /** * Listener used to get events from {@link PopupMenu}. * @since 1.0.2 */ public interface PopupMenuListener { /** * Called when active menu item (the highlighted one) has changed. This can't be used to listen when * {@link MenuItem} was pressed, add {@link ChangeListener} to {@link MenuItem} directly to achieve this. * @param newActiveItem new item that is now active. May be null. * @param changedByKeyboard whether the change occurred by keyboard (arrows keys) or by mouse. */ void activeItemChanged (MenuItem newActiveItem, boolean changedByKeyboard); } static public class PopupMenuStyle { public Drawable background; public Drawable border; public PopupMenuStyle () { } public PopupMenuStyle (Drawable background, Drawable border) { this.background = background; this.border = border; } public PopupMenuStyle (PopupMenuStyle style) { this.background = style.background; this.border = style.border; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy