/* * ****************************************************************************** * * Copyright 2015 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.uwsoft.editor.view.stage; import com.badlogic.ashley.core.Entity; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.SnapshotArray; import com.commons.MsgAPI; import com.uwsoft.editor.Overlap2DFacade; import com.uwsoft.editor.renderer.components.NodeComponent; import com.uwsoft.editor.renderer.data.LayerItemVO; import com.uwsoft.editor.renderer.utils.ComponentRetriever; import com.uwsoft.editor.utils.Constants; import com.uwsoft.editor.utils.EntityBounds; import com.uwsoft.editor.utils.MoveCommandBuilder; import com.uwsoft.editor.utils.runtime.EntityUtils; import com.uwsoft.editor.view.SceneControlMediator; import com.uwsoft.editor.view.ui.FollowersUIMediator; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; /** * Managing item selections, selecting by criteria and so on * * @author azakhary */ public class ItemSelector { /** commands reference */ private Sandbox sandbox; private SceneControlMediator sceneControl; /** list of current selected panels */ private Set<Entity> currentSelection = new HashSet<>(); private FollowersUIMediator followersUIMediator; private MoveCommandBuilder moveCommandBuilder = new MoveCommandBuilder(); public ItemSelector(Sandbox sandbox) { this.sandbox = sandbox; sceneControl = sandbox.sceneControl; followersUIMediator = Overlap2DFacade.getInstance().retrieveMediator(FollowersUIMediator.NAME); } /***************************** Getters *********************************/ /** * @return HashMap of selection rectangles that contain panels */ public Set<Entity> getCurrentSelection() { return currentSelection; } /** * @return one selected item */ public Entity getSelectedItem() { if(currentSelection.size() > 0) { return currentSelection.iterator().next(); } return null; } /** public SelectionRectangle getSelectedItemSelectionRectangle() { ArrayList<SelectionRectangle> items = new ArrayList<SelectionRectangle>(); for (SelectionRectangle value : currentSelection.values()) { items.add(value); break; } if(items.size() > 0) { return items.get(0); } return null; } */ /** * @return list of currently selected panels */ public Set<Entity> getSelectedItems() { return currentSelection; } public BiConsumer<Entity, AccContainer> broadestItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MIN; EntityBounds bounds = new EntityBounds(i); final float width = bounds.getVisualWidth(); if (width > acc.carryVal) { acc.carryVal = width; acc.carry = i; } }; public BiConsumer<Entity, AccContainer> highestItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MIN; EntityBounds bounds = new EntityBounds(i); final float height = bounds.getVisualHeight(); if (height > acc.carryVal) { acc.carryVal = height; acc.carry = i; } }; public BiConsumer<Entity, AccContainer> rightmostItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MIN; EntityBounds bounds = new EntityBounds(i); final float x = bounds.getVisualRightX(); if (x > acc.carryVal) { acc.carryVal = x; acc.carry = i; } System.out.println("MaxFloat = " + Float.MAX_VALUE + " MinFloat = " + Float.MIN_VALUE); }; public BiConsumer<Entity, AccContainer> leftmostItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MAX; EntityBounds bounds = new EntityBounds(i); final float x = bounds.getVisualX(); if (x < acc.carryVal) { acc.carryVal = x; acc.carry = i; } }; public BiConsumer<Entity, AccContainer> topmostItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MIN; EntityBounds bounds = new EntityBounds(i); final float y = bounds.getVisualTopY(); if (y > acc.carryVal) { acc.carryVal = y; acc.carry = i; } }; public BiConsumer<Entity, AccContainer> bottommostItem = (i, acc) -> { if (acc.carryVal == null) acc.carryVal = Constants.FLOAT_MAX; EntityBounds bounds = new EntityBounds(i); final float y = bounds.getVisualY(); if (y < acc.carryVal) { acc.carryVal = y; acc.carry = i; } }; /** * used as accumulator container */ private static class AccContainer { public Float carryVal = null; public Entity carry = null; } public Entity get(BiConsumer<Entity, AccContainer> checkSelection) { final AccContainer acc = new AccContainer(); for (Entity entity : currentSelection) { checkSelection.accept(entity, acc); } return acc.carry; } /** * Finds all panels that are on particular layer and selects them * @param name of the layer */ public void selectItemsByLayerName(String name) { //TODO fix and uncomment // ArrayList<Entity> itemsArr = new ArrayList<Entity>(); // for (int i = 0; i < sceneControl.getCurrentScene().getItems().size(); i++) { // if (sceneControl.getCurrentScene().getItems().get(i).getDataVO().layerName.equals(name)) { // itemsArr.add(sceneControl.getCurrentScene().getItems().get(i)); // } // } // // setSelections(itemsArr, true); } /** * sets selection to particular item * @param item to select * @param removeOthers if set to true this item will become the only selection, otherwise will be added to existing */ public void setSelection(Entity item, boolean removeOthers) { if (currentSelection.contains(item)) return; if (removeOthers) clearSelections(); currentSelection.add(item); Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_SELECTION_CHANGED, currentSelection); } /** * adds to selection a list of items * @param items list of panels to select */ public void addSelections(Set<Entity> items) { for (Entity item : items) { setSelection(item, false); } } public boolean isSelected(Entity entity) { return currentSelection.contains(entity); } /** * set selection to a list of items * @param items list of panels to select * @param alsoShow if false, selection will remain hidden at this moment */ public void setSelections(Set<Entity> items, boolean alsoShow) { currentSelection.clear(); if(items == null) { Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_SELECTION_CHANGED, currentSelection); return; } currentSelection.addAll(items.stream().collect(Collectors.toList())); if (alsoShow) { Overlap2DFacade.getInstance().sendNotification(MsgAPI.SHOW_SELECTIONS, currentSelection); } else { Overlap2DFacade.getInstance().sendNotification(MsgAPI.HIDE_SELECTIONS, currentSelection); } Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_SELECTION_CHANGED, currentSelection); } /** * remove selection to a list of items * @param items list of panels to remove selection */ public void releaseSelections(Set<Entity> items) { for (Entity item : items) { releaseSelection(item); } } /** * Un-selects item * @param item to un-select */ public void releaseSelection(Entity item) { currentSelection.remove(item); Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_SELECTION_CHANGED, currentSelection); } /** * clears all selections */ public void clearSelections() { currentSelection.clear(); Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_SELECTION_CHANGED, currentSelection); } /** * Selects all panels on currently active scene */ public HashSet<Entity> getAllFreeItems() { NodeComponent nodeComponent = ComponentRetriever.get(sandbox.getCurrentViewingEntity(), NodeComponent.class); SnapshotArray<Entity> childrenEntities = nodeComponent.children; Entity[] array = childrenEntities.toArray(); HashSet<Entity> result = new HashSet<>(Arrays.asList(array)); for (Iterator<Entity> i = result.iterator(); i.hasNext();) { Entity element = i.next(); LayerItemVO layerItemVO = EntityUtils.getEntityLayer(element); if(layerItemVO != null && layerItemVO.isLocked) { i.remove(); } } return result; } /************************ Manipulate selected panels ******************************/ /** * removes all selected panels from the scene */ public void removeCurrentSelectedItems() { for (Entity item : currentSelection) { followersUIMediator.removeFollower(item); sandbox.getEngine().removeEntity(item); } currentSelection.clear(); } public void alignSelectionsByX(Entity relativeTo, boolean toHighestX) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToX = (toHighestX)? (bounds.getVisualRightX()) : bounds.getVisualX(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { EntityBounds entityBounds = new EntityBounds(entity); final float deltaX = entityBounds.getX() - entityBounds.getVisualX(); final float visualX = relativeToX - ((toHighestX)? 1 : 0) * entityBounds.getVisualWidth(); moveCommandBuilder.setX(entity, visualX + deltaX); } moveCommandBuilder.execute(); } public void alignSelectionsByY(Entity relativeTo, boolean toHighestY) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToY = (toHighestY)? bounds.getVisualTopY() : bounds.getVisualY(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { EntityBounds entityBounds = new EntityBounds(entity); final float deltaY = entityBounds.getY() - entityBounds.getVisualY(); final float visualY = relativeToY - ((toHighestY)? 1 : 0) * entityBounds.getVisualHeight(); moveCommandBuilder.setY(entity, visualY + deltaY); } moveCommandBuilder.execute(); } public void alignSelectionsAtLeftEdge(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToX = bounds.getVisualX(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaX = entityBounds.getX() - entityBounds.getVisualX(); final float visualX = relativeToX - entityBounds.getVisualWidth(); moveCommandBuilder.setX(entity, visualX + deltaX); } moveCommandBuilder.execute(); } public void alignSelectionsAtRightEdge(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToRightX = bounds.getVisualRightX(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaX = entityBounds.getX() - entityBounds.getVisualX(); moveCommandBuilder.setX(entity, relativeToRightX + deltaX); } moveCommandBuilder.execute(); } public void alignSelectionsAtTopEdge(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToTopY = bounds.getVisualTopY(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaY = entityBounds.getY() - entityBounds.getVisualY(); moveCommandBuilder.setY(entity, relativeToTopY + deltaY); } moveCommandBuilder.execute(); } public void alignSelectionsAtBottomEdge(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToY = bounds.getVisualY(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaY = entityBounds.getY() - entityBounds.getVisualY(); final float visualY = relativeToY - entityBounds.getVisualHeight(); moveCommandBuilder.setY(entity, visualY + deltaY); } moveCommandBuilder.execute(); } public void alignSelectionsVerticallyCentered(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToY = bounds.getVisualY(); final float relativeToHeight = bounds.getVisualHeight(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaY = entityBounds.getY() - entityBounds.getVisualY(); final float visualY = relativeToY + (relativeToHeight - entityBounds.getVisualHeight()) / 2; moveCommandBuilder.setY(entity, visualY + deltaY); } moveCommandBuilder.execute(); } public void alignSelectionsHorizontallyCentered(Entity relativeTo) { //TODO fix and uncomment if (relativeTo == null) return; EntityBounds bounds = new EntityBounds(relativeTo); final float relativeToX = bounds.getVisualX(); final float relativeToWidth = bounds.getVisualWidth(); moveCommandBuilder.clear(); for (Entity entity : currentSelection) { if (entity == relativeTo) continue; EntityBounds entityBounds = new EntityBounds(entity); final float deltaX = entityBounds.getX() - entityBounds.getVisualX(); final float visualX = relativeToX + (relativeToWidth - entityBounds.getVisualWidth()) / 2; moveCommandBuilder.setX(entity, visualX + deltaX); } moveCommandBuilder.execute(); } public void alignSelections(int align) { //ResolutionEntryVO resolutionEntryVO = dataManager.getCurrentProjectInfoVO().getResolution(dataManager.currentResolutionName); switch (align) { case Align.top: alignSelectionsByY(get(topmostItem), true); break; case Align.left: alignSelectionsByX(get(leftmostItem), false); break; case Align.bottom: alignSelectionsByY(get(bottommostItem), false); break; case Align.right: alignSelectionsByX(get(rightmostItem), true); break; case Align.center | Align.left: //horizontal alignSelectionsHorizontallyCentered(get(broadestItem)); break; case Align.center | Align.bottom: //vertical alignSelectionsVerticallyCentered(get(highestItem)); break; } } public void alignSelectionsAtEdge(int align) { switch (align) { case Align.top: alignSelectionsAtTopEdge(get(bottommostItem)); break; case Align.left: alignSelectionsAtLeftEdge(get(rightmostItem)); break; case Align.bottom: alignSelectionsAtBottomEdge(get(topmostItem)); break; case Align.right: alignSelectionsAtRightEdge(get(leftmostItem)); break; } } /** * Moves selected panels by specified values in both directions * @param x * @param y */ public void moveSelectedItemsBy(float x, float y) { for (Entity entity : currentSelection) { sandbox.itemControl.moveItemBy(entity, x, y); Overlap2DFacade.getInstance().sendNotification(MsgAPI.ITEM_DATA_UPDATED, entity); } sandbox.saveSceneCurrentSceneData(); } public boolean selectionIsOneItem() { return getCurrentSelection().size() == 1; } public boolean selectionIsComposite() { if(currentSelection.isEmpty()) return false; Entity entity = currentSelection.stream().findFirst().get(); NodeComponent nodeComponent = entity.getComponent(NodeComponent.class); return nodeComponent != null; } }