From ef4e15ee509ac9d2a3fa53a26593cabf35e92a81 Mon Sep 17 00:00:00 2001 From: fgnm Date: Thu, 6 Jan 2022 12:44:20 +0100 Subject: [PATCH] Fully undecorated window in Windows OS --- .../games/rednblack/editor/HyperLap2DApp.java | 36 +-- .../editor/utils/HyperLap2DUtils.java | 279 +++++++----------- .../rednblack/editor/view/ui/UIMainTable.java | 21 +- .../editor/view/ui/UIWindowTitle.java | 7 +- 4 files changed, 140 insertions(+), 203 deletions(-) diff --git a/src/main/java/games/rednblack/editor/HyperLap2DApp.java b/src/main/java/games/rednblack/editor/HyperLap2DApp.java index e8a21754..4dd601ab 100644 --- a/src/main/java/games/rednblack/editor/HyperLap2DApp.java +++ b/src/main/java/games/rednblack/editor/HyperLap2DApp.java @@ -8,6 +8,7 @@ import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Window; import games.rednblack.editor.proxy.SettingsManager; import games.rednblack.editor.splash.SplashScreenAdapter; import games.rednblack.editor.utils.AppConfig; +import games.rednblack.editor.utils.HyperLap2DUtils; import games.rednblack.h2d.common.view.ui.StandardWidgetsFactory; import org.apache.commons.lang3.SystemUtils; @@ -54,27 +55,26 @@ public class HyperLap2DApp extends ApplicationAdapter { splashWindow = app.newWindow(new SplashScreenAdapter(), config2); - Gdx.app.postRunnable(new Runnable() { - @Override - public void run() { - hyperlap2D = new HyperLap2D(settingsManager); + Gdx.app.postRunnable(() -> { + hyperlap2D = new HyperLap2D(settingsManager); - Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); - config.setTitle("HyperLap2D - Beta v" + AppConfig.getInstance().versionString); - config.setResizable(true); + Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); + config.setTitle("HyperLap2D - Beta v" + AppConfig.getInstance().versionString); + config.setResizable(true); + if (!SystemUtils.IS_OS_WINDOWS) config.setWindowedMode((int) (windowWidth), (int) (windowHeight)); - config.setIdleFPS(60); - config.setForegroundFPS(settingsManager.editorConfigVO.fpsLimit); - config.useVsync(false); - config.setInitialVisible(false); - config.setMaximized(true); - config.setWindowIcon("hyperlap_icon_96.png"); - config.setWindowSizeLimits(920, 800, -1, -1); - if (SystemUtils.IS_OS_WINDOWS) - config.setWindowPosition(0, (int) (windowHeight * .04)); + config.setIdleFPS(60); + config.setForegroundFPS(settingsManager.editorConfigVO.fpsLimit); + config.useVsync(false); + config.setInitialVisible(false); + config.setMaximized(true); + config.setWindowIcon("hyperlap_icon_96.png"); + config.setWindowSizeLimits(920, 800, -1, -1); - mainWindow = app.newWindow(hyperlap2D, config); - mainWindow.setWindowListener(hyperlap2D); + mainWindow = app.newWindow(hyperlap2D, config); + mainWindow.setWindowListener(hyperlap2D); + if (SystemUtils.IS_OS_WINDOWS) { + Gdx.app.postRunnable(() -> HyperLap2DUtils.overwriteWindowProc2(mainWindow.getWindowHandle())); } }); } diff --git a/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java b/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java index c466b889..d3204a63 100644 --- a/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java +++ b/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java @@ -1,39 +1,26 @@ -/* - * ****************************************************************************** - * * Copyright 2015 See AUTHORS file. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * ***************************************************************************** - */ - package games.rednblack.editor.utils; import com.badlogic.gdx.math.MathUtils; -import com.badlogic.gdx.scenes.scene2d.Actor; -import com.badlogic.gdx.scenes.scene2d.InputEvent; -import com.badlogic.gdx.scenes.scene2d.InputListener; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; import games.rednblack.editor.HyperLap2DFacade; -import games.rednblack.editor.view.ui.UIWindowAction; -import games.rednblack.editor.view.ui.UIWindowActionMediator; +import games.rednblack.editor.view.ui.UIWindowTitle; +import games.rednblack.editor.view.ui.UIWindowTitleMediator; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.commons.lang3.SystemUtils; import org.lwjgl.BufferUtils; import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWNativeWin32; +import org.lwjgl.system.JNI; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.windows.RECT; +import org.lwjgl.system.windows.User32; +import org.lwjgl.system.windows.WINDOWPLACEMENT; +import org.lwjgl.system.windows.WindowProc; import java.io.File; import java.io.FilenameFilter; import java.nio.DoubleBuffer; -import java.nio.IntBuffer; /** * Created by sargis on 4/1/15. @@ -64,15 +51,27 @@ public class HyperLap2DUtils { return System.getProperty("user.home") + File.separator + "Documents"; } - public static void setWindowDragListener(Actor actor) { - actor.addListener(new InputListener() { - private final long context = GLFW.glfwGetCurrentContext(); - private float startX = 0; - private float startY = 0; + /** + * Function to override Windows behavior to use undecorated style while preserving snap to edge and resize. + * Works only on Windows OS + * From: https://gist.github.com/SWinxy/d48469ac83834219a0044c2c5df55f56 + * @author SWinxy + * + * @param lwjglWindow lwjgl pointer to window object + */ + public static void overwriteWindowProc2(long lwjglWindow) { + if (!SystemUtils.IS_OS_WINDOWS) return; + + long hwnd = GLFWNativeWin32.glfwGetWin32Window(lwjglWindow); + long pWindowProc = User32.GetWindowLongPtr(hwnd, User32.GWL_WNDPROC); + System.out.println("oldptr: " + pWindowProc); + + WindowProc proc = new WindowProc() { + private final Vector2 tmp = new Vector2(); + private final Rectangle titleBounds = new Rectangle(); private final DoubleBuffer cursorX = BufferUtils.createDoubleBuffer(1); private final DoubleBuffer cursorY = BufferUtils.createDoubleBuffer(1); - private final IntBuffer windowX = BufferUtils.createIntBuffer(1); - private final IntBuffer windowY = BufferUtils.createIntBuffer(1); + private RECT rect; private int getX() { return MathUtils.floor((float) cursorX.get(0)); @@ -82,154 +81,90 @@ public class HyperLap2DUtils { } @Override - public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { - UIWindowActionMediator uiWindowActionMediator = HyperLap2DFacade.getInstance().retrieveMediator(UIWindowActionMediator.NAME); - UIWindowAction uiWindowAction = uiWindowActionMediator.getViewComponent(); - if (uiWindowAction.isMaximized()) - return false; - GLFW.glfwGetCursorPos(context, cursorX, cursorY); - startX = getX(); - startY = getY(); - return true; - } + public long invoke(long hwnd, int uMsg, long wParam, long lParam) { + if (uMsg == User32.WM_NCHITTEST) { + try (MemoryStack stack = MemoryStack.stackPush()) { + short x = (short) (lParam & 0xFFFF); + short y = (short) ((lParam & 0xFFFF0000) >> 16); + GLFW.glfwGetCursorPos(GLFW.glfwGetCurrentContext(), cursorX, cursorY); - @Override - public void touchDragged(InputEvent event, float x, float y, int pointer) { - GLFW.glfwGetCursorPos(context, cursorX, cursorY); - float offsetX = getX() - startX; - float offsetY = getY() - startY; - GLFW.glfwGetWindowPos(context, windowX, windowY); - GLFW.glfwSetWindowPos(context, (int)(windowX.get(0) + offsetX), (int)(windowY.get(0) + offsetY)); - } - }); - } + if (rect == null) + rect = RECT.calloc(stack); + User32.GetWindowRect(hwnd, rect); - public static void setWindowResizeListener(Actor actor) { - actor.addListener(new InputListener() { - private static final int TOP_BORDER = 0; - private static final int BOTTOM_BORDER = 1; - private static final int LEFT_BORDER = 2; - private static final int RIGHT_BORDER = 3; - private static final int BOTTOM_LEFT_CORNER = 4; - private static final int BOTTOM_RIGHT_CORNER = 5; - private static final int TOP_LEFT_CORNER = 6; - private static final int TOP_RIGHT_CORNER = 7; + if (y < rect.top() + 16 && x < rect.left() + 16) { + return User32.HTTOPLEFT; + } + if (y > rect.bottom() - 16 && x > rect.right() - 16) { + return User32.HTBOTTOMRIGHT; + } + if (y < rect.top() + 12 && x > rect.right() - 16) { + return User32.HTTOPRIGHT; + } + if (y > rect.bottom() - 16 && x < rect.left() + 16) { + return User32.HTBOTTOMLEFT; + } - private static final int BORDER_SIZE = 10; + if (y < rect.top() + 8) { + return User32.HTTOP; + } + if (x < rect.left() + 16) { + return User32.HTLEFT; + } + if (y > rect.bottom() - 16) { + return User32.HTBOTTOM; + } + if (x > rect.right() - 16) { + return User32.HTRIGHT; + } - private final long context = GLFW.glfwGetCurrentContext(); - private float startX = 0; - private float startY = 0; - private float startW = 0; - private float startH = 0; - private float startWindowX = 0; - private float startWindowY = 0; - private int anchorPoint = -1; - private final DoubleBuffer cursorX = BufferUtils.createDoubleBuffer(1); - private final DoubleBuffer cursorY = BufferUtils.createDoubleBuffer(1); + //Test if the pointer is in Title Bar + UIWindowTitleMediator uiWindowTitleMediator = HyperLap2DFacade.getInstance().retrieveMediator(UIWindowTitleMediator.NAME); + UIWindowTitle uiWindowTitle = uiWindowTitleMediator.getViewComponent(); + uiWindowTitle.localToScreenCoordinates(tmp.set(0, 0)); + titleBounds.set(tmp.x, tmp.y, uiWindowTitle.getWidth() + ((11 * uiWindowTitle.getWidth() / 100)), uiWindowTitle.getHeight()); - private final IntBuffer windowX = BufferUtils.createIntBuffer(1); - private final IntBuffer windowY = BufferUtils.createIntBuffer(1); - private final IntBuffer windowW = BufferUtils.createIntBuffer(1); - private final IntBuffer windowH = BufferUtils.createIntBuffer(1); - - private int getX() { - return MathUtils.floor((float) cursorX.get(0)); - } - private int getY() { - return MathUtils.floor((float) cursorY.get(0)); - } - private int getWidth() { - return MathUtils.floor((float) windowW.get(0)); - } - private int getHeight() { - return MathUtils.floor((float) windowH.get(0)); - } - private int getWindowX() { - return MathUtils.floor((float) windowX.get(0)); - } - private int getWindowY() { - return MathUtils.floor((float) windowY.get(0)); - } - - @Override - public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { - UIWindowActionMediator uiWindowActionMediator = HyperLap2DFacade.getInstance().retrieveMediator(UIWindowActionMediator.NAME); - UIWindowAction uiWindowAction = uiWindowActionMediator.getViewComponent(); - if (uiWindowAction.isMaximized()) - return false; - - GLFW.glfwGetCursorPos(context, cursorX, cursorY); - GLFW.glfwGetWindowSize(context, windowW, windowH); - GLFW.glfwGetWindowPos(context, windowX, windowY); - anchorPoint = getAnchorPoint(); - - if (anchorPoint == -1) - return false; - - startX = getX(); - startY = getY(); - startW = getWidth(); - startH = getHeight(); - startWindowX = getWindowX(); - startWindowY = getWindowY(); - return true; - } - - @Override - public void touchDragged(InputEvent event, float x, float y, int pointer) { - GLFW.glfwGetWindowPos(context, windowX, windowY); - GLFW.glfwGetCursorPos(context, cursorX, cursorY); - float offsetX = getX() - startX + (startWindowX - getWindowX()); - float offsetY = getY() - startY; - GLFW.glfwGetWindowSize(context, windowW, windowH); - switch (anchorPoint) { - case BOTTOM_BORDER: - GLFW.glfwSetWindowSize(context, (int) startW, (int)(startH + offsetY)); - break; - case TOP_BORDER: - GLFW.glfwSetWindowSize(context, (int) startW, (int)(startH - offsetY)); - break; - case LEFT_BORDER: - GLFW.glfwSetWindowSize(context, (int)(startW - offsetX), (int) startH); - break; - case RIGHT_BORDER: - GLFW.glfwSetWindowSize(context, (int)(startW + offsetX), (int) startH); - break; - case BOTTOM_RIGHT_CORNER: - GLFW.glfwSetWindowSize(context, (int)(startW + offsetX), (int)(startH + offsetY)); - break; - case TOP_RIGHT_CORNER: - GLFW.glfwSetWindowSize(context, (int)(startW + offsetX), (int)(startH - offsetY)); - break; - case BOTTOM_LEFT_CORNER: - GLFW.glfwSetWindowSize(context, (int)(startW - offsetX), (int)(startH + offsetY)); - break; - case TOP_LEFT_CORNER: - GLFW.glfwSetWindowSize(context, (int)(startW - offsetX), (int)(startH - offsetY)); - break; + int glfwX = getX(); + int glfwY = getY(); + if (titleBounds.x <= glfwX && titleBounds.x + titleBounds.width >= glfwX + && titleBounds.y >= glfwY && titleBounds.y - titleBounds.height <= glfwY) { + return User32.HTCAPTION; + } + return JNI.callPPPP(hwnd, uMsg, wParam, lParam, pWindowProc); + } } - } + if (uMsg == User32.WM_NCCALCSIZE) { + if (wParam == 1) { + try (MemoryStack stack = MemoryStack.stackPush()) { + WINDOWPLACEMENT windowplacement = WINDOWPLACEMENT.calloc(stack); + User32.GetWindowPlacement(hwnd, windowplacement); + // ...but instead we're gonna just pretend it's just a RECT struct + // the NCCALCSIZE_PARAMS struct conveniently has what we need + // at the very start, so we can quietly say it's a RECT struct lol + // hacky because LWJGL doesn't include the structs to the aforementioned + // struct, nor some of the other structs contained + RECT rect = RECT.create(lParam); + if (windowplacement.showCmd() != User32.SW_MAXIMIZE) { + rect.left(rect.left() + 8); +// rect.top(rect.top() + 0); + rect.right(rect.right() - 8); + rect.bottom(rect.bottom() - 8); + } else { + rect.left(rect.left() + 8); + rect.top(rect.top() + 8); + rect.right(rect.right() - 8); + rect.bottom(rect.bottom() - 8); + } - private int getAnchorPoint() { - if (getX() < BORDER_SIZE && getY() > BORDER_SIZE && getY() < getHeight() - BORDER_SIZE) - return LEFT_BORDER; - if (getX() < BORDER_SIZE && getY() < BORDER_SIZE) - return TOP_LEFT_CORNER; - if (getX() < BORDER_SIZE && getY() > getHeight() - BORDER_SIZE) - return BOTTOM_LEFT_CORNER; - if (getX() > BORDER_SIZE && getY() > getHeight() - BORDER_SIZE && getX() < getWidth() - BORDER_SIZE) - return BOTTOM_BORDER; - if (getX() > getWidth() - BORDER_SIZE && getY() > getHeight() - BORDER_SIZE) - return BOTTOM_RIGHT_CORNER; - if (getX() > getWidth() - BORDER_SIZE && getY() > BORDER_SIZE && getY() < getHeight() - BORDER_SIZE) - return RIGHT_BORDER; - if (getX() > getWidth() - BORDER_SIZE && getY() < BORDER_SIZE) - return TOP_RIGHT_CORNER; - if (getX() > BORDER_SIZE && getY() < BORDER_SIZE && getX() < getWidth() - BORDER_SIZE) - return TOP_BORDER; - return -1; + return rect.address(); + } + } + } + return JNI.callPPPP(hwnd, uMsg, wParam, lParam, pWindowProc); } - }); + }; + System.out.println("procaddr: " + proc.address()); + System.out.println("setptr: " + User32.SetWindowLongPtr(hwnd, User32.GWL_WNDPROC, proc.address())); + System.out.println("setwinptr: " + User32.SetWindowPos(hwnd, 0, 0, 0, 0, 0, User32.SWP_NOMOVE | User32.SWP_NOSIZE | User32.SWP_NOZORDER | User32.SWP_FRAMECHANGED)); } } diff --git a/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java b/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java index 68ba2f6b..cab74b02 100644 --- a/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java +++ b/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java @@ -20,11 +20,11 @@ package games.rednblack.editor.view.ui; import com.kotcrab.vis.ui.widget.VisTable; import games.rednblack.editor.HyperLap2DFacade; -import games.rednblack.editor.utils.HyperLap2DUtils; import games.rednblack.editor.view.menu.HyperLap2DMenuBar; import games.rednblack.editor.view.menu.HyperLap2DMenuBarMediator; import games.rednblack.editor.view.ui.box.*; import games.rednblack.editor.view.ui.widget.H2DLogo; +import org.apache.commons.lang3.SystemUtils; public class UIMainTable extends VisTable { private final VisTable topTable, middleTable; @@ -47,8 +47,6 @@ public class UIMainTable extends VisTable { initToolsPanel(); initLeftBoxesPanel(); initRightBoxesPanel(); - - HyperLap2DUtils.setWindowDragListener(topTable); } private void initMenuBar() { @@ -56,16 +54,17 @@ public class UIMainTable extends VisTable { HyperLap2DMenuBarMediator hyperlap2DMenuBarMediator = facade.retrieveMediator(HyperLap2DMenuBarMediator.NAME); HyperLap2DMenuBar menuBar = hyperlap2DMenuBarMediator.getViewComponent(); - topTable.add(menuBar.getTable()).height(32).growX(); + topTable.add(menuBar.getTable()).height(32); - //TODO Undecorated window is cool but too much glitches - /*UIWindowTitleMediator uiWindowTitleMediator = facade.retrieveMediator(UIWindowTitleMediator.NAME); - UIWindowTitle uiWindowTitle = uiWindowTitleMediator.getViewComponent(); - topTable.add(uiWindowTitle).growX().fillY(); + if (SystemUtils.IS_OS_WINDOWS) { + UIWindowTitleMediator uiWindowTitleMediator = facade.retrieveMediator(UIWindowTitleMediator.NAME); + UIWindowTitle uiWindowTitle = uiWindowTitleMediator.getViewComponent(); + topTable.add(uiWindowTitle).growX().fillY(); - UIWindowActionMediator uiWindowActionMediator = facade.retrieveMediator(UIWindowActionMediator.NAME); - UIWindowAction uiWindowAction = uiWindowActionMediator.getViewComponent(); - topTable.add(uiWindowAction).padTop(-1).fillY();*/ + UIWindowActionMediator uiWindowActionMediator = facade.retrieveMediator(UIWindowActionMediator.NAME); + UIWindowAction uiWindowAction = uiWindowActionMediator.getViewComponent(); + topTable.add(uiWindowAction).padTop(-1).fillY(); + } } private void initSupportMenus() { diff --git a/src/main/java/games/rednblack/editor/view/ui/UIWindowTitle.java b/src/main/java/games/rednblack/editor/view/ui/UIWindowTitle.java index d04aa76c..ce5da170 100644 --- a/src/main/java/games/rednblack/editor/view/ui/UIWindowTitle.java +++ b/src/main/java/games/rednblack/editor/view/ui/UIWindowTitle.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.utils.Align; import com.kotcrab.vis.ui.VisUI; import com.kotcrab.vis.ui.widget.VisLabel; import com.kotcrab.vis.ui.widget.VisTable; +import games.rednblack.editor.utils.AppConfig; import games.rednblack.h2d.common.view.ui.StandardWidgetsFactory; public class UIWindowTitle extends VisTable { @@ -13,8 +14,10 @@ public class UIWindowTitle extends VisTable { public UIWindowTitle() { setBackground(VisUI.getSkin().getDrawable("menu-bg")); - title = StandardWidgetsFactory.createLabel("", "default", Align.center); - add(title); + title = StandardWidgetsFactory.createLabel("HyperLap2D - Beta v" + AppConfig.getInstance().versionString, "default", Align.center); + title.setEllipsis(true); + title.setWrap(true); + add(title).growX(); setTouchable(Touchable.enabled); }