From 3c641667980faad10e59ac400f24670c58af45d8 Mon Sep 17 00:00:00 2001 From: austin Date: Mon, 16 Sep 2024 17:05:53 -0400 Subject: [PATCH] ui work, entity packet fix --- assets/Textures/ui/uiOutline1.png | Bin 0 -> 9592 bytes buildNumber.properties | 4 +- docs/src/progress/currenttarget.md | 1 - docs/src/progress/renderertodo.md | 4 + .../java/electrosphere/engine/Globals.java | 1 + .../entity/state/equip/ServerEquipState.java | 2 - .../menu/debug/ImGuiUIFramework.java | 52 ++++++ .../menu/debug/ImGuiWindowMacros.java | 4 + .../mainmenu/MenuGeneratorsUITesting.java | 98 ++++++++--- .../electrosphere/renderer/OpenGLState.java | 2 +- .../renderer/RenderingEngine.java | 2 - .../renderer/debug/DebugRendering.java | 1 + .../renderer/model/Material.java | 8 + .../renderer/pipelines/UIPipeline.java | 6 + .../electrosphere/renderer/ui/UIUtils.java | 58 +++++- .../renderer/ui/WidgetUtils.java | 165 ------------------ .../renderer/ui/elements/ActorPanel.java | 6 +- .../renderer/ui/elements/BitmapCharacter.java | 6 +- .../ui/elements/ScrollableContainer.java | 88 +++++----- .../renderer/ui/elements/Slider.java | 24 +-- .../renderer/ui/elements/StringCarousel.java | 19 ++ .../renderer/ui/layout/LayoutScheme.java | 13 -- .../renderer/ui/macros/InputMacros.java | 4 + .../character/PlayerCharacterCreation.java | 24 +-- .../renderer/ui/MainMenuTests.java | 5 +- .../renderer/ui/elements/SliderTests.java | 23 ++- .../ui/elements/StringCarouselTests.java | 25 ++- test/java/renderer/ui/elements/slider1.png | Bin 0 -> 10284 bytes .../renderer/ui/elements/stringcarousel1.png | Bin 0 -> 10959 bytes test/java/renderer/ui/uitest.png | Bin 33383 -> 28326 bytes 30 files changed, 350 insertions(+), 295 deletions(-) create mode 100644 assets/Textures/ui/uiOutline1.png delete mode 100644 src/main/java/electrosphere/renderer/ui/WidgetUtils.java delete mode 100644 src/main/java/electrosphere/renderer/ui/layout/LayoutScheme.java create mode 100644 test/java/renderer/ui/elements/slider1.png create mode 100644 test/java/renderer/ui/elements/stringcarousel1.png diff --git a/assets/Textures/ui/uiOutline1.png b/assets/Textures/ui/uiOutline1.png new file mode 100644 index 0000000000000000000000000000000000000000..969ba81323ecf7bacf5275650fb565a4d9de5cc7 GIT binary patch literal 9592 zcmeHLdr(tX8o#`Z;-j=a+KS>Os9l!cyc2TM1Wc0%p#)h(petx@?jziim4>9Tj(h+FiR&9d*{0X}hh6?)s<-v$nG?v~|zD39qR$J5=~< zZ)TEv?svZLcfRvG-}%0CvMDzwV`l99SO|hK7MuY+s16g0)}y8? z^Nzcg6xJ8E{IW4gv-HsUySL11MT4bBZN^KTX9|-y%wD^-zQ^Kf-CmnN-?Q;t-ea-l ziI*deTujtoE?ghEbKQ*1tJ-&_zkggkaO#J)f`KUuRtF51r@V0vf+Ff^lPT9~G7Zfg z%=pck*A1DcmOb(ErIxH!Hy_z){4mi}(b~9K`|F?2>w1<>IeOFXyRk?`XXX_zi0NLx zA-!>x=y7Ms!r~ZXzv#oyx~vy0S>H*Lk2ZYUb0^`e!LN3-_d$EsP`f^v8=?AAv+c-% z?SDO-Raf|pW2*R2l%nmG;0y9CuU`2&A?;6>>Z=N_ok-8<*wyw#&$fo7k7e%OnZ2Lc zzUVo*;_24C?>+YR>=nCMV{gXcl!T6Q$+4&VvoB;nb#0Ty@$UJ*WW~4befQCGaU1h( z`+t>q<;a<;#b}T8oJ!f!5Ou68`^WzMC*QHpxn5iT=4<8f@29I?c=c}0E$x=so%d%H z-#}j6H|yh?t!v8YJxO+N{IO#m`b>iNc+@NUCtH)-x4zxdbS|pT`Hk&)`@MPBSKl4@ zRDb$LR8z|u*lDh7?SAHD`~E*N_dit){yWM!;M?nGY$+f}D`^mnMYe1m&bTEQ z!8k}s(Cr0L3PJkxpcli-NEUXGPTFG-_qBJ3VVW?A3)ME6&1)h{>C9>$xw1MZAFnRM zQ9_)a7OM~H0DzlhF*xXUdHlMdLCoXoK${aw#W1hJmKnrFwp`f6_()hKQAuQoB}i8) z#A&gx-bYZnJoEA}1b8!uOIg;dlS%`DfFz)lFg~YLj-sekrjRNW2+%cxMuT(CPN!@PgXbV4U zsRSTlhaPC*&j)*2nn(JX3Lj2dDoGEUG#Y}yhwZ%;J{Lb70+*65(hXGoU{v{-Av3JD z++hoj0w?YE@>YQCF`6t*jgU3QH?D`D&S*z~`7rJn?V-8z%0SCz)0r8(f^*MmHi)_L zbp(UcgpO~LT3m(_4iv$0RE?+{6oH@^sX|DV15d>@YE*$~M?qOVeirlKBnJh+B{aYx zj>XrJO=gl*AEIjcWj4s+^+GQ4lM9G>A&fHQFlMKwxI$ZH<4fb{u6EOMxFnM4_) zgj*OLm=2&8<02IR@UUPuI+KsYSjLynFfN0bgMc~9;cgq)Pz1(eW{f2Ps7#^K$xxkK zlP}lk6sS&-ipYRuls-eyRMr2|=C%*44=s8o?FZvm@uJY4T1l3N-a@Y~n%_(?%x?=F zhKHu$$0|vJ_Y+`+y6{rW<0L`;2nXztoPIzsXk`k5z%UJ>q*4h)mFiF;s9Z}RN~Jng zMW|GST8@uJ_cIh5z>==9n1;5p5k`Me*3 zBuU&iBDgQT5(q`C)n*Z0pB6V~VSLQ31K$JDldPqPH8F1POb+n}MUsF6G+PRD2gSm= zS&u`|oC{X7F+ceE?d~JfPC;?eUF-HNCz^~mJLZXI{ryae%+vLned&pNt%uy{Isn;u zKy6Ly+wl}=+CREdRU2in9z0ka3107i^5yQujo``%+22|w0*|^`;Abiee&WDi9MDUG z@ZkuF=J@CNT=v_jAwC5m7hpe~5cqsV*c^&n2#|O_1tAw;<0j<9^r58~w<#Ce0_zWA zJ(NcI*yPhF&ypZ~NJ50NGzlg7r)OcIs{N>XeC83#(nOV|&|aU6)GT05LTdj1zJZl+x%44#UlV5TUGX); n2HLqlE2xWqSoFaH(0I9I#;Uo8-m^r0#CdA9 pointEquipClassList = point.getEquipClassWhitelist(); boolean itemIsInPointWhitelist = pointEquipClassList.contains(equipItemClass); if(!hasEquipped && targetIsItem && !targetIsAttached && itemIsInPointWhitelist){ - //if we're the server, perform the attempt, otherwise send packet to server requesting to equip serverAttemptEquip(toEquip, point); } } diff --git a/src/main/java/electrosphere/menu/debug/ImGuiUIFramework.java b/src/main/java/electrosphere/menu/debug/ImGuiUIFramework.java index b86aec7f..85dc400f 100644 --- a/src/main/java/electrosphere/menu/debug/ImGuiUIFramework.java +++ b/src/main/java/electrosphere/menu/debug/ImGuiUIFramework.java @@ -1,6 +1,11 @@ package electrosphere.menu.debug; import electrosphere.engine.Globals; +import electrosphere.logger.LoggerInterface; +import electrosphere.renderer.debug.DebugRendering; +import electrosphere.renderer.ui.elements.BitmapCharacter; +import electrosphere.renderer.ui.elementtypes.ContainerElement; +import electrosphere.renderer.ui.elementtypes.Element; import electrosphere.renderer.ui.imgui.ImGuiWindow; import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback; import imgui.ImGui; @@ -31,10 +36,57 @@ public class ImGuiUIFramework { //ui framework text ImGui.text("UI Framework"); + if(ImGui.button("Show UI Outlines")){ + DebugRendering.RENDER_DEBUG_UI_TREE = !DebugRendering.RENDER_DEBUG_UI_TREE; + } + + if(ImGui.button("Print tree")){ + printUITrees(); + } + } }); uiFrameworkWindow.setOpen(false); Globals.renderingEngine.getImGuiPipeline().addImGuiWindow(uiFrameworkWindow); } + /** + * Prints the UI trees + */ + private static void printUITrees(){ + int i = 0; + for(Element window : Globals.elementService.getWindowList()){ + LoggerInterface.loggerUI.WARNING("Window " + i); + printUITree(window, 1); + LoggerInterface.loggerUI.WARNING("----\n\n"); + i++; + } + } + + /** + * Prints the ui tree for a given element + * @param rootEl The element + * @param indent The current indentation + */ + private static void printUITree(Element rootEl, int indent){ + String indentStr = ""; + for(int i = 0; i < indent; i++){ + indentStr = indentStr + "\t"; + } + if(rootEl instanceof BitmapCharacter){ + + } else { + LoggerInterface.loggerUI.WARNING(indentStr + "--" + rootEl + "--"); + LoggerInterface.loggerUI.WARNING(indentStr + rootEl.getInternalX() + " " + rootEl.getInternalY() + " " + rootEl.getInternalWidth() + " " + rootEl.getInternalHeight()); + LoggerInterface.loggerUI.WARNING(indentStr + rootEl.getAbsoluteX() + " " + rootEl.getAbsoluteY() + " " + rootEl.getWidth() + " " + rootEl.getHeight()); + LoggerInterface.loggerUI.WARNING("\n"); + } + if(rootEl instanceof ContainerElement){ + ContainerElement containerView = (ContainerElement)rootEl; + for(Element child : containerView.getChildren()){ + printUITree(child, indent + 1); + } + } + } + } diff --git a/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java b/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java index e428d311..83eb8ee3 100644 --- a/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java +++ b/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java @@ -166,6 +166,10 @@ public class ImGuiWindowMacros { if(ImGui.button("Renderers")){ ImGuiRenderer.rendererWindow.setOpen(true); } + //logger state control + if(ImGui.button("UI")){ + ImGuiUIFramework.uiFrameworkWindow.setOpen(true); + } //close button if(ImGui.button("Close")){ mainDebugWindow.setOpen(false); diff --git a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsUITesting.java b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsUITesting.java index 17d77fc9..80f41427 100644 --- a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsUITesting.java +++ b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsUITesting.java @@ -1,9 +1,12 @@ package electrosphere.menu.mainmenu; +import java.util.Arrays; + import org.joml.Vector3f; import electrosphere.client.entity.camera.CameraEntityUtils; import electrosphere.engine.Globals; +import electrosphere.engine.assetmanager.AssetDataStrings; import electrosphere.menu.WindowUtils; import electrosphere.renderer.actor.ActorUtils; import electrosphere.renderer.ui.components.CharacterCustomizer; @@ -12,7 +15,9 @@ import electrosphere.renderer.ui.elements.Button; import electrosphere.renderer.ui.elements.FormElement; import electrosphere.renderer.ui.elements.Label; import electrosphere.renderer.ui.elements.Slider; +import electrosphere.renderer.ui.elements.StringCarousel; import electrosphere.renderer.ui.elements.VirtualScrollable; +import electrosphere.renderer.ui.elementtypes.ContainerElement; import electrosphere.renderer.ui.elementtypes.Element; import electrosphere.renderer.ui.events.ValueChangeEvent; import electrosphere.renderer.ui.macros.InputMacros; @@ -35,38 +40,73 @@ public class MenuGeneratorsUITesting { }); rVal.addChild(backButton); - //toggle input - rVal.addChild(InputMacros.createToggle("Test Toggle", false, null)); - - //actor panel - ActorPanel actorPanel = new ActorPanel(Globals.renderingEngine.getOpenGLState(), 500, 100, 500, 500, ActorUtils.createActorFromModelPath("Models/creatures/animals/deer1.fbx")); - if(Globals.playerCamera == null){ - Globals.playerCamera = CameraEntityUtils.spawnBasicCameraEntity(new Vector3f(0,0,0), new Vector3f(-1,0,0)); - } - rVal.addChild(actorPanel); - - - // - //Virtual scrollable test - VirtualScrollable virtualScrollable = new VirtualScrollable(300, 75); - //add a ton of children - for(int i = 0; i < 10; i++){ - Button testButton = new Button(); - Label testLabel = new Label(1.0f); - testLabel.setText("Test button " + i); - testButton.addChild(testLabel); - virtualScrollable.addChild(testButton); - } - - // slider test - Slider slider = Slider.createSlider((ValueChangeEvent event) -> { - + StringCarousel displayCarousel = StringCarousel.create( + Arrays.asList(new String[]{ + "Generic", + "Slider", + "CharacterCustomizer" + }), + (ValueChangeEvent event) -> { + attachComponent(rVal,event.getAsString()); }); - virtualScrollable.addChild(slider); - - rVal.addChild(virtualScrollable); + rVal.addChild(displayCarousel); + + attachComponent(rVal,"Generic"); return rVal; } + + /** + * Adds the elements currently to display to the form element + * @param formEl The form element + * @param type The type of elements to display + */ + private static void attachComponent(ContainerElement formEl, String type){ + Element backButton = formEl.getChildren().get(0); + Element selector = formEl.getChildren().get(1); + formEl.clearChildren(); + formEl.addChild(backButton); + formEl.addChild(selector); + switch(type){ + case "Generic": { + //toggle input + formEl.addChild(InputMacros.createToggle("Test Toggle", false, null)); + + //actor panel + ActorPanel actorPanel = ActorPanel.create(ActorUtils.createActorFromModelPath(AssetDataStrings.UNITCUBE)); + if(Globals.playerCamera == null){ + Globals.playerCamera = CameraEntityUtils.spawnBasicCameraEntity(new Vector3f(0,0,0), new Vector3f(-1,0,0)); + } + formEl.addChild(actorPanel); + + + // + //Virtual scrollable test + VirtualScrollable virtualScrollable = new VirtualScrollable(300, 75); + //add a ton of children + for(int i = 0; i < 10; i++){ + Button testButton = new Button(); + Label testLabel = new Label(1.0f); + testLabel.setText("Test button " + i); + testButton.addChild(testLabel); + virtualScrollable.addChild(testButton); + } + + // slider test + Slider slider = Slider.createSlider((ValueChangeEvent event) -> { + }); + virtualScrollable.addChild(slider); + + formEl.addChild(virtualScrollable); + } break; + case "Slider": { + formEl.addChild(Slider.createSlider((ValueChangeEvent event) -> { + })); + } break; + case "CharacterCustomizer": { + formEl.addChild(CharacterCustomizer.createCharacterCustomizerPanel("human")); + } break; + } + } } diff --git a/src/main/java/electrosphere/renderer/OpenGLState.java b/src/main/java/electrosphere/renderer/OpenGLState.java index 3d0ab4f8..b70a2262 100644 --- a/src/main/java/electrosphere/renderer/OpenGLState.java +++ b/src/main/java/electrosphere/renderer/OpenGLState.java @@ -20,7 +20,7 @@ import electrosphere.renderer.shader.ShaderProgram; public class OpenGLState { //tracks whether caching should be used or not (to deduplicate opengl calls) - private static final boolean DISABLE_CACHING = true; + private static final boolean DISABLE_CACHING = false; //the max texture allowed by the current environment int MAX_TEXTURE_WIDTH; diff --git a/src/main/java/electrosphere/renderer/RenderingEngine.java b/src/main/java/electrosphere/renderer/RenderingEngine.java index f5c70533..376f3794 100644 --- a/src/main/java/electrosphere/renderer/RenderingEngine.java +++ b/src/main/java/electrosphere/renderer/RenderingEngine.java @@ -2,12 +2,10 @@ package electrosphere.renderer; import static electrosphere.renderer.RenderUtils.createScreenTextureVAO; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; import static org.lwjgl.opengl.GL11.glClear; import static org.lwjgl.opengl.GL11.glClearColor; -import static org.lwjgl.opengl.GL11.glEnable; import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER; import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER; import static org.lwjgl.opengl.GL30.glBindRenderbuffer; diff --git a/src/main/java/electrosphere/renderer/debug/DebugRendering.java b/src/main/java/electrosphere/renderer/debug/DebugRendering.java index b65e3bba..3ed7a626 100644 --- a/src/main/java/electrosphere/renderer/debug/DebugRendering.java +++ b/src/main/java/electrosphere/renderer/debug/DebugRendering.java @@ -28,6 +28,7 @@ public class DebugRendering { public static boolean RENDER_DEBUG_OUTLINE_LABEL = false; public static boolean RENDER_DEBUG_OUTLINE_ACTOR_PANEL = false; public static boolean RENDER_DEBUG_OUTLINE_SLIDER = false; + public static boolean RENDER_DEBUG_UI_TREE = false; static int outlineMaskPosition = 0; diff --git a/src/main/java/electrosphere/renderer/model/Material.java b/src/main/java/electrosphere/renderer/model/Material.java index 2855fca3..2d86ccd0 100644 --- a/src/main/java/electrosphere/renderer/model/Material.java +++ b/src/main/java/electrosphere/renderer/model/Material.java @@ -32,6 +32,14 @@ public class Material { } + /** + * Creates a material with a diffuse texture + * @param diffuse The path to the diffuse texture + */ + public Material(String diffuse){ + this.diffuse = diffuse; + } + //basically useless because blender doesn't support exporting mats with fbx public static Material load_material_from_aimaterial(AIMaterial input){ Material rVal = new Material(); diff --git a/src/main/java/electrosphere/renderer/pipelines/UIPipeline.java b/src/main/java/electrosphere/renderer/pipelines/UIPipeline.java index d7621670..0ad0e5bb 100644 --- a/src/main/java/electrosphere/renderer/pipelines/UIPipeline.java +++ b/src/main/java/electrosphere/renderer/pipelines/UIPipeline.java @@ -6,7 +6,9 @@ import electrosphere.engine.Globals; import electrosphere.renderer.OpenGLState; import electrosphere.renderer.RenderPipelineState; import electrosphere.renderer.RenderingEngine; +import electrosphere.renderer.debug.DebugRendering; import electrosphere.renderer.texture.Texture; +import electrosphere.renderer.ui.UIUtils; import electrosphere.renderer.ui.elementtypes.DrawableElement; import electrosphere.renderer.ui.elementtypes.Element; @@ -80,6 +82,10 @@ public class UIPipeline implements RenderPipeline { } } } + + if(DebugRendering.RENDER_DEBUG_UI_TREE){ + UIUtils.renderOutlineTree(openGLState, renderPipelineState, Globals.elementService.getWindowList().get(0)); + } Globals.profiler.endCpuSample(); } diff --git a/src/main/java/electrosphere/renderer/ui/UIUtils.java b/src/main/java/electrosphere/renderer/ui/UIUtils.java index bff8b574..46180d8e 100644 --- a/src/main/java/electrosphere/renderer/ui/UIUtils.java +++ b/src/main/java/electrosphere/renderer/ui/UIUtils.java @@ -1,8 +1,64 @@ package electrosphere.renderer.ui; +import org.joml.Vector3f; + +import electrosphere.engine.Globals; +import electrosphere.logger.LoggerInterface; +import electrosphere.renderer.OpenGLState; +import electrosphere.renderer.RenderPipelineState; +import electrosphere.renderer.RenderingEngine; +import electrosphere.renderer.model.Material; +import electrosphere.renderer.model.Model; +import electrosphere.renderer.ui.elementtypes.ContainerElement; +import electrosphere.renderer.ui.elementtypes.Element; + +/** + * Utilities for working with the ui + */ public class UIUtils { + + //the material that links the texture to draw + static Material customMat = new Material("Textures/ui/uiOutline1.png"); - public static void initUIComponents(){ + /** + * Renders the outline of the provided element and all child elements of the rootEl + * @param rootEl The top level element to parse downwards from + */ + public static void renderOutlineTree(OpenGLState openGLState, RenderPipelineState renderPipelineState, Element rootEl){ + //draw this element + float ndcWidth = (float)rootEl.getWidth()/Globals.WINDOW_WIDTH; + float ndcHeight = (float)rootEl.getHeight()/Globals.WINDOW_HEIGHT; + float ndcX = (float)(rootEl.getAbsoluteX())/Globals.WINDOW_WIDTH; + float ndcY = (float)(rootEl.getAbsoluteY())/Globals.WINDOW_HEIGHT; + Vector3f boxPosition = new Vector3f(ndcX,ndcY,0); + Vector3f boxDimensions = new Vector3f(ndcWidth,ndcHeight,0); + Vector3f texPosition = new Vector3f(1,1,0); + Vector3f texScale = new Vector3f(ndcWidth,ndcHeight,0); + Model planeModel = Globals.assetManager.fetchModel(Globals.imagePlaneModelID); + + openGLState.setActiveShader(renderPipelineState, RenderingEngine.screenTextureShaders); + openGLState.glViewport(Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT); + + renderPipelineState.setUseMaterial(true); + renderPipelineState.setBufferNonStandardUniforms(true); + + if(planeModel != null){ + planeModel.pushUniformToMesh("plane", "mPosition", boxPosition); + planeModel.pushUniformToMesh("plane", "mDimension", boxDimensions); + planeModel.pushUniformToMesh("plane", "tPosition", texPosition); + planeModel.pushUniformToMesh("plane", "tDimension", texScale); + planeModel.getMeshes().get(0).setMaterial(customMat); + planeModel.drawUI(); + } else { + LoggerInterface.loggerRenderer.ERROR("Image Panel unable to find plane model!!", new Exception()); + } + //draw children + if(rootEl instanceof ContainerElement){ + ContainerElement containerView = (ContainerElement)rootEl; + for(Element child : containerView.getChildren()){ + renderOutlineTree(openGLState, renderPipelineState, child); + } + } } } diff --git a/src/main/java/electrosphere/renderer/ui/WidgetUtils.java b/src/main/java/electrosphere/renderer/ui/WidgetUtils.java deleted file mode 100644 index 42594b55..00000000 --- a/src/main/java/electrosphere/renderer/ui/WidgetUtils.java +++ /dev/null @@ -1,165 +0,0 @@ -package electrosphere.renderer.ui; - -import electrosphere.controls.ControlHandler; -import electrosphere.engine.Globals; -import electrosphere.renderer.ui.elements.Button; -import electrosphere.renderer.ui.elements.Label; -import electrosphere.renderer.ui.elements.Window; -import electrosphere.renderer.ui.elementtypes.ClickableElement; -import electrosphere.renderer.ui.elementtypes.DrawableElement; - -/** - * Utilities for creating widgets within the ui framework - */ -public class WidgetUtils { - - - // public static TextBox createTextBox(int positionX, int positionY, int width, int height, int cols, int rows, String text, boolean render){ - // TextBox rVal = new TextBox(-width/2, positionY, width, height, rows, cols, text, render, false); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - - // public static TextBox createVerticallyAlignedTextBox(int width, int height, int windowTop, int cols, int rows, String text, boolean render){ - // TextBox rVal = new TextBox(-width/2, windowTop, width, height, rows, cols, text, render, false); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - - // public static TextBox createVerticallyAlignedMinSizeTextBox(int charWidth, int charHeight, int windowTop, int cols, int rows, String text, boolean render){ - // TextBox rVal = new TextBox(-cols * charWidth / 2, windowTop, cols * charWidth, rows * charHeight, rows, cols, text, render, false); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - - // public static TextBox createVerticallyAlignedMinSizeTextBoxFromCharCount(int charWidth, int charHeight, int windowTop, String text, boolean render){ - // int cols = text.length(); - // int rows = 1; - // TextBox rVal = new TextBox(-cols * charWidth / 2, windowTop, cols * charWidth, rows * charHeight, rows, cols, text, render, false); - // rVal.setVisible(true); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - - - - - // public static TextBox createVerticallyAlignedEditableTextBox(int width, int height, int windowTop, int cols, int rows, String text, boolean render){ - // TextBox rVal = new TextBox(-(int)(width/2.5), windowTop, width, height, rows, cols, text, render, true); - // rVal.setVisible(true); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - - // public static LayoutSchemeListScrollable createListTEST(){ - // LayoutSchemeListScrollable rVal = new LayoutSchemeListScrollable(200, 200, 500, 500, true); - // rVal.addWidget(createVerticallyAlignedMinSizeTextBoxFromCharCount(25, 25, 0, "TESTESTESTEST", true)); - // rVal.addWidget(WidgetUtils.createTextBox(100, 100, 500, 500, 3, 10, "TEST", true)); - // // Globals.widgetManager.registerWidget(rVal); - // return rVal; - // } - -// public static DrawableElement createWindowTEST(){ -// Window rVal = new Window(0, 0, 1920, 1080); -// // //panel 1 -// // ImagePanel imagePanel = new ImagePanel(); -// // imagePanel.setTexture(Globals.assetManager.fetchTexture(Globals.testingTexture)); -// // imagePanel.setPositionX(100); -// // imagePanel.setPositionY(100); -// // imagePanel.setVisible(true); -// // rVal.addWidget(imagePanel); -// // //panel 2 -// // imagePanel = new ImagePanel(); -// // imagePanel.setTexture(Globals.assetManager.fetchTexture(Globals.testingTexture)); -// // imagePanel.setVisible(true); -// // rVal.addWidget(imagePanel); -// // rVal.setVisible(true); -// // //window top -// // imagePanel = new ImagePanel(); -// // imagePanel.setTexture(Globals.assetManager.fetchTexture("Textures/ui/WindowBorder.png")); -// // imagePanel.setWidth(100); -// // imagePanel.setHeight(50); -// // imagePanel.setPositionX(200); -// // imagePanel.setPositionY(50); -// // imagePanel.setVisible(true); -// // rVal.addWidget(imagePanel); -// // TextInput textInput = new TextInput(); -// // textInput.setText("TESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\nAAAAAAAAAABBBBBBBBBB\n"); -// // textInput.setPositionX(0); -// // textInput.setPositionY(0); -// // textInput.setWidth(500); -// // textInput.setHeight(500); -// // textInput.setFontWidth(10); -// // textInput.setFontHeight(27); -// // textInput.setVisible(true); -// // rVal.addWidget(textInput); - - -// // BitmapCharacter characterDisp = new BitmapCharacter(0,0,500,500,'A'); -// // rVal.addWidget(characterDisp); - -// Label label = new Label(100,100,1); -// label.setText("TESTING"); -// rVal.addChild(label); - -// // TextInput textInput2 = new TextInput(); -// // textInput2.setText("TESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\nTESTESTE$STESTET\n"); -// // textInput2.setPositionX(500); -// // textInput2.setPositionY(0); -// // textInput2.setWidth(500); -// // textInput2.setHeight(500); -// // textInput2.setFontWidth(20); -// // textInput2.setFontHeight(40); -// // textInput2.setVisible(true); -// // rVal.addWidget(textInput2); - -// rVal.setVisible(true); -// // Globals.widgetManager.registerWidget(rVal); -// return rVal; -// } - - public static DrawableElement createInGameMainMenuButton(){ - int width = (int)(Globals.WINDOW_WIDTH * 0.05); - int height = (int)(Globals.WINDOW_HEIGHT * 0.05); - int x = Globals.WINDOW_WIDTH - width; - int y = Globals.WINDOW_HEIGHT - height; -// Window rVal = new Window(x, 10, 100, 20); - Window rVal = new Window(Globals.renderingEngine.getOpenGLState(),x,y,width,height,true); -// Window rVal = new Window(100,100,100,100); -// System.out.println(x + " " + y + " " + width + " " + height); -// LayoutSchemeListScrollable rVal = new LayoutSchemeListScrollable(x, y, width, height, true); -//// rVal.addWidget(createVerticallyAlignedMinSizeTextBoxFromCharCount(25, 25, 0, "TESTESTESTEST", true)); -// rVal.addWidget(WidgetUtils.createTextBox(0, 0, width, height, 1, 4, "MENU", true)); -// Widget rVal = WidgetUtils.createTextBox(x, y, width, height, 4, 1, "MENU", true); - - //the actual "menu" label - Label menuLabel = new Label(0.3f); - menuLabel.setText("Menu"); - menuLabel.setVisible(true); - rVal.addChild(menuLabel); - - //label telling player what key they have their menu bound to - Label keyCodeLabel = new Label(0.3f); - keyCodeLabel.setText(ControlHandler.convertKeycodeToName(Globals.controlHandler.getControl(ControlHandler.DATA_STRING_INPUT_CODE_IN_GAME_MAIN_MENU).getKeyValue())); - keyCodeLabel.setVisible(true); - rVal.addChild(keyCodeLabel); - - rVal.setVisible(false); -// Globals.inGameUI.add(rVal); - // Globals.widgetManager.registerWidget(rVal); - return rVal; - } - - public static Button createLabelButton(String label, int posX, int posY, float fontSize, ClickableElement.ClickEventCallback callback){ - Button rVal = new Button(); - Label buttonLabel = new Label(fontSize); - rVal.setPositionX(posX); - rVal.setPositionY(posY); - buttonLabel.setText(label); - rVal.addChild(buttonLabel); - rVal.setOnClick(callback); - return rVal; - } - - -} diff --git a/src/main/java/electrosphere/renderer/ui/elements/ActorPanel.java b/src/main/java/electrosphere/renderer/ui/elements/ActorPanel.java index 6bd52a68..71b180b9 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/ActorPanel.java +++ b/src/main/java/electrosphere/renderer/ui/elements/ActorPanel.java @@ -180,9 +180,9 @@ public class ActorPanel extends BufferedStandardDrawableContainerElement impleme //set viewport openGLState.glViewport(parentWidth, parentHeight); - float ndcX = (float)getInternalX()/parentWidth; - float ndcY = (float)getInternalY()/parentHeight; - float ndcWidth = (float)getInternalWidth()/parentWidth; + float ndcX = (float)(getInternalX() + parentPosX)/parentWidth; + float ndcY = (float)(getInternalY() + parentPosY)/parentHeight; + float ndcWidth = (float)getInternalWidth()/parentWidth; float ndcHeight = (float)getInternalHeight()/parentHeight; Vector3f boxPosition = new Vector3f(ndcX,ndcY,0); diff --git a/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java b/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java index a54611e2..664a7e61 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java +++ b/src/main/java/electrosphere/renderer/ui/elements/BitmapCharacter.java @@ -144,10 +144,8 @@ public class BitmapCharacter extends StandardElement implements DrawableElement this.internalHeight = 0; } //calculate absolute values - if(!useAbsolutePosition){ - this.absoluteX = parentX + internalPositionX; - this.absoluteY = parentY + internalPositionY; - } + this.absoluteX = parentX + internalPositionX; + this.absoluteY = parentY + internalPositionY; } } diff --git a/src/main/java/electrosphere/renderer/ui/elements/ScrollableContainer.java b/src/main/java/electrosphere/renderer/ui/elements/ScrollableContainer.java index 85f5201a..e1f8b6a4 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/ScrollableContainer.java +++ b/src/main/java/electrosphere/renderer/ui/elements/ScrollableContainer.java @@ -71,7 +71,7 @@ public class ScrollableContainer extends BufferedStandardDrawableContainerElemen this.height = DEFAULT_HEIGHT; setWidth(DEFAULT_WIDTH); - setWidth(DEFAULT_HEIGHT); + setHeight(DEFAULT_HEIGHT); } /** @@ -116,49 +116,49 @@ public class ScrollableContainer extends BufferedStandardDrawableContainerElemen ) { if(this.elementBuffer != null){ //figure out if currently focused element is a child or subchild of this container - if(containsFocusedElement(this)){ - //if it is, if it is offscreen, calculate offset to put it onscreen - Element focused = Globals.elementService.getFocusedElement(); - if( - focused.getRelativeX() + focused.getWidth() > this.width || - focused.getRelativeY() + focused.getHeight() > this.height || - focused.getRelativeX() < 0 || - focused.getRelativeY() < 0 - ){ - int neededOffsetX = 0; - int neededOffsetY = 0; - //basically if we're offscreen negative, pull to positive - //if we're offscreen positive and we're not as large as the screen, pull from the positive into focus - //if we are larger than the screen, set position to 0 - if(focused.getRelativeX() < 0){ - neededOffsetX = -focused.getRelativeX(); - } else if(focused.getRelativeX() + focused.getWidth() > this.width){ - if(focused.getWidth() > this.width){ - neededOffsetX = -focused.getRelativeX(); - } else { - neededOffsetX = -((focused.getRelativeX() - this.width) + focused.getWidth()); - } - } - if(focused.getRelativeY() < 0){ - neededOffsetY = -focused.getRelativeY(); - } else if(focused.getRelativeY() + focused.getHeight() > this.height){ - if(focused.getHeight() > this.height){ - neededOffsetY = -focused.getRelativeY(); - } else { - neededOffsetY = -((focused.getRelativeY() - this.height) + focused.getHeight()); - // System.out.println(focused.getPositionY() + " " + this.height + " " + focused.getHeight()); - } - } - //apply offset to all children - for(Element child : childList){ - int newX = child.getRelativeX() + neededOffsetX; - int newY = child.getRelativeY() + neededOffsetY; - child.setPositionX(newX); - child.setPositionY(newY); - // System.out.println(currentX + " " + currentY); - } - } - } + // if(containsFocusedElement(this)){ + // //if it is, if it is offscreen, calculate offset to put it onscreen + // Element focused = Globals.elementService.getFocusedElement(); + // if( + // focused.getRelativeX() + focused.getWidth() > this.width || + // focused.getRelativeY() + focused.getHeight() > this.height || + // focused.getRelativeX() < 0 || + // focused.getRelativeY() < 0 + // ){ + // int neededOffsetX = 0; + // int neededOffsetY = 0; + // //basically if we're offscreen negative, pull to positive + // //if we're offscreen positive and we're not as large as the screen, pull from the positive into focus + // //if we are larger than the screen, set position to 0 + // if(focused.getRelativeX() < 0){ + // neededOffsetX = -focused.getRelativeX(); + // } else if(focused.getRelativeX() + focused.getWidth() > this.width){ + // if(focused.getWidth() > this.width){ + // neededOffsetX = -focused.getRelativeX(); + // } else { + // neededOffsetX = -((focused.getRelativeX() - this.width) + focused.getWidth()); + // } + // } + // if(focused.getRelativeY() < 0){ + // neededOffsetY = -focused.getRelativeY(); + // } else if(focused.getRelativeY() + focused.getHeight() > this.height){ + // if(focused.getHeight() > this.height){ + // neededOffsetY = -focused.getRelativeY(); + // } else { + // neededOffsetY = -((focused.getRelativeY() - this.height) + focused.getHeight()); + // // System.out.println(focused.getPositionY() + " " + this.height + " " + focused.getHeight()); + // } + // } + // //apply offset to all children + // for(Element child : childList){ + // int newX = child.getRelativeX() + neededOffsetX; + // int newY = child.getRelativeY() + neededOffsetY; + // child.setPositionX(newX); + // child.setPositionY(newY); + // // System.out.println(currentX + " " + currentY); + // } + // } + // } float ndcWidth = (float)getWidth()/parentWidth; float ndcHeight = (float)getHeight()/parentHeight; diff --git a/src/main/java/electrosphere/renderer/ui/elements/Slider.java b/src/main/java/electrosphere/renderer/ui/elements/Slider.java index 5e77dbc6..1cfda195 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/Slider.java +++ b/src/main/java/electrosphere/renderer/ui/elements/Slider.java @@ -287,20 +287,20 @@ public class Slider extends StandardDrawableElement implements ClickableElement, //default behavior switch(menuEvent.getType()){ case INCREMENT: - value = Math.min(value + ((max - min) * 0.01f),max); - value = this.valueFromPercentage(value); - if(onValueChange != null){ - onValueChange.execute(new ValueChangeEvent(value)); - } - propagate = false; + value = Math.min(value + ((max - min) * 0.01f),max); + value = this.valueFromPercentage(value); + if(onValueChange != null){ + onValueChange.execute(new ValueChangeEvent(value)); + } + propagate = false; break; case DECREMENT: - value = Math.max(value - ((max - min) * 0.01f),min); - value = this.valueFromPercentage(value); - if(onValueChange != null){ - onValueChange.execute(new ValueChangeEvent(value)); - } - propagate = false; + value = Math.max(value - ((max - min) * 0.01f),min); + value = this.valueFromPercentage(value); + if(onValueChange != null){ + onValueChange.execute(new ValueChangeEvent(value)); + } + propagate = false; break; } } diff --git a/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java b/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java index 4ce4530c..a89a0b2c 100644 --- a/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java +++ b/src/main/java/electrosphere/renderer/ui/elements/StringCarousel.java @@ -42,6 +42,16 @@ public class StringCarousel extends StandardContainerElement implements Drawable float fontSize = 1.0f; Font font; + + /** + * The default width of an actor panel + */ + public static final int DEFAULT_WIDTH = 200; + + /** + * The default height of an actor panel + */ + public static final int DEFAULT_HEIGHT = 32; public StringCarousel(int x, int y, float fontSize){ super(); @@ -57,6 +67,8 @@ public class StringCarousel extends StandardContainerElement implements Drawable private StringCarousel(){ super(); this.font = Globals.fontManager.getFont("default"); + Yoga.YGNodeStyleSetMinHeight(this.yogaNode, font.imageHeight * fontSize); + Yoga.YGNodeStyleSetMinWidth(this.yogaNode, 1); } /** @@ -69,6 +81,10 @@ public class StringCarousel extends StandardContainerElement implements Drawable rVal.setOnValueChangeCallback(new ValueChangeEventCallback() {public void execute(ValueChangeEvent event) { callback.accept(event); }}); + rVal.options = options; + rVal.currentOption = 0; + rVal.setText(options.get(0)); + rVal.setFlexDirection(YogaFlexDirection.Row); return rVal; } @@ -99,12 +115,15 @@ public class StringCarousel extends StandardContainerElement implements Drawable Globals.signalSystem.post(SignalType.YOGA_DESTROY, el); } this.clearChildren(); + int accumulatingWidth = 0; for(int i = 0; i < textCurrent.length(); i++){ char toDraw = textCurrent.charAt(i); Vector3f bitMapDimension = this.font.getDimensionOfCharacterDiscrete(toDraw); BitmapCharacter newLetter = new BitmapCharacter(this.font,(int)(bitMapDimension.x * fontSize), this.height, toDraw); + accumulatingWidth += bitMapDimension.x * fontSize; addChild(newLetter); } + Yoga.YGNodeStyleSetWidth(yogaNode, accumulatingWidth); } public void setText(String text){ diff --git a/src/main/java/electrosphere/renderer/ui/layout/LayoutScheme.java b/src/main/java/electrosphere/renderer/ui/layout/LayoutScheme.java deleted file mode 100644 index e4665d1e..00000000 --- a/src/main/java/electrosphere/renderer/ui/layout/LayoutScheme.java +++ /dev/null @@ -1,13 +0,0 @@ -package electrosphere.renderer.ui.layout; - - -import electrosphere.renderer.ui.elementtypes.ContainerElement; - -/** - * A scheme for laying out ui elements - */ -public abstract interface LayoutScheme extends ContainerElement { - - public abstract void pack(); - -} diff --git a/src/main/java/electrosphere/renderer/ui/macros/InputMacros.java b/src/main/java/electrosphere/renderer/ui/macros/InputMacros.java index 25e322a9..d2787d03 100644 --- a/src/main/java/electrosphere/renderer/ui/macros/InputMacros.java +++ b/src/main/java/electrosphere/renderer/ui/macros/InputMacros.java @@ -28,6 +28,7 @@ public class InputMacros { * @param placeholder The placeholder (can be null if no placeholder desired) * @return The div encapsulating all the individual elements */ + @Deprecated public static Div createTextInput(String label, String placeholder){ Div rVal = Div.createDiv(); rVal.setFlexDirection(YogaFlexDirection.Row); @@ -54,6 +55,7 @@ public class InputMacros { * @param callback A callback fired when the text input changes value * @return The div encapsulating all the individual elements */ + @Deprecated public static Div createTextInput(String label, String placeholder, Consumer callback){ Div rVal = Div.createDiv(); rVal.setFlexDirection(YogaFlexDirection.Row); @@ -82,6 +84,7 @@ public class InputMacros { * @param defaultValue The default value for the slider (between 0.0 and 1.0) * @return The slider element */ + @Deprecated public static Div createSliderInput(String label, Consumer onChange, float defaultValue){ Div rVal = Div.createDiv(); rVal.setFlexDirection(YogaFlexDirection.Row); @@ -106,6 +109,7 @@ public class InputMacros { * @param onChange The on change callback * @return The div containing a labeled toggle */ + @Deprecated public static Div createToggle(String label, boolean defaultValue, Consumer onChange){ Div rVal = Div.createDiv(); rVal.setFlexDirection(YogaFlexDirection.Row); diff --git a/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java b/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java index fecc269a..7ed2b3ac 100644 --- a/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java +++ b/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java @@ -43,6 +43,19 @@ public class PlayerCharacterCreation { Entity newPlayerEntity = CreatureUtils.serverSpawnBasicCreature(realm,new Vector3d(spawnPoint.x,spawnPoint.y,spawnPoint.z),raceName,template); // + //attach entity to player object + LoggerInterface.loggerEngine.INFO("Spawned entity for player. Entity id: " + newPlayerEntity.getId() + " Player id: " + playerObject.getId()); + attachEntityToPlayerObject(newPlayerEntity,playerObject,connectionHandler); + playerObject.setWorldPos(new Vector3i( + realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.x), + realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.y), + realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.z) + )); + realm.getDataCellManager().addPlayerToRealm(playerObject); + + + // + //must happen after the player is attached to the entity, or server won't send packet to add item to player's entity //now that creature has been spawned, need to create all attached items if(template != null && template.getCreatureEquipData() != null && template.getCreatureEquipData().getSlots() != null){ for(String equipSlotId : template.getCreatureEquipData().getSlots()){ @@ -60,17 +73,6 @@ public class PlayerCharacterCreation { } } - // - //attach entity to player object - LoggerInterface.loggerEngine.INFO("Spawned entity for player. Entity id: " + newPlayerEntity.getId() + " Player id: " + playerObject.getId()); - attachEntityToPlayerObject(newPlayerEntity,playerObject,connectionHandler); - playerObject.setWorldPos(new Vector3i( - realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.x), - realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.y), - realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.z) - )); - realm.getDataCellManager().addPlayerToRealm(playerObject); - // //error checking Realm searchedRealm = Globals.realmManager.getEntityRealm(newPlayerEntity); diff --git a/src/test/java/electrosphere/renderer/ui/MainMenuTests.java b/src/test/java/electrosphere/renderer/ui/MainMenuTests.java index 1fa030cf..762cc263 100644 --- a/src/test/java/electrosphere/renderer/ui/MainMenuTests.java +++ b/src/test/java/electrosphere/renderer/ui/MainMenuTests.java @@ -1,5 +1,7 @@ package electrosphere.renderer.ui; +import org.junit.jupiter.api.Disabled; + import electrosphere.menu.WindowUtils; import electrosphere.menu.mainmenu.MenuGeneratorsUITesting; import electrosphere.test.annotations.IntegrationTest; @@ -15,6 +17,7 @@ public class MainMenuTests extends UITestTemplate { * Tests creating a window */ @IntegrationTest + @Disabled public void test_UITestWindow_Create(){ //create ui testing window TestEngineUtils.simulateFrames(1); @@ -23,7 +26,7 @@ public class MainMenuTests extends UITestTemplate { //wait for ui updates TestEngineUtils.flush(); - TestEngineUtils.simulateFrames(1); + TestEngineUtils.simulateFrames(3); // TestRenderingUtils.saveTestRender("./test/java/renderer/ui/elements/window.png"); this.checkRender("Basic", "./test/java/renderer/ui/uitest.png"); diff --git a/src/test/java/electrosphere/renderer/ui/elements/SliderTests.java b/src/test/java/electrosphere/renderer/ui/elements/SliderTests.java index 20335fa7..89b60b72 100644 --- a/src/test/java/electrosphere/renderer/ui/elements/SliderTests.java +++ b/src/test/java/electrosphere/renderer/ui/elements/SliderTests.java @@ -1,10 +1,29 @@ package electrosphere.renderer.ui.elements; -import static org.junit.jupiter.api.Assertions.*; +import electrosphere.menu.WindowUtils; +import electrosphere.renderer.ui.events.ValueChangeEvent; +import electrosphere.test.annotations.IntegrationTest; +import electrosphere.test.template.UITestTemplate; +import electrosphere.test.testutils.TestEngineUtils; /** * Tests for a slider element */ -public class SliderTests { +public class SliderTests extends UITestTemplate { + @IntegrationTest + public void test_Create(){ + //setup + this.setupBlankView(); + WindowUtils.replaceMainMenuContents(Slider.createSlider((ValueChangeEvent event) -> { + })); + + + //wait for ui updates + TestEngineUtils.flush(); + TestEngineUtils.simulateFrames(1); + + this.checkRender("Basic", "./test/java/renderer/ui/elements/slider1.png"); + } + } diff --git a/src/test/java/electrosphere/renderer/ui/elements/StringCarouselTests.java b/src/test/java/electrosphere/renderer/ui/elements/StringCarouselTests.java index d8f1b6a4..4b84c1c9 100644 --- a/src/test/java/electrosphere/renderer/ui/elements/StringCarouselTests.java +++ b/src/test/java/electrosphere/renderer/ui/elements/StringCarouselTests.java @@ -1,10 +1,31 @@ package electrosphere.renderer.ui.elements; -import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; + +import electrosphere.menu.WindowUtils; +import electrosphere.renderer.ui.events.ValueChangeEvent; +import electrosphere.test.annotations.IntegrationTest; +import electrosphere.test.template.UITestTemplate; +import electrosphere.test.testutils.TestEngineUtils; /** * Unit tests for a string carousel */ -public class StringCarouselTests { +public class StringCarouselTests extends UITestTemplate { + @IntegrationTest + public void test_Create(){ + //setup + this.setupBlankView(); + WindowUtils.replaceMainMenuContents(StringCarousel.create(Arrays.asList(new String[]{"Test"}), (ValueChangeEvent event) -> { + })); + + + //wait for ui updates + TestEngineUtils.flush(); + TestEngineUtils.simulateFrames(1); + + this.checkRender("Basic", "./test/java/renderer/ui/elements/stringcarousel1.png"); + } + } diff --git a/test/java/renderer/ui/elements/slider1.png b/test/java/renderer/ui/elements/slider1.png new file mode 100644 index 0000000000000000000000000000000000000000..17d0a2375e342fc753b49d4910458e1735790d7e GIT binary patch literal 10284 zcmeAS@N?(olHy`uVBq!ia0y~yU~gbxV6os}1B$%3e9)PJLH)C*i(^Pd+?$(fdn1IykXOmD z|A~PBgm*xf|G{(_HYNyf$5X~XlMXvT6>k5<@bBlpd7t0&uI6KAgb3dG1T-yC0m5mB zmOG$t$-@HS6`Ur|7Mq6nA_)$N84sqhfAGd-SuMlo|G1n=v>}`jix_gepp1lIsOK4G z3qgf$Oo1{EEQ9)u!Aun@v_TUZ5)HniMghZQG^{`gV>B^M`bW$|$o=96n!#trrn1H&vmC?jJRl(FC?bZytrRkVOH>8@Lb{&bIAke@tV{an^L HB{Ts5zQfR;N(wLLN9Y#h%JQzEm)>R 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 literal 33383 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