package electrosphere.renderer.ui.elements; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; import static org.lwjgl.opengl.GL11.glClear; import static org.lwjgl.opengl.GL11.glClearColor; import java.util.LinkedList; import java.util.List; import org.joml.Vector3f; import org.lwjgl.util.yoga.YGLayout; import org.lwjgl.util.yoga.YGNode; import org.lwjgl.util.yoga.Yoga; import electrosphere.engine.Globals; import electrosphere.engine.signal.Signal.SignalType; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.RenderPipelineState; import electrosphere.renderer.framebuffer.Framebuffer; import electrosphere.renderer.framebuffer.FramebufferUtils; import electrosphere.renderer.model.Material; import electrosphere.renderer.model.Model; import electrosphere.renderer.texture.Texture; import electrosphere.renderer.ui.elementtypes.ContainerElement; import electrosphere.renderer.ui.elementtypes.DrawableElement; import electrosphere.renderer.ui.elementtypes.Element; import electrosphere.renderer.ui.elementtypes.NavigableElement; import electrosphere.renderer.ui.events.Event; import electrosphere.renderer.ui.events.NavigationEvent; /** * A window */ public class Window implements DrawableElement, ContainerElement, NavigableElement { static Vector3f color = new Vector3f(1.0f); List childList = new LinkedList(); Framebuffer widgetBuffer; Material customMat = new Material(); Vector3f texPosition = new Vector3f(0,0,0); Vector3f texScale = new Vector3f(1,1,0); NavigationEventCallback navCallback; static final Vector3f windowDrawDebugColor = new Vector3f(1.0f,0.0f,0.0f); //controls whether to show window decorations (ie the frame) boolean showDecorations = true; //Yoga node for controlling placement of the window on the screen //IE, if you want to place a window in the upper right hand side of the screen, //this node can be used to control placement alignment to accomplish that //NOTE: It should always be set to the current size of the window (width, height) //NOTE: It is updated every time the applyYoga function is called long parentWindowYogaNode = -1; /** * Constructor * @param showDecorations * @param positionX * @param positionY * @param width * @param height */ @Deprecated public Window(OpenGLState openGLState, int positionX, int positionY, int width, int height, boolean showDecorations){ try { widgetBuffer = FramebufferUtils.generateTextureFramebuffer(openGLState, width, height); } catch(Exception e){ LoggerInterface.loggerRenderer.ERROR(e); } customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer()); this.showDecorations = showDecorations; //yoga node for the actually visible part this.yogaNode = Yoga.YGNodeNew(); this.layout = YGNode.create(this.yogaNode).layout(); //yoga node for placement this.parentWindowYogaNode = Yoga.YGNodeNew(); Yoga.YGNodeInsertChild(this.parentWindowYogaNode, this.yogaNode, 0); setParentAlignContent(YogaAlignment.Start); setParentAlignItem(YogaAlignment.Start); setParentJustifyContent(YogaJustification.Start); this.setFlexDirection(YogaFlexDirection.Column); this.setWidth(width); this.setHeight(height); } /** * Private constructor * @param openGLState * @param positionX * @param positionY * @param width * @param height */ private Window(OpenGLState openGLState, int positionX, int positionY, int width, int height){ try { widgetBuffer = FramebufferUtils.generateTextureFramebuffer(openGLState, width, height); } catch(Exception e){ LoggerInterface.loggerRenderer.ERROR(e); } customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer()); //yoga node for the actually visible part this.yogaNode = Yoga.YGNodeNew(); this.layout = YGNode.create(this.yogaNode).layout(); //yoga node for placement this.parentWindowYogaNode = Yoga.YGNodeNew(); Yoga.YGNodeInsertChild(this.parentWindowYogaNode, this.yogaNode, 0); setParentAlignContent(YogaAlignment.Start); setParentAlignItem(YogaAlignment.Start); setParentJustifyContent(YogaJustification.Start); this.setFlexDirection(YogaFlexDirection.Column); this.setMinWidth(width); this.setMinHeight(height); } /** * Creates a window * @param openGLState The opengl state * @param posX The x position of the window * @param posY The y position of the window * @param width The width of the window * @param height The height of the window * @param showDecorations true to show window decorations, false otherwise * @return The window element */ public static Window create(OpenGLState openGLState, int posX, int posY, int width, int height, boolean showDecorations){ Window rVal = new Window(openGLState, posX, posY, width, height, showDecorations); return rVal; } /** * Creates an expandable window * @param openGLState The opengl state * @param posX The x position of the window * @param posY The y position of the window * @param width The width of the window * @param height The height of the window * @return The window element */ public static Window createExpandable(OpenGLState openGLState, int posX, int posY, int width, int height){ Window rVal = new Window(openGLState, posX, posY, width, height); return rVal; } /** * Creates an expandable window * @param openGLState The opengl state * @param posX The x position of the window * @param posY The y position of the window * @param width The width of the window * @param height The height of the window * @return The window element */ public static Window createExpandableCenterAligned(OpenGLState openGLState, int width, int height){ Window rVal = new Window(openGLState, 0, 0, width, height); rVal.setParentAlignItem(YogaAlignment.Center); rVal.setParentJustifyContent(YogaJustification.Center); return rVal; } @Override public void draw( RenderPipelineState renderPipelineState, OpenGLState openGLState, Framebuffer framebuffer, int framebufferPosX, int framebufferPosY ) { float ndcWidth = (float)this.getWidth()/framebuffer.getWidth(); float ndcHeight = (float)this.getHeight()/framebuffer.getHeight(); float ndcX = (float)this.absoluteToFramebuffer(getAbsoluteX(),framebufferPosX)/framebuffer.getWidth(); float ndcY = (float)this.absoluteToFramebuffer(getAbsoluteY(),framebufferPosY)/framebuffer.getHeight(); Vector3f boxPosition = new Vector3f(ndcX,ndcY,0); Vector3f boxDimensions = new Vector3f(ndcWidth,ndcHeight,0); widgetBuffer.bind(openGLState); openGLState.glViewport(width, height); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //grab assets required to render window Model planeModel = Globals.assetManager.fetchModel(Globals.imagePlaneModelID); Texture windowFrame = Globals.assetManager.fetchTexture("Textures/ui/uiFrame1.png"); for(Element child : childList){ if(child instanceof DrawableElement){ DrawableElement drawableChild = (DrawableElement) child; drawableChild.draw(renderPipelineState,openGLState,widgetBuffer,this.getAbsoluteX(),this.getAbsoluteY()); } } //this call binds the screen as the "texture" we're rendering to //have to call before actually rendering framebuffer.bind(openGLState); openGLState.glViewport(framebuffer.getWidth(), framebuffer.getHeight()); //error if assets are null if(planeModel == null || windowFrame == null){ LoggerInterface.loggerRenderer.ERROR("Window unable to find plane model or window frame!!", new Exception()); } //render background of window if(planeModel != null && windowFrame != null && showDecorations){ planeModel.pushUniformToMesh("plane", "mPosition", boxPosition); planeModel.pushUniformToMesh("plane", "mDimension", boxDimensions); planeModel.pushUniformToMesh("plane", "tPosition", texPosition); planeModel.pushUniformToMesh("plane", "tDimension", texScale); planeModel.pushUniformToMesh(planeModel.getMeshes().get(0).getMeshName(), "color", color); customMat.setTexturePointer(windowFrame.getTexturePointer()); planeModel.getMeshes().get(0).setMaterial(customMat); planeModel.drawUI(); } //render content of window if(planeModel != null){ planeModel.pushUniformToMesh("plane", "mPosition", boxPosition); planeModel.pushUniformToMesh("plane", "mDimension", boxDimensions); planeModel.pushUniformToMesh("plane", "tPosition", texPosition); planeModel.pushUniformToMesh("plane", "tDimension", texScale); planeModel.pushUniformToMesh(planeModel.getMeshes().get(0).getMeshName(), "color", color); customMat.setTexturePointer(widgetBuffer.getTexture().getTexturePointer()); planeModel.getMeshes().get(0).setMaterial(customMat); planeModel.drawUI(); } } /** * Destroys the element */ public void destroy(){ this.yogaNode = Element.NULL_YOGA_ELEMENT; for(Element el : getChildren()){ Globals.signalSystem.post(SignalType.YOGA_DESTROY, el); } this.clearChildren(); if(this.yogaNode != Element.NULL_YOGA_ELEMENT){ Yoga.YGNodeFree(this.yogaNode); } } /** * clears all children */ public void clear(){ for(Element el : getChildren()){ Globals.signalSystem.post(SignalType.YOGA_DESTROY, el); } this.clearChildren(); } @Override public void setWidth(int width){ this.width = width; Yoga.YGNodeStyleSetWidth(this.yogaNode, width); } @Override public void setHeight(int height){ this.height = height; Yoga.YGNodeStyleSetHeight(this.yogaNode, height); } @Override public void setMaxWidth(int width) { Yoga.YGNodeStyleSetMaxWidth(yogaNode, width); } @Override public void setMaxWidthPercent(float percent) { Yoga.YGNodeStyleSetMaxWidthPercent(yogaNode, percent); } @Override public void setMaxHeight(int height) { Yoga.YGNodeStyleSetMaxHeight(yogaNode, height); } @Override public void setMaxHeightPercent(float percent) { Yoga.YGNodeStyleSetMaxHeight(yogaNode, percent); } @Override public void setMinWidth(int width) { Yoga.YGNodeStyleSetMinWidth(yogaNode, width); } @Override public void setMinWidthPercent(float percent) { Yoga.YGNodeStyleSetMinWidthPercent(yogaNode, percent); } @Override public void setMinHeight(int height) { Yoga.YGNodeStyleSetMinHeight(yogaNode, height); } @Override public void setMinHeightPercent(float percent) { Yoga.YGNodeStyleSetMinHeightPercent(yogaNode, percent); } public int width = 1; public int height = 1; public int absoluteX = 0; public int absoluteY = 0; int marginTop = 0; int marginRight = 0; int marginBottom = 0; int marginLeft = 0; public boolean visible = false; public int getWidth() { return width; } public int getHeight() { return height; } public boolean getVisible() { return visible; } public void setVisible(boolean draw) { this.visible = draw; } public int getMarginTop(){ return marginTop; } public int getMarginRight(){ return marginRight; } public int getMarginBottom(){ return marginBottom; } public int getMarginLeft(){ return marginLeft; } public void setMarginTop(int marginTop){ this.marginTop = marginTop; } public void setMarginRight(int marginRight){ this.marginRight = marginRight; } public void setMarginBottom(int marginBottom){ this.marginBottom = marginBottom; } public void setMarginLeft(int marginLeft){ this.marginLeft = marginLeft; } //the yoga node id long yogaNode = -1; //the layout object YGLayout layout; @Override public long getYogaNode() { return yogaNode; } @Override public void applyYoga(int parentX, int parentY) { if(this.yogaNode != Element.NULL_YOGA_ELEMENT && parentWindowYogaNode != Element.NULL_YOGA_ELEMENT){ if( Globals.WINDOW_WIDTH <= 0 || Globals.WINDOW_HEIGHT <= 0 || width <= 0 || height <= 0 ){ String message = "Window has invalid dimensions!\n" + "Globals.WINDOW_WIDTH: " + Globals.WINDOW_WIDTH + "\n" + "Globals.WINDOW_HEIGHT: " + Globals.WINDOW_HEIGHT + "\n" + "width: " + width + "\n" + "height: " + height + "\n" ; throw new IllegalStateException(message); } Yoga.YGNodeStyleSetWidth(parentWindowYogaNode, Globals.WINDOW_WIDTH); Yoga.YGNodeStyleSetHeight(parentWindowYogaNode, Globals.WINDOW_HEIGHT); //calculate yoga layout Yoga.YGNodeCalculateLayout(parentWindowYogaNode, width, height, Yoga.YGDirectionInherit); //get the values from yoga float leftRaw = Yoga.YGNodeLayoutGetLeft(yogaNode); float topRaw = Yoga.YGNodeLayoutGetTop(yogaNode); float widthRaw = Yoga.YGNodeLayoutGetWidth(yogaNode); float heightRaw = Yoga.YGNodeLayoutGetHeight(yogaNode); //apply the values to this component this.absoluteX = (int)leftRaw; this.absoluteY = (int)topRaw; this.width = (int)widthRaw; this.height = (int)heightRaw; //apply yoga values to all children LoggerInterface.loggerUI.DEBUG("==Apply yoga to windoow=="); for(Element child : this.getChildren()){ child.applyYoga(this.absoluteX,this.absoluteY); } } } @Override public void setDirection(int layout) { Yoga.YGNodeStyleSetDirection(yogaNode, Yoga.YGFlexDirectionColumn); } @Override public void setFlexDirection(YogaFlexDirection layout){ int directionInteger = Yoga.YGFlexDirectionColumn; switch(layout){ case Column: directionInteger = Yoga.YGFlexDirectionColumn; break; case Column_Reverse: directionInteger = Yoga.YGFlexDirectionColumnReverse; break; case Row: directionInteger = Yoga.YGFlexDirectionRow; break; case Row_Reverse: directionInteger = Yoga.YGFlexDirectionRowReverse; break; } Yoga.YGNodeStyleSetFlexDirection(yogaNode, directionInteger); } @Override public void setJustifyContent(YogaJustification justification){ int justificationInteger = Yoga.YGJustifyFlexStart; switch(justification){ case Center: justificationInteger = Yoga.YGJustifyCenter; break; case Start: justificationInteger = Yoga.YGJustifyFlexStart; break; case End: justificationInteger = Yoga.YGJustifyFlexEnd; break; case Around: justificationInteger = Yoga.YGJustifySpaceAround; break; case Between: justificationInteger = Yoga.YGJustifySpaceBetween; break; case Evenly: justificationInteger = Yoga.YGJustifySpaceEvenly; break; } Yoga.YGNodeStyleSetJustifyContent(this.yogaNode, justificationInteger); } @Override public void setAlignItems(YogaAlignment alignment){ int alignmentInteger = Yoga.YGAlignAuto; switch(alignment){ case Auto: alignmentInteger = Yoga.YGAlignAuto; break; case Start: alignmentInteger = Yoga.YGAlignFlexStart; break; case End: alignmentInteger = Yoga.YGAlignFlexEnd; break; case Around: alignmentInteger = Yoga.YGAlignSpaceAround; break; case Between: alignmentInteger = Yoga.YGAlignSpaceBetween; break; case Stretch: alignmentInteger = Yoga.YGAlignStretch; break; case Baseline: alignmentInteger = Yoga.YGAlignBaseline; break; case Center: alignmentInteger = Yoga.YGAlignCenter; break; } Yoga.YGNodeStyleSetAlignItems(this.yogaNode, alignmentInteger); } @Override public void setAlignContent(YogaAlignment alignment){ int alignmentInteger = Yoga.YGAlignAuto; switch(alignment){ case Auto: alignmentInteger = Yoga.YGAlignAuto; break; case Start: alignmentInteger = Yoga.YGAlignFlexStart; break; case End: alignmentInteger = Yoga.YGAlignFlexEnd; break; case Around: alignmentInteger = Yoga.YGAlignSpaceAround; break; case Between: alignmentInteger = Yoga.YGAlignSpaceBetween; break; case Stretch: alignmentInteger = Yoga.YGAlignStretch; break; case Baseline: alignmentInteger = Yoga.YGAlignBaseline; break; case Center: alignmentInteger = Yoga.YGAlignCenter; break; } Yoga.YGNodeStyleSetAlignContent(this.yogaNode, alignmentInteger); } @Override public void setAlignSelf(YogaAlignment alignment){ int alignmentInteger = Yoga.YGAlignAuto; switch(alignment){ case Auto: alignmentInteger = Yoga.YGAlignAuto; break; case Start: alignmentInteger = Yoga.YGAlignFlexStart; break; case End: alignmentInteger = Yoga.YGAlignFlexEnd; break; case Around: alignmentInteger = Yoga.YGAlignSpaceAround; break; case Between: alignmentInteger = Yoga.YGAlignSpaceBetween; break; case Stretch: alignmentInteger = Yoga.YGAlignStretch; break; case Baseline: alignmentInteger = Yoga.YGAlignBaseline; break; case Center: alignmentInteger = Yoga.YGAlignCenter; break; } Yoga.YGNodeStyleSetAlignSelf(this.yogaNode, alignmentInteger); } @Override public void addChild(Element child) { childList.add(child); child.setParent(this); if(child instanceof DrawableElement){ DrawableElement drawableChild = (DrawableElement) child; drawableChild.setVisible(false); Yoga.YGNodeInsertChild(yogaNode, drawableChild.getYogaNode(), childList.size() - 1); } } @Override public List getChildren() { return childList; } @Override public void removeChild(Element child) { childList.remove(child); child.setParent(null); Yoga.YGNodeRemoveChild(yogaNode, child.getYogaNode()); } @Override public void clearChildren(){ Yoga.YGNodeRemoveAllChildren(yogaNode); childList.clear(); } public boolean handleEvent(Event event){ boolean propagate = true; if(event instanceof NavigationEvent && navCallback != null){ if(!navCallback.execute((NavigationEvent)event)){ propagate = false; } } return propagate; } @Override public void setOnNavigationCallback(NavigationEventCallback callback) { navCallback = callback; } public Element getParent(){ return null; } @Override public int getChildOffsetX(){ return 0; } @Override public int getChildOffsetY(){ return 0; } @Override public float getChildScaleX(){ return 1; } @Override public float getChildScaleY(){ return 1; } /** * Sets the alignment of items on the parent to the window yoga node * @param alignment The alignment value */ public void setParentAlignItem(YogaAlignment alignment){ int alignmentInteger = Yoga.YGAlignAuto; switch(alignment){ case Auto: alignmentInteger = Yoga.YGAlignAuto; break; case Start: alignmentInteger = Yoga.YGAlignFlexStart; break; case End: alignmentInteger = Yoga.YGAlignFlexEnd; break; case Around: alignmentInteger = Yoga.YGAlignSpaceAround; break; case Between: alignmentInteger = Yoga.YGAlignSpaceBetween; break; case Stretch: alignmentInteger = Yoga.YGAlignStretch; break; case Baseline: alignmentInteger = Yoga.YGAlignBaseline; break; case Center: alignmentInteger = Yoga.YGAlignCenter; break; } Yoga.YGNodeStyleSetAlignItems(this.parentWindowYogaNode, alignmentInteger); } /** * Sets the alignment of content on the parent to the window yoga node * @param alignment The alignment */ public void setParentAlignContent(YogaAlignment alignment){ int alignmentInteger = Yoga.YGAlignAuto; switch(alignment){ case Auto: alignmentInteger = Yoga.YGAlignAuto; break; case Start: alignmentInteger = Yoga.YGAlignFlexStart; break; case End: alignmentInteger = Yoga.YGAlignFlexEnd; break; case Around: alignmentInteger = Yoga.YGAlignSpaceAround; break; case Between: alignmentInteger = Yoga.YGAlignSpaceBetween; break; case Stretch: alignmentInteger = Yoga.YGAlignStretch; break; case Baseline: alignmentInteger = Yoga.YGAlignBaseline; break; case Center: alignmentInteger = Yoga.YGAlignCenter; break; } Yoga.YGNodeStyleSetAlignContent(this.parentWindowYogaNode, alignmentInteger); } /** * Sets the justification of the parent yoga node containing this window * @param justification The justification mode */ public void setParentJustifyContent(YogaJustification justification){ int justificationInteger = Yoga.YGJustifyFlexStart; switch(justification){ case Center: justificationInteger = Yoga.YGJustifyCenter; break; case Start: justificationInteger = Yoga.YGJustifyFlexStart; break; case End: justificationInteger = Yoga.YGJustifyFlexEnd; break; case Around: justificationInteger = Yoga.YGJustifySpaceAround; break; case Between: justificationInteger = Yoga.YGJustifySpaceBetween; break; case Evenly: justificationInteger = Yoga.YGJustifySpaceEvenly; break; } Yoga.YGNodeStyleSetJustifyContent(this.parentWindowYogaNode, justificationInteger); } @Override public void setWrap(YogaWrap wrap) { switch(wrap){ case WRAP: Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapWrap); break; case NO_WRAP: Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapNoWrap); break; case REVERSE: Yoga.YGNodeStyleSetFlexWrap(yogaNode, Yoga.YGWrapReverse); break; } } @Override public void setOverflow(YogaOverflow overflow){ switch(overflow){ case Visible: Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowVisible); break; case Hidden: Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowHidden); break; case Scroll: Yoga.YGNodeStyleSetOverflow(yogaNode, Yoga.YGOverflowScroll); break; } } @Override public void setAbsolutePosition(boolean useAbsolutePosition) { //not implemented throw new UnsupportedOperationException(); } @Override public void setParent(Element parent) { //not implemented throw new UnsupportedOperationException(); } /** * Converts an absolute (to the screen) position to a position within a framebuffer * @param absolutePos The absolute position * @param framebufferPos The position of the framebuffer on the screen * @return The position within the framebuffer */ public int absoluteToFramebuffer(int absolutePos, int framebufferPos){ return absolutePos - framebufferPos; } @Override public int getRelativeX() { return absoluteX; } @Override public int getRelativeY() { return absoluteY; } @Override public int getAbsoluteX() { return absoluteX; } @Override public int getAbsoluteY() { return absoluteY; } @Override public void setPositionX(int positionX) { Yoga.YGNodeStyleSetPosition(this.yogaNode, Yoga.YGEdgeLeft, positionX); } @Override public void setPositionY(int positionY) { Yoga.YGNodeStyleSetPosition(this.yogaNode, Yoga.YGEdgeTop, positionY); } }