393 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package electrosphere.renderer.ui;
 | |
| 
 | |
| import java.util.LinkedList;
 | |
| import java.util.List;
 | |
| import java.util.ListIterator;
 | |
| import java.util.Map;
 | |
| import java.util.Stack;
 | |
| import java.util.concurrent.ConcurrentHashMap;
 | |
| import java.util.concurrent.CopyOnWriteArrayList;
 | |
| 
 | |
| import electrosphere.renderer.ui.elements.Window;
 | |
| import electrosphere.renderer.ui.elementtypes.ContainerElement;
 | |
| import electrosphere.renderer.ui.elementtypes.DraggableElement;
 | |
| import electrosphere.renderer.ui.elementtypes.Element;
 | |
| import electrosphere.renderer.ui.elementtypes.FocusableElement;
 | |
| import electrosphere.renderer.ui.elementtypes.HoverableElement;
 | |
| import electrosphere.renderer.ui.events.ClickEvent;
 | |
| import electrosphere.renderer.ui.events.DragEvent;
 | |
| import electrosphere.renderer.ui.events.Event;
 | |
| import electrosphere.renderer.ui.events.FocusEvent;
 | |
| import electrosphere.renderer.ui.events.HoverEvent;
 | |
| import electrosphere.renderer.ui.events.NavigationEvent;
 | |
| import electrosphere.renderer.ui.events.DragEvent.DragEventType;
 | |
| import electrosphere.renderer.ui.events.NavigationEvent.NavigationEventType;
 | |
| 
 | |
| /**
 | |
|  * The main interface for working with the ui system
 | |
|  */
 | |
| public class ElementManager {
 | |
|     
 | |
|     
 | |
|     Map<String,Element> elementMap = new ConcurrentHashMap<String,Element>();
 | |
|     List<Element> elementList = new CopyOnWriteArrayList<Element>();
 | |
|     FocusableElement currentFocusedElement = null;
 | |
|     DraggableElement currentDragElement = null;
 | |
| 
 | |
|     // the element currently hovered over
 | |
|     HoverableElement currentHoveredElement = null;
 | |
|     
 | |
|     /**
 | |
|      * Registers a window
 | |
|      * @param name the name associated with the window
 | |
|      * @param w The window element
 | |
|      */
 | |
|     public void registerWindow(String name, Element w){
 | |
|         elementMap.put(name,w);
 | |
|         if(!elementList.contains(w)){
 | |
|             elementList.add(w);
 | |
|         }
 | |
|         if(elementList.size() == 1){
 | |
|             focusFirstElement();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets a window element by the name associated with it
 | |
|      * @param name The associated name
 | |
|      * @return The window element if it exists, null otherwise
 | |
|      */
 | |
|     public Element getWindow(String name){
 | |
|         return elementMap.get(name);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the list of all registered windows
 | |
|      * @return The list of all registered windows
 | |
|      */
 | |
|     public List<Element> getWindowList(){
 | |
|         return elementList;
 | |
|     }
 | |
|     
 | |
|     public void unregisterWindow(String name){
 | |
|         Element w = elementMap.remove(name);
 | |
|         if(elementList.contains(w)){
 | |
|             elementList.remove(w);
 | |
|         }
 | |
|         if(elementList.size() > 0){
 | |
|             focusFirstElement();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public boolean containsWindow(String name){
 | |
|         return elementMap.containsKey(name);
 | |
|     }
 | |
| 
 | |
|     public void pushWindowToFront(Window window){
 | |
|         elementList.remove(window);
 | |
|         elementList.add(window);
 | |
|     }
 | |
| 
 | |
|     List<FocusableElement> getFocusableList(Element topLevel, List<FocusableElement> input){
 | |
|         if(topLevel instanceof FocusableElement){
 | |
|             input.add((FocusableElement)topLevel);
 | |
|         }
 | |
|         if(topLevel instanceof ContainerElement){
 | |
|             ContainerElement container = (ContainerElement) topLevel;
 | |
|             for(Element child : container.getChildren()){
 | |
|                 getFocusableList(child,input);
 | |
|             }
 | |
|         }
 | |
|         return input;
 | |
|     }
 | |
| 
 | |
|     public FocusableElement getFocusedElement(){
 | |
|         return currentFocusedElement;
 | |
|     }
 | |
| 
 | |
|     public void focusFirstElement(){
 | |
|         List<FocusableElement> focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList<FocusableElement>());
 | |
|         if(focusables.size() > 0){
 | |
|             if(currentFocusedElement != null){
 | |
|                 currentFocusedElement.handleEvent(new FocusEvent(false));
 | |
|             }
 | |
|             currentFocusedElement = focusables.get(0);
 | |
|             currentFocusedElement.handleEvent(new FocusEvent(true));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void focusNextElement(){
 | |
|         List<FocusableElement> focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList<FocusableElement>());
 | |
|         if(focusables.contains(currentFocusedElement)){
 | |
|             int index = focusables.indexOf(currentFocusedElement);
 | |
|             if(index + 1 >= focusables.size()){
 | |
|                 index = 0;
 | |
|             } else {
 | |
|                 index++;
 | |
|             }
 | |
|             if(currentFocusedElement != null){
 | |
|                 currentFocusedElement.handleEvent(new FocusEvent(false));
 | |
|             }
 | |
|             currentFocusedElement = focusables.get(index);
 | |
|             currentFocusedElement.handleEvent(new FocusEvent(true));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public void focusPreviousElement(){
 | |
|         List<FocusableElement> focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList<FocusableElement>());
 | |
|         if(focusables.contains(currentFocusedElement)){
 | |
|             int index = focusables.indexOf(currentFocusedElement);
 | |
|             if(index - 1 < 0){
 | |
|                 index = focusables.size() - 1;
 | |
|             } else {
 | |
|                 index--;
 | |
|             }
 | |
|             if(currentFocusedElement != null){
 | |
|                 currentFocusedElement.handleEvent(new FocusEvent(false));
 | |
|             }
 | |
|             currentFocusedElement = focusables.get(index);
 | |
|             currentFocusedElement.handleEvent(new FocusEvent(true));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets a specific element to be focused
 | |
|      * @param focusableElement The focusable element
 | |
|      */
 | |
|     public void focusElement(FocusableElement focusableElement){
 | |
|         if(currentFocusedElement != null){
 | |
|             currentFocusedElement.handleEvent(new FocusEvent(false));
 | |
|         }
 | |
|         this.currentFocusedElement = focusableElement;
 | |
|         focusableElement.handleEvent(new FocusEvent(true));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fires an event at a given position
 | |
|      * @param event The event
 | |
|      * @param x the x coordinate of the position
 | |
|      * @param y the y coordinate of the position
 | |
|      */
 | |
|     public void fireEvent(Event event, int x, int y){
 | |
|         boolean propagate = true;
 | |
|         ListIterator<Element> windowIterator = elementList.listIterator(elementList.size());
 | |
|         while(windowIterator.hasPrevious()){
 | |
|             Element currentWindow = windowIterator.previous();
 | |
|             Stack<Element> elementStack = buildElementPositionalStack(new Stack<Element>(), currentWindow, x, y, 0, 0);
 | |
|             Element currentElement = null;
 | |
|             while(elementStack.size() > 0 && propagate == true){
 | |
|                 currentElement = elementStack.pop();
 | |
|                 propagate = currentElement.handleEvent(event);
 | |
|             }
 | |
|             if(!propagate){
 | |
|                 currentWindow.applyYoga();
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Fires an event that does not have a screen position on a given element. Event propagation works by element ancestry regardless of position.
 | |
|      * @param event The event
 | |
|      * @param el The element to handle the event
 | |
|      */
 | |
|     public void fireEventNoPosition(Event event, Element el){
 | |
|         List<Element> ancestryList = constructNonPositionalAncestryList(el);
 | |
|         boolean propagate = true;
 | |
|         while(ancestryList.size() > 0 && propagate == true){
 | |
|             Element currentElement = ancestryList.remove(0);
 | |
|             propagate = currentElement.handleEvent(event);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Constructs a list of elements starting with el and containing all its ancestor elements, parent, grandparent, etc
 | |
|      * @param el The target element
 | |
|      * @return The ancestry list
 | |
|      */
 | |
|     private List<Element> constructNonPositionalAncestryList(Element el){
 | |
|         //a list of elements with 0 being the target of the event, 1 being the parent of the target, 2 being the parent of 1, etc
 | |
|         List<Element> elementPropagation = new LinkedList<Element>();
 | |
|         elementPropagation.add(el);
 | |
|         Element parentElement = null;
 | |
|         Element targetElement = el;
 | |
|         while(true){
 | |
|             for(Element window : this.getWindowList()){
 | |
|                 if(window instanceof ContainerElement){
 | |
|                     parentElement = recursivelySearchParent((ContainerElement)window,targetElement);
 | |
|                 }
 | |
|             }
 | |
|             if(parentElement == null){
 | |
|                 break;
 | |
|             } else {
 | |
|                 elementPropagation.add(parentElement);
 | |
|                 targetElement = parentElement;
 | |
|                 parentElement = null;
 | |
|             }
 | |
|         }
 | |
|         return elementPropagation;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Recursively searched for a target element starting at the searchRoot
 | |
|      * @param searchRoot The root element to search from propagating down the tree
 | |
|      * @param target The target to search for
 | |
|      * @return The parent of target if it exists, null otherwise
 | |
|      */
 | |
|     public Element recursivelySearchParent(ContainerElement searchRoot, Element target){
 | |
|         if(searchRoot instanceof ContainerElement){
 | |
|             for(Element child : searchRoot.getChildren()){
 | |
|                 if(child == target){
 | |
|                     return searchRoot;
 | |
|                 } else if(child instanceof ContainerElement){
 | |
|                     Element result = recursivelySearchParent((ContainerElement)child, target);
 | |
|                     if(result != null){
 | |
|                         return result;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Recursively uilds a stack of elements at a given position based on their depth into the tree
 | |
|      * @param inputStack The empty stack to fill
 | |
|      * @param current The current element
 | |
|      * @param x the x position to query
 | |
|      * @param y the y position to query
 | |
|      * @param offsetX the x offset accumulated
 | |
|      * @param offsetY the y offset accumulated
 | |
|      * @return the stack, filled with all relevant elements
 | |
|      */
 | |
|     Stack<Element> buildElementPositionalStack(Stack<Element> inputStack, Element current, int x, int y, int offsetX, int offsetY){
 | |
|         inputStack.push(current);
 | |
|         if(current instanceof ContainerElement){
 | |
|             ContainerElement container = (ContainerElement)current;
 | |
|             int xLoc = x - container.getInternalX() - container.getChildOffsetX();
 | |
|             int yLoc = y - container.getInternalY() - container.getChildOffsetY();
 | |
|             for(Element el : ((ContainerElement)current).getChildren()){
 | |
|                 //if contains x,y, call function on el
 | |
|                 if(elementContainsPoint(el,xLoc,yLoc,offsetX,offsetY)){
 | |
|                     buildElementPositionalStack(inputStack, el, xLoc, yLoc, offsetX, offsetY);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return inputStack;
 | |
|     }
 | |
| 
 | |
|     public Element resolveFirstDraggable(DragEvent event){
 | |
|         ListIterator<Element> windowIterator = elementList.listIterator(elementList.size());
 | |
|         while(windowIterator.hasPrevious()){
 | |
|             Element currentWindow = windowIterator.previous();
 | |
|             Stack<Element> elementStack = buildElementPositionalStack(new Stack<Element>(), currentWindow, event.getCurrentX(), event.getCurrentY(), 0, 0);
 | |
|             Element currentElement = null;
 | |
|             while(elementStack.size() > 0){
 | |
|                 currentElement = elementStack.pop();
 | |
|                 if(currentElement instanceof DraggableElement){
 | |
|                     return currentElement;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Gets the first hoverable element in the view stack
 | |
|      * @param currentX the current x position of the mouse
 | |
|      * @param currentY the current y position of the mouse
 | |
|      * @return The first hoverable element if it exists, null otherwise
 | |
|      */
 | |
|     public Element resolveFirstHoverable(int currentX, int currentY){
 | |
|         ListIterator<Element> windowIterator = elementList.listIterator(elementList.size());
 | |
|         while(windowIterator.hasPrevious()){
 | |
|             Element currentWindow = windowIterator.previous();
 | |
|             Stack<Element> elementStack = buildElementPositionalStack(new Stack<Element>(), currentWindow, currentX, currentY, 0, 0);
 | |
|             Element currentElement = null;
 | |
|             while(elementStack.size() > 0){
 | |
|                 currentElement = elementStack.pop();
 | |
|                 if(currentElement instanceof HoverableElement){
 | |
|                     return currentElement;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Checks if an element contains a given screen coordinate
 | |
|      * @param el The element
 | |
|      * @param x the x component of the coordinate
 | |
|      * @param y the y component of the coordinate
 | |
|      * @return True if it contains that point, false otherwise
 | |
|      */
 | |
|     boolean elementContainsPoint(Element el, int x, int y, int offsetX, int offsetY){
 | |
|         return
 | |
|         x >= el.getInternalX() + offsetX &&
 | |
|         x <= el.getInternalX() + offsetX + el.getInternalWidth() &&
 | |
|         y >= el.getInternalY() + offsetY &&
 | |
|         y <= el.getInternalY() + offsetY + el.getInternalHeight();
 | |
|     }
 | |
| 
 | |
|     // public void click(MouseEvent event){
 | |
|     //     if(currentFocusedElement instanceof ClickableElement){
 | |
|     //         ((ClickableElement)currentFocusedElement).onClick();
 | |
|     //     }
 | |
|     // }
 | |
| 
 | |
|     public void click(ClickEvent event){
 | |
|         fireEvent(event,event.getCurrentX(),event.getCurrentY());
 | |
|     }
 | |
| 
 | |
|     public void dragStart(int x, int y, int lastX, int lastY, int deltaX, int deltaY){
 | |
|         DragEvent event = new DragEvent(x, y, lastX, lastY, deltaX, deltaY, DragEventType.START, null);
 | |
|         currentDragElement = (DraggableElement)resolveFirstDraggable(event);
 | |
|         event.setTarget(currentDragElement);
 | |
|         fireEvent(event,x,y);
 | |
|     }
 | |
| 
 | |
|     public void drag(int x, int y, int lastX, int lastY, int deltaX, int deltaY){
 | |
|         DragEvent event = new DragEvent(x, y, lastX, lastY, deltaX, deltaY, DragEventType.DRAG, currentDragElement);
 | |
|         if(currentDragElement != null){
 | |
|             currentDragElement.handleEvent(event);
 | |
|         }
 | |
|         // fireEvent(event,event.getCurrentX(),event.getCurrentY());
 | |
|     }
 | |
| 
 | |
|     public void dragRelease(int x, int y, int lastX, int lastY, int deltaX, int deltaY){
 | |
|         DragEvent event = new DragEvent(x, y, lastX, lastY, deltaX, deltaY, DragEventType.RELEASE, currentDragElement);
 | |
|         if(currentDragElement != null){
 | |
|             currentDragElement.handleEvent(event);
 | |
|             currentDragElement = null;
 | |
|         }
 | |
|         fireEvent(event,event.getCurrentX(),event.getCurrentY());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Navigates backwards
 | |
|      */
 | |
|     public void navigateBackwards(){
 | |
|         NavigationEvent event = new NavigationEvent(NavigationEventType.BACKWARD);
 | |
|         fireEvent(event,currentFocusedElement.getPositionX(),currentFocusedElement.getPositionY());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Updates the hover state
 | |
|      * @param currentX The current mouse X
 | |
|      * @param currentY The current mouse Y
 | |
|      */
 | |
|     public void updateHover(int currentX, int currentY){
 | |
|         Element newHoverableElement = resolveFirstHoverable(currentX,currentY);
 | |
|         if(currentHoveredElement != newHoverableElement){
 | |
|             if(currentHoveredElement != null){
 | |
|                 currentHoveredElement.handleEvent(new HoverEvent(false));
 | |
|             }
 | |
|             if(newHoverableElement != null){
 | |
|                 newHoverableElement.handleEvent(new HoverEvent(true));
 | |
|             }
 | |
|             currentHoveredElement = (HoverableElement)newHoverableElement;
 | |
|         }
 | |
|     }
 | |
|     
 | |
| }
 |