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 elementMap = new ConcurrentHashMap(); List elementList = new CopyOnWriteArrayList(); 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 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 getFocusableList(Element topLevel, List 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 focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); 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 focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); 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 focusables = getFocusableList(elementList.get(elementList.size() - 1),new LinkedList()); 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 windowIterator = elementList.listIterator(elementList.size()); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); Stack elementStack = buildElementPositionalStack(new Stack(), 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 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 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 elementPropagation = new LinkedList(); 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 buildElementPositionalStack(Stack 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 windowIterator = elementList.listIterator(elementList.size()); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); Stack elementStack = buildElementPositionalStack(new Stack(), 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 windowIterator = elementList.listIterator(elementList.size()); while(windowIterator.hasPrevious()){ Element currentWindow = windowIterator.previous(); Stack elementStack = buildElementPositionalStack(new Stack(), 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; } } }