From cfcaedbe045b3e7d2c193e5528d28ca5dfbbad5d Mon Sep 17 00:00:00 2001 From: fgnm Date: Sun, 30 Jan 2022 18:33:08 +0100 Subject: [PATCH] [Editor only] Undecorated style for macOS --- CHANGES | 2 +- .../games/rednblack/editor/HyperLap2DApp.java | 3 + .../rednblack/editor/proxy/FontManager.java | 2 +- .../editor/utils/HyperLap2DUtils.java | 115 ++++++++++++++++++ .../rednblack/editor/view/ui/UIMainTable.java | 8 ++ .../editor/view/ui/widget/H2DLogo.java | 26 ++-- 6 files changed, 135 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index e31de7c1..6234011e 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ = Editor = - New polygons management with open-ended shapes -- Fully undecorated in Windows OS +- Fully undecorated in Windows OS and macOS - Better UI = Runtime = diff --git a/src/main/java/games/rednblack/editor/HyperLap2DApp.java b/src/main/java/games/rednblack/editor/HyperLap2DApp.java index 4dd601ab..4b3372fd 100644 --- a/src/main/java/games/rednblack/editor/HyperLap2DApp.java +++ b/src/main/java/games/rednblack/editor/HyperLap2DApp.java @@ -76,6 +76,9 @@ public class HyperLap2DApp extends ApplicationAdapter { if (SystemUtils.IS_OS_WINDOWS) { Gdx.app.postRunnable(() -> HyperLap2DUtils.overwriteWindowProc2(mainWindow.getWindowHandle())); } + if (SystemUtils.IS_OS_MAC) { + Gdx.app.postRunnable(() -> HyperLap2DUtils.setCocoaCustomTitleBar(mainWindow.getWindowHandle(), true)); + } }); } diff --git a/src/main/java/games/rednblack/editor/proxy/FontManager.java b/src/main/java/games/rednblack/editor/proxy/FontManager.java index a512f16d..ce9961bc 100644 --- a/src/main/java/games/rednblack/editor/proxy/FontManager.java +++ b/src/main/java/games/rednblack/editor/proxy/FontManager.java @@ -118,7 +118,7 @@ public class FontManager extends Proxy { systemFontMap.put(f.getName(), file.getAbsolutePath()); } } catch (FontFormatException | IOException e) { - e.printStackTrace(); + System.err.println("Could not load " + file.getName() + " font file."); } } diff --git a/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java b/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java index eede2a05..bd5a0760 100644 --- a/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java +++ b/src/main/java/games/rednblack/editor/utils/HyperLap2DUtils.java @@ -2,6 +2,9 @@ package games.rednblack.editor.utils; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.InputListener; import games.rednblack.editor.HyperLap2DFacade; import games.rednblack.editor.view.ui.UIWindowTitle; import games.rednblack.editor.view.ui.UIWindowTitleMediator; @@ -9,9 +12,12 @@ 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.GLFWNativeCocoa; import org.lwjgl.glfw.GLFWNativeWin32; import org.lwjgl.system.JNI; import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.SharedLibrary; +import org.lwjgl.system.macosx.ObjCRuntime; import org.lwjgl.system.windows.RECT; import org.lwjgl.system.windows.User32; import org.lwjgl.system.windows.WINDOWPLACEMENT; @@ -20,6 +26,7 @@ import org.lwjgl.system.windows.WindowProc; import java.io.File; import java.io.FilenameFilter; import java.nio.DoubleBuffer; +import java.nio.IntBuffer; public class HyperLap2DUtils { public static final FilenameFilter PNG_FILTER = new SuffixFileFilter(".png"); @@ -46,6 +53,114 @@ public class HyperLap2DUtils { return System.getProperty("user.home") + File.separator + "Documents"; } + /** + * Fairly based on this article: http://svenandersson.se/2016/rendering-to-full-size-of-an-nswindow-using-glfw3.html + * Still not finished (Window dragging is not working) but useful stuff here to interact with native OS + * + * @param lwjglWindow lwjgl pointer to window object + * @param FullSizeContentView apply the FullSizeContentView styleMask + */ + public static void setCocoaCustomTitleBar(long lwjglWindow, boolean FullSizeContentView) { + System.out.println(lwjglWindow); + long nswindow = GLFWNativeCocoa.glfwGetCocoaWindow(lwjglWindow); + System.out.println(nswindow); + SharedLibrary objc = ObjCRuntime.getLibrary(); + long objc_msgSend = objc.getFunctionAddress("objc_msgSend"); + + boolean bool = JNI.invokePPZ(nswindow, ObjCRuntime.sel_getUid("titlebarAppearsTransparent"), objc_msgSend); + System.out.println("titlebarAppearsTransparent = " + bool); + + JNI.invokePPV(nswindow, ObjCRuntime.sel_getUid("setTitlebarAppearsTransparent:"), true, objc_msgSend); + JNI.invokePPV(nswindow, ObjCRuntime.sel_getUid("setMovableByWindowBackground:"), true, objc_msgSend); + + bool = JNI.invokePPZ(nswindow, ObjCRuntime.sel_getUid("titlebarAppearsTransparent"), objc_msgSend); + System.out.println("titlebarAppearsTransparent = " + bool); + + long NSColor = ObjCRuntime.objc_getClass("NSColor"); + long darkGrayColor = JNI.invokePPP(NSColor, ObjCRuntime.sel_getUid("darkGrayColor"), objc_msgSend); + JNI.invokePPPV(nswindow, ObjCRuntime.sel_getUid("setBackgroundColor:"), darkGrayColor, objc_msgSend); + //0 - visible + //1 - hidden + JNI.invokePPV(nswindow, ObjCRuntime.sel_getUid("setTitleVisibility:"), 1, objc_msgSend); + + if (FullSizeContentView) { + //Borderless - 0b0000000000000000 + //Titled - 0b0000000000000001 + //Closable - 0b0000000000000010 + //Miniaturizable - 0b0000000000000100 + //Resizable - 0b0000000000001000 + //UtilityWindow - 0b0000000000010000 + //DocumentModalWindow - 0b0000000001000000 + //NonActivatingPanel - 0b0000000010000000 + //Textured - 0b0000000100000000 + //UnifiedTitleAndToolbar - 0b0001000000000000 + //HudWindow - 0b0010000000000000 + //FullScreen - 0b0100000000000000 + //FullSizeContentView - 0b1000000000010010 + JNI.invokePPV(nswindow, ObjCRuntime.sel_getUid("setStyleMask:"), 0b1001000000001111, objc_msgSend); + + int defaultMask = JNI.invokePPI(nswindow, ObjCRuntime.sel_getUid("styleMask"), objc_msgSend); + System.out.println("styleMask = " + defaultMask); + + GLFW.glfwMaximizeWindow(lwjglWindow); + + /** + * NSView* glView = [wnd contentView]; + * Method originalMethod = class_getInstanceMethod([glView class], @selector(mouseDownCanMoveWindow)); + * Method categoryMethod = class_getInstanceMethod(FakeView.class, @selector(fakeMouseDownCanMoveWindow)); + * method_exchangeImplementations(originalMethod, categoryMethod); + */ + + /*long glView = JNI.invokePPP(nswindow, ObjCRuntime.sel_getUid("contentView"), objc_msgSend); + System.out.println("glView: " + glView); + long glViewClass = ObjCRuntime.object_getClass(glView); + System.out.println("glViewClass: " + glViewClass); + long originalMethod = ObjCRuntime.class_getInstanceMethod(glViewClass, ObjCRuntime.sel_getUid("mouseDownCanMoveWindow")); + System.out.println("originalMethod: " + originalMethod); + + //TODO Missing a way to create the categoryMethod + + ObjCRuntime.method_exchangeImplementations(originalMethod, categoryMethod); + */ + } + } + + public static void setWindowDragListener(Actor actor) { + actor.addListener(new InputListener() { + private final long context = GLFW.glfwGetCurrentContext(); + private float startX = 0; + private float startY = 0; + 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 int getX() { + return MathUtils.floor((float) cursorX.get(0)); + } + private int getY() { + return MathUtils.floor((float) cursorY.get(0)); + } + + @Override + public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) { + GLFW.glfwGetCursorPos(context, cursorX, cursorY); + startX = getX(); + startY = getY(); + return true; + } + + @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)); + } + }); + } + /** * Function to override Windows behavior to use undecorated style while preserving snap to edge and resize. * Works only on Windows OS 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 7abcea3e..a266e0d3 100644 --- a/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java +++ b/src/main/java/games/rednblack/editor/view/ui/UIMainTable.java @@ -20,6 +20,7 @@ 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.*; @@ -65,6 +66,13 @@ public class UIMainTable extends VisTable { UIWindowActionMediator uiWindowActionMediator = facade.retrieveMediator(UIWindowActionMediator.NAME); UIWindowAction uiWindowAction = uiWindowActionMediator.getViewComponent(); topTable.add(uiWindowAction).padTop(-1).fillY(); + } else if (SystemUtils.IS_OS_MAC) { + topTable.add(menuBar.getTable()).height(32); + + UIWindowTitleMediator uiWindowTitleMediator = facade.retrieveMediator(UIWindowTitleMediator.NAME); + UIWindowTitle uiWindowTitle = uiWindowTitleMediator.getViewComponent(); + HyperLap2DUtils.setWindowDragListener(uiWindowTitle); + topTable.add(uiWindowTitle).growX().fillY(); } else { topTable.add(menuBar.getTable()).growX().height(32); } diff --git a/src/main/java/games/rednblack/editor/view/ui/widget/H2DLogo.java b/src/main/java/games/rednblack/editor/view/ui/widget/H2DLogo.java index cffaf682..ffeb5fa0 100644 --- a/src/main/java/games/rednblack/editor/view/ui/widget/H2DLogo.java +++ b/src/main/java/games/rednblack/editor/view/ui/widget/H2DLogo.java @@ -1,21 +1,3 @@ -/* - * ****************************************************************************** - * * 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.view.ui.widget; import com.badlogic.gdx.scenes.scene2d.InputEvent; @@ -25,13 +7,19 @@ import com.kotcrab.vis.ui.VisUI; import com.kotcrab.vis.ui.widget.VisImage; import com.kotcrab.vis.ui.widget.VisTable; import games.rednblack.editor.HyperLap2DApp; +import games.rednblack.editor.HyperLap2DFacade; +import games.rednblack.editor.proxy.SettingsManager; +import org.apache.commons.lang3.SystemUtils; public class H2DLogo extends VisTable { public H2DLogo() { + SettingsManager settingsManager = HyperLap2DFacade.getInstance().retrieveProxy(SettingsManager.NAME); Skin skin = VisUI.getSkin(); setBackground(skin.getDrawable("menu-bg")); VisImage logo = new VisImage(VisUI.getSkin().getDrawable("logo")); - add(logo).width(logo.getWidth()).height(logo.getHeight()).padLeft(7); + float pad = SystemUtils.IS_OS_MAC ? 73 : 7; + pad *= settingsManager.editorConfigVO.uiScaleDensity; + add(logo).width(logo.getWidth()).height(logo.getHeight()).padLeft(pad); logo.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) {