diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/VRKeyboardAccessor.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/VRKeyboardAccessor.java index dac07fe2..d476d383 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/VRKeyboardAccessor.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/VRKeyboardAccessor.java @@ -26,7 +26,7 @@ public interface VRKeyboardAccessor { * * @param attachTo the screen that has to be attached to keyboard */ - void showKeyboard(@NotNull Screen attachTo); + void showKeyboard(@Nullable Screen attachTo); /** * The screen, keyboard is attached to diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/VROverlay.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/VROverlay.java index aecc2ac8..47bdb6d1 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/VROverlay.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/VROverlay.java @@ -12,6 +12,8 @@ import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Vector3f; import java.util.Collection; @@ -79,6 +81,7 @@ default boolean isInViewDistance(){ */ boolean isVisible(); + /** * If this overlay is custom, * i.e. created from template @@ -99,6 +102,18 @@ default boolean isBuiltIn(){ return asTemplate() == null; } + /** + * Get this overlay as template + * + * @return overlay template or null if not an instance of {@link VROverlayTemplate} + */ + default @Nullable VROverlayTemplate asTemplate(){ + if(this instanceof VROverlayTemplate overlayTemplate){ + return overlayTemplate; + }else{ + return null; + } + } /** * Get overlay name @@ -129,6 +144,11 @@ default Component getDescription(){ } + + //--------------------------- + //--------- OPTIONS --------- + //--------------------------- + /** * Get collection of overlay options * @@ -217,6 +237,9 @@ default void reloadOption(@NotNull String id){ + //--------------------------------------------- + //--------- FORCED ANCHOR && DRAGGING --------- + //--------------------------------------------- /** * Get the forced anchor @@ -242,19 +265,61 @@ default void reloadOption(@NotNull String id){ /** - * Get this overlay as template + * Start dragging this overlay with the currently active cursor hand + */ + void startDragging(); + + /** + * Stop dragging and persist the pose back into pose options when available + */ + void stopDragging(); + + /** + * If overlay is being dragged rightt now * - * @return overlay template or null if not an instance of {@link VROverlayTemplate} + * @return true/false */ - default @Nullable VROverlayTemplate asTemplate(){ - if(this instanceof VROverlayTemplate overlayTemplate){ - return overlayTemplate; - }else{ - return null; + default boolean isBeingDragged() { + return false; + } + + /** + * If specified raw cursor position is over the drag handle + * + * @param rawX raw cursor x + * @param rawY raw cursor y + * @return true/false + */ + default boolean isCursorOnDragHandle(float rawX, float rawY) { + if (!supportsDragging()) { + return false; + } + int edgeX = getCursorBoundsX(); + int edgeY = getCursorBoundsY(); + int edgeWidth = getCursorBoundsWidth(); + int edgeHeight = getCursorBoundsHeight(); + int width = getWidth(); + int height = getHeight(); + //If cursorBounds are set + if (width > 0 && height > 0 + && edgeX >= 0 && edgeY >= 0 + && edgeWidth >= 0 && edgeHeight >= 0) { + float rawLeft = (float) edgeX / width; + float rawRight = (float) (edgeX + edgeWidth) / width; + float rawTop = (float) (edgeY + edgeHeight) / height; + float rawBottom = rawTop + 0.15f; + return rawX >= rawLeft && rawX <= rawRight + && rawY > rawTop && rawY <= rawBottom; } + return rawX >= 0f && rawX <= 1f + && rawY > 1.0f && rawY <= 1.15f; } + //-------------------------------------- + //--------- SUPPORTED FEATURES --------- + //-------------------------------------- + /** * If supports update of visibility each render call, instead of tick() * @return true/false @@ -316,7 +381,19 @@ default boolean supportsTwoCursors(){ return false; } + /** + * If overlay can be dragged and repositioned by the player + * + * @return true/false + */ + default boolean supportsDragging() { + return false; + } + + //---------------------------------------- + //--------- CURSOR && RESOLUTION --------- + //---------------------------------------- /** * Get Data for active cursor @@ -472,6 +549,10 @@ default boolean isWithinCursorBounds(float rawX, float rawY) { } + //---------------------------- + //--------- MC STUFF --------- + //---------------------------- + /** * Get active cursor position X * @@ -635,6 +716,10 @@ boolean mouseDragged(double mouseX, double mouseY, boolean charTyped(char chr, int modifiers); + //------------------------- + //--------- EXTRA --------- + //------------------------- + /** * Override of {@link PrioritySupporter#compareTo(PrioritySupporter)} * to sort components in reverse priority order, diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayFrameBuffer.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayFrameBuffer.java index 6aaba7d8..f4978caa 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayFrameBuffer.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayFrameBuffer.java @@ -6,14 +6,22 @@ import me.phoenixra.atumconfig.api.config.ConfigFile; import org.vmstudio.visor.api.VisorAPI; import org.vmstudio.visor.api.client.gui.overlays.*; +import org.vmstudio.visor.api.client.gui.overlays.options.types.OverlayOptionsPose; +import org.vmstudio.visor.api.client.player.pose.PlayerPoseType; import org.vmstudio.visor.api.client.player.pose.PoseAnchor; import org.vmstudio.visor.api.client.gui.overlays.options.OverlayOptionGroup; +import org.vmstudio.visor.api.client.player.pose.VRPlayerPoseClient; +import org.vmstudio.visor.api.common.HandType; import org.vmstudio.visor.api.common.VRException; import org.vmstudio.visor.api.common.addon.component.ComponentPriority; import org.vmstudio.visor.api.common.addon.VisorAddon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.joml.Vector3f; +import org.joml.Vector3fc; +import org.vmstudio.visor.api.common.player.VRPose; import java.io.IOException; import java.util.*; @@ -66,6 +74,10 @@ public abstract class VROverlayFrameBuffer implements VROverlay { @Getter private boolean visible = false; + private boolean beingDragged = false; + private Vector3f dragPositionOffset = new Vector3f(0, 0, -0.3f); + private Matrix4f dragRotationMatrix = new Matrix4f(); + public VROverlayFrameBuffer(@NotNull VisorAddon owner, @NotNull String id){ @@ -127,9 +139,10 @@ protected void onRender(float partialTicks) {} protected abstract void onUpdatePose(float partialTicks); - protected abstract boolean updateVisibility(); + protected void onStoppedDragging() {}; + protected void onEnable() {} protected void onDisable() {} @@ -174,15 +187,7 @@ public void render(float partialTick){ @Override public final void updatePose(float partialTicks) { if(forcedAnchor != null) { - VROverlayHelper.applyPose( - this, - forcedAnchor, - forcedAnchor, - getPose().getScale(), - false, - new Vector3f(0,0,-0.3f), - new Vector3f(0,0,0) - ); + applyForcedPose(); return; } onUpdatePose(partialTicks); @@ -203,11 +208,96 @@ public void setEnabled(boolean flag) { } } + + @Override + public void startDragging() { + var vrClient = VisorAPI.client(); + + HandType cursorHand = vrClient.getGuiManager().getCursorHandler().getCursorHand(); + PoseAnchor dragAnchor = cursorHand == HandType.MAIN + ? PoseAnchor.MAIN_HAND + : PoseAnchor.OFFHAND; + VRPlayerPoseClient renderPose = vrClient.getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + VRPose anchorPose = dragAnchor.getSupplier().apply(renderPose); + + Vector3f dragPositionOffset = anchorPose.reverseCustomVector( + getPose().getPosition().sub(anchorPose.getPosition(), new Vector3f()) + ).div(renderPose.getWorldScale()); + Matrix4f dragRotation = anchorPose.getRotation() + .invert(new Matrix4f()) + .mul(getPose().getRotation(), new Matrix4f()); + + this.dragPositionOffset.set(dragPositionOffset); + this.dragRotationMatrix.set(dragRotation); + this.beingDragged = true; + setForcedAnchor(dragAnchor); + } + + @Override + public void stopDragging() { + setForcedAnchor(null); + this.beingDragged = false; + + OverlayOptionsPose poseOptions = getOption(OverlayOptionsPose.ID, OverlayOptionsPose.class); + if (poseOptions != null) { + VRPlayerPoseClient renderPose = VisorAPI.client().getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + + PoseAnchor posAnchor = poseOptions.getPositionAnchor(); + VRPose posAnchorPose = posAnchor.getSupplier().apply(renderPose); + Vector3f offsetPos = posAnchorPose.reverseCustomVector( + getPose().getPosition().sub(posAnchorPose.getPosition(), new Vector3f()) + ).div(renderPose.getWorldScale()); + + poseOptions.setPositionOffset(offsetPos); + + if (!poseOptions.isAimedRotation()) { + PoseAnchor rotAnchor = poseOptions.getRotationAnchor(); + VRPose rotAnchorPose = rotAnchor.getSupplier().apply(renderPose); + Vector3f rotOffset = rotAnchor.reverseAnchoredRotation( + rotAnchorPose.getRotation(), getPose().getRotation() + ); + poseOptions.setRotationOffset(rotOffset); + } + + poseOptions.save(); + } + + onStoppedDragging(); + } + + protected void applyForcedPose() { + if (forcedAnchor == null) { + return; + } + + VRPlayerPoseClient renderPose = VisorAPI.client().getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + VRPose anchorPose = forcedAnchor.getSupplier().apply(renderPose); + + Vector3f positionOffset = new Vector3f(dragPositionOffset) + .mul(renderPose.getWorldScale()); + Vector3f newPosition = anchorPose.getCustomVector(positionOffset) + .add(anchorPose.getPosition()); + Matrix4f newRotation = new Matrix4f(anchorPose.getRotation()) + .mul(dragRotationMatrix, new Matrix4f()); + + getPose().update( + newPosition, + newRotation, + getPose().getScale() + ); + } + @Override public @Nullable OverlayOptionGroup getOption(@NotNull String id) { return optionsMap.get(id); } + @Override + public boolean isBeingDragged() { + return beingDragged; + } + + @Override public boolean supportsCursor() { return false; diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayScreen.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayScreen.java index d2e22dd9..5ec0edaa 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayScreen.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/VROverlayScreen.java @@ -6,8 +6,12 @@ import me.phoenixra.atumconfig.api.config.ConfigFile; import org.vmstudio.visor.api.VisorAPI; import org.vmstudio.visor.api.client.gui.overlays.*; +import org.vmstudio.visor.api.client.gui.overlays.options.types.OverlayOptionsPose; +import org.vmstudio.visor.api.client.player.pose.PlayerPoseType; import org.vmstudio.visor.api.client.player.pose.PoseAnchor; import org.vmstudio.visor.api.client.gui.overlays.options.OverlayOptionGroup; +import org.vmstudio.visor.api.client.player.pose.VRPlayerPoseClient; +import org.vmstudio.visor.api.common.HandType; import org.vmstudio.visor.api.common.VRException; import org.vmstudio.visor.api.common.addon.component.ComponentPriority; import org.vmstudio.visor.api.common.addon.VisorAddon; @@ -18,7 +22,9 @@ import net.minecraft.util.Mth; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; import org.joml.Vector3f; +import org.vmstudio.visor.api.common.player.VRPose; import java.io.IOException; import java.util.*; @@ -89,6 +95,10 @@ public abstract class VROverlayScreen extends Screen implements VROverlay { private static long mouseDragDelay; + private boolean beingDragged = false; + private Vector3f dragPositionOffset = new Vector3f(0, 0, -0.3f); + private Matrix4f dragRotationMatrix = new Matrix4f(); + public VROverlayScreen(@NotNull VisorAddon owner, @@ -155,9 +165,10 @@ protected void onRender(GuiGraphics guiGraphics, protected abstract void onUpdatePose(float partialTicks); - protected abstract boolean updateVisibility(); + protected void onStoppedDragging() {}; + protected void onEnable() {}; protected void onDisable() {}; @@ -249,15 +260,7 @@ public final void render(@NotNull GuiGraphics guiGraphics, @Override public final void updatePose(float partialTicks) { if(forcedAnchor != null) { - VROverlayHelper.applyPose( - this, - forcedAnchor, - forcedAnchor, - getPose().getScale(), - false, - new Vector3f(0,0,-0.3f), - new Vector3f(0,0,0) - ); + applyForcedPose(); return; } onUpdatePose(partialTicks); @@ -283,21 +286,12 @@ public void setEnabled(boolean flag) { .getOverlayManager() .getKeyboardAccessor(); if (keyboardAccessor.getAttachedTo() == this) { - keyboardAccessor.setVisible(false); + keyboardAccessor.showKeyboard(null); } onDisable(); } } - public boolean canDragMouse(){ - return mouseDragDelay < System.currentTimeMillis(); - } - public void startDragMouse(){ - mouseDragDelay = System.currentTimeMillis() + 100L; - } - public void finishDragMouse(){ - mouseDragDelay = Long.MAX_VALUE; - } public void updateSize(){ guiScaleFactor = VisorAPI.client().getGuiManager().calculateScale( 0, @@ -311,12 +305,101 @@ public void updateSize(){ ); } + @Override + public void startDragging() { + var vrClient = VisorAPI.client(); + + HandType cursorHand = vrClient.getGuiManager().getCursorHandler().getCursorHand(); + PoseAnchor dragAnchor = cursorHand == HandType.MAIN + ? PoseAnchor.MAIN_HAND + : PoseAnchor.OFFHAND; + VRPlayerPoseClient renderPose = vrClient.getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + VRPose anchorPose = dragAnchor.getSupplier().apply(renderPose); + + Vector3f dragPositionOffset = anchorPose.reverseCustomVector( + getPose().getPosition().sub(anchorPose.getPosition(), new Vector3f()) + ).div(renderPose.getWorldScale()); + Matrix4f dragRotation = anchorPose.getRotation() + .invert(new Matrix4f()) + .mul(getPose().getRotation(), new Matrix4f()); + + this.dragPositionOffset.set(dragPositionOffset); + this.dragRotationMatrix.set(dragRotation); + this.beingDragged = true; + setForcedAnchor(dragAnchor); + } + + public void stopDragging() { + setForcedAnchor(null); + this.beingDragged = false; + + OverlayOptionsPose poseOptions = getOption(OverlayOptionsPose.ID, OverlayOptionsPose.class); + if (poseOptions != null) { + VRPlayerPoseClient renderPose = VisorAPI.client().getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + + PoseAnchor posAnchor = poseOptions.getPositionAnchor(); + VRPose posAnchorPose = posAnchor.getSupplier().apply(renderPose); + Vector3f offsetPos = posAnchorPose.reverseCustomVector( + getPose().getPosition().sub(posAnchorPose.getPosition(), new Vector3f()) + ).div(renderPose.getWorldScale()); + + poseOptions.setPositionOffset(offsetPos); + + if (!poseOptions.isAimedRotation()) { + PoseAnchor rotAnchor = poseOptions.getRotationAnchor(); + VRPose rotAnchorPose = rotAnchor.getSupplier().apply(renderPose); + Vector3f rotOffset = rotAnchor.reverseAnchoredRotation( + rotAnchorPose.getRotation(), getPose().getRotation() + ); + poseOptions.setRotationOffset(rotOffset); + } + + poseOptions.save(); + } + + onStoppedDragging(); + } + + protected void applyForcedPose() { + if (forcedAnchor == null) { + return; + } + + VRPlayerPoseClient renderPose = VisorAPI.client().getVRLocalPlayer().getPoseData(PlayerPoseType.RENDER); + VRPose anchorPose = forcedAnchor.getSupplier().apply(renderPose); + + Vector3f positionOffset = new Vector3f(dragPositionOffset) + .mul(renderPose.getWorldScale()); + Vector3f newPosition = anchorPose.getCustomVector(positionOffset) + .add(anchorPose.getPosition()); + Matrix4f newRotation = new Matrix4f(anchorPose.getRotation()) + .mul(dragRotationMatrix, new Matrix4f()); + + getPose().update( + newPosition, + newRotation, + getPose().getScale() + ); + } + + public boolean canDragMouse(){ + return mouseDragDelay < System.currentTimeMillis(); + } + public void startDragMouse(){ + mouseDragDelay = System.currentTimeMillis() + 100L; + } + public void finishDragMouse(){ + mouseDragDelay = Long.MAX_VALUE; + } + @Override public void updateCursorData(boolean activeCursor, float rawX, float rawY) { if (!enabled) return; - if (rawX < 0f || rawX > 1f - || rawY < 0f || rawY > 1f) { - return; + boolean withinGui = rawX >= 0f && rawX <= 1f + && rawY >= 0f && rawY <= 1f; + boolean onDragHandle = isCursorOnDragHandle(rawX, rawY); + if (!withinGui && !onDragHandle) { + return; } // ---- Preparing @@ -342,6 +425,10 @@ public void updateCursorData(boolean activeCursor, float rawX, float rawY) { return; } + if (!withinGui) { + return; + } + // ---- Move and Drag events mouseMoved(cursorData.getCursorX(), cursorData.getCursorY()); @@ -357,7 +444,10 @@ public void updateCursorData(boolean activeCursor, float rawX, float rawY) { } - + @Override + public boolean isBeingDragged() { + return beingDragged; + } public boolean isVisible() { @@ -372,10 +462,18 @@ public boolean isVisible() { @Override public boolean mouseClicked(double mouseX, double mouseY, int buttonType) { + if (buttonType == 0 && isCursorOnDragHandle(getRawMouseX(), getRawMouseY())) { + startDragging(); + return true; + } return super.mouseClicked(mouseX, mouseY, buttonType); } @Override public boolean mouseReleased(double mouseX, double mouseY, int buttonType) { + if (buttonType == 0 && isBeingDragged()) { + stopDragging(); + return true; + } return super.mouseReleased(mouseX, mouseY, buttonType); } @Override diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/screen/VROverlayScreenInScreen.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/screen/VROverlayScreenInScreen.java index 8c89fdc5..66aa813b 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/screen/VROverlayScreenInScreen.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/gui/overlays/framework/screen/VROverlayScreenInScreen.java @@ -61,12 +61,20 @@ protected void onRender(GuiGraphics guiGraphics, @Override public boolean mouseClicked(double mouseX, double mouseY, int buttonType) { + if (buttonType == 0 && isCursorOnDragHandle(getRawMouseX(), getRawMouseY())) { + startDragging(); + return true; + } if(screen==null) return true; return screen.mouseClicked(mouseX, mouseY, buttonType); } @Override public boolean mouseReleased(double mouseX, double mouseY, int buttonType) { + if (buttonType == 0 && isBeingDragged()) { + stopDragging(); + return true; + } if(screen==null) return true; return screen.mouseReleased(mouseX, mouseY, buttonType); } diff --git a/visor-api/src/main/java/org/vmstudio/visor/api/client/player/pose/PoseAnchor.java b/visor-api/src/main/java/org/vmstudio/visor/api/client/player/pose/PoseAnchor.java index c2fa6621..dfa5a825 100644 --- a/visor-api/src/main/java/org/vmstudio/visor/api/client/player/pose/PoseAnchor.java +++ b/visor-api/src/main/java/org/vmstudio/visor/api/client/player/pose/PoseAnchor.java @@ -125,11 +125,8 @@ public enum PoseAnchor { public @NotNull Vector3f reverseAnchoredRotation(@NotNull Matrix4fc anchorRotation, @NotNull Matrix4fc objRotation) { - - Matrix4f invRotation = objRotation.invert(new Matrix4f()); - - - Matrix4f matrix4f = invRotation.mul(anchorRotation); + Matrix4f matrix4f = anchorRotation.invert(new Matrix4f()) + .mul(objRotation, new Matrix4f()); float offsetY = (float) Math.asin(-matrix4f.m20()); float offsetZ = (float) Mth.atan2(matrix4f.m10(), matrix4f.m00()); diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VRCursorHandlerImpl.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VRCursorHandlerImpl.java index 65037f73..b4625e9b 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VRCursorHandlerImpl.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VRCursorHandlerImpl.java @@ -180,7 +180,10 @@ private void updateOverlays() { cursorPos.x(), cursorPos.y() ); - if (withinBounds) { + + boolean onDragHandle = overlay.isCursorOnDragHandle(cursorPos.x(), cursorPos.y()); + + if (withinBounds || onDragHandle) { finalCursorPos = cursorPos; collidingOverlay = overlay; closestDistance = cursorPos.z(); diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VROverlayManagerImpl.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VROverlayManagerImpl.java index 086c0d9c..6ef289fa 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VROverlayManagerImpl.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/VROverlayManagerImpl.java @@ -16,6 +16,7 @@ import org.vmstudio.visor.api.client.gui.overlays.options.OptionsScreen; import org.vmstudio.visor.api.client.gui.overlays.framework.VROverlayFrameBuffer; import org.vmstudio.visor.api.client.gui.overlays.framework.VROverlayScreen; +import org.vmstudio.visor.api.common.HandType; import org.vmstudio.visor.core.client.ClientContext; import org.vmstudio.visor.core.client.gui.registry.VROverlayRegistry; import org.vmstudio.visor.core.client.gui.registry.VROverlayTemplateRegistry; @@ -207,6 +208,11 @@ public void renderDepthOverlays(float partialTicks, throw new RuntimeException("Tried to render overlay quad with null renderTarget: " + overlay.getId()); } + boolean drawDragHandle = overlay.supportsDragging() && + (overlay.isBeingDragged() || + ((ClientContext.cursorHandler.getFocusedOverlay(HandType.MAIN) == overlay + || ClientContext.cursorHandler.getFocusedOverlay(HandType.OFFHAND) == overlay))); + RenderGuiHelper.renderOverlayQuad( overlay, poseStack, @@ -214,6 +220,7 @@ public void renderDepthOverlays(float partialTicks, overlay.getPose().getRotation(), false, // depthAlways = false, use GL_LEQUAL overlay.supportsLight(), + drawDragHandle, overlay.getPose().getScale() ); GLUtils.checkGLError("post depth VROverlay quad: " + overlay.getId()); @@ -251,6 +258,10 @@ public void renderHudOverlays(float partialTicks, throw new RuntimeException("Tried to render overlay quad with null renderTarget: " + overlay.getId()); } + boolean drawDragHandle = overlay.supportsDragging() && + (overlay.isBeingDragged() || + ((ClientContext.cursorHandler.getFocusedOverlay(HandType.MAIN) == overlay + || ClientContext.cursorHandler.getFocusedOverlay(HandType.OFFHAND) == overlay))); RenderGuiHelper.renderOverlayQuad( overlay, poseStack, @@ -258,8 +269,10 @@ public void renderHudOverlays(float partialTicks, overlay.getPose().getRotation(), true, // depthAlways = true, use GL_ALWAYS overlay.supportsLight(), + drawDragHandle, overlay.getPose().getScale() ); + GLUtils.checkGLError("post hud VROverlay quad: " + overlay.getId()); } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/VROverlayGameScreen.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/VROverlayGameScreen.java index 59857a71..dfb0bd8c 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/VROverlayGameScreen.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/VROverlayGameScreen.java @@ -36,6 +36,8 @@ public class VROverlayGameScreen extends VROverlayFrameBuffer { private float overlayScale = 1.0f; + + private boolean inMainMenu = true; public VROverlayGameScreen(@NotNull VisorAddon owner, @NotNull String id) { super( @@ -94,7 +96,7 @@ public void onScreenChanged(Screen previousGuiScreen, Screen attachedTo = keyboardAccessor.getAttachedTo(); if (attachedTo != null && attachedTo == previousGuiScreen) { - keyboardAccessor.setVisible(false); + keyboardAccessor.showKeyboard(null); } } else if (newScreen instanceof ChatScreen) { if(!keyboardAccessor.isVisible() @@ -109,9 +111,9 @@ public void onScreenChanged(Screen previousGuiScreen, private void orient(Screen previousGuiScreen, Screen newScreen){ - boolean mainMenu = (MC.gameRenderer == null + inMainMenu = (MC.gameRenderer == null || willBeInMenuRoom(newScreen)); - if (mainMenu) { + if (inMainMenu) { orientMainMenu(); return; } @@ -157,8 +159,6 @@ private void orient(Screen previousGuiScreen, } - ClientContext.overlayManager.getKeyboardAccessor() - .resetPose(); } private void orientMainMenu(){ @@ -204,8 +204,10 @@ public void updateCursorData(boolean activeCursor, if (!isEnabled()) return; if(!activeCursor) return; - if (rawX < 0f || rawX > 1f - || rawY < 0f || rawY > 1f) { + boolean withinGui = rawX >= 0f && rawX <= 1f + && rawY >= 0f && rawY <= 1f; + boolean onDragHandle = isCursorOnDragHandle(rawX, rawY); + if (!withinGui && !onDragHandle) { //do nothing. If we change mouse position here // to emulate mouse exiting the screen, bugs appear // (todo find a way to emulate without bugs) @@ -235,6 +237,10 @@ public void updateCursorData(boolean activeCursor, cursorData.setCursorX((int)(rawX * (double) guiScaledWidth)); cursorData.setCursorY((int)(rawY * (double) guiScaledHeight)); + if (!withinGui) { + return; + } + //here as an input it requires NOT SCALED position InputHelper.setMousePos( (int)(rawX * (double) screenWidth), @@ -258,8 +264,26 @@ public boolean willBeInMenuRoom(Screen newScreen) { MC.getOverlay() != null; } + @Override + public void onStoppedDragging() { + var relativePose = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE); + relativePosition = relativePose.convertPositionFrom( + PlayerPoseType.RENDER, + getPose().getPosition() + ); + relativeRotation = relativePose.convertRotationFrom( + PlayerPoseType.RENDER, + getPose().getRotation() + ); + overlayScale = getPose().getScale(); + } + @Override public boolean mouseClicked(double x, double y, int buttonType) { + if (buttonType == 0 && isCursorOnDragHandle(getRawMouseX(), getRawMouseY())) { + startDragging(); + return true; + } //we need it to go through minecraft InputHelper.pressMouse(MouseButtonType.fromId(buttonType)); return true; @@ -267,6 +291,10 @@ public boolean mouseClicked(double x, double y, int buttonType) { @Override public boolean mouseReleased(double mouseX, double mouseY, int buttonType) { + if (buttonType == 0 && isBeingDragged()) { + stopDragging(); + return true; + } //we need it to go through minecraft InputHelper.releaseMouse(MouseButtonType.fromId(buttonType)); return true; @@ -299,4 +327,8 @@ public boolean supportsCursor() { return Component.translatable("visor.overlay.%s.description".formatted(getId())); } + @Override + public boolean supportsDragging() { + return !inMainMenu; + } } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardKey.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardKey.java new file mode 100644 index 00000000..136ebe6b --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardKey.java @@ -0,0 +1,28 @@ +package org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard; + +import lombok.Getter; + +public class KeyboardKey { + @Getter + private final String label; + @Getter + private final String input; + @Getter + private final int fallbackKey; + @Getter + private final int fallbackModifiers; + + public KeyboardKey(String label, + String input, + int fallbackKey, + int fallbackModifiers) { + this.label = label; + this.input = input; + this.fallbackKey = fallbackKey; + this.fallbackModifiers = fallbackModifiers; + } + + public boolean hasFallback() { + return fallbackKey != -1; + } +} \ No newline at end of file diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayout.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayout.java new file mode 100644 index 00000000..2da45897 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayout.java @@ -0,0 +1,38 @@ +package org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +public class KeyboardLayout { + @Getter + private final KeyboardLayoutId id; + @Getter + private final String switchLabel; + private final KeyboardKey[][] normalLayer; + private final KeyboardKey[][] shiftLayer; + private final int maxColumns; + + public KeyboardLayout(@NotNull KeyboardLayoutId id, + @NotNull String switchLabel, + @NotNull KeyboardKey[][] normalLayer, + @NotNull KeyboardKey[][] shiftLayer) { + this.id = id; + this.switchLabel = switchLabel; + this.normalLayer = normalLayer; + this.shiftLayer = shiftLayer; + + int columns = 0; + for (KeyboardKey[] row : normalLayer) { + columns = Math.max(columns, row.length); + } + this.maxColumns = columns; + } + + public @NotNull KeyboardKey[][] getLayer(boolean shifted) { + return shifted ? shiftLayer : normalLayer; + } + + public int getMaxColumns() { + return maxColumns; + } +} \ No newline at end of file diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayoutId.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayoutId.java new file mode 100644 index 00000000..1b75fdcf --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayoutId.java @@ -0,0 +1,41 @@ +package org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public enum KeyboardLayoutId { + EN_US("EN", "English (US)"), + RU("RU", "Russian"), + UA("UA", "Ukrainian"), + DE("DE", "German"), + FR("FR", "French"), + ES_ES("ES", "Spanish"), + IT("IT", "Italian"), + PT_PT("PT", "Portuguese"); + + @Getter + private final String buttonLabel; + @Getter + private final String displayName; + + KeyboardLayoutId(@NotNull String buttonLabel, + @NotNull String displayName) { + this.buttonLabel = buttonLabel; + this.displayName = displayName; + } + + public @NotNull KeyboardLayoutId next() { + KeyboardLayoutId[] values = values(); + return values[(ordinal() + 1) % values.length]; + } + + public static @Nullable KeyboardLayoutId byName(@NotNull String rawValue) { + for (KeyboardLayoutId value : values()) { + if (value.name().equalsIgnoreCase(rawValue)) { + return value; + } + } + return null; + } +} \ No newline at end of file diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayouts.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayouts.java new file mode 100644 index 00000000..b7237ead --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/KeyboardLayouts.java @@ -0,0 +1,317 @@ +package org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +public final class KeyboardLayouts { + + private static final int[][] ROW_KEY_CODES = new int[][]{ + { + GLFW.GLFW_KEY_GRAVE_ACCENT, + GLFW.GLFW_KEY_1, + GLFW.GLFW_KEY_2, + GLFW.GLFW_KEY_3, + GLFW.GLFW_KEY_4, + GLFW.GLFW_KEY_5, + GLFW.GLFW_KEY_6, + GLFW.GLFW_KEY_7, + GLFW.GLFW_KEY_8, + GLFW.GLFW_KEY_9, + GLFW.GLFW_KEY_0, + GLFW.GLFW_KEY_MINUS, + GLFW.GLFW_KEY_EQUAL + }, + { + GLFW.GLFW_KEY_Q, + GLFW.GLFW_KEY_W, + GLFW.GLFW_KEY_E, + GLFW.GLFW_KEY_R, + GLFW.GLFW_KEY_T, + GLFW.GLFW_KEY_Y, + GLFW.GLFW_KEY_U, + GLFW.GLFW_KEY_I, + GLFW.GLFW_KEY_O, + GLFW.GLFW_KEY_P, + GLFW.GLFW_KEY_LEFT_BRACKET, + GLFW.GLFW_KEY_RIGHT_BRACKET, + GLFW.GLFW_KEY_BACKSLASH + }, + { + GLFW.GLFW_KEY_A, + GLFW.GLFW_KEY_S, + GLFW.GLFW_KEY_D, + GLFW.GLFW_KEY_F, + GLFW.GLFW_KEY_G, + GLFW.GLFW_KEY_H, + GLFW.GLFW_KEY_J, + GLFW.GLFW_KEY_K, + GLFW.GLFW_KEY_L, + GLFW.GLFW_KEY_SEMICOLON, + GLFW.GLFW_KEY_APOSTROPHE + }, + { + GLFW.GLFW_KEY_Z, + GLFW.GLFW_KEY_X, + GLFW.GLFW_KEY_C, + GLFW.GLFW_KEY_V, + GLFW.GLFW_KEY_B, + GLFW.GLFW_KEY_N, + GLFW.GLFW_KEY_M, + GLFW.GLFW_KEY_COMMA, + GLFW.GLFW_KEY_PERIOD, + GLFW.GLFW_KEY_SLASH + } + }; + + private static final Map LAYOUTS = new EnumMap<>( + KeyboardLayoutId.class + ); + + static { + register(build( + KeyboardLayoutId.EN_US, + new String[]{ + "`1234567890-=", + "qwertyuiop[]\\", + "asdfghjkl;'", + "zxcvbnm,./" + }, + new String[]{ + "~!@#$%^&*()_+", + "QWERTYUIOP{}|", + "ASDFGHJKL:\"", + "ZXCVBNM<>?" + } + )); + register(build( + KeyboardLayoutId.RU, + new String[]{ + "ё1234567890-=", + "йцукенгшщзхъ\\", + "фывапролджэ", + "ячсмитьбю." + }, + new String[]{ + "Ё!\"№;%:?*()_+", + "ЙЦУКЕНГШЩЗХЪ/", + "ФЫВАПРОЛДЖЭ", + "ЯЧСМИТЬБЮ," + } + )); + register(build( + KeyboardLayoutId.UA, + new String[]{ + "'1234567890-=", + "йцукенгшщзхїґ", + "фівапролджє", + "ячсмитьбю." + }, + new String[]{ + "₴!\"№;%:?*()_+", + "ЙЦУКЕНГШЩЗХЇҐ", + "ФІВАПРОЛДЖЄ", + "ЯЧСМИТЬБЮ," + } + )); + register(build( + KeyboardLayoutId.DE, + new String[]{ + "^1234567890ß´", + "qwertzuiopü+#", + "asdfghjklöä", + "yxcvbnm,.-" + }, + new String[]{ + "°!\"§$%&/()=?`", + "QWERTZUIOPÜ*'", + "ASDFGHJKLÖÄ", + "YXCVBNM;:_" + } + )); + register(build( + KeyboardLayoutId.FR, + new String[]{ + "²&é\"'(-è_çà)=", + "azertyuiop^$*", + "qsdfghjklmù", + "wxcvbn,;:!" + }, + new String[]{ + "³1234567890°+", + "AZERTYUIOP¨£µ", + "QSDFGHJKLM%", + "WXCVBN?./§" + } + )); + register(build( + KeyboardLayoutId.ES_ES, + new String[]{ + "º1234567890'¡", + "qwertyuiop`+ç", + "asdfghjklñ´", + "zxcvbnm,.-" + }, + new String[]{ + "ª!\"·$%&/()=?¿", + "QWERTYUIOP^*Ç", + "ASDFGHJKLѨ", + "ZXCVBNM;:_" + } + )); + register(build( + KeyboardLayoutId.IT, + new String[]{ + "\\1234567890'ì", + "qwertyuiopè+ù", + "asdfghjklòà", + "zxcvbnm,.-" + }, + new String[]{ + "|!\"£$%&/()=?^", + "QWERTYUIOPé*§", + "ASDFGHJKLç°", + "ZXCVBNM;:_" + } + )); + register(build( + KeyboardLayoutId.PT_PT, + new String[]{ + "\\1234567890'«", + "qwertyuiop+´~", + "asdfghjklçº", + "zxcvbnm,.-" + }, + new String[]{ + "|!\"#$%&/()=?»", + "QWERTYUIOP*`^", + "ASDFGHJKLǪ", + "ZXCVBNM;:_" + } + )); + } + + private KeyboardLayouts() { + } + + public static @NotNull KeyboardLayout get(@NotNull KeyboardLayoutId id) { + KeyboardLayout layout = LAYOUTS.get(id); + if (layout == null) { + throw new IllegalArgumentException("Unknown keyboard layout: " + id); + } + return layout; + } + + public static @NotNull KeyboardLayout getDefault() { + return get(KeyboardLayoutId.EN_US); + } + + public static @NotNull List getSelectableLayouts() { + return List.of(KeyboardLayoutId.values()); + } + + public static @NotNull List getEnabled( + @Nullable String rawValue + ) { + LinkedHashSet result = new LinkedHashSet<>(); + if (rawValue != null && !rawValue.isBlank()) { + for (String part : rawValue.split(",")) { + String trimmed = part.trim(); + if (trimmed.isEmpty()) { + continue; + } + KeyboardLayoutId layoutId = KeyboardLayoutId.byName(trimmed); + if (layoutId != null) { + result.add(layoutId); + } + } + } + + return List.copyOf(result); + } + + public static @NotNull String serializeEnabled( + @NotNull Iterable layoutIds + ) { + LinkedHashSet normalized = new LinkedHashSet<>(); + for (KeyboardLayoutId layoutId : layoutIds) { + if (layoutId != null) { + normalized.add(layoutId); + } + } + + StringJoiner joiner = new StringJoiner(","); + for (KeyboardLayoutId layoutId : normalized) { + joiner.add(layoutId.name()); + } + return joiner.toString(); + } + + private static void register(@NotNull KeyboardLayout layout) { + LAYOUTS.put(layout.getId(), layout); + } + + private static @NotNull KeyboardLayout build(@NotNull KeyboardLayoutId id, + @NotNull String[] normalRows, + @NotNull String[] shiftRows) { + if (normalRows.length != ROW_KEY_CODES.length + || shiftRows.length != ROW_KEY_CODES.length) { + throw new IllegalArgumentException( + "Keyboard layout " + id + " must define " + ROW_KEY_CODES.length + " rows" + ); + } + + KeyboardKey[][] normalLayer = new KeyboardKey[normalRows.length][]; + KeyboardKey[][] shiftLayer = new KeyboardKey[shiftRows.length][]; + + for (int row = 0; row < normalRows.length; row++) { + normalLayer[row] = buildRow(id, row, normalRows[row], 0); + shiftLayer[row] = buildRow(id, row, shiftRows[row], GLFW.GLFW_MOD_SHIFT); + } + + return new KeyboardLayout(id, id.getButtonLabel(), normalLayer, shiftLayer); + } + + private static @NotNull KeyboardKey[] buildRow(@NotNull KeyboardLayoutId id, + int rowIndex, + @NotNull String rowContent, + int fallbackModifiers) { + int[] rowKeyCodes = ROW_KEY_CODES[rowIndex]; + String[] symbols = splitSymbols(rowContent); + if (symbols.length != rowKeyCodes.length) { + throw new IllegalArgumentException( + "Keyboard layout " + id + + " row " + rowIndex + + " expected " + rowKeyCodes.length + + " symbols but got " + symbols.length + ); + } + + KeyboardKey[] result = new KeyboardKey[symbols.length]; + for (int i = 0; i < symbols.length; i++) { + result[i] = new KeyboardKey( + symbols[i], + symbols[i], + rowKeyCodes[i], + fallbackModifiers + ); + } + return result; + } + + private static @NotNull String[] splitSymbols(@NotNull String rowContent) { + int[] codePoints = rowContent.codePoints().toArray(); + String[] symbols = new String[codePoints.length]; + for (int i = 0; i < codePoints.length; i++) { + symbols[i] = new String(Character.toChars(codePoints[i])); + } + return symbols; + } +} diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/VROverlayKeyboard.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/VROverlayKeyboard.java index 7e94a41d..3e5ac232 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/VROverlayKeyboard.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/builtin/keyboard/VROverlayKeyboard.java @@ -1,30 +1,31 @@ package org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard; - import lombok.Getter; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector3fc; import org.vmstudio.visor.api.VisorAPI; import org.vmstudio.visor.api.client.ClientFeature; -import org.vmstudio.visor.api.client.player.pose.PoseAnchor; -import org.vmstudio.visor.api.client.player.pose.PlayerPoseType; import org.vmstudio.visor.api.client.events.AllowClientFeatureVREvent; import org.vmstudio.visor.api.client.gui.VRKeyboardAccessor; import org.vmstudio.visor.api.client.gui.overlays.VROverlayHelper; import org.vmstudio.visor.api.client.gui.overlays.framework.screen.VROverlayScreenInScreen; -import org.vmstudio.visor.api.common.addon.component.ComponentPriority; +import org.vmstudio.visor.api.client.player.pose.PlayerPoseType; +import org.vmstudio.visor.api.client.player.pose.PoseAnchor; import org.vmstudio.visor.api.common.addon.VisorAddon; +import org.vmstudio.visor.api.common.addon.component.ComponentPriority; import org.vmstudio.visor.api.common.eventbus.listener.VREventHandler; import org.vmstudio.visor.api.common.eventbus.listener.VREventListener; import org.vmstudio.visor.core.client.ClientContext; import org.vmstudio.visor.core.client.gui.screens.VRKeyboardScreen; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.network.chat.Component; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.joml.Vector3fc; +import org.vmstudio.visor.core.client.settings.VRClientSettings; +import java.util.List; public class VROverlayKeyboard extends VROverlayScreenInScreen implements VRKeyboardAccessor, VREventListener { @@ -39,6 +40,9 @@ public class VROverlayKeyboard extends VROverlayScreenInScreen @Getter private boolean shiftPressed = false; + @Getter + private KeyboardLayoutId activeLayoutId = KeyboardLayoutId.EN_US; + @Getter @Nullable private Screen attachedTo; @@ -59,6 +63,14 @@ public VROverlayKeyboard(@NotNull VisorAddon owner, VisorAPI.eventBus().registerListener(owner,this); } + @Override + protected void init() { + super.init(); + cursorBoundsX = getScreen().getCursorBoundsX(); + cursorBoundsY = getScreen().getCursorBoundsY(); + cursorBoundsWidth = getScreen().getCursorBoundsWidth(); + cursorBoundsHeight = getScreen().getCursorBoundsHeight(); + } @VREventHandler public void disableWorldHands(AllowClientFeatureVREvent event){ @@ -104,7 +116,7 @@ public void setVisible(boolean flag){ } @Override - public void showKeyboard(@NotNull Screen attachTo) { + public void showKeyboard(@Nullable Screen attachTo) { setVisible(true, attachTo); } @@ -113,7 +125,6 @@ public boolean updateVisibility() { return shown; } - @Override public void onUpdatePose(float partialTicks) { VROverlayHelper.applyRelativePose( @@ -124,21 +135,39 @@ public void onUpdatePose(float partialTicks) { ); } - + @Override + public void onStoppedDragging() { + var relativePose = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE); + relativePosition = relativePose.convertPositionFrom( + PlayerPoseType.RENDER, + getPose().getPosition() + ); + relativeRotation = relativePose.convertRotationFrom( + PlayerPoseType.RENDER, + getPose().getRotation() + ); + } @Override public boolean supportsTwoCursors() { return true; } + @Override + public boolean supportsDragging() { + return true; + } + public void setVisible(boolean flag, @Nullable Screen attachedTo) { + boolean changePose = flag != shown; shown = flag; if (shown) { - orient(attachedTo); + orient(attachedTo, changePose); shiftPressed = false; + activeLayoutId = getEnabledLayoutIds().get(0); initAgain = true; } else { getScreen().clearPress(); @@ -154,7 +183,38 @@ public void setShiftPressed(boolean shift) { } } - private void orient(@Nullable Screen attachedTo) { + public void cycleLayout() { + List enabledLayouts = getEnabledLayoutIds(); + int currentIndex = enabledLayouts.indexOf(activeLayoutId); + if (currentIndex < 0) { + setActiveLayoutId(enabledLayouts.get(0)); + return; + } + setActiveLayoutId( + enabledLayouts.get((currentIndex + 1) % enabledLayouts.size()) + ); + } + + public void setActiveLayoutId(@NotNull KeyboardLayoutId activeLayoutId) { + if (this.activeLayoutId != activeLayoutId) { + this.activeLayoutId = activeLayoutId; + this.initAgain = true; + } + } + + public @NotNull List getEnabledLayoutIds() { + List enabledLayouts = VRClientSettings.getEnabledKeyboardLayouts(); + if (enabledLayouts.isEmpty()) { + return List.of(KeyboardLayoutId.EN_US); + } + return enabledLayouts; + } + + public boolean hasMultipleLayouts() { + return getEnabledLayoutIds().size() > 1; + } + + private void orient(@Nullable Screen attachedTo, boolean changePose) { if (!shown) { this.attachedTo = null; return; @@ -162,19 +222,21 @@ private void orient(@Nullable Screen attachedTo) { this.attachedTo = attachedTo; - VROverlayHelper.applyPose( - this, - PoseAnchor.HMD, - PoseAnchor.HMD, - getPose().getScale(), - true, - posOffset, - rotationOffset - ); - relativePosition = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE) - .convertPositionFrom(PlayerPoseType.RENDER, getPose().getPosition()); - relativeRotation = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE) - .convertRotationFrom(PlayerPoseType.RENDER, getPose().getRotation()); + if(changePose) { + VROverlayHelper.applyPose( + this, + PoseAnchor.HMD, + PoseAnchor.HMD, + getPose().getScale(), + true, + posOffset, + rotationOffset + ); + relativePosition = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE) + .convertPositionFrom(PlayerPoseType.RENDER, getPose().getPosition()); + relativeRotation = ClientContext.localPlayer.getPoseData(PlayerPoseType.RELATIVE) + .convertRotationFrom(PlayerPoseType.RENDER, getPose().getRotation()); + } } @@ -187,5 +249,4 @@ private void orient(@Nullable Screen attachedTo) { public @NotNull Component getDescription() { return Component.translatable("visor.overlay.%s.description".formatted(getId())); } - } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/templates/VROverlayChat.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/templates/VROverlayChat.java index 63d08577..b06efe80 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/templates/VROverlayChat.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/overlays/templates/VROverlayChat.java @@ -7,9 +7,7 @@ import org.vmstudio.visor.api.client.gui.overlays.options.OverlayOptionGroup; import org.vmstudio.visor.api.client.gui.overlays.options.types.OverlayOptionsMisc; import org.vmstudio.visor.api.client.gui.overlays.options.types.OverlayOptionsPose; - import org.vmstudio.visor.api.client.gui.overlays.framework.template.VROverlayTemplateScreen; -import org.vmstudio.visor.api.common.HandType; import org.vmstudio.visor.api.common.addon.VisorAddon; import org.vmstudio.visor.core.client.ClientContext; import net.minecraft.client.gui.GuiGraphics; @@ -18,7 +16,6 @@ import java.util.List; - @RegisterVROverlayTemplate( id = VROverlayChat.ID, name = VROverlayChat.NAME, @@ -51,11 +48,6 @@ public boolean updateVisibility() { if(minecraft.level == null) return false; if(minecraft.isPaused() || ClientContext.overlayManager.getKeyboardAccessor().isVisible()) return false; - if (!ClientContext.rawPoseHandler.getControllerData(HandType.OFFHAND) - .isTracking()) { - return false; - } - return !minecraft.gui.getChat().trimmedMessages.isEmpty() && minecraft.options.chatVisibility().get() != ChatVisiblity.HIDDEN; @@ -69,7 +61,7 @@ public boolean supportsCursor() { @Override public boolean isHudLayer() { - return false; + return true; } @Override @@ -87,23 +79,22 @@ public boolean isHudLayer() { it.setTickPose(true); it.setAimedRotation(false); - it.setPositionAnchor(PoseAnchor.OFFHAND); + it.setPositionAnchor(PoseAnchor.HMD); it.setPositionOffset( - -0.15f, - 0.06f, - -0.13f + 0.06f + 0.0f, + 0.0f, + -2.5f ); - it.setRotationAnchor(PoseAnchor.OFFHAND); + it.setRotationAnchor(PoseAnchor.HMD); it.setRotationOffset( - (float) (-Math.PI / 2), - (float) (Math.PI / 2), + 0, + 0, 0 ); - it.setScale(0.5f); + it.setScale(1.2f); } ) ); } - } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/GameMenuScreen.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/GameMenuScreen.java index 2d5a7d1c..6dc7b151 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/GameMenuScreen.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/GameMenuScreen.java @@ -1,23 +1,49 @@ package org.vmstudio.visor.core.client.gui.screens; -import org.vmstudio.visor.core.client.ClientContext; -import org.vmstudio.visor.core.client.settings.VRClientSettings; -import org.vmstudio.visor.core.client.tasks.types.TaskHotBar; +import com.mojang.realmsclient.RealmsMainScreen; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.layouts.FrameLayout; -import net.minecraft.client.gui.layouts.GridLayout; -import net.minecraft.client.gui.screens.ChatScreen; -import net.minecraft.client.gui.screens.PauseScreen; -import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.*; import net.minecraft.client.gui.screens.inventory.InventoryScreen; +import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.network.chat.Component; +import org.vmstudio.visor.core.client.ClientContext; +import org.vmstudio.visor.core.client.gui.screens.settings.VRSettingsScreen; +import org.vmstudio.visor.core.client.settings.VRClientSettings; +import org.vmstudio.visor.core.client.tasks.types.TaskHotBar; +import java.util.ArrayList; +import java.util.List; public class GameMenuScreen extends Screen { - private static final Component OPEN_CHAT = Component.literal("Chat"); - private static final Component OPEN_INVENTORY = Component.literal("Inventory"); - private static final Component OPEN_PAUSE_MENU = Component.literal("Main Menu"); - private static final Component OPEN_KEYBOARD = Component.literal("Keyboard"); + private static final int COLUMN_W = 204; + private static final int BTN_H = 20; + private static final int BTN_HALF = (COLUMN_W - 4) / 2; + private static final int GAP = 4; + private static final int TAB_W = (COLUMN_W - 8) / 3; + + private static final int BTN_QUARTER = (COLUMN_W - 3 * GAP) / 4; + private static final int BTN_THIRD = (COLUMN_W - 2 * GAP) / 3; + + private static final int LABEL_H = 11; + private static final int SECTION_GAP = 6; + + private enum Tab{ + MAIN(Component.literal("Main")), + COMMANDS(Component.literal("Commands")), + TOOLS(Component.literal("Tools")); + + Component label; + + Tab(Component l) { + this.label = l; + } + } + + private Tab currentTab = Tab.MAIN; + + private final List sectionHeaderPos = new ArrayList<>(); // {x, y} + private final List sectionHeaderTexts = new ArrayList<>(); public GameMenuScreen() { super(Component.literal("Game Menu")); @@ -26,70 +52,227 @@ public GameMenuScreen() { @Override protected void init() { - create(); - //@TODO temporary. Get rid of it when player tick tasks - // start to reset on player world leave - TaskHotBar.getInstance().setResetData(true); - var keyboardAccessor = ClientContext.overlayManager - .getKeyboardAccessor(); - keyboardAccessor.setVisible(false); - } + TaskHotBar.setResetData(true); + boolean hasPerms = this.minecraft.player != null && this.minecraft.player.hasPermissions(2); - private void create(){ - GridLayout gridLayout = new GridLayout(); - gridLayout.defaultCellSetting().padding(4, 4, 4, 0); - GridLayout.RowHelper rowHelper = gridLayout.createRowHelper(2); - - - //INVENTORY - rowHelper.addChild(Button.builder(OPEN_INVENTORY, (button) -> { - this.minecraft.setScreen(null); - this.minecraft.setScreen(new InventoryScreen( - this.minecraft.player) - ); - }).width(104).build(), - 2, gridLayout.newCellSettings().paddingTop(50)); - - //CHAT - rowHelper.addChild(new Button.Builder(OPEN_CHAT, (p) -> - { - this.minecraft.setScreen(null); - this.minecraft.setScreen(new ChatScreen("")); - }).width(104).build(), - 2, gridLayout.newCellSettings().paddingTop(20)); - //---CHAT END - - - //KEYBOARD - rowHelper.addChild(Button.builder(OPEN_KEYBOARD, (button) -> { - this.minecraft.setScreen(null); - var keyboardAccessor = ClientContext.overlayManager - .getKeyboardAccessor(); - keyboardAccessor.setVisible(true); - }).width(104).build(), - 2, gridLayout.newCellSettings().paddingTop(20) - ); + if (this.currentTab == Tab.COMMANDS && !hasPerms) { + this.currentTab = Tab.MAIN; + } - rowHelper.addChild(new Button.Builder(Component.translatable("visor.button.calibrate_height"), (p) -> - { - VRClientSettings.calibrateHeight(); - ClientContext.settingsManager.saveOptions(); - this.minecraft.setScreen(null); - }).width(104).build(), - 2, gridLayout.newCellSettings().paddingTop(20)); - - //PAUSE MENU - rowHelper.addChild(Button.builder(OPEN_PAUSE_MENU, (button) -> { - this.minecraft.setScreen(null); - this.minecraft.setScreen(new PauseScreen(true)); - }).width(104).build(), - 2, gridLayout.newCellSettings().paddingTop(20) + sectionHeaderPos.clear(); + sectionHeaderTexts.clear(); + + int cx = this.width / 2; + int startY = this.height / 2 - totalColumnHeight() / 2; + int y = startY + 9 + 16; + + int tx = cx - COLUMN_W / 2; + for (Tab tab : Tab.values()) { + final Tab t = tab; + Button btn = Button.builder(tab.label, b -> { + this.currentTab = t; + this.rebuildWidgets(); + }).pos(tx + tab.ordinal() * (TAB_W + 4), y).width(TAB_W).build(); + + if (t == Tab.COMMANDS && !hasPerms) { + btn.visible = false; + } else { + btn.visible = true; + btn.active = (this.currentTab != tab); + } + addRenderableWidget(btn); + } + + // dont touch this please 🤞 + y += BTN_H + GAP + 2; + y = buildContent(cx, y); + y += 4; + + addRenderableWidget( + Button.builder(Component.literal("Save and Quit to Title"), b -> this.minecraft.getReportingContext().draftReportHandled(this.minecraft, this, this::onDisconnect, true)) + .pos(cx - COLUMN_W / 2, y).width(COLUMN_W).build() ); + } + + private int buildContent(int cx, int y) { + int left = cx - COLUMN_W / 2; + int right = left + BTN_HALF + GAP; + + switch (this.currentTab) { + case MAIN -> { + addRenderableWidget(makeHalfBtn("Inventory", left, y, + b -> this.minecraft.setScreen(new InventoryScreen(this.minecraft.player)))); + addRenderableWidget(makeHalfBtn("Calibrate Height", right, y, b -> { + VRClientSettings.calibrateHeight(); + ClientContext.settingsManager.saveOptions(); + })); + y += BTN_H + GAP; + + addRenderableWidget(makeHalfBtn("Keyboard", left, y, b -> + ClientContext.overlayManager.getKeyboardAccessor().setVisible(true))); + addRenderableWidget(makeHalfBtn("Chat", right, y, + b -> this.minecraft.setScreen(new ChatScreen("")))); + y += BTN_H + GAP; + + addRenderableWidget(makeHalfBtn("Pause Menu", left, y, + b -> this.minecraft.setScreen(new PauseScreen(true)))); + addRenderableWidget(makeHalfBtn("VR Settings", right, y, + b -> this.minecraft.setScreen(new VRSettingsScreen(this)))); + y += BTN_H + GAP; + } + + case COMMANDS -> { + registerSection(left, y, "GAME MODE"); + y += LABEL_H; + + addRenderableWidget(makeHalfBtn("Survival", left, y, b -> sendCommand("gamemode survival"))); + addRenderableWidget(makeHalfBtn("Creative", right, y, b -> sendCommand("gamemode creative"))); + y += BTN_H + GAP; + + addRenderableWidget(makeHalfBtn("Spectator", left, y, b -> sendCommand("gamemode spectator"))); + addRenderableWidget(makeHalfBtn("Adventure", right, y, b -> sendCommand("gamemode adventure"))); + y += BTN_H + GAP; + + y += SECTION_GAP; + + registerSection(left, y, "TIME"); + y += LABEL_H; + + int[] timeTicks = {0, 6000, 12000, 18000}; + String[] timeLabels = {"Dawn", "Noon", "Dusk", "Night"}; + for (int i = 0; i < 4; i++) { + final int tick = timeTicks[i]; + addRenderableWidget(Button.builder(Component.literal(timeLabels[i]), + b -> sendCommand("time set " + tick)) + .pos(left + i * (BTN_QUARTER + GAP), y) + .width(BTN_QUARTER) + .build()); + } + y += BTN_H + GAP; + + y += SECTION_GAP; + + registerSection(left, y, "WEATHER"); + y += LABEL_H; + + String[] weatherLabels = {"Clear", "Rain", "Thunder"}; + String[] weatherCmds = {"weather clear", "weather rain", "weather thunder"}; + for (int i = 0; i < 3; i++) { + final String cmd = weatherCmds[i]; + addRenderableWidget(Button.builder(Component.literal(weatherLabels[i]), b -> sendCommand(cmd)) + .pos(left + i * (BTN_THIRD + GAP), y) + .width(BTN_THIRD) + .build()); + } + y += BTN_H + GAP; + } + + case TOOLS -> { + addRenderableWidget(makeHalfBtn("Hitboxes", left, y, b -> { + boolean cur = this.minecraft.getEntityRenderDispatcher().shouldRenderHitBoxes(); + this.minecraft.getEntityRenderDispatcher().setRenderHitBoxes(!cur); + })); + addRenderableWidget(makeHalfBtn("Chunk Borders", right, y, + b -> this.minecraft.debugRenderer.switchRenderChunkborder())); + y += BTN_H + GAP; + + addRenderableWidget(makeHalfBtn("Reload Chunks", left, y, + b -> this.minecraft.levelRenderer.allChanged())); + addRenderableWidget(makeHalfBtn("Clear Chat", right, y, + b -> this.minecraft.gui.getChat().clearMessages(false))); + y += BTN_H + GAP; + } + } + + return y; + } + + @Override + public void render(GuiGraphics gfx, int mouseX, int mouseY, float delta) { + super.renderBackground(gfx); + + int cx = this.width / 2; + int startY = this.height / 2 - totalColumnHeight() / 2; + + gfx.drawCenteredString(this.font, Component.literal("Game Menu"), cx, startY, 0xFFFFFFFF); + + int dividerColor = 0xFF555555; + + int divY = startY + 9 + 10; + gfx.fill(cx - COLUMN_W / 2, divY, cx + COLUMN_W / 2, divY + 1, dividerColor); + + int tabStripY = startY + 9 + 16; + int accentX = cx - COLUMN_W / 2 + currentTab.ordinal() * (TAB_W + 4); + gfx.fill(accentX, tabStripY - 2, accentX + TAB_W, tabStripY - 1, 0xFF55FF55); + + if (this.currentTab == Tab.COMMANDS) { + for (int i = 0; i < sectionHeaderPos.size(); i++) { + int sx = sectionHeaderPos.get(i)[0]; + int sy = sectionHeaderPos.get(i)[1]; + + int lblW = this.font.width(sectionHeaderTexts.get(i)); + int lineY = sy + 4; + + gfx.fill(sx, lineY, sx + 18, lineY + 1, dividerColor); + gfx.fill(sx + 22 + lblW, lineY, sx + COLUMN_W, lineY + 1, dividerColor); + + gfx.drawString(this.font, sectionHeaderTexts.get(i), sx + 20, sy, 0xFF88FF88, false); + } + } + + super.render(gfx, mouseX, mouseY, delta); + } + + private int totalColumnHeight() { + int contentH = switch (this.currentTab) { + case MAIN -> 3 * (BTN_H + GAP); + case TOOLS -> 2 * (BTN_H + GAP); + case COMMANDS -> 3 * LABEL_H + 4 * (BTN_H + GAP) + 2 * SECTION_GAP; + }; + return 9 + 16 + BTN_H + 6 + contentH + 6 + BTN_H; + } + + private void registerSection(int x, int y, String text) { + sectionHeaderPos.add(new int[]{x, y}); + sectionHeaderTexts.add(text); + } + + private Button makeHalfBtn(String label, int x, int y, Button.OnPress action) { + return Button.builder(Component.literal(label), action) + .pos(x, y).width(BTN_HALF).build(); + } + + private Button makeFullBtn(String label, int x, int y, Button.OnPress action) { + return Button.builder(Component.literal(label), action) + .pos(x, y).width(COLUMN_W).build(); + } + + private void sendCommand(String command) { + if (this.minecraft.player != null) { + this.minecraft.player.connection.sendCommand(command); + } + } + + // took from PauseScreen + private void onDisconnect() { + boolean bl = this.minecraft.isLocalServer(); + boolean bl2 = this.minecraft.isConnectedToRealms(); + this.minecraft.level.disconnect(); + if (bl) { + this.minecraft.clearLevel(new GenericDirtMessageScreen(Component.literal("Saving world"))); + } else { + this.minecraft.clearLevel(); + } + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + this.minecraft.setScreen(titleScreen); + } else if (bl2) { + this.minecraft.setScreen(new RealmsMainScreen(titleScreen)); + } else { + this.minecraft.setScreen(new JoinMultiplayerScreen(titleScreen)); + } - gridLayout.arrangeElements(); - FrameLayout.alignInRectangle(gridLayout, 0, 0, this.width, this.height, 0.5F, 0.25F); - gridLayout.visitWidgets(this::addRenderableWidget); } } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/VRKeyboardScreen.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/VRKeyboardScreen.java index 5b64d5e2..52c55ff5 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/VRKeyboardScreen.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/VRKeyboardScreen.java @@ -2,128 +2,134 @@ import lombok.Getter; import lombok.Setter; -import org.vmstudio.visor.api.client.input.InputHelper; -import org.vmstudio.visor.core.client.ClientContext; -import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardButton; -import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.VROverlayKeyboard; -import org.vmstudio.visor.core.client.settings.VRClientSettings; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; - +import org.vmstudio.visor.api.client.input.InputHelper; +import org.vmstudio.visor.core.client.ClientContext; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardButton; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardKey; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayout; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayouts; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.VROverlayKeyboard; public class VRKeyboardScreen extends Screen { - @Getter @Setter + @Getter + @Setter private VROverlayKeyboard overlayKeyboard; - @Getter @Setter + @Getter + @Setter private Runnable pressedTask; - @Getter @Setter + @Getter + @Setter private int pressTick; + @Getter + private int cursorBoundsX = -1; + @Getter + private int cursorBoundsY = -1; + @Getter + private int cursorBoundsWidth = -1; + @Getter + private int cursorBoundsHeight = -1; + public VRKeyboardScreen(Component component) { super(component); } @Override public void init() { - String keys = VRClientSettings.getKeyboardKeys(); - String keysShift = VRClientSettings.getKeyboardKeysShift(); this.clearWidgets(); - if (overlayKeyboard.isShiftPressed()) { - keys = keysShift; - } - - int keysPerRow = 13; - int rows; - int yPos = 32; - int l = 2; - int i1 = 25; - double preRows = (double) keys.length() / (double) keysPerRow; - - if (Math.floor(preRows) == preRows) { - rows = (int) preRows; - } else { - rows = (int) (preRows + 1.0D); - } + int gridStart = 32; + int keyGap = 2; + int keyWidth = 25; + int keyHeight = 20; + int smallButtonWidth = 30; + int sideButtonWidth = 35; + int shiftButtonWidth = 50; + int languageButtonWidth = 38; + int spaceX; - for (int row = 0; row < rows; ++row) { - for (int column = 0; column < keysPerRow; ++column) { - int index = row * keysPerRow + column; - char keyChar = ' '; + KeyboardLayout layout = KeyboardLayouts.get(overlayKeyboard.getActiveLayoutId()); + KeyboardKey[][] rows = layout.getLayer(overlayKeyboard.isShiftPressed()); + int maxColumns = layout.getMaxColumns(); + int gridRightX = gridStart + maxColumns * (keyWidth + keyGap); - if (index < keys.length()) { - keyChar = keys.charAt(index); - } + for (int row = 0; row < rows.length; ++row) { + KeyboardKey[] rowKeys = rows[row]; + int rowStartX = gridStart + ((maxColumns - rowKeys.length) * (keyWidth + keyGap)) / 2; + int rowY = gridStart + row * (keyHeight + keyGap); - String label = String.valueOf(keyChar); + for (int column = 0; column < rowKeys.length; ++column) { + KeyboardKey key = rowKeys[column]; KeyboardButton button = new KeyboardButton.Builder( this, - Component.literal(label), (p) -> { - - InputHelper.typeChars(label); - - }).size(i1, 20) - .pos(yPos + column * (i1 + l), yPos + row * (20 + l)) + Component.literal(key.getLabel()), + (p) -> pressKeyboardKey(key) + ).size(keyWidth, keyHeight) + .pos(rowStartX + column * (keyWidth + keyGap), rowY) .build(); this.addRenderableWidget(button); } } + int bottomY = gridStart + rows.length * (keyHeight + keyGap); + spaceX = gridStart + ((maxColumns - 5) / 2) * (keyWidth + keyGap); + //SHIFT this.addRenderableWidget( new KeyboardButton.Builder(this, - Component.literal(overlayKeyboard.isShiftPressed() - ? "SHIFT" - : "Shift"), - (p) -> - { - overlayKeyboard - .setShiftPressed(!overlayKeyboard.isShiftPressed()); - }) - .size(overlayKeyboard.isShiftPressed() ? 32 : 30, 20) - .pos(0, yPos + 3 * (20 + l)) + Component.literal( + overlayKeyboard.isShiftPressed() + ? "SHIFT" + : "Shift" + ), + (p) -> overlayKeyboard.setShiftPressed(!overlayKeyboard.isShiftPressed())) + .size(overlayKeyboard.isShiftPressed() ? (shiftButtonWidth + 2) : shiftButtonWidth, keyHeight) + .pos(0, gridStart + (rows.length - 1) * (keyHeight + keyGap)) .usePressTask(false) .build() ); + //LANGUAGE + KeyboardButton languageButton = new KeyboardButton.Builder(this, + Component.literal(layout.getSwitchLabel()), + (p) -> overlayKeyboard.cycleLayout()) + .size(languageButtonWidth, keyHeight) + .pos(3 * (sideButtonWidth + keyGap) + gridStart, gridStart - (keyHeight + keyGap)) + .usePressTask(false) + .build(); + languageButton.active = overlayKeyboard.hasMultipleLayouts(); + this.addRenderableWidget(languageButton); //SPACE this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal(" "), - (p) -> - { - InputHelper.typeChars(" "); - }) - .size(5 * (i1 + l), 20) - .pos(yPos + 4 * (i1 + l), yPos + rows * (20 + l)) + (p) -> pressSpace()) + .size(5 * (keyWidth + keyGap), keyHeight) + .pos(spaceX, bottomY) .build() ); //BACKSPACE this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal("BKSP"), - (p) -> - { - InputHelper.pressKey(GLFW.GLFW_KEY_BACKSPACE); - InputHelper.releaseKey(GLFW.GLFW_KEY_BACKSPACE); - }) - .size(35, 20) - .pos(keysPerRow * (i1 + l) + yPos, yPos) + (p) -> pressKeyAction(GLFW.GLFW_KEY_BACKSPACE)) + .size(sideButtonWidth, keyHeight) + .pos(gridRightX, gridStart) .build() ); //ENTER this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal("ENTER"), - (p) -> - { - InputHelper.pressKey(GLFW.GLFW_KEY_ENTER); - InputHelper.releaseKey(GLFW.GLFW_KEY_ENTER); - }) - .size(35, 20) - .pos(keysPerRow * (i1 + l) + yPos, yPos + 2 * (20 + l)) + (p) -> pressKeyAction(GLFW.GLFW_KEY_ENTER)) + .size(sideButtonWidth, keyHeight) + .pos(gridRightX, gridStart + 2 * (keyHeight + keyGap)) .usePressTask(false) .build() ); @@ -131,118 +137,70 @@ public void init() { this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal("TAB"), - (p) -> - { - InputHelper.pressKey(GLFW.GLFW_KEY_TAB); - InputHelper.releaseKey(GLFW.GLFW_KEY_TAB); - }) - .size(30, 20) - .pos(0, yPos + 20 + l) + (p) -> pressKeyAction(GLFW.GLFW_KEY_TAB)) + .size(smallButtonWidth, keyHeight) + .pos(0, gridStart + keyHeight + keyGap) .usePressTask(false) .build() ); - //CLOSE this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal("§cx"), (p) -> { - var keyboardAccessor = ClientContext.overlayManager - .getKeyboardAccessor(); + var keyboardAccessor = ClientContext.overlayManager.getKeyboardAccessor(); keyboardAccessor.setVisible(false); }) - .size(30, 20) - .pos(0, yPos + -1 * (20 + l)) + .size(smallButtonWidth, keyHeight) + .pos(0, gridStart - (keyHeight + keyGap)) .usePressTask(false) .build() ); - //ESCAPE this.addRenderableWidget( new KeyboardButton.Builder(this, Component.literal("ESC"), - (p) -> - { - InputHelper.pressKey(GLFW.GLFW_KEY_ESCAPE); - InputHelper.releaseKey(GLFW.GLFW_KEY_ESCAPE); - }) - .size(30, 20) - .pos(0, yPos) + (p) -> pressKeyAction(GLFW.GLFW_KEY_ESCAPE)) + .size(smallButtonWidth, keyHeight) + .pos(0, gridStart) .usePressTask(false) .build() ); //ARROW UP this.addRenderableWidget( new KeyboardButton.Builder(this, - Component.literal("\u2191"), - (p) -> - { - if(overlayKeyboard.isShiftPressed()){ - InputHelper.pressKey(GLFW.GLFW_KEY_LEFT_SHIFT); - } - - InputHelper.pressKey(GLFW.GLFW_KEY_UP); - InputHelper.releaseKey(GLFW.GLFW_KEY_UP); - - InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_SHIFT); - }) - .size(i1, 20) - .pos((keysPerRow - 1) * (i1 + l) + yPos, yPos + rows * (20 + l)) + Component.literal("↑"), + (p) -> pressNavigationKey(GLFW.GLFW_KEY_UP)) + .size(keyWidth, keyHeight) + .pos((maxColumns - 1) * (keyWidth + keyGap) + gridStart, bottomY) .build() ); //ARROW DOWN this.addRenderableWidget( new KeyboardButton.Builder(this, - Component.literal("\u2193"), - (p) -> - { - if(overlayKeyboard.isShiftPressed()){ - InputHelper.pressKey(GLFW.GLFW_KEY_LEFT_SHIFT); - } - InputHelper.pressKey(GLFW.GLFW_KEY_DOWN); - InputHelper.releaseKey(GLFW.GLFW_KEY_DOWN); - - InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_SHIFT); - }) - .size(i1, 20) - .pos((keysPerRow - 1) * (i1 + l) + yPos, yPos + (rows + 1) * (20 + l)) + Component.literal("↓"), + (p) -> pressNavigationKey(GLFW.GLFW_KEY_DOWN)) + .size(keyWidth, keyHeight) + .pos((maxColumns - 1) * (keyWidth + keyGap) + gridStart, bottomY + keyHeight + keyGap) .build() ); //ARROW LEFT this.addRenderableWidget( new KeyboardButton.Builder(this, - Component.literal("\u2190"), - (p) -> - { - if(overlayKeyboard.isShiftPressed()){ - InputHelper.pressKey(GLFW.GLFW_KEY_LEFT_SHIFT); - } - InputHelper.pressKey(GLFW.GLFW_KEY_LEFT); - InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT); - - InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_SHIFT); - }) - .size(i1, 20) - .pos((keysPerRow - 2) * (i1 + l) + yPos, yPos + (rows + 1) * (20 + l)) + Component.literal("←"), + (p) -> pressNavigationKey(GLFW.GLFW_KEY_LEFT)) + .size(keyWidth, keyHeight) + .pos((maxColumns - 2) * (keyWidth + keyGap) + gridStart, bottomY + keyHeight + keyGap) .build() ); //ARROW RIGHT this.addRenderableWidget( new KeyboardButton.Builder(this, - Component.literal("\u2192"), - (p) -> - { - if(overlayKeyboard.isShiftPressed()){ - InputHelper.pressKey(GLFW.GLFW_KEY_LEFT_SHIFT); - } - InputHelper.pressKey(GLFW.GLFW_KEY_RIGHT); - InputHelper.releaseKey(GLFW.GLFW_KEY_RIGHT); - - InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_SHIFT); - }) - .size(i1, 20) - .pos(keysPerRow * (i1 + l) + yPos, yPos + (rows + 1) * (20 + l)) + Component.literal("→"), + (p) -> pressNavigationKey(GLFW.GLFW_KEY_RIGHT)) + .size(keyWidth, keyHeight) + .pos(maxColumns * (keyWidth + keyGap) + gridStart, bottomY + keyHeight + keyGap) .build() ); //CUT @@ -256,8 +214,8 @@ public void init() { InputHelper.releaseKey(GLFW.GLFW_KEY_X); InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_CONTROL); }) - .size(35, 20) - .pos(yPos, yPos + -1 * (20 + l)) + .size(sideButtonWidth, keyHeight) + .pos(gridStart, gridStart - (keyHeight + keyGap)) .usePressTask(false) .build() ); @@ -272,8 +230,8 @@ public void init() { InputHelper.releaseKey(GLFW.GLFW_KEY_C); InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_CONTROL); }) - .size(35, 20) - .pos(35 + l + yPos, yPos + -1 * (20 + l)) + .size(sideButtonWidth, keyHeight) + .pos(sideButtonWidth + keyGap + gridStart, gridStart - (keyHeight + keyGap)) .usePressTask(false) .build() ); @@ -288,11 +246,97 @@ public void init() { InputHelper.releaseKey(GLFW.GLFW_KEY_V); InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_CONTROL); }) - .size(35, 20) - .pos(2 * (35 + l) + yPos, yPos + -1 * (20 + l)) + .size(sideButtonWidth, keyHeight) + .pos(2 * (sideButtonWidth + keyGap) + gridStart, gridStart - (keyHeight + keyGap)) .usePressTask(false) .build() ); + //CURSOR BOUNDS + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + for (var child : this.children()) { + if (child instanceof AbstractWidget widget) { + minX = Math.min(minX, widget.getX()); + minY = Math.min(minY, widget.getY()); + maxX = Math.max(maxX, widget.getX() + widget.getWidth()); + maxY = Math.max(maxY, widget.getY() + widget.getHeight()); + } + } + if (minX == Integer.MAX_VALUE) { + cursorBoundsX = -1; + cursorBoundsY = -1; + cursorBoundsWidth = -1; + cursorBoundsHeight = -1; + } else { + cursorBoundsX = minX; + cursorBoundsY = minY; + cursorBoundsWidth = maxX - minX; + cursorBoundsHeight = maxY - minY; + } + } + + private void pressKeyboardKey(KeyboardKey key) { + if (canTypeText()) { + InputHelper.typeChars(key.getInput()); + return; + } + + pressFallbackKey(key); + } + + private void pressFallbackKey(KeyboardKey key) { + if (!key.hasFallback()) { + return; + } + + pressKeyAction(key.getFallbackKey(), key.getFallbackModifiers()); + } + + private void pressSpace() { + if (canTypeText()) { + InputHelper.typeChars(" "); + return; + } + + pressKeyAction(GLFW.GLFW_KEY_SPACE); + } + + private void pressNavigationKey(int key) { + int modifiers = overlayKeyboard.isShiftPressed() + ? GLFW.GLFW_MOD_SHIFT + : 0; + pressKeyAction(key, modifiers); + } + + private void pressKeyAction(int key) { + pressKeyAction(key, 0); + } + + private void pressKeyAction(int key, + int temporaryModifiers) { + pressModifiers(temporaryModifiers); + InputHelper.pressKey(key, temporaryModifiers); + InputHelper.releaseKey(key, temporaryModifiers); + releaseModifiers(temporaryModifiers); + } + + private boolean canTypeText() { + return overlayKeyboard.getAttachedTo() != null + || Minecraft.getInstance().screen != null; + } + + private void pressModifiers(int modifiers) { + if ((modifiers & GLFW.GLFW_MOD_SHIFT) != 0) { + InputHelper.pressKey(GLFW.GLFW_KEY_LEFT_SHIFT); + } + } + + private void releaseModifiers(int modifiers) { + if ((modifiers & GLFW.GLFW_MOD_SHIFT) != 0) { + InputHelper.releaseKey(GLFW.GLFW_KEY_LEFT_SHIFT); + } } @Override diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/VRSettingsControls.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/VRSettingsControls.java index 0bb9c76d..c98483dd 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/VRSettingsControls.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/VRSettingsControls.java @@ -3,6 +3,7 @@ import org.vmstudio.visor.core.client.gui.screens.settings.VROptionsSet; import org.vmstudio.visor.core.client.gui.screens.settings.VRSettingsScreen; import org.vmstudio.visor.core.client.gui.screens.settings.categories.controls.VRSettingsActionSets; +import org.vmstudio.visor.core.client.gui.screens.settings.categories.controls.VRSettingsKeyboardLayouts; import org.vmstudio.visor.core.client.settings.VROptionWidgetType; import org.vmstudio.visor.core.client.gui.screens.settings.OptionWidgetEntry; import org.vmstudio.visor.core.client.gui.screens.settings.OptionWidgetPosition; @@ -39,6 +40,13 @@ protected OptionWidgetEntry[] getOptionEntries() { 0, "visor.options.controls.action_sets.button" ), + new OptionWidgetEntry( + this, + new VRSettingsKeyboardLayouts(getScreen(), this, onWidgetsChanged), + OptionWidgetPosition.LEFT, + 1, + "visor.options.controls.keyboard_layouts.button" + ), }; diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/controls/VRSettingsKeyboardLayouts.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/controls/VRSettingsKeyboardLayouts.java new file mode 100644 index 00000000..5d907f36 --- /dev/null +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/gui/screens/settings/categories/controls/VRSettingsKeyboardLayouts.java @@ -0,0 +1,145 @@ +package org.vmstudio.visor.core.client.gui.screens.settings.categories.controls; + +import me.phoenixra.atumvr.api.misc.color.AtumColor; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.vmstudio.visor.api.client.gui.helpers.GuiHelper; +import org.vmstudio.visor.api.client.gui.overlays.options.OptionTextures; +import org.vmstudio.visor.api.client.gui.widgets.info.WidgetInfoCheckboxList; +import org.vmstudio.visor.api.client.gui.widgets.lists.CheckboxList; +import org.vmstudio.visor.core.client.ClientContext; +import org.vmstudio.visor.core.client.gui.overlays.builtin.settings.SettingsTextures; +import org.vmstudio.visor.core.client.gui.screens.settings.OptionWidgetEntry; +import org.vmstudio.visor.core.client.gui.screens.settings.VROptionsSet; +import org.vmstudio.visor.core.client.gui.screens.settings.VRSettingsScreen; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayoutId; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayouts; +import org.vmstudio.visor.core.client.settings.VRClientSettings; +import org.vmstudio.visor.core.client.settings.VROptionWidgetType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.vmstudio.visor.core.client.VisorClientImpl.MC; + +public class VRSettingsKeyboardLayouts extends VROptionsSet { + + private CheckboxList listWidget; + + public VRSettingsKeyboardLayouts(@NotNull VRSettingsScreen screen, + @Nullable VROptionsSet previousOptions, + @NotNull Runnable onWidgetsChanged) { + super(screen, previousOptions, onWidgetsChanged); + } + + @Override + protected VROptionWidgetType[] getOptionTypes() { + return null; + } + + @Override + protected OptionWidgetEntry[] getOptionEntries() { + return null; + } + + @Override + public List initWidgets() { + var scaleHelper = getScreen().getScaleHelper(); + + Map rawEntries = new LinkedHashMap<>(); + for (KeyboardLayoutId layoutId : KeyboardLayouts.getSelectableLayouts()) { + rawEntries.put(layoutId.name(), layoutId.getDisplayName()); + } + + List selectedIds = VRClientSettings.getEnabledKeyboardLayouts() + .stream() + .map(Enum::name) + .toList(); + + listWidget = new CheckboxList( + new WidgetInfoCheckboxList() + .pos(scaleHelper.scaledX(57), scaleHelper.scaledY(43)) + .size(scaleHelper.scaledSize(142), scaleHelper.scaledSize(90)) + .textures( + OptionTextures.GRAY_TEXTURE, + SettingsTextures.CHECKBOX_BUTTON, + SettingsTextures.CHECKBOX_BUTTON_HOVERED, + SettingsTextures.CHECKBOX_BUTTON_SELECTED, + SettingsTextures.CHECKBOX_BUTTON_HOVERED_SELECTED + ), + rawEntries, + selectedIds, + (entry) -> saveSelectedLayouts() + ); + + return getWidgets(); + } + + @Override + public List getWidgets() { + List list = new ArrayList<>(); + list.add((T) listWidget); + return list; + } + + @Override + public void onPostRender(@NotNull GuiGraphics guiGraphics, + int mouseX, int mouseY, + float partialTicks) { + var scaleHelper = getScreen().getScaleHelper(); + GuiHelper.renderScalableText( + guiGraphics, + MC.font, + Component.translatable("visor.options.controls.keyboard_layouts.title").getString(), + AtumColor.WHITE.asInt(), + scaleHelper.scaledX(84), scaleHelper.scaledY(30), + scaleHelper.scaledSize(86), scaleHelper.scaledSize(10), + true + ); + GuiHelper.renderScalableText( + guiGraphics, + MC.font, + Component.translatable("visor.options.controls.keyboard_layouts.always_enabled").getString(), + VRSettingsScreen.INACTIVE_COLOR.asInt(), + scaleHelper.scaledX(60), scaleHelper.scaledY(136), + scaleHelper.scaledSize(126), scaleHelper.scaledSize(8), + true + ); + } + + @Override + public void loadDefaults() { + VRClientSettings.setEnabledKeyboardLayouts(List.of(KeyboardLayoutId.EN_US)); + ClientContext.settingsManager.saveOptions(); + reinit(); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double delta) { + listWidget.mouseScrolled(mouseX, mouseY, delta); + return super.mouseScrolled(mouseX, mouseY, delta); + } + + private void saveSelectedLayouts() { + List enabledLayouts = new ArrayList<>(); + for (String selectedId : listWidget.getSelectedEntriesId()) { + KeyboardLayoutId layoutId = KeyboardLayoutId.byName(selectedId); + if (layoutId != null) { + enabledLayouts.add(layoutId); + } + } + VRClientSettings.setEnabledKeyboardLayouts(enabledLayouts); + ClientContext.settingsManager.saveOptions(); + } +} \ No newline at end of file diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/input/mouse/MouseClickHandler.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/input/mouse/MouseClickHandler.java index 3bb11e8c..71d98b5a 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/input/mouse/MouseClickHandler.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/input/mouse/MouseClickHandler.java @@ -263,6 +263,12 @@ private void processScreen() { } private void processGame(@NotNull HandType handType) { + //disable keyboard (clicked outside it and not with screen opened) + var keyboardAccessor = ClientContext.overlayManager.getKeyboardAccessor(); + if(keyboardAccessor.isVisible()){ + keyboardAccessor.setVisible(false); + return; + } // update active hand if only one hand is pressed var activeHand = ClientContext.localPlayer.getActiveHand(); if (activeHand != handType) { diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/render/helpers/RenderGuiHelper.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/render/helpers/RenderGuiHelper.java index d606c8ec..60420822 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/render/helpers/RenderGuiHelper.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/render/helpers/RenderGuiHelper.java @@ -4,6 +4,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import me.phoenixra.atumvr.api.misc.color.AtumColor; +import net.minecraft.client.renderer.GameRenderer; import org.vmstudio.visor.api.client.player.pose.VRPlayerPoseClient; import org.vmstudio.visor.api.client.player.pose.PlayerPoseType; import org.vmstudio.visor.api.client.gui.overlays.VROverlay; @@ -43,9 +44,9 @@ public static void renderOverlayQuad(VROverlay overlay, Matrix4fc orientation, boolean depthAlways, boolean useLight, + boolean drawDragHandle, float scale ) { - // --- Prepare variables --- VRPlayerPoseClient renderPose = ClientContext.localPlayer .getPoseData(PlayerPoseType.RENDER); @@ -55,27 +56,32 @@ public static void renderOverlayQuad(VROverlay overlay, ); scale = scale * renderPose.getWorldScale(); - float fogStartCache = RenderSystem.getShaderFogStart(); var color = AtumColor.WHITE.asMutable(); - // --- Setup GL --- + boolean dragging = overlay.isBeingDragged(); + var barColor = (dragging + ? AtumColor.immutable(220, 220, 220, 230) + : AtumColor.immutable(190, 190, 190, 150)).asMutable(); + var renderTarget = overlay.getRenderTarget(); assert renderTarget != null; - renderTarget.bindRead(); RenderSystem.disableCull(); RenderSystem.setShaderTexture(0, renderTarget.getColorTextureId()); if (!VRRenderState.isInMainMenu()) { - RenderSystem.setShaderFogStart(Float.MAX_VALUE); if (MC.player != null && MC.player.isShiftKeyDown()) { color.setRGBA( color.getRed(), color.getGreen(), color.getBlue(), - color.getAlpha() * 0.75F + (int) (color.getAlpha() * 0.75F) + ); + barColor.setRGBA( + barColor.getRed(), barColor.getGreen(), barColor.getBlue(), + (int) (barColor.getAlpha() * 0.75F) ); } @@ -96,33 +102,34 @@ public static void renderOverlayQuad(VROverlay overlay, if (depthAlways) { RenderSystem.depthFunc(GL11C.GL_ALWAYS); - //disable mask to not mess up depth for something RenderSystem.depthMask(false); } else { RenderSystem.depthFunc(GL11C.GL_LEQUAL); RenderSystem.depthMask(true); } - RenderSystem.enableDepthTest(); - // --- Setup Pose --- + // --- Pose --- poseStack.pushPose(); poseStack.translate(position.x() - eye.x(), position.y() - eye.y(), position.z() - eye.z()); poseStack.mulPoseMatrix((Matrix4f) orientation); poseStack.scale(scale, scale, scale); - - // --- Render --- + // --- Quad + light --- + int packedLight = -1; if (MC.level != null && useLight) { + Vector3fc lightPos = position; if (RenderHelper.isInSolidBlock(position) || ((GameRendererExtension) MC.gameRenderer).visor$isInBlock()) { - position = ClientContext.localPlayer.getPoseData(PlayerPoseType.RENDER).getHmd().getPosition(); + lightPos = ClientContext.localPlayer + .getPoseData(PlayerPoseType.RENDER) + .getHmd() + .getPosition(); } - int minLight = ShadersHelper.shaderLight(); - int light = ClientUtils.getCombinedLightWithMin( + packedLight = ClientUtils.getCombinedLightWithMin( MC.level, - BlockPos.containing(new Vec3((Vector3f) position)), + BlockPos.containing(new Vec3((Vector3f) lightPos)), minLight ); RenderHelper.renderDisplayQuadWithLight( @@ -131,7 +138,7 @@ public static void renderOverlayQuad(VROverlay overlay, (float) overlay.getWidth(), (float) overlay.getHeight(), VROverlayPose.QUAD_SCALE, - light, + packedLight, false ); } else { @@ -144,6 +151,17 @@ public static void renderOverlayQuad(VROverlay overlay, ); } + // --- Drag handle bar + if (drawDragHandle && overlay.supportsDragging()) { + float brightness = 1f; + if (packedLight >= 0) { + int blockLight = (packedLight >> 4) & 0xF; + int skyLight = (packedLight >> 20) & 0xF; + brightness = Math.max(0.2f, Math.max(blockLight, skyLight) / 15f); + } + drawDragHandleBar(overlay, poseStack, barColor, brightness); + } + // --- Restore --- RenderSystem.setShaderFogStart(fogStartCache); RenderSystem.depthFunc(GL11C.GL_LEQUAL); @@ -153,10 +171,60 @@ public static void renderOverlayQuad(VROverlay overlay, RenderSystem.enableCull(); poseStack.popPose(); - - } + private static void drawDragHandleBar(VROverlay overlay, + PoseStack poseStack, + AtumColor barColor, + float brightness) { + RenderSystem.setShader(GameRenderer::getPositionColorShader); + + float aspect = overlay.getAspectRatio(); + float halfWidth = VROverlayPose.QUAD_SCALE * 0.5f; + float halfHeight = halfWidth * aspect; + + float barCenterX = 0f; + float barHalfWidth = halfWidth * 0.18f; + float regionBottom = -halfHeight; + + int edgeX = overlay.getCursorBoundsX(); + int edgeY = overlay.getCursorBoundsY(); + int edgeWidth = overlay.getCursorBoundsWidth(); + int edgeHeight = overlay.getCursorBoundsHeight(); + int width = overlay.getWidth(); + int height = overlay.getHeight(); + if (width > 0 && height > 0 + && edgeX >= 0 && edgeY >= 0 + && edgeWidth >= 0 && edgeHeight >= 0) { + float nx0 = -halfWidth + ((float) edgeX / width) * (2f * halfWidth); + float nx1 = -halfWidth + ((float) (edgeX + edgeWidth) / width) * (2f * halfWidth); + regionBottom = halfHeight - ((float) (edgeY + edgeHeight) / height) * (2f * halfHeight); + barCenterX = (nx0 + nx1) * 0.5f; + barHalfWidth = (nx1 - nx0) * 0.18f; + } + float barHalfHeight = halfHeight * 0.025f; + float barGap = halfHeight * 0.04f; + float barCenterY = regionBottom - barGap - barHalfHeight; + + var pose = poseStack.last().pose(); + BufferBuilder buf = Tesselator.getInstance().getBuilder(); + buf.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + float r = barColor.getRed() * brightness; + float g = barColor.getGreen() * brightness; + float b = barColor.getBlue() * brightness; + float a = barColor.getAlpha(); + float left = barCenterX - barHalfWidth; + float right = barCenterX + barHalfWidth; + float top = barCenterY + barHalfHeight; + float bottom = barCenterY - barHalfHeight; + buf.vertex(pose, left, bottom, 0f).color(r, g, b, a).endVertex(); + buf.vertex(pose, right, bottom, 0f).color(r, g, b, a).endVertex(); + buf.vertex(pose, right, top, 0f).color(r, g, b, a).endVertex(); + buf.vertex(pose, left, top, 0f).color(r, g, b, a).endVertex(); + + BufferUploader.drawWithShader(buf.end()); + } } diff --git a/visor-core/src/main/java/org/vmstudio/visor/core/client/settings/VRClientSettings.java b/visor-core/src/main/java/org/vmstudio/visor/core/client/settings/VRClientSettings.java index 808ad499..b2e73774 100644 --- a/visor-core/src/main/java/org/vmstudio/visor/core/client/settings/VRClientSettings.java +++ b/visor-core/src/main/java/org/vmstudio/visor/core/client/settings/VRClientSettings.java @@ -7,6 +7,8 @@ import org.vmstudio.visor.api.server.SupportedMovement; import org.vmstudio.visor.api.server.VRServerSettings; import org.vmstudio.visor.core.client.VisorClientImpl; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayoutId; +import org.vmstudio.visor.core.client.gui.overlays.builtin.keyboard.KeyboardLayouts; import org.vmstudio.visor.core.client.player.body.VRBodyTypeHandsOnly; import org.vmstudio.visor.core.client.settings.options.VROptionField; import org.vmstudio.visor.core.client.settings.options.enums.MirrorMode; @@ -25,6 +27,9 @@ import org.joml.Quaternionfc; import org.joml.Vector3fc; +import java.util.Collection; +import java.util.List; + import static org.vmstudio.visor.core.client.VisorClientImpl.MC; public class VRClientSettings { @@ -47,6 +52,12 @@ public class VRClientSettings { @Getter @VROptionField(key = "keyboard.keysShift") protected static String keyboardKeysShift = "~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL;':\"ZXCVBNM,./?<>"; + + @Getter + @VROptionField(key = "keyboard.layouts", category = VROptionCategory.CONTROLS) + protected static String keyboardLayouts = KeyboardLayouts.serializeEnabled( + List.of(KeyboardLayoutId.EN_US) + ); //--- @@ -366,6 +377,16 @@ public static RotationMode getRotationMode() { return rotationMode; } + public static @NotNull List getEnabledKeyboardLayouts() { + return KeyboardLayouts.getEnabled(keyboardLayouts); + } + + public static void setEnabledKeyboardLayouts( + @NotNull Collection layouts + ) { + keyboardLayouts = KeyboardLayouts.serializeEnabled(layouts); + } + public static void updateThirdPersonCamera(@NotNull Vector3fc position, @NotNull Quaternionfc rotation, boolean save){ diff --git a/visor-core/src/main/resources/assets/visor/lang/en_us.json b/visor-core/src/main/resources/assets/visor/lang/en_us.json index c8d5dc8b..e8afa5e8 100644 --- a/visor-core/src/main/resources/assets/visor/lang/en_us.json +++ b/visor-core/src/main/resources/assets/visor/lang/en_us.json @@ -104,6 +104,9 @@ "visor.options.controls.left_handed.tooltip": "If left hand should be dominant", "visor.options.controls.action_sets": "Select action set", "visor.options.controls.action_sets.button": "Action Bindings...", + "visor.options.controls.keyboard_layouts.button": "Keyboard Layouts...", + "visor.options.controls.keyboard_layouts.title": "Keyboard layouts", + "visor.options.controls.keyboard_layouts.always_enabled": "If all layouts are off, English (US) is used as fallback", "visor.options.controls.action": "Action", "_comment.immersion": "----Immersion Settings----",