Renderer/src/main/java/electrosphere/renderer/ui/ElementManager.java
austin 3e5cade90e
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
searchable voxel selection ui
2024-04-10 22:11:02 -04:00

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;
}
}
}