Renderer/src/main/java/electrosphere/renderer/ui/elements/Window.java
austin e6d946db53
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
gut old ui handling of positioning
2024-09-17 16:53:36 -04:00

815 lines
27 KiB
Java

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<Element> childList = new LinkedList<Element>();
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<Element> 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);
}
}