├── ClearVG ├── .gitignore ├── .project ├── pom.xml └── src │ ├── demo │ └── java │ │ └── nokori │ │ └── clear │ │ └── vg │ │ ├── ClearCircleDemo.java │ │ ├── ClearDraggableWidgetDemo.java │ │ ├── ClearHelloWorldDemo.java │ │ ├── ClearInputPopupAppDemo.java │ │ ├── ClearTextAreaCodeEditorDemo.java │ │ ├── ClearTextAreaDemo.java │ │ ├── ClearTextFieldDemo.java │ │ └── references │ │ ├── Demo.java │ │ ├── DemoProgram.java │ │ └── MemoryMappedFileReadExample.java │ └── main │ └── java │ └── nokori │ └── clear │ ├── vg │ ├── ClearApp.java │ ├── ClearColor.java │ ├── ClearStaticResources.java │ ├── NanoVGContext.java │ ├── apps │ │ └── ClearInputApp.java │ ├── font │ │ ├── Font.java │ │ └── FontStyle.java │ ├── transition │ │ ├── FillTransition.java │ │ ├── SizeTransition.java │ │ ├── Transition.java │ │ ├── TransitionImpl.java │ │ ├── TransitionManager.java │ │ ├── WidgetPositionTransition.java │ │ └── WidgetSizeTransition.java │ ├── util │ │ ├── BezierLineRenderer.java │ │ └── NanoVGScaler.java │ └── widget │ │ ├── ButtonAssembly.java │ │ ├── CircleWidget.java │ │ ├── DropShadowWidget.java │ │ ├── HalfCircleWidget.java │ │ ├── LabelWidget.java │ │ ├── RectangleWidget.java │ │ ├── TickerWidget.java │ │ ├── assembly │ │ ├── DraggableWidgetAssembly.java │ │ ├── RootWidgetAssembly.java │ │ ├── Widget.java │ │ ├── WidgetAssembly.java │ │ ├── WidgetClip.java │ │ ├── WidgetContainer.java │ │ ├── WidgetSynch.java │ │ └── WidgetUtils.java │ │ ├── listener │ │ ├── CharEventListener.java │ │ ├── EventListener.java │ │ ├── KeyEventListener.java │ │ ├── MouseButtonEventListener.java │ │ ├── MouseEnteredEventListener.java │ │ ├── MouseExitedEventListener.java │ │ ├── MouseMotionEventListener.java │ │ └── MouseScrollEventListener.java │ │ └── text │ │ ├── ClearEscapeSequences.java │ │ ├── DefaultTextAreaContentInputHandler.java │ │ ├── EditingEndedCallback.java │ │ ├── ObservableTextAreaWidget.java │ │ ├── TextAreaAutoFormatterWidget.java │ │ ├── TextAreaContentHandler.java │ │ ├── TextAreaContentInputHandler.java │ │ ├── TextAreaHistory.java │ │ ├── TextAreaInputSettings.java │ │ ├── TextAreaWidget.java │ │ └── TextFieldWidget.java │ └── windows │ └── event │ └── vg │ ├── MouseEnteredEvent.java │ └── MouseExitedEvent.java ├── ClearWindows ├── .gitignore ├── .project ├── pom.xml └── src │ └── main │ └── java │ └── nokori │ └── clear │ └── windows │ ├── ContextParams.java │ ├── Cursor.java │ ├── FrameRateLimiter.java │ ├── GLFWException.java │ ├── Joystick.java │ ├── Monitor.java │ ├── PixelFormat.java │ ├── VideoMode.java │ ├── Window.java │ ├── WindowManager.java │ ├── callback │ ├── CharCallback.java │ ├── InputCallback.java │ ├── JoystickCallback.java │ ├── JoystickStateCallback.java │ ├── KeyCallback.java │ ├── MouseButtonCallback.java │ ├── MouseMotionCallback.java │ ├── MouseScrollCallback.java │ ├── WindowFramebufferSizeCallback.java │ ├── WindowPosCallback.java │ └── WindowSizeCallback.java │ ├── event │ ├── CharEvent.java │ ├── Event.java │ ├── EventImpl.java │ ├── JoystickAxisEvent.java │ ├── JoystickButtonEvent.java │ ├── JoystickStateEvent.java │ ├── KeyEvent.java │ ├── MouseButtonEvent.java │ ├── MouseEventImpl.java │ ├── MouseMotionEvent.java │ └── MouseScrollEvent.java │ ├── pool │ ├── Pool.java │ └── Poolable.java │ └── util │ ├── FastArrayList.java │ ├── ImmutableList.java │ ├── JVMUtil.java │ ├── OperatingSystemUtil.java │ ├── Stopwatch.java │ ├── TinyFileDialog.java │ └── WindowedApplication.java ├── README.md └── fonts ├── NotoSans ├── NotoSans-Bold.ttf ├── NotoSans-Italic.ttf ├── NotoSans-Light.ttf └── NotoSans-Regular.ttf └── NotoSerif ├── NotoSerif-Bold.ttf ├── NotoSerif-Italic.ttf ├── NotoSerif-Light.ttf └── NotoSerif-Regular.ttf /ClearVG/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | /.settings/ 4 | /.classpath 5 | -------------------------------------------------------------------------------- /ClearVG/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ClearVG 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /ClearVG/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | nokori.clear 6 | ClearVG 7 | 1.0.0 8 | 9 | 10 | 11 | maven-compiler-plugin 12 | 3.7.0 13 | 14 | 8 15 | 8 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1.0.0 23 | 3.2.1 24 | 1.9.12 25 | natives-windows 26 | 27 | 28 | 29 | 30 | nokori.clear 31 | ClearWindows 32 | ${clear.version} 33 | 34 | 35 | org.lwjgl 36 | lwjgl 37 | ${lwjgl.version} 38 | 39 | 40 | org.lwjgl 41 | lwjgl-egl 42 | ${lwjgl.version} 43 | 44 | 45 | org.lwjgl 46 | lwjgl-glfw 47 | ${lwjgl.version} 48 | 49 | 50 | org.lwjgl 51 | lwjgl-jemalloc 52 | ${lwjgl.version} 53 | 54 | 55 | org.lwjgl 56 | lwjgl-nanovg 57 | ${lwjgl.version} 58 | 59 | 60 | org.lwjgl 61 | lwjgl-nfd 62 | ${lwjgl.version} 63 | 64 | 65 | org.lwjgl 66 | lwjgl-openal 67 | ${lwjgl.version} 68 | 69 | 70 | org.lwjgl 71 | lwjgl-opencl 72 | ${lwjgl.version} 73 | 74 | 75 | org.lwjgl 76 | lwjgl-opengl 77 | ${lwjgl.version} 78 | 79 | 80 | org.lwjgl 81 | lwjgl-opengles 82 | ${lwjgl.version} 83 | 84 | 85 | org.lwjgl 86 | lwjgl-stb 87 | ${lwjgl.version} 88 | 89 | 90 | org.lwjgl 91 | lwjgl-tinyfd 92 | ${lwjgl.version} 93 | 94 | 95 | org.lwjgl 96 | lwjgl 97 | ${lwjgl.version} 98 | ${lwjgl.natives} 99 | 100 | 101 | org.lwjgl 102 | lwjgl-glfw 103 | ${lwjgl.version} 104 | ${lwjgl.natives} 105 | 106 | 107 | org.lwjgl 108 | lwjgl-jemalloc 109 | ${lwjgl.version} 110 | ${lwjgl.natives} 111 | 112 | 113 | org.lwjgl 114 | lwjgl-nanovg 115 | ${lwjgl.version} 116 | ${lwjgl.natives} 117 | 118 | 119 | org.lwjgl 120 | lwjgl-nfd 121 | ${lwjgl.version} 122 | ${lwjgl.natives} 123 | 124 | 125 | org.lwjgl 126 | lwjgl-openal 127 | ${lwjgl.version} 128 | ${lwjgl.natives} 129 | 130 | 131 | org.lwjgl 132 | lwjgl-opengl 133 | ${lwjgl.version} 134 | ${lwjgl.natives} 135 | 136 | 137 | org.lwjgl 138 | lwjgl-opengles 139 | ${lwjgl.version} 140 | ${lwjgl.natives} 141 | 142 | 143 | org.lwjgl 144 | lwjgl-stb 145 | ${lwjgl.version} 146 | ${lwjgl.natives} 147 | 148 | 149 | org.lwjgl 150 | lwjgl-tinyfd 151 | ${lwjgl.version} 152 | ${lwjgl.natives} 153 | 154 | 155 | org.joml 156 | joml 157 | ${joml.version} 158 | 159 | 160 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearCircleDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.widget.CircleWidget; 4 | import nokori.clear.vg.widget.HalfCircleWidget; 5 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 6 | import nokori.clear.windows.GLFWException; 7 | import nokori.clear.windows.Window; 8 | import nokori.clear.windows.WindowManager; 9 | 10 | public class ClearCircleDemo extends ClearApp { 11 | 12 | private static final int WINDOW_WIDTH = 256; 13 | private static final int WINDOW_HEIGHT = 256; 14 | 15 | public static void main(String[] args) { 16 | ClearApp.launch(new ClearCircleDemo(), args); 17 | } 18 | 19 | @Override 20 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 21 | float circleRadius = 25f; 22 | float circleY = rootWidgetAssembly.getHeight()/2 - circleRadius; 23 | float circlePadding = circleRadius; 24 | 25 | /* 26 | * Half Circle 27 | */ 28 | 29 | float sectorCircleX = circlePadding; 30 | HalfCircleWidget sectorCircleWidget = new HalfCircleWidget(sectorCircleX, circleY, circleRadius, ClearColor.BLACK, ClearColor.CORAL, HalfCircleWidget.Orientation.LEFT); 31 | sectorCircleWidget.setOnMouseEnteredEvent(e -> { 32 | System.out.println("Mouse entered sector circle"); 33 | }); 34 | 35 | sectorCircleWidget.setOnMouseExitedEvent(e -> { 36 | System.out.println("Mouse exited sector circle"); 37 | }); 38 | 39 | rootWidgetAssembly.addChild(sectorCircleWidget); 40 | 41 | /* 42 | * Normal Circle 43 | */ 44 | float circleX = rootWidgetAssembly.getWidth() - (circleRadius * 2) - circlePadding; 45 | CircleWidget circleWidget = new CircleWidget(circleX, circleY, circleRadius, ClearColor.CORAL, ClearColor.BLACK); 46 | 47 | circleWidget.setOnMouseEnteredEvent(e -> { 48 | System.out.println("Mouse entered circle"); 49 | }); 50 | 51 | circleWidget.setOnMouseExitedEvent(e -> { 52 | System.out.println("Mouse exited circle"); 53 | }); 54 | 55 | rootWidgetAssembly.addChild(circleWidget); 56 | } 57 | 58 | 59 | @Override 60 | protected void endOfNanoVGApplicationCallback() { 61 | 62 | } 63 | 64 | @Override 65 | public Window createWindow(WindowManager windowManager) throws GLFWException { 66 | return windowManager.createWindow("Clear", WINDOW_WIDTH, WINDOW_HEIGHT, true, true); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearDraggableWidgetDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.font.Font; 4 | import nokori.clear.vg.font.FontStyle; 5 | import nokori.clear.vg.widget.LabelWidget; 6 | import nokori.clear.vg.widget.assembly.DraggableWidgetAssembly; 7 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 8 | import nokori.clear.vg.widget.assembly.WidgetClip; 9 | import nokori.clear.vg.widget.assembly.WidgetSynch; 10 | import nokori.clear.windows.GLFWException; 11 | import nokori.clear.windows.Window; 12 | import nokori.clear.windows.WindowManager; 13 | 14 | import java.io.IOException; 15 | 16 | public class ClearDraggableWidgetDemo extends ClearApp { 17 | 18 | private static final int WINDOW_WIDTH = 1280; 19 | private static final int WINDOW_HEIGHT = 720; 20 | 21 | public static void main(String[] args) { 22 | ClearApp.launch(new ClearDraggableWidgetDemo(), args); 23 | } 24 | 25 | public ClearDraggableWidgetDemo() { 26 | super(new WidgetAssembly(new WidgetSynch(WidgetSynch.Mode.WITH_FRAMEBUFFER))); 27 | } 28 | 29 | @Override 30 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 31 | 32 | try { 33 | Font font = new Font("fonts/NotoSans/", "NotoSans-Regular", "NotoSans-Bold", "NotoSans-Italic", "NotoSans-Light").load(context); 34 | 35 | /* 36 | * Create canvas that can be panned around 37 | */ 38 | 39 | DraggableWidgetAssembly canvas = new DraggableWidgetAssembly(); 40 | canvas.setRequiresMouseToBeWithinWidgetToDrag(false); 41 | 42 | /* 43 | * Create draggable nodes 44 | */ 45 | 46 | int draggableWidth = 100; 47 | int draggableHeight = 100; 48 | 49 | float x1 = WINDOW_WIDTH/2 - draggableWidth; 50 | float y1 = WINDOW_HEIGHT/2 - draggableHeight; 51 | 52 | canvas.addChild(createDraggableDemoNode(x1, y1, draggableWidth, draggableHeight, ClearColor.LIGHT_BLACK, font, "Drag me!")); 53 | 54 | float x2 = WINDOW_WIDTH/2; 55 | float y2 = WINDOW_HEIGHT/2; 56 | 57 | canvas.addChild(createDraggableDemoNode(x2, y2, draggableWidth, draggableHeight, ClearColor.LIGHT_BLACK, font, "Drag me too!")); 58 | 59 | /* 60 | * Add button to root assembly 61 | */ 62 | 63 | rootWidgetAssembly.addChild(canvas); 64 | 65 | } catch (IOException e1) { 66 | e1.printStackTrace(); 67 | } 68 | } 69 | 70 | public DraggableWidgetAssembly createDraggableDemoNode(float x, float y, float width, float height, ClearColor fill, Font font, String text) { 71 | DraggableWidgetAssembly draggable = new DraggableWidgetAssembly(x, y, width, height); 72 | draggable.setIgnoreChildrenWidgets(true); 73 | draggable.setBackgroundFill(fill); 74 | 75 | LabelWidget label = new LabelWidget(ClearColor.WHITE_SMOKE, text, font, FontStyle.REGULAR, 20); 76 | label.addChild(new WidgetClip(WidgetClip.Alignment.CENTER)); 77 | 78 | draggable.addChild(label); 79 | 80 | return draggable; 81 | } 82 | 83 | 84 | @Override 85 | protected void endOfNanoVGApplicationCallback() { 86 | 87 | } 88 | 89 | @Override 90 | public Window createWindow(WindowManager windowManager) throws GLFWException { 91 | return windowManager.createWindow("Clear DraggableWidget Demo", WINDOW_WIDTH, WINDOW_HEIGHT, true, true); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearHelloWorldDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.font.Font; 4 | import nokori.clear.vg.font.FontStyle; 5 | import nokori.clear.vg.widget.DropShadowWidget; 6 | import nokori.clear.vg.widget.LabelWidget; 7 | import nokori.clear.vg.widget.RectangleWidget; 8 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 9 | import nokori.clear.vg.widget.assembly.WidgetClip; 10 | import nokori.clear.windows.Cursor; 11 | import nokori.clear.windows.GLFWException; 12 | import nokori.clear.windows.Window; 13 | import nokori.clear.windows.WindowManager; 14 | 15 | import java.io.IOException; 16 | 17 | public class ClearHelloWorldDemo extends ClearApp { 18 | 19 | private static final int WINDOW_WIDTH = 256; 20 | private static final int WINDOW_HEIGHT = 256; 21 | 22 | protected WidgetAssembly button; 23 | protected Font font; 24 | 25 | public static void main(String[] args) { 26 | ClearApp.launch(new ClearHelloWorldDemo(), args); 27 | } 28 | 29 | @Override 30 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 31 | 32 | /* 33 | * WidgetAssemblies act as containers for various widgets. This will allow you to "assemble" a variety of UI components. 34 | * 35 | * Note: I recommend building classes that do the following assembly for you instead of redoing this entire process 36 | * every single time. Normally I'd provide classes that do this for you, but the goal of Clear is to be somewhat lightweight. 37 | * It's best that you make your own implementations that fit your specific needs perfectly. 38 | * 39 | * E.G. Make a class that extends WidgetAssembly and then add all of the children in its constructor. 40 | * 41 | * If you're interested in seeing a real use-case of Clear and how I personally use it, check out my program, JDialogue, here: 42 | * https://github.com/SkyAphid/JDialogue/ 43 | */ 44 | 45 | button = new WidgetAssembly(100, 50, new WidgetClip(WidgetClip.Alignment.CENTER)); 46 | 47 | /* 48 | * Background - rectangle with dropshadow 49 | */ 50 | 51 | float cornerRadius = 3f; 52 | 53 | button.addChild(new DropShadowWidget(cornerRadius)); 54 | button.addChild(new RectangleWidget(cornerRadius, ClearColor.CORAL, true)); 55 | 56 | /* 57 | * Text 58 | */ 59 | 60 | try { 61 | font = new Font("fonts/NotoSans/", "NotoSans-Regular", "NotoSans-Bold", "NotoSans-Italic", "NotoSans-Light").load(context); 62 | 63 | LabelWidget label = new LabelWidget(ClearColor.WHITE_SMOKE, "Hello World!", font, FontStyle.REGULAR, 20); 64 | label.addChild(new WidgetClip(WidgetClip.Alignment.CENTER)); 65 | button.addChild(label); 66 | } catch (IOException e1) { 67 | e1.printStackTrace(); 68 | } 69 | 70 | /* 71 | * Input - click the WidgetAssembly to toggle rendering and show the bounding 72 | */ 73 | 74 | button.setOnMouseMotionEvent(e -> { 75 | if (button.isMouseIntersectingThisWidget(window)) { 76 | ClearStaticResources.getCursor(Cursor.Type.HAND).apply(window); 77 | } else { 78 | ClearStaticResources.getCursor(Cursor.Type.ARROW).apply(window); 79 | } 80 | }); 81 | 82 | button.setOnMouseButtonEvent(e -> { 83 | if (e.isPressed() && button.isMouseIntersectingThisWidget(e.getWindow())) { 84 | button.setBackgroundFill(button.getBackgroundFill() != null ? null : ClearColor.LIGHT_GRAY); 85 | button.setRenderChildren(!button.isRenderingChildren()); 86 | } 87 | }); 88 | 89 | /* 90 | * Add button to root assembly 91 | */ 92 | 93 | rootWidgetAssembly.addChild(button); 94 | } 95 | 96 | 97 | @Override 98 | protected void endOfNanoVGApplicationCallback() { 99 | 100 | } 101 | 102 | @Override 103 | public Window createWindow(WindowManager windowManager) throws GLFWException { 104 | return windowManager.createWindow("Clear", WINDOW_WIDTH, WINDOW_HEIGHT, true, true); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearInputPopupAppDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.apps.ClearInputApp; 4 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 5 | import nokori.clear.windows.GLFWException; 6 | import nokori.clear.windows.Window; 7 | import nokori.clear.windows.WindowManager; 8 | import nokori.clear.windows.util.TinyFileDialog; 9 | 10 | import java.io.File; 11 | 12 | public class ClearInputPopupAppDemo extends ClearHelloWorldDemo { 13 | 14 | public static void main(String[] args) { 15 | ClearApp.launch(new ClearInputPopupAppDemo(), args); 16 | } 17 | 18 | @Override 19 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 20 | super.init(windowManager, window, context, rootWidgetAssembly, args); 21 | 22 | button.setOnMouseButtonEvent(e -> { 23 | if (button.isMouseWithin() && e.isPressed()) { 24 | 25 | try { 26 | 27 | new ClearInputApp(this, 300, 200, ClearColor.BABY_BLUE, new File("fonts/NotoSans/"), 28 | "Input Window Demo", 29 | "Input something here.\n\nThis is a new line.\nNew lines should offset the input box...", 30 | "Default text") { 31 | 32 | @Override 33 | protected void confirmButtonPressed(String text) { 34 | TinyFileDialog.showMessageDialog("ClearInputWindow Text Confirmed", text, TinyFileDialog.Icon.INFORMATION); 35 | } 36 | }.show(); 37 | 38 | } catch (GLFWException e1) { 39 | e1.printStackTrace(); 40 | } 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearTextAreaCodeEditorDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 4 | import nokori.clear.vg.widget.text.TextAreaAutoFormatterWidget; 5 | import nokori.clear.windows.Window; 6 | import nokori.clear.windows.WindowManager; 7 | 8 | import static nokori.clear.vg.widget.text.ClearEscapeSequences.*; 9 | 10 | /** 11 | * This demo extends the TextArea demo and re-purposes it into a code-editor. 12 | */ 13 | public class ClearTextAreaCodeEditorDemo extends ClearTextAreaDemo { 14 | 15 | public static void main(String[] args) { 16 | ClearApp.launch(new ClearTextAreaCodeEditorDemo(), args); 17 | } 18 | 19 | @Override 20 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 21 | super.init(windowManager, window, context, rootWidgetAssembly, args); 22 | 23 | //Disable word wrapping for a more code-editor-ish look 24 | textAreaWidget.setWordWrappingEnabled(false); 25 | 26 | //Create the auto-formatting widget and add it to the text area we want it to operate on. 27 | TextAreaAutoFormatterWidget syntaxHighlighter = new TextAreaAutoFormatterWidget(); 28 | textAreaWidget.addChild(syntaxHighlighter); 29 | 30 | //Create definitions for the highlighting. Keep in mind, the auto-formatter is a general use widget for TextAreaWidgets. 31 | //In our case, we're building it to work as a syntax highlighter. 32 | syntaxHighlighter.addSyntax("public void", ESCAPE_SEQUENCE_COLOR, "#950055"); 33 | syntaxHighlighter.addSyntax("BlueText", ESCAPE_SEQUENCE_COLOR, "#0026FF"); 34 | syntaxHighlighter.addSyntax("BoldText", ESCAPE_SEQUENCE_BOLD); 35 | syntaxHighlighter.addSyntax("ItalicText", ESCAPE_SEQUENCE_ITALIC); 36 | syntaxHighlighter.addSyntax("LightText", ESCAPE_SEQUENCE_LIGHT); 37 | syntaxHighlighter.addSyntax("//", ESCAPE_SEQUENCE_COLOR, "#3F7F5F", TextAreaAutoFormatterWidget.SyntaxResetMode.RESET_AFTER_NEW_LINE); 38 | } 39 | 40 | @Override 41 | protected String getText() { 42 | return "public void highlightingExample() {" 43 | + "\n\t//This is a comment" 44 | + "\n\tBlueText BoldText ItalicText LightText NormalText" 45 | + "\n}"; 46 | } 47 | 48 | @Override 49 | public String getTitle() { 50 | return "Clear TextAreaWidget Demo (Code Editor Version)"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearTextAreaDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.font.Font; 4 | import nokori.clear.vg.widget.DropShadowWidget; 5 | import nokori.clear.vg.widget.RectangleWidget; 6 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 7 | import nokori.clear.vg.widget.assembly.WidgetClip; 8 | import nokori.clear.vg.widget.text.TextAreaWidget; 9 | import nokori.clear.windows.GLFWException; 10 | import nokori.clear.windows.Window; 11 | import nokori.clear.windows.WindowManager; 12 | 13 | import java.io.IOException; 14 | 15 | import static nokori.clear.vg.widget.text.ClearEscapeSequences.*; 16 | 17 | /** 18 | * This demo show-cases the TextArea functionality available in ClearVG. 19 | * 20 | */ 21 | public class ClearTextAreaDemo extends ClearApp { 22 | 23 | private static final int WINDOW_WIDTH = 1280; 24 | private static final int WINDOW_HEIGHT = 720; 25 | 26 | protected TextAreaWidget textAreaWidget; 27 | 28 | public static void main(String[] args) { 29 | ClearApp.launch(new ClearTextAreaDemo(), args); 30 | } 31 | 32 | @Override 33 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 34 | //WidgetAssemblies act as containers for various widgets. This will allow you to "assemble" a variety of UI components. 35 | WidgetAssembly assembly = new WidgetAssembly(1000, 500, new WidgetClip(WidgetClip.Alignment.CENTER)); 36 | 37 | assembly.addChild(new DropShadowWidget()); 38 | assembly.addChild(new RectangleWidget(ClearColor.WHITE_SMOKE, ClearColor.LIGHT_GRAY, true)); 39 | 40 | try { 41 | Font font = new Font("fonts/NotoSans/", "NotoSans-Regular", "NotoSans-Bold", "NotoSans-Italic", "NotoSans-Light").load(context); 42 | 43 | textAreaWidget = new TextAreaWidget(900, 400, ClearColor.LIGHT_BLACK, getText(), font, 18); 44 | 45 | textAreaWidget.addChild(new WidgetClip(WidgetClip.Alignment.CENTER)); 46 | assembly.addChild(textAreaWidget); 47 | } catch (IOException e1) { 48 | e1.printStackTrace(); 49 | } 50 | 51 | rootWidgetAssembly.addChild(assembly); 52 | } 53 | 54 | protected String getText() { 55 | String s = ""; 56 | 57 | for (int j = 0; j < 50; j++) { 58 | if (j > 0) { 59 | s += "\n\n"; 60 | } 61 | 62 | String b = Character.toString(ESCAPE_SEQUENCE_BOLD); 63 | String c = Character.toString(ESCAPE_SEQUENCE_COLOR) + "#FF7F50"; 64 | String i = Character.toString(ESCAPE_SEQUENCE_ITALIC); 65 | String r = Character.toString(ESCAPE_SEQUENCE_RESET); 66 | 67 | //You can either tab or use \t - either works. You can also reference escape sequences via strings or use the constants I've provided. 68 | s += b + c + "Hello World!" + r + " " + i + "This is entry number " + j + r + "\n"; 69 | s += "\tLorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "; 70 | s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. "; 71 | s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."; 72 | } 73 | 74 | return s; 75 | } 76 | 77 | @Override 78 | protected void endOfNanoVGApplicationCallback() { 79 | 80 | } 81 | 82 | @Override 83 | public Window createWindow(WindowManager windowManager) throws GLFWException { 84 | return windowManager.createWindow(getTitle(), WINDOW_WIDTH, WINDOW_HEIGHT, true, true); 85 | } 86 | 87 | public String getTitle() { 88 | return "Clear TextAreaWidget Demo"; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/ClearTextFieldDemo.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.font.Font; 4 | import nokori.clear.vg.widget.DropShadowWidget; 5 | import nokori.clear.vg.widget.RectangleWidget; 6 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 7 | import nokori.clear.vg.widget.assembly.WidgetClip; 8 | import nokori.clear.vg.widget.text.TextFieldWidget; 9 | import nokori.clear.windows.GLFWException; 10 | import nokori.clear.windows.Window; 11 | import nokori.clear.windows.WindowManager; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * This demo show-cases the TextArea functionality available in ClearVG. 17 | * 18 | */ 19 | public class ClearTextFieldDemo extends ClearApp { 20 | 21 | private static final int WINDOW_WIDTH = 512; 22 | private static final int WINDOW_HEIGHT = 256; 23 | 24 | protected TextFieldWidget textFieldWidget; 25 | 26 | public static void main(String[] args) { 27 | ClearApp.launch(new ClearTextFieldDemo(), args); 28 | } 29 | 30 | @Override 31 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 32 | //WidgetAssemblies act as containers for various widgets. This will allow you to "assemble" a variety of UI components. 33 | WidgetAssembly assembly = new WidgetAssembly(450, 100, new WidgetClip(WidgetClip.Alignment.CENTER)); 34 | 35 | assembly.addChild(new DropShadowWidget()); 36 | assembly.addChild(new RectangleWidget(ClearColor.WHITE_SMOKE, ClearColor.LIGHT_GRAY, true)); 37 | 38 | try { 39 | Font font = new Font("fonts/NotoSans/", "NotoSans-Regular", "NotoSans-Bold", "NotoSans-Italic", "NotoSans-Light").load(context); 40 | 41 | textFieldWidget = new TextFieldWidget(350, ClearColor.LIGHT_BLACK, getText(), font, 18); 42 | textFieldWidget.setBackgroundFill(ClearColor.WHITE_SMOKE.multiply(0.9f)); 43 | textFieldWidget.setUnderlineFill(ClearColor.LIGHT_BLACK); 44 | 45 | textFieldWidget.addChild(new WidgetClip(WidgetClip.Alignment.CENTER)); 46 | assembly.addChild(textFieldWidget); 47 | } catch (IOException e1) { 48 | e1.printStackTrace(); 49 | } 50 | 51 | rootWidgetAssembly.addChild(assembly); 52 | } 53 | 54 | protected String getText() { 55 | return "This is a text field! Try typing!"; 56 | } 57 | 58 | @Override 59 | protected void endOfNanoVGApplicationCallback() { 60 | 61 | } 62 | 63 | @Override 64 | public Window createWindow(WindowManager windowManager) throws GLFWException { 65 | return windowManager.createWindow(getTitle(), WINDOW_WIDTH, WINDOW_HEIGHT, true, true); 66 | } 67 | 68 | public String getTitle() { 69 | return "Clear TextFieldWidget Demo"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/references/DemoProgram.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.references; 2 | 3 | 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.references.Demo.DemoData; 6 | import nokori.clear.windows.GLFWException; 7 | import nokori.clear.windows.Window; 8 | import nokori.clear.windows.WindowManager; 9 | import nokori.clear.windows.util.WindowedApplication; 10 | 11 | import static org.lwjgl.glfw.GLFW.glfwGetTime; 12 | import static org.lwjgl.glfw.GLFW.glfwSetTime; 13 | import static org.lwjgl.nanovg.NanoVG.nvgBeginFrame; 14 | import static org.lwjgl.nanovg.NanoVG.nvgEndFrame; 15 | import static org.lwjgl.opengl.GL11.*; 16 | 17 | /** 18 | * Runs LWJGL3's Demo.java using Clear components. 19 | */ 20 | public class DemoProgram extends WindowedApplication { 21 | 22 | private NanoVGContext context; 23 | private DemoData data = new DemoData(); 24 | 25 | private double mouseX, mouseY; 26 | 27 | public static void main(String args[]) { 28 | WindowedApplication.launch(new DemoProgram(), args); 29 | } 30 | 31 | @Override 32 | public void init(WindowManager windowManager, Window window, String[] args) { 33 | context = new NanoVGContext().init(); 34 | glfwSetTime(0); 35 | } 36 | 37 | @Override 38 | public void run() { 39 | glViewport(0, 0, window.getFramebufferWidth(), window.getFramebufferHeight()); 40 | glClearColor(1, 1, 1, 1); 41 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 42 | 43 | float pxRatio = window.getFramebufferWidth() / (float) window.getHeight(); 44 | nvgBeginFrame(context.get(), window.getWidth(), window.getHeight(), pxRatio); 45 | 46 | Demo.renderDemo(context.get(), (float) mouseX, (float) mouseY, window.getWidth(), window.getHeight(), (float) glfwGetTime(), false, data); 47 | 48 | nvgEndFrame(context.get()); 49 | } 50 | 51 | @Override 52 | public Window createWindow(WindowManager windowManager) throws GLFWException { 53 | return windowManager.createWindow("NanoVG Demo", 50, 50, 512, 512, true, true); 54 | } 55 | 56 | @Override 57 | protected void endOfApplicationCallback() { 58 | Demo.freeDemoData(); 59 | context.dispose(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /ClearVG/src/demo/java/nokori/clear/vg/references/MemoryMappedFileReadExample.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.references; 2 | 3 | import java.io.File; 4 | import java.io.RandomAccessFile; 5 | import java.nio.MappedByteBuffer; 6 | import java.nio.channels.FileChannel; 7 | 8 | public class MemoryMappedFileReadExample 9 | { 10 | private static String bigExcelFile = "bigFile.xls"; 11 | 12 | public static void main(String[] args) throws Exception 13 | { 14 | try (RandomAccessFile file = new RandomAccessFile(new File(bigExcelFile), "r")) 15 | { 16 | //Get file channel in read-only mode 17 | FileChannel fileChannel = file.getChannel(); 18 | 19 | //Get direct byte buffer access using channel.map() operation 20 | MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); 21 | 22 | // the buffer now reads the file as if it were loaded in memory. 23 | System.out.println(buffer.isLoaded()); //prints false 24 | System.out.println(buffer.capacity()); //Get the size based on content size of file 25 | 26 | //You can read the file from this buffer the way you like. 27 | for (int i = 0; i < buffer.limit(); i++) 28 | { 29 | System.out.print((char) buffer.get()); //Print the content of file 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/ClearApp.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.transition.TransitionManager; 4 | import nokori.clear.vg.widget.assembly.RootWidgetAssembly; 5 | import nokori.clear.vg.widget.assembly.Widget; 6 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 7 | import nokori.clear.vg.widget.assembly.WidgetSynch; 8 | import nokori.clear.windows.Window; 9 | import nokori.clear.windows.WindowManager; 10 | import nokori.clear.windows.callback.*; 11 | import nokori.clear.windows.event.*; 12 | import nokori.clear.windows.util.WindowedApplication; 13 | import org.joml.Vector4f; 14 | 15 | import static org.lwjgl.nanovg.NanoVG.*; 16 | 17 | /** 18 | * An application wrapper that allows users to quickly create ClearVG applications. 19 | * 20 | * This wrapper will still work whether you're starting the program or if you need to make a second window during runtime. It's extremely flexible. 21 | */ 22 | public abstract class ClearApp extends WindowedApplication { 23 | 24 | private Vector4f bgClearColor = new Vector4f(1f, 1f, 1f, 1f); 25 | 26 | private NanoVGContext context; 27 | 28 | private WidgetAssembly rootWidgetAssembly; 29 | 30 | private boolean paused = false; 31 | 32 | private ClearApp queueLaunch = null; 33 | private int queueLaunchFrameDelay = 0; 34 | 35 | /** 36 | * Initializes the ClearApplication with a RootWidgetAssembly 37 | */ 38 | public ClearApp() { 39 | this(new RootWidgetAssembly()); 40 | } 41 | 42 | public ClearApp(WidgetAssembly rootWidgetAssembly) { 43 | this.rootWidgetAssembly = rootWidgetAssembly; 44 | } 45 | 46 | public ClearApp(WindowManager windowManager, WidgetAssembly rootWidgetAssembly) { 47 | super(windowManager); 48 | this.rootWidgetAssembly = rootWidgetAssembly; 49 | } 50 | 51 | public void init() { 52 | init(windowManager, window, null); 53 | } 54 | 55 | @Override 56 | public void init(WindowManager windowManager, Window window, String[] args) { 57 | ClearStaticResources.loadAllCursors(); 58 | 59 | addInputCallbacks(window, rootWidgetAssembly); 60 | context = new NanoVGContext().init(); 61 | 62 | //Calls synch() on the roots WidgetSynch that way it's up to date for the init() function 63 | for (int i = 0; i < rootWidgetAssembly.getNumChildren(); i++) { 64 | Widget w = rootWidgetAssembly.getChild(i); 65 | 66 | if (w instanceof WidgetSynch) { 67 | ((WidgetSynch) w).synch(context); 68 | } 69 | } 70 | 71 | init(windowManager, window, context, rootWidgetAssembly, args); 72 | } 73 | 74 | public abstract void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args); 75 | 76 | @Override 77 | public void run() { 78 | /* 79 | * Ticking 80 | */ 81 | 82 | if (!paused) { 83 | TransitionManager.tick(); 84 | rootWidgetAssembly.tick(context, rootWidgetAssembly); 85 | rootWidgetAssembly.tickChildren(context, rootWidgetAssembly); 86 | } 87 | 88 | /* 89 | * Rendering 90 | */ 91 | 92 | NanoVGContext.glClearFrame(window.getFramebufferWidth(), window.getFramebufferHeight(), bgClearColor.x(), bgClearColor.y(), bgClearColor.z(), bgClearColor.w()); 93 | context.beginFrame(window.getWidth(), window.getHeight(), window.getFramebufferWidth(), window.getFramebufferHeight()); 94 | 95 | rootWidgetAssembly.render(context, rootWidgetAssembly); 96 | rootWidgetAssembly.renderChildren(context, rootWidgetAssembly); 97 | 98 | if (paused) { 99 | long vg = context.get(); 100 | 101 | ClearColor.LIGHT_GRAY.alpha(0.75f).tallocNVG(fill -> { 102 | nvgBeginPath(vg); 103 | nvgRect(vg, 0, 0, window.getFramebufferWidth(), window.getFramebufferHeight()); 104 | nvgFillColor(vg, fill); 105 | nvgFill(vg); 106 | nvgClosePath(vg); 107 | }); 108 | } 109 | 110 | context.endFrame(); 111 | 112 | if (queueLaunch != null) { 113 | if (queueLaunchFrameDelay > 0) { 114 | queueLaunchFrameDelay--; 115 | } else { 116 | launch(queueLaunch, null, false); 117 | queueLaunch = null; 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | protected void endOfApplicationCallback() { 124 | endOfNanoVGApplicationCallback(); 125 | rootWidgetAssembly.dispose(); 126 | 127 | if (exitProgramOnEndOfApplication()) { 128 | ClearStaticResources.destroyAllCursors(); 129 | context.dispose(); 130 | } 131 | } 132 | 133 | /** 134 | * Called at the end of the program right before the rootWidgetAssembly and NanoVGContext are disposed. 135 | */ 136 | protected abstract void endOfNanoVGApplicationCallback(); 137 | 138 | public WidgetAssembly getRootWidgetAssembly() { 139 | return rootWidgetAssembly; 140 | } 141 | 142 | private void addInputCallbacks(Window window, WidgetAssembly rootWidgetAssembly) { 143 | //Char events 144 | window.addInputCallback(new CharCallback() { 145 | 146 | @Override 147 | public void charEvent(Window window, long timestamp, int codepoint, String c, int mods) { 148 | if (!paused) { 149 | CharEvent event = CharEvent.fire(window, timestamp, codepoint, c, mods); 150 | rootWidgetAssembly.charEvent(window, event); 151 | rootWidgetAssembly.childrenCharEvent(window, event); 152 | } 153 | } 154 | 155 | }); 156 | 157 | //Key events 158 | window.addInputCallback(new KeyCallback() { 159 | 160 | @Override 161 | public void keyEvent(Window window, long timestamp, int key, int scanCode, boolean pressed, boolean repeat, int mods) { 162 | if (!paused) { 163 | KeyEvent event = KeyEvent.fire(window, timestamp, key, scanCode, pressed, repeat, mods); 164 | rootWidgetAssembly.keyEvent(window, event); 165 | rootWidgetAssembly.childrenKeyEvent(window, event); 166 | } 167 | } 168 | 169 | }); 170 | 171 | //Mouse button events 172 | window.addInputCallback(new MouseButtonCallback() { 173 | 174 | @Override 175 | public void mouseButtonEvent(Window window, long timestamp, double mouseX, double mouseY, int button, boolean pressed, int mods) { 176 | if (!paused) { 177 | MouseButtonEvent event = MouseButtonEvent.fire(window, timestamp, mouseX, mouseY, button, pressed, mods); 178 | rootWidgetAssembly.mouseButtonEvent(window, event); 179 | rootWidgetAssembly.childrenMouseButtonEvent(window, event); 180 | } 181 | } 182 | 183 | }); 184 | 185 | //Mouse motion events 186 | window.addInputCallback(new MouseMotionCallback() { 187 | 188 | @Override 189 | public void mouseMotionEvent(Window window, long timestamp, double mouseX, double mouseY, double dx, double dy) { 190 | if (!paused) { 191 | MouseMotionEvent event = MouseMotionEvent.fire(window, timestamp, mouseX, mouseY, dx, dy); 192 | rootWidgetAssembly.mouseMotionEvent(window, event); 193 | rootWidgetAssembly.childrenMouseMotionEvent(window, event); 194 | } 195 | } 196 | 197 | }); 198 | 199 | //Mouse scroll events 200 | window.addInputCallback(new MouseScrollCallback() { 201 | 202 | @Override 203 | public void scrollEvent(Window window, long timestamp, double mouseX, double mouseY, double xoffset, double yoffset) { 204 | if (!paused) { 205 | MouseScrollEvent event = MouseScrollEvent.fire(window, timestamp, mouseX, mouseY, xoffset, yoffset); 206 | rootWidgetAssembly.mouseScrollEvent(window, event); 207 | rootWidgetAssembly.childrenMouseScrollEvent(window, event); 208 | } 209 | } 210 | 211 | }); 212 | } 213 | 214 | public NanoVGContext getContext() { 215 | return context; 216 | } 217 | 218 | /** 219 | * Whether or not this ClearApp is paused (ticking and input is disabled, but rendering will continue) 220 | */ 221 | public boolean isPaused() { 222 | return paused; 223 | } 224 | 225 | /** 226 | * Toggles whether or not this ClearApp is paused. If true, ticking and input is disabled, but rendering will continue. 227 | * 228 | * @param context 229 | * @param paused 230 | */ 231 | public void setPaused(boolean paused) { 232 | this.paused = paused; 233 | window.setClosingEnabled(!paused); 234 | } 235 | 236 | /** 237 | * Queues a ClearApp for launch after a one frame delay. This value is set to null after launch. It's recommended that you pause the ClearApp when you queue up a launch. 238 | * 239 | * @param queueLaunch 240 | */ 241 | public void queueLaunch(ClearApp queueLaunch) { 242 | this.queueLaunch = queueLaunch; 243 | queueLaunchFrameDelay = 1; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/ClearStaticResources.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import nokori.clear.vg.widget.assembly.Widget; 4 | import nokori.clear.windows.Cursor; 5 | import nokori.clear.windows.Cursor.Type; 6 | 7 | ; 8 | 9 | /** 10 | * This class contains some static utility variables and functions that can be used easily around the system. It will be initialized automatically if you're using a ClearApplication, 11 | * otherwise, you can initialize it yourself with the various resource initialization functions (allowing you to choose what you load and what you don't load). Don't forget to dispose 12 | * what you end up loading if applicable! 13 | */ 14 | public class ClearStaticResources { 15 | private static Widget focusedWidget = null; 16 | 17 | private static Cursor[] loadedCursors; 18 | 19 | public static Widget getFocusedWidget() { 20 | return focusedWidget; 21 | } 22 | 23 | public static void setFocusedWidget(Widget focusedWidget) { 24 | ClearStaticResources.focusedWidget = focusedWidget; 25 | //System.err.println(focusedWidget); 26 | //Thread.dumpStack(); 27 | } 28 | 29 | public static boolean isFocusedOrCanFocus(Widget widget) { 30 | return (focusedWidget == null || focusedWidget == widget); 31 | } 32 | 33 | public static boolean isFocused(Widget widget) { 34 | return (focusedWidget == widget); 35 | } 36 | 37 | public static boolean isFocused() { 38 | return (focusedWidget != null); 39 | } 40 | 41 | public static boolean clearFocusIfApplicable(Widget widget) { 42 | if (isFocused(widget)) { 43 | setFocusedWidget(null); 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | /** 51 | * Loads all of the available Cursors and stores them statically for use around the program. This only needs to be called once. 52 | *

53 | * IMPORTANT: Make sure to call disposeAllCursors() at the end of the program's lifecycle. 54 | */ 55 | public static void loadAllCursors() { 56 | loadedCursors = new Cursor[Type.values().length]; 57 | 58 | for (int i = 0; i < loadedCursors.length; i++) { 59 | loadedCursors[i] = new Cursor(Type.values()[i]); 60 | } 61 | } 62 | 63 | /** 64 | * @return Gets the loaded Cursor object for the given default system type. 65 | */ 66 | public static Cursor getCursor(Type type) { 67 | for (int i = 0; i < loadedCursors.length; i++) { 68 | if (loadedCursors[i].getType() == type) { 69 | return loadedCursors[i]; 70 | } 71 | } 72 | 73 | return null; 74 | } 75 | 76 | /** 77 | * Destroys all of the loaded static cursors. To be used at the end of a program's lifecycle if loadAllCursors() was called at any point during runtime. 78 | */ 79 | public static void destroyAllCursors() { 80 | if (loadedCursors != null) { 81 | for (int i = 0; i < loadedCursors.length; i++) { 82 | loadedCursors[i].destroy(); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/NanoVGContext.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg; 2 | 3 | import org.lwjgl.nanovg.NanoVGGL2; 4 | import org.lwjgl.nanovg.NanoVGGL3; 5 | import org.lwjgl.opengl.GL11; 6 | import org.lwjgl.opengl.GL30; 7 | 8 | import static org.lwjgl.nanovg.NanoVG.nvgBeginFrame; 9 | import static org.lwjgl.nanovg.NanoVG.nvgEndFrame; 10 | import static org.lwjgl.opengl.GL11.*; 11 | 12 | public class NanoVGContext { 13 | private boolean modernOpenGL; 14 | private long nvgContext; 15 | 16 | private int framebufferWidth, framebufferHeight; 17 | 18 | /** 19 | * Initializes a NanoVG context and returns this object. 20 | */ 21 | public NanoVGContext init() { 22 | modernOpenGL = (GL11.glGetInteger(GL30.GL_MAJOR_VERSION) > 3) || (GL11.glGetInteger(GL30.GL_MAJOR_VERSION) == 3 && GL11.glGetInteger(GL30.GL_MINOR_VERSION) >= 2); 23 | 24 | if (modernOpenGL) { 25 | int flags = NanoVGGL3.NVG_STENCIL_STROKES | NanoVGGL3.NVG_ANTIALIAS; 26 | nvgContext = NanoVGGL3.nvgCreate(flags); 27 | } else { 28 | int flags = NanoVGGL2.NVG_STENCIL_STROKES | NanoVGGL2.NVG_ANTIALIAS; 29 | nvgContext = NanoVGGL2.nvgCreate(flags); 30 | } 31 | 32 | return this; 33 | } 34 | 35 | /** 36 | * This is a shortcut function for the typical OpenGL clearing sequence you have to do before rendering. The following functions are called in the given order:
37 | *
glViewport(0, 0, viewportWidth, viewportHeight) 38 | *
glClearColor(clearColorR, G, B, A) 39 | *
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) 40 | */ 41 | public static void glClearFrame(int viewportWidth, int viewportHeight, float clearColorR, float clearColorG, float clearColorB, float clearColorA) { 42 | glViewport(0, 0, viewportWidth, viewportHeight); 43 | glClearColor(clearColorR, clearColorG, clearColorB, clearColorA); 44 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 45 | } 46 | 47 | /** 48 | * Automatically configures NanoVG for rendering a single frame (nvgBeginFrame(context)). If this is called, make sure endFrame() is called at the end of the corresponding frame. 49 | * 50 | * @param windowWidth - the actual window width in pixels 51 | * @param windowHeight - the actual window height in pixels 52 | * @param framebufferWidth - the window framebuffer width (internal rendering) 53 | * @param framebufferHeight - the window framebuffer height (internal rendering) 54 | */ 55 | public void beginFrame(int windowWidth, int windowHeight, int framebufferWidth, int framebufferHeight) { 56 | this.framebufferWidth = framebufferWidth; 57 | this.framebufferHeight = framebufferHeight; 58 | 59 | float pxRatio = (float) framebufferWidth / (float) windowHeight; 60 | nvgBeginFrame(nvgContext, windowWidth, windowHeight, pxRatio); 61 | } 62 | 63 | /** 64 | * Ends the current frame of rendering (nvgEndFrame(context)) 65 | */ 66 | public void endFrame() { 67 | nvgEndFrame(nvgContext); 68 | } 69 | 70 | /** 71 | * @return true if the machine this program is running on supports modern OpenGL (Version 3 and above) 72 | */ 73 | public boolean isModernOpenGL() { 74 | return modernOpenGL; 75 | } 76 | 77 | /** 78 | * @return the NanoVG context long ID. 79 | */ 80 | public long get() { 81 | return nvgContext; 82 | } 83 | 84 | /** 85 | * @return the framebuffer width of the window configured in beginFrame() 86 | */ 87 | public int getFramebufferWidth() { 88 | return framebufferWidth; 89 | } 90 | 91 | /** 92 | * @return the framebuffer height of the window configured in beginFrame() 93 | */ 94 | public int getFramebufferHeight() { 95 | return framebufferHeight; 96 | } 97 | 98 | /** 99 | * Disposes this NanoVG context. 100 | */ 101 | public void dispose() { 102 | if (modernOpenGL) { 103 | NanoVGGL3.nvgDelete(nvgContext); 104 | } else { 105 | NanoVGGL2.nvgDelete(nvgContext); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/apps/ClearInputApp.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.apps; 2 | 3 | import nokori.clear.vg.ClearApp; 4 | import nokori.clear.vg.ClearColor; 5 | import nokori.clear.vg.NanoVGContext; 6 | import nokori.clear.vg.font.Font; 7 | import nokori.clear.vg.font.FontStyle; 8 | import nokori.clear.vg.widget.ButtonAssembly; 9 | import nokori.clear.vg.widget.LabelWidget; 10 | import nokori.clear.vg.widget.assembly.RootWidgetAssembly; 11 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 12 | import nokori.clear.vg.widget.assembly.WidgetClip; 13 | import nokori.clear.vg.widget.text.TextFieldWidget; 14 | import nokori.clear.windows.GLFWException; 15 | import nokori.clear.windows.Window; 16 | import nokori.clear.windows.WindowManager; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | 21 | /** 22 | * This ClearApp is an input window you can open and use to get user input. See ClearInputAppDemo.java to learn more about it. 23 | */ 24 | public abstract class ClearInputApp extends ClearApp { 25 | 26 | private static final int FONT_SIZE = 16; 27 | 28 | private int width, height; 29 | private ClearApp parent; 30 | private ClearColor buttonOutline; 31 | private File fontLocation; 32 | private String title, message, defaultInput; 33 | 34 | public ClearInputApp(ClearApp parent, int windowWidth, int windowHeight, ClearColor buttonOutline, File fontLocation, String title, String message, String defaultInput) { 35 | this(parent.getWindowManager(), new RootWidgetAssembly(), parent, windowWidth, windowHeight, buttonOutline, fontLocation, title, message, defaultInput); 36 | } 37 | 38 | private ClearInputApp(WindowManager windowManager, WidgetAssembly rootWidgetAssembly, 39 | ClearApp parent, int windowWidth, int windowHeight, ClearColor buttonOutline, File fontLocation, String title, String message, String defaultInput) { 40 | 41 | super(windowManager, rootWidgetAssembly); 42 | this.parent = parent; 43 | this.width = windowWidth; 44 | this.height = windowHeight; 45 | this.buttonOutline = buttonOutline; 46 | this.fontLocation = fontLocation; 47 | this.title = title; 48 | this.message = message; 49 | this.defaultInput = defaultInput; 50 | } 51 | 52 | public void show() throws GLFWException { 53 | parent.setPaused(true); 54 | parent.queueLaunch(this); 55 | } 56 | 57 | @Override 58 | public void init(WindowManager windowManager, Window window, NanoVGContext context, WidgetAssembly rootWidgetAssembly, String[] args) { 59 | 60 | float xPadding = 20f; 61 | float yPadding = 20f; 62 | 63 | try { 64 | Font font = new Font(fontLocation).load(context); 65 | 66 | /* 67 | * Message 68 | */ 69 | 70 | float widgetWidth = width - (xPadding * 2f); 71 | 72 | LabelWidget messageLabel = new LabelWidget(widgetWidth, ClearColor.LIGHT_BLACK, message, font, FontStyle.REGULAR, FONT_SIZE).calculateBounds(context); 73 | messageLabel.addChild(new WidgetClip(WidgetClip.Alignment.TOP_LEFT, xPadding, yPadding)); 74 | 75 | rootWidgetAssembly.addChild(messageLabel); 76 | 77 | /* 78 | * Input field 79 | */ 80 | 81 | TextFieldWidget inputField = new TextFieldWidget(widgetWidth, ClearColor.LIGHT_BLACK, defaultInput, font, FONT_SIZE); 82 | inputField.setBackgroundFill(ClearColor.LIGHT_GRAY); 83 | inputField.addChild(new WidgetClip(WidgetClip.Alignment.TOP_LEFT, xPadding, (yPadding * 2f) + messageLabel.getHeight())); 84 | 85 | rootWidgetAssembly.addChild(inputField); 86 | 87 | /* 88 | * Confirm button 89 | */ 90 | 91 | float buttonSynchX = xPadding; 92 | float buttonSynchY = (yPadding * 4f) + messageLabel.getHeight() + inputField.getHeight(); 93 | 94 | ButtonAssembly confirmButton = new ButtonAssembly(75, 25, ClearColor.LIGHT_GRAY, buttonOutline, 0f, font, FONT_SIZE, ClearColor.LIGHT_BLACK, "Confirm"); 95 | confirmButton.addChild(new WidgetClip(WidgetClip.Alignment.TOP_LEFT, buttonSynchX, buttonSynchY)); 96 | 97 | confirmButton.setOnMouseButtonEvent(e -> { 98 | if (confirmButton.isMouseWithin() && !e.isPressed()) { 99 | confirmButtonPressed(inputField.getTextBuilder().toString()); 100 | window.requestClose(); 101 | } 102 | }); 103 | 104 | rootWidgetAssembly.addChild(confirmButton); 105 | 106 | /* 107 | * Exit button 108 | */ 109 | 110 | ButtonAssembly exitButton = new ButtonAssembly(75, 25, ClearColor.LIGHT_GRAY, buttonOutline, 0f, font, FONT_SIZE, ClearColor.LIGHT_BLACK, "Cancel"); 111 | exitButton.addChild(new WidgetClip(WidgetClip.Alignment.TOP_LEFT, buttonSynchX + confirmButton.getWidth() + xPadding, buttonSynchY)); 112 | 113 | exitButton.setOnMouseButtonEvent(e -> { 114 | if (exitButton.isMouseWithin() && !e.isPressed()) { 115 | window.requestClose(); 116 | } 117 | }); 118 | 119 | rootWidgetAssembly.addChild(exitButton); 120 | 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | 126 | protected abstract void confirmButtonPressed(String text); 127 | 128 | @Override 129 | protected void endOfNanoVGApplicationCallback() { 130 | parent.setPaused(false); 131 | } 132 | 133 | @Override 134 | public Window createWindow(WindowManager windowManager) throws GLFWException { 135 | Window parentWindow = parent.getWindow(); 136 | 137 | int centerX = parentWindow.getX() + parentWindow.getWidth()/2 - width/2; 138 | int centerY = parentWindow.getY() + parentWindow.getHeight()/2 - height/2; 139 | 140 | Window newWindow = windowManager.createWindow(title, centerX, centerY, width, height, false, true); 141 | 142 | if (parentWindow.getIconFiles() != null) { 143 | newWindow.setIcons(parentWindow.getIconFiles()); 144 | } 145 | 146 | return newWindow; 147 | } 148 | 149 | @Override 150 | protected boolean exitProgramOnEndOfApplication() { 151 | return false; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/font/Font.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/ClearVG/src/main/java/nokori/clear/vg/font/Font.java -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/font/FontStyle.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.font; 2 | 3 | public enum FontStyle { 4 | REGULAR, BOLD, LIGHT, ITALIC; 5 | } -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/FillTransition.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | 5 | /** 6 | * This transition will transition the fill from one color to another smoothly over time. 7 | */ 8 | public class FillTransition extends Transition { 9 | 10 | private ClearColor fromFill, toFill, storeFill; 11 | 12 | /** 13 | * Transitions one color to another. This constructor is simplified to where the color that the result is stored in is also the one that the transition starts at. 14 | * 15 | * @param durationInMillis - time in milliseconds for the transition to occur 16 | * @param fromAndStoreFill - the starting fill and the fill to store the blend results in 17 | * @param toFill - the destination fill 18 | */ 19 | public FillTransition(long durationInMillis, ClearColor fromAndStoreFill, ClearColor toFill) { 20 | this(durationInMillis, fromAndStoreFill, toFill, fromAndStoreFill); 21 | } 22 | 23 | /** 24 | * Transitions one color to another and stores the result in a third color. If you set a Control's fill with the storeFill, it should be automatically updated 25 | * as this Transition progresses (if you're using an immutable color, use copy() when setting it on the Control to ensure that it's recycled). 26 | * 27 | * @param durationInMillis - time in milliseconds for the transition to occur 28 | * @param fromFill - the starting fill 29 | * @param toFill - the destination fill 30 | * @param storeFill - the fill to store the blend results in 31 | */ 32 | public FillTransition(long durationInMillis, ClearColor fromFill, ClearColor toFill, ClearColor storeFill) { 33 | super(durationInMillis); 34 | this.fromFill = fromFill; 35 | this.toFill = toFill; 36 | this.storeFill = storeFill; 37 | } 38 | 39 | @Override 40 | public void tick(float progress) { 41 | blend(fromFill, toFill, storeFill, progress); 42 | } 43 | 44 | protected void blend(ClearColor fromFill, ClearColor toFill, ClearColor storeFill, float progress) { 45 | ClearColor.blend(fromFill, toFill, storeFill, progress); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/SizeTransition.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | /** 4 | * This transition is used to resize objects to various sizes. 5 | */ 6 | public abstract class SizeTransition extends Transition { 7 | private float targetWidth, targetHeight; 8 | 9 | public SizeTransition(long durationInMillis, float targetWidth, float targetHeight) { 10 | super(durationInMillis); 11 | this.targetWidth = targetWidth; 12 | this.targetHeight = targetHeight; 13 | } 14 | 15 | @Override 16 | public void tick(float progress) { 17 | setWidth(changeSize(progress, getCurrentWidth(), targetWidth)); 18 | setHeight(changeSize(progress, getCurrentHeight(), targetHeight)); 19 | } 20 | 21 | private float changeSize(float progress, float current, float target) { 22 | if (current > target) { 23 | float d = (current- target); 24 | return (target + (d * (1f - progress))); 25 | } else { 26 | float d = (target - current); 27 | return (current + (d * progress)); 28 | } 29 | } 30 | 31 | /** 32 | * Override this to return the current width of whatever element is being affected by this transition so that the iteration works correctly. 33 | */ 34 | protected abstract float getCurrentWidth(); 35 | 36 | /** 37 | * Override this to return the current height of whatever element is being affected by this transition so that the iteration works correctly. 38 | */ 39 | protected abstract float getCurrentHeight(); 40 | 41 | /** 42 | * This is the callback for the width size changes. 43 | */ 44 | protected abstract void setWidth(float width); 45 | 46 | /** 47 | * This is the callback for the height size changes. 48 | */ 49 | protected abstract void setHeight(float height); 50 | } 51 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/Transition.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | import nokori.clear.windows.util.Stopwatch; 4 | 5 | /** 6 | * Transitions are objects that allow the smooth animation of Nodes via the use of timestamps. 7 | * 8 | * By creating multipliers of duration from 0 to 1, it's possible to create a variety of animations that can improve the polish of a Node. 9 | * 10 | */ 11 | public abstract class Transition { 12 | 13 | private long durationInMillis; 14 | 15 | private boolean isPlaying = false; 16 | private Stopwatch stopwatch = new Stopwatch(); 17 | 18 | private Object linkedObject = null; 19 | 20 | TransitionCompletedCallback completedCallback = null; 21 | 22 | public Transition(long durationInMillis) { 23 | this.durationInMillis = durationInMillis; 24 | } 25 | 26 | public long getDurationInMillis() { 27 | return durationInMillis; 28 | } 29 | 30 | public void setDurationInMillis(long durationInMillis) { 31 | this.durationInMillis = durationInMillis; 32 | } 33 | 34 | /** 35 | * Will start this transition. If it's already playing, it will be reset. 36 | */ 37 | public Transition play() { 38 | if (isPlaying) { 39 | stop(); 40 | } 41 | 42 | TransitionManager.removeLinkedTransitions(this, linkedObject); 43 | 44 | stopwatch.timeInMilliseconds(durationInMillis); 45 | TransitionManager.add(this); 46 | isPlaying = true; 47 | 48 | return this; 49 | } 50 | 51 | /** 52 | * Stops this Transition from playing. This function will not call the onCompletedCallback unless this transition has finished. 53 | * @return 54 | */ 55 | public Transition stop() { 56 | if (isFinished() && completedCallback != null) { 57 | completedCallback.callback(this); 58 | } 59 | 60 | TransitionManager.remove(this); 61 | isPlaying = false; 62 | return this; 63 | } 64 | 65 | /** 66 | * Called by the TransitionManager regularly. 67 | * 68 | * @param progress - the progress of the Transition to completion (between 0-1, where 1 is 100% complete) 69 | */ 70 | public abstract void tick(float progress); 71 | 72 | public void setOnCompleted(TransitionCompletedCallback completedCallback) { 73 | this.completedCallback = completedCallback; 74 | } 75 | 76 | public interface TransitionCompletedCallback { 77 | public void callback(Transition t); 78 | }; 79 | 80 | /** 81 | * @return a value from 0 to 1 based on the transition time. 82 | */ 83 | public float getProgress() { 84 | return stopwatch.getNormalizedDistanceBetweenTime(); 85 | } 86 | 87 | public boolean isFinished() { 88 | return (isPlaying && stopwatch.isCurrentTimePassedEndTime()); 89 | } 90 | 91 | /** 92 | * @return the linked Object 93 | * @see setLinkedObject(object) 94 | */ 95 | public Object getLinkedObject() { 96 | return linkedObject; 97 | } 98 | 99 | /** 100 | * Setting a linked object will cause this Transition to be associated with that object. If you attempt to make another Transition of the same class 101 | * and play it when it has the same linkedObject, then this Transition will be stopped and deleted automatically by the TransitionManager. 102 | * 103 | * The purpose of this system is to prevent two contradicting Transitions (e.g. fading in/fading out) from playing at the same time, 104 | * so that the user doesn't have to manually check for this. 105 | * 106 | * @param linkedObject 107 | */ 108 | public void setLinkedObject(Object linkedObject) { 109 | this.linkedObject = linkedObject; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/TransitionImpl.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | /** 4 | * This Transition can be used to track a value going from the start value to the end value via the normalized progress variable. 5 | * It's useful for creating basic transitions. 6 | * 7 | */ 8 | public class TransitionImpl extends Transition { 9 | 10 | private float start, end; 11 | private float currentValue; 12 | 13 | private ProgressCallback callback = null; 14 | private CurveProcessor curveProcessor = null; 15 | 16 | public TransitionImpl(long durationInMillis, float start, float end, ProgressCallback callback, CurveProcessor curveProcessor) { 17 | this(durationInMillis, start, end, callback); 18 | this.curveProcessor = curveProcessor; 19 | } 20 | 21 | public TransitionImpl(long durationInMillis, float start, float end, ProgressCallback callback) { 22 | this(durationInMillis, start, end); 23 | this.callback = callback; 24 | } 25 | 26 | public TransitionImpl(long durationInMillis, float start, float end) { 27 | super(durationInMillis); 28 | this.start = start; 29 | this.end = end; 30 | currentValue = start; 31 | } 32 | 33 | public void setStartAndEnd(float start, float end) { 34 | this.start = start; 35 | this.end = end; 36 | } 37 | 38 | public float getStart() { 39 | return start; 40 | } 41 | 42 | public void setStart(float start) { 43 | this.start = start; 44 | } 45 | 46 | public float getEnd() { 47 | return end; 48 | } 49 | 50 | public void setEnd(float end) { 51 | this.end = end; 52 | } 53 | 54 | @Override 55 | public void tick(float progress) { 56 | currentValue = start + ((end - start) * progress); 57 | 58 | if (curveProcessor != null) { 59 | currentValue = curveProcessor.modifyValue(currentValue); 60 | } 61 | 62 | if (callback != null) { 63 | callback.callback(currentValue); 64 | } 65 | } 66 | 67 | /** 68 | * Sets a callback that is called as the transitioning value changes. The value passed through the callback is the current value during its transition. 69 | */ 70 | public void setProgressCallback(ProgressCallback callback) { 71 | this.callback = callback; 72 | } 73 | 74 | /** 75 | * Sets a processor for the value produced by this Transition, allowing you to tweak its curve 76 | * @param curveProcessor 77 | */ 78 | public void setCurveProcessor(CurveProcessor curveProcessor) { 79 | this.curveProcessor = curveProcessor; 80 | } 81 | 82 | public float getCurrent() { 83 | return currentValue; 84 | } 85 | 86 | public interface ProgressCallback { 87 | public void callback(float value); 88 | } 89 | 90 | public interface CurveProcessor { 91 | public float modifyValue(float value); 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/TransitionManager.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class TransitionManager { 6 | private static ArrayList activeTransitions = new ArrayList<>(); 7 | 8 | static void add(Transition transition) { 9 | activeTransitions.add(transition); 10 | } 11 | 12 | static boolean remove(Transition transition) { 13 | return activeTransitions.remove(transition); 14 | } 15 | 16 | static void removeLinkedTransitions(Transition transition, Object linkedObject) { 17 | if (linkedObject == null || activeTransitions.isEmpty()) { 18 | return; 19 | } 20 | 21 | for (int i = 0; i < activeTransitions.size(); i++) { 22 | Transition t = activeTransitions.get(i); 23 | 24 | if (transition.getClass() == t.getClass() && t.getLinkedObject() == linkedObject) { 25 | t.stop(); 26 | activeTransitions.remove(t); 27 | i--; 28 | } 29 | } 30 | } 31 | 32 | public static void tick() { 33 | for (int i = 0; i < activeTransitions.size(); i++) { 34 | Transition t = activeTransitions.get(i); 35 | t.tick(t.getProgress()); 36 | 37 | if (t.isFinished()) { 38 | 39 | if (t.completedCallback != null) { 40 | t.completedCallback.callback(t); 41 | } 42 | 43 | activeTransitions.remove(i); 44 | i--; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/WidgetPositionTransition.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | import nokori.clear.vg.widget.assembly.Widget; 4 | 5 | /** 6 | * This transition will move a Widget to the target position. Good for fancy transitions in when a new widget is created. 7 | */ 8 | public class WidgetPositionTransition extends Transition { 9 | 10 | private Widget widget; 11 | private float startX, startY, targetX, targetY; 12 | 13 | public WidgetPositionTransition(Widget widget, long durationInMillis, float targetX, float targetY) { 14 | this(widget, durationInMillis, widget.getX(), widget.getY(), targetX, targetY); 15 | } 16 | 17 | public WidgetPositionTransition(Widget widget, long durationInMillis, float startX, float startY, float targetX, float targetY) { 18 | super(durationInMillis); 19 | this.widget = widget; 20 | this.startX = startX; 21 | this.startY = startY; 22 | this.targetX = targetX; 23 | this.targetY = targetY; 24 | } 25 | 26 | @Override 27 | public void tick(float progress) { 28 | float endX = startX + ((targetX - startX) * progress); 29 | float endY = startY + ((targetY - startY) * progress); 30 | 31 | widget.setX(endX); 32 | widget.setY(endY); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/transition/WidgetSizeTransition.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.transition; 2 | 3 | import nokori.clear.vg.widget.assembly.Widget; 4 | 5 | /** 6 | * This transition is an extended SizeTransition geared toward resizing Widgets automatically. 7 | */ 8 | public class WidgetSizeTransition extends SizeTransition { 9 | 10 | private Widget widget; 11 | 12 | public WidgetSizeTransition(Widget widget, long durationInMillis, float targetWidth, float targetHeight) { 13 | super(durationInMillis, targetWidth, targetHeight); 14 | this.widget = widget; 15 | } 16 | 17 | protected float getCurrentWidth() { 18 | return widget.getWidth(); 19 | } 20 | 21 | protected float getCurrentHeight() { 22 | return widget.getHeight(); 23 | } 24 | 25 | protected void setWidth(float width) { 26 | widget.setWidth(width); 27 | } 28 | 29 | protected void setHeight(float height) { 30 | widget.setHeight(height); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/util/BezierLineRenderer.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.util; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | 6 | import static org.lwjgl.nanovg.NanoVG.*; 7 | 8 | public class BezierLineRenderer { 9 | 10 | private float sx, sy, ex, ey, c1x, c1y, c2x, c2y; 11 | private float strokeThickness = 1f; 12 | 13 | private ClearColor strokeFill = null; 14 | 15 | public BezierLineRenderer(ClearColor strokeFill) { 16 | this.strokeFill = strokeFill; 17 | } 18 | 19 | public void render(NanoVGContext context) { 20 | if (strokeFill != null) { 21 | long vg = context.get(); 22 | 23 | float c1x = (float) (sx + this.c1x); 24 | float c1y = (float) (sy + this.c1y); 25 | 26 | float c2x = (float) (sx + this.c2x); 27 | float c2y = (float) (sy + this.c2y); 28 | 29 | nvgBeginPath(vg); 30 | nvgMoveTo(vg, sx, sy); 31 | nvgBezierTo(vg, c1x, c1y, c2x, c2y, ex, ey); 32 | 33 | strokeFill.tallocNVG(strokeFill -> { 34 | nvgStrokeColor(vg, strokeFill); 35 | nvgStrokeWidth(vg, strokeThickness); 36 | nvgStroke(vg); 37 | }); 38 | 39 | nvgClosePath(vg); 40 | } 41 | } 42 | 43 | public void setStartAndControl1Position(float sx, float sy, float cx, float cy) { 44 | setStartPosition(sx, sy); 45 | setControl1Position(cx, cy); 46 | } 47 | 48 | public void setEndAndControl2Position(float ex, float ey, float cx, float cy) { 49 | setEndPosition(ex, ey); 50 | setControl2Position(cx, cy); 51 | } 52 | 53 | /** 54 | * Equivalent to setAbsolutePosition(). This method is purely syntax sugar to make the start/end points more clear from a writing perspective. 55 | * 56 | * @param sx 57 | * @param sy 58 | */ 59 | public void setStartPosition(float sx, float sy) { 60 | this.sx = sx; 61 | this.sy = sy; 62 | } 63 | 64 | /** 65 | * Sets the ending point of the bezier line. 66 | * 67 | * @param x1 - end x 68 | * @param x2 - end y 69 | */ 70 | public void setEndPosition(float ex, float ey) { 71 | this.ex = ex; 72 | this.ey = ey; 73 | } 74 | 75 | /** 76 | * Sets the first control point of the line. 77 | * 78 | * @param c1x 79 | * @param c1y 80 | */ 81 | public void setControl1Position(float c1x, float c1y) { 82 | this.c1x = c1x; 83 | this.c1y = c1y; 84 | } 85 | 86 | /** 87 | * Sets the first control point of the line. 88 | * 89 | * @param c1x 90 | * @param c1y 91 | */ 92 | public void setControl2Position(float c2x, float c2y) { 93 | this.c2x = c2x; 94 | this.c2y = c2y; 95 | } 96 | 97 | /** 98 | * Sets the thickness of the line. By default it's 1.0f. 99 | * 100 | * @param strokeThickness 101 | */ 102 | public void setStrokeThickness(float strokeThickness) { 103 | this.strokeThickness = strokeThickness; 104 | } 105 | 106 | public void setStrokeAlpha(float alpha) { 107 | strokeFill.alpha(alpha); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/util/NanoVGScaler.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.util; 2 | 3 | import nokori.clear.vg.NanoVGContext; 4 | import org.lwjgl.nanovg.NanoVG; 5 | 6 | /** 7 | * This is a wrapper that allows for the easy managing of scaling in NanoVG. This class will store the current scale and allow you to apply it and reset it as needed. 8 | */ 9 | public class NanoVGScaler { 10 | private float scale = 1.0f; 11 | 12 | /** 13 | * Calls nvgSave() and nvgScale() with the settings in this object. 14 | */ 15 | public void pushScale(NanoVGContext context) { 16 | if (scale != 1.0f) { 17 | long ctx = context.get(); 18 | 19 | NanoVG.nvgSave(ctx); 20 | NanoVG.nvgScale(ctx, scale, scale); 21 | } 22 | } 23 | 24 | /** 25 | * Shortcut for calling nvgRestore() after you've used pushScale() already, but this only works if you called pushScale() first. 26 | */ 27 | public void popScale(NanoVGContext context) { 28 | if (scale != 1.0f) { 29 | NanoVG.nvgRestore(context.get()); 30 | } 31 | } 32 | 33 | public float getScale() { 34 | return scale; 35 | } 36 | 37 | public void setScale(float scale) { 38 | this.scale = scale; 39 | } 40 | 41 | public void offsetScale(float amount) { 42 | scale += amount; 43 | } 44 | 45 | public float applyScale(float value) { 46 | return applyScale(value, scale); 47 | } 48 | 49 | public static float applyScale(float value, float scale) { 50 | return value / scale; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/ButtonAssembly.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.ClearStaticResources; 5 | import nokori.clear.vg.font.Font; 6 | import nokori.clear.vg.font.FontStyle; 7 | import nokori.clear.vg.transition.FillTransition; 8 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 9 | import nokori.clear.vg.widget.assembly.WidgetClip; 10 | import nokori.clear.windows.Cursor; 11 | 12 | public class ButtonAssembly extends WidgetAssembly { 13 | 14 | public static final ClearColor DEFAULT_OUTLINE_COLOR = ClearColor.GRAY; 15 | 16 | public ButtonAssembly(float width, float height, ClearColor fill, ClearColor outlineFill, float cornerRadius, Font font, int fontSize, ClearColor fontColor, String label) { 17 | this(0f, 0f, width, height, fill, outlineFill, cornerRadius, font, fontSize, fontColor, label); 18 | } 19 | 20 | public ButtonAssembly(float x, float y, float width, float height, ClearColor fill, ClearColor outlineFill, float cornerRadius, Font font, int fontSize, ClearColor fontColor, String label) { 21 | super(x, y, width, height); 22 | 23 | /* 24 | * Background - rectangle with dropshadow 25 | */ 26 | 27 | addChild(new DropShadowWidget(cornerRadius)); 28 | 29 | RectangleWidget background = new RectangleWidget(cornerRadius, fill, DEFAULT_OUTLINE_COLOR.copy(), true); 30 | addChild(background); 31 | 32 | /* 33 | * Text 34 | */ 35 | 36 | LabelWidget labelWidget = new LabelWidget(fontColor, label, font, FontStyle.REGULAR, fontSize); 37 | labelWidget.addChild(new WidgetClip(WidgetClip.Alignment.CENTER)); 38 | addChild(labelWidget); 39 | 40 | /* 41 | * Input 42 | */ 43 | 44 | setOnInternalMouseEnteredEvent(e -> { 45 | ClearStaticResources.getCursor(Cursor.Type.HAND).apply(e.getWindow()); 46 | 47 | FillTransition outlineFader = new FillTransition(200, background.getStrokeFill(), outlineFill); 48 | outlineFader.play(); 49 | }); 50 | 51 | setOnInternalMouseExitedEvent(e -> { 52 | ClearStaticResources.getCursor(Cursor.Type.ARROW).apply(e.getWindow()); 53 | 54 | FillTransition outlineFader = new FillTransition(200, background.getStrokeFill(), DEFAULT_OUTLINE_COLOR); 55 | outlineFader.play(); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/CircleWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.widget.assembly.Widget; 6 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 7 | 8 | import static org.lwjgl.nanovg.NanoVG.*; 9 | 10 | public class CircleWidget extends Widget { 11 | 12 | protected float radius; 13 | protected ClearColor fill, strokeFill; 14 | 15 | public CircleWidget(ClearColor fill, float radius) { 16 | this(0f, 0f, radius, fill, null); 17 | } 18 | 19 | public CircleWidget(float x, float y, float radius, ClearColor fill, ClearColor strokeFill) { 20 | super(x, y, radius * 2, radius * 2); 21 | this.fill = fill; 22 | this.strokeFill = strokeFill; 23 | this.radius = radius; 24 | } 25 | 26 | @Override 27 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 28 | 29 | @Override 30 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 31 | long vg = context.get(); 32 | 33 | nvgBeginPath(vg); 34 | nvgCircle(vg, getClippedX() + getWidth()/2f, getClippedY() + getHeight()/2f, radius); 35 | 36 | if (fill != null) { 37 | fill.tallocNVG(fill -> { 38 | nvgFillColor(vg, fill); 39 | nvgFill(vg); 40 | }); 41 | } 42 | 43 | if (strokeFill != null) { 44 | strokeFill.tallocNVG(strokeFill -> { 45 | nvgStrokeColor(vg, strokeFill); 46 | nvgStroke(vg); 47 | }); 48 | } 49 | 50 | nvgClosePath(vg); 51 | } 52 | 53 | public float getRadius() { 54 | return radius; 55 | } 56 | 57 | public void setRadius(float radius) { 58 | this.radius = radius; 59 | } 60 | 61 | public ClearColor getFill() { 62 | return fill; 63 | } 64 | 65 | public void setFill(ClearColor fill) { 66 | this.fill = fill; 67 | } 68 | 69 | public ClearColor getStrokeFill() { 70 | return strokeFill; 71 | } 72 | 73 | public void setStrokeFill(ClearColor strokeFill) { 74 | this.strokeFill = strokeFill; 75 | } 76 | 77 | @Override 78 | public void dispose() {} 79 | } 80 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/DropShadowWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 6 | import nokori.clear.vg.widget.assembly.WidgetSynch; 7 | import org.joml.Vector2f; 8 | import org.lwjgl.nanovg.NVGColor; 9 | import org.lwjgl.nanovg.NVGPaint; 10 | import org.lwjgl.nanovg.NanoVG; 11 | import org.lwjgl.system.MemoryStack; 12 | 13 | public class DropShadowWidget extends RectangleWidget { 14 | 15 | public static final ClearColor DEFAULT_FILL = ClearColor.LIGHT_BLACK; 16 | public static final float DEFAULT_SHADOW_RADIUS = 4f; 17 | public static final float DEFAULT_SHADOW_OFFSET = 2f; 18 | 19 | private float shadowRadius; 20 | private Vector2f shadowOffset = new Vector2f(0, 0); 21 | 22 | public DropShadowWidget() { 23 | this(DEFAULT_FILL); 24 | } 25 | 26 | public DropShadowWidget(float cornerRadius) { 27 | this(cornerRadius, DEFAULT_FILL); 28 | } 29 | 30 | public DropShadowWidget(ClearColor fill) { 31 | this(0, DEFAULT_SHADOW_RADIUS, DEFAULT_SHADOW_OFFSET, DEFAULT_SHADOW_OFFSET, fill); 32 | } 33 | 34 | public DropShadowWidget(float cornerRadius, ClearColor fill) { 35 | this(cornerRadius, DEFAULT_SHADOW_RADIUS, DEFAULT_SHADOW_OFFSET, DEFAULT_SHADOW_OFFSET, fill); 36 | } 37 | 38 | public DropShadowWidget(float cornerRadius, float shadowRadius, float shadowOffset) { 39 | this(cornerRadius, shadowRadius, shadowOffset, shadowOffset, DEFAULT_FILL); 40 | } 41 | 42 | public DropShadowWidget(float cornerRadius, float shadowRadius, float shadowOffsetX, float shadowOffsetY, ClearColor fill) { 43 | this(0, 0, 0, 0, cornerRadius, shadowRadius, shadowOffsetX, shadowOffsetY, fill); 44 | addChild(new WidgetSynch(WidgetSynch.Mode.WITH_PARENT)); 45 | } 46 | 47 | public DropShadowWidget(float x, float y, float width, float height, float cornerRadius, float shadowRadius, float shadowOffsetX, float shadowOffsetY, ClearColor fill) { 48 | super(x, y, width, height, fill, null); 49 | this.shadowRadius = shadowRadius; 50 | shadowOffset.x = shadowOffsetX; 51 | shadowOffset.y = shadowOffsetY; 52 | } 53 | 54 | @Override 55 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 56 | long vg = context.get(); 57 | 58 | float x = getClippedX() + shadowOffset.x; 59 | float y = getClippedY() + shadowOffset.y; 60 | float w = getWidth(); 61 | float h = getHeight(); 62 | 63 | try (MemoryStack stack = MemoryStack.stackPush()){ 64 | NVGColor fill = this.fill.mallocNVG(stack); 65 | NVGColor transparent = this.fill.copy().alpha(0f).mallocNVG(stack); 66 | 67 | NVGPaint paint = NanoVG.nvgBoxGradient(vg, x + shadowRadius, y + shadowRadius, w - (shadowRadius * 2), h - (shadowRadius * 2), 4, 12, fill, transparent, NVGPaint.create()); 68 | NanoVG.nvgBeginPath(vg); 69 | NanoVG.nvgRect(vg, x, y, w, h); 70 | NanoVG.nvgFillPaint(vg, paint); 71 | NanoVG.nvgFill(vg); 72 | NanoVG.nvgClosePath(vg); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/HalfCircleWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 6 | 7 | import static org.lwjgl.nanovg.NanoVG.*; 8 | 9 | public class HalfCircleWidget extends CircleWidget { 10 | 11 | public enum Orientation { 12 | LEFT, 13 | RIGHT; 14 | 15 | public float getCenterX(HalfCircleWidget widget) { 16 | switch(this) { 17 | case RIGHT: 18 | return widget.getClippedX(); 19 | case LEFT: 20 | default: 21 | return widget.getClippedX() + widget.radius; 22 | } 23 | } 24 | }; 25 | 26 | protected Orientation orientation; 27 | 28 | public HalfCircleWidget(float radius, ClearColor fill, Orientation orientation) { 29 | this(0f, 0f, radius, fill, null, orientation); 30 | } 31 | 32 | public HalfCircleWidget(float x, float y, float radius, ClearColor fill, ClearColor strokeFill, Orientation orientation) { 33 | super(x, y, radius, fill, strokeFill); 34 | this.orientation = orientation; 35 | 36 | setWidth(radius); 37 | } 38 | 39 | @Override 40 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 41 | 42 | @Override 43 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 44 | long vg = context.get(); 45 | 46 | float x = orientation.getCenterX(this); 47 | float y = getClippedY() + radius; 48 | 49 | float startAngle; 50 | float endAngle; 51 | 52 | switch(orientation) { 53 | case RIGHT: 54 | startAngle = -NVG_PI/2; 55 | endAngle = NVG_PI/2; 56 | break; 57 | case LEFT: 58 | default: 59 | startAngle = NVG_PI/2; 60 | endAngle = NVG_PI * 1.5f; 61 | break; 62 | 63 | } 64 | 65 | nvgBeginPath(vg); 66 | 67 | nvgArc(vg, x, y, radius, startAngle, endAngle, NVG_CW); 68 | 69 | if (fill != null) { 70 | fill.tallocNVG(fill -> { 71 | nvgFillColor(vg, fill); 72 | nvgFill(vg); 73 | }); 74 | } 75 | 76 | if (strokeFill != null) { 77 | strokeFill.tallocNVG(strokeFill -> { 78 | nvgStrokeColor(vg, strokeFill); 79 | nvgStroke(vg); 80 | }); 81 | } 82 | 83 | nvgClosePath(vg); 84 | } 85 | 86 | @Override 87 | public void dispose() {} 88 | 89 | } 90 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/LabelWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.font.Font; 6 | import nokori.clear.vg.font.FontStyle; 7 | import nokori.clear.vg.widget.assembly.Widget; 8 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 9 | import org.joml.Vector2f; 10 | import org.lwjgl.nanovg.NVGColor; 11 | 12 | import java.util.ArrayList; 13 | 14 | import static org.lwjgl.nanovg.NanoVG.*; 15 | 16 | public class LabelWidget extends Widget { 17 | 18 | private static final float AUTO_CALCULATE_WIDTH = Float.MAX_VALUE; 19 | 20 | private String text; 21 | private Font font; 22 | private FontStyle style; 23 | private float fontSize; 24 | private int textAlignment; 25 | 26 | private boolean autoCalculateWidth = false; 27 | 28 | private ArrayList lines = null; 29 | private Vector2f bounds = new Vector2f(); 30 | 31 | private ClearColor fill; 32 | 33 | /* 34 | * Auto calculate width based on text bounds 35 | */ 36 | 37 | public LabelWidget(ClearColor fill, String text, Font font, FontStyle style, float fontSize) { 38 | this(0, 0, AUTO_CALCULATE_WIDTH, fill, text, font, style, fontSize, Font.DEFAULT_TEXT_ALIGNMENT); 39 | } 40 | 41 | public LabelWidget(ClearColor fill, String text, Font font, FontStyle style, float fontSize, int textAlignment) { 42 | this(0, 0, AUTO_CALCULATE_WIDTH, fill, text, font, style, fontSize, textAlignment); 43 | } 44 | 45 | public LabelWidget(float x, float y, ClearColor fill, String text, Font font, FontStyle style, float fontSize) { 46 | this(x, y, AUTO_CALCULATE_WIDTH, fill, text, font, style, fontSize); 47 | } 48 | 49 | public LabelWidget(float x, float y, ClearColor fill, String text, Font font, FontStyle style, float fontSize, int textAlignment) { 50 | this(x, y, AUTO_CALCULATE_WIDTH, fill, text, font, style, fontSize, textAlignment); 51 | } 52 | 53 | /* 54 | * Width is set manually constructors 55 | */ 56 | 57 | public LabelWidget(float width, ClearColor fill, String text, Font font, FontStyle style, float fontSize) { 58 | this(0, 0, width, fill, text, font, style, fontSize, Font.DEFAULT_TEXT_ALIGNMENT); 59 | } 60 | 61 | public LabelWidget(float width, ClearColor fill, String text, Font font, FontStyle style, float fontSize, int textAlignment) { 62 | this(0, 0, width, fill, text, font, style, fontSize, textAlignment); 63 | } 64 | 65 | public LabelWidget(float x, float y, float width, ClearColor fill, String text, Font font, FontStyle style, float fontSize) { 66 | this(x, y, width, fill, text, font, style, fontSize, Font.DEFAULT_TEXT_ALIGNMENT); 67 | } 68 | 69 | public LabelWidget(float x, float y, float width, ClearColor fill, String text, Font font, FontStyle style, float fontSize, int textAlignment) { 70 | super(x, y, 0, 0); 71 | this.fill = fill; 72 | this.text = text; 73 | this.font = font; 74 | this.style = style; 75 | this.fontSize = fontSize; 76 | this.textAlignment = textAlignment; 77 | 78 | if (width == AUTO_CALCULATE_WIDTH) { 79 | autoCalculateWidth = true; 80 | } else { 81 | setWidth(width); 82 | } 83 | } 84 | 85 | @Override 86 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 87 | 88 | /** 89 | * Splits the text into lines (if there are line breaks) and calculates the height of this LabelWidget. This is called by tick() automatically, 90 | * but if desired, this can be called manually if the bounds are needed immediately. 91 | * 92 | * @param context 93 | * @return - this LabelWidget (for pretty code purposes) 94 | */ 95 | public LabelWidget calculateBounds(NanoVGContext context) { 96 | //Calculate the line split and then derive the height from that 97 | if (lines == null) { 98 | font.split(context, lines = new ArrayList(), text, autoCalculateWidth ? AUTO_CALCULATE_WIDTH : getWidth(), fontSize, textAlignment, style); 99 | 100 | font.getTextBounds(context, bounds, lines, fontSize, textAlignment, style); 101 | 102 | //Calculate width automatically if applicable 103 | if (autoCalculateWidth) { 104 | setWidth(bounds.x()); 105 | } 106 | 107 | setHeight(bounds.y()); 108 | } 109 | 110 | return this; 111 | } 112 | 113 | @Override 114 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 115 | font.configureNVG(context, fontSize, textAlignment, style); 116 | calculateBounds(context); 117 | 118 | long vg = context.get(); 119 | 120 | NVGColor fill = (this.fill != null ? this.fill.callocNVG() : null); 121 | 122 | nvgBeginPath(vg); 123 | 124 | if (fill != null) { 125 | nvgFillColor(vg, fill); 126 | } 127 | 128 | for (int i = 0; i < lines.size(); i++) { 129 | String renderText = lines.get(i).replaceAll("\n", ""); 130 | nvgText(vg, getClippedX(), getClippedY() + (font.getHeight(context) * i), renderText); 131 | } 132 | 133 | nvgClosePath(vg); 134 | } 135 | 136 | @Override 137 | public void dispose() { 138 | 139 | } 140 | 141 | public String getText() { 142 | return text; 143 | } 144 | 145 | public void setText(NanoVGContext context, String text) { 146 | if (!this.text.equals(text)) { 147 | lines = null; 148 | } 149 | 150 | this.text = text; 151 | 152 | calculateBounds(context); 153 | } 154 | 155 | public Font getFont() { 156 | return font; 157 | } 158 | 159 | public void setFont(Font font) { 160 | this.font = font; 161 | } 162 | 163 | public FontStyle getStyle() { 164 | return style; 165 | } 166 | 167 | public void setStyle(FontStyle style) { 168 | this.style = style; 169 | } 170 | 171 | public float getFontSize() { 172 | return fontSize; 173 | } 174 | 175 | public void setFontSize(float fontSize) { 176 | this.fontSize = fontSize; 177 | } 178 | 179 | public int getTextAlignment() { 180 | return textAlignment; 181 | } 182 | 183 | public void setTextAlignment(int textAlignment) { 184 | this.textAlignment = textAlignment; 185 | } 186 | 187 | public ClearColor getFill() { 188 | return fill; 189 | } 190 | 191 | public void setFill(ClearColor fill) { 192 | this.fill = fill; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/RectangleWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.widget.assembly.Widget; 6 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 7 | import nokori.clear.vg.widget.assembly.WidgetSynch; 8 | import org.lwjgl.nanovg.NanoVG; 9 | 10 | /** 11 | * A widget that draws a rectangle at the given coordinates. It can be manually configured or set to sync up to the parent container. 12 | */ 13 | public class RectangleWidget extends Widget { 14 | 15 | protected ClearColor fill, strokeFill; 16 | protected float cornerRadius; 17 | private float strokeWidth = 1.0f; 18 | 19 | /* 20 | * 21 | * Sync to parent modes 22 | * 23 | */ 24 | 25 | public RectangleWidget(ClearColor fill, boolean addWidgetSynch) { 26 | this(fill, null, addWidgetSynch); 27 | } 28 | 29 | public RectangleWidget(float cornerRadius, ClearColor fill, boolean addWidgetSynch) { 30 | this(cornerRadius, fill, null, addWidgetSynch); 31 | } 32 | 33 | public RectangleWidget(ClearColor fill, ClearColor strokeFill, boolean addWidgetSynch) { 34 | this(0f, fill, strokeFill, addWidgetSynch); 35 | } 36 | 37 | /** 38 | * Creates a new automatically configured RectangleWidget 39 | * 40 | * @param cornerRadius - determines the radius of the corners. Set to 0 to create a hard rectangle. 41 | * @param fill - the internal fill of the rectangle 42 | * @param strokeFill - the outline fill of the rectangle 43 | * @param addWidgetSynch - if true, a WidgetSynch is added to this RectangleWidget, configuring it to synchronize itself with its parent widget. 44 | */ 45 | public RectangleWidget(float cornerRadius, ClearColor fill, ClearColor strokeFill, boolean addWidgetSynch) { 46 | this(0, 0, 0, 0, cornerRadius, fill, strokeFill); 47 | 48 | if (addWidgetSynch) { 49 | addChild(new WidgetSynch(WidgetSynch.Mode.WITH_PARENT)); 50 | } 51 | } 52 | 53 | /* 54 | * 55 | * Manual configuration modes 56 | * 57 | */ 58 | 59 | public RectangleWidget(float width, float height, ClearColor fill) { 60 | this(0f, 0f, width, height, fill); 61 | } 62 | 63 | public RectangleWidget(float x, float y, float width, float height, ClearColor fill) { 64 | this(x, y, width, height, 0, fill); 65 | } 66 | 67 | public RectangleWidget(float x, float y, float width, float height, ClearColor fill, ClearColor strokeFill) { 68 | this(x, y, width, height, 0, fill, strokeFill); 69 | } 70 | 71 | public RectangleWidget(float x, float y, float width, float height, float cornerRadius, ClearColor fill) { 72 | this(x, y, width, height, fill, null); 73 | } 74 | 75 | public RectangleWidget(float x, float y, float width, float height, float cornerRadius, ClearColor fill, ClearColor strokeFill) { 76 | super(x, y, width, height); 77 | this.cornerRadius = cornerRadius; 78 | this.fill = fill; 79 | this.strokeFill = strokeFill; 80 | } 81 | 82 | /* 83 | * 84 | * Methods start 85 | * 86 | */ 87 | 88 | @Override 89 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 90 | 91 | @Override 92 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 93 | long vg = context.get(); 94 | 95 | float x = getClippedX(); 96 | float y = getClippedY(); 97 | float w = getWidth(); 98 | float h = getHeight(); 99 | 100 | if (fill != null) { 101 | fill.tallocNVG(fill -> { 102 | NanoVG.nvgBeginPath(vg); 103 | NanoVG.nvgRoundedRect(vg, x, y, w, h, cornerRadius); 104 | NanoVG.nvgFillColor(vg, fill); 105 | NanoVG.nvgFill(vg); 106 | NanoVG.nvgClosePath(vg); 107 | }); 108 | } 109 | 110 | if (strokeFill != null) { 111 | strokeFill.tallocNVG(strokeFill -> { 112 | NanoVG.nvgBeginPath(vg); 113 | NanoVG.nvgRoundedRect(vg, x, y, w, h, cornerRadius); 114 | NanoVG.nvgStrokeWidth(vg, strokeWidth); 115 | NanoVG.nvgStrokeColor(vg, strokeFill); 116 | NanoVG.nvgStroke(vg); 117 | NanoVG.nvgClosePath(vg); 118 | }); 119 | } 120 | 121 | } 122 | 123 | public float getStrokeWidth() { 124 | return strokeWidth; 125 | } 126 | 127 | public void setStrokeWidth(float strokeWidth) { 128 | this.strokeWidth = strokeWidth; 129 | } 130 | 131 | @Override 132 | public void dispose() { 133 | 134 | } 135 | 136 | public ClearColor getStrokeFill() { 137 | return strokeFill; 138 | } 139 | 140 | public ClearColor getFill() { 141 | return fill; 142 | } 143 | 144 | public void setFill(ClearColor fill) { 145 | this.fill = fill; 146 | } 147 | 148 | public void setStrokeFill(ClearColor strokeFill) { 149 | this.strokeFill = strokeFill; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/TickerWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget; 2 | 3 | import nokori.clear.vg.NanoVGContext; 4 | import nokori.clear.vg.widget.assembly.Widget; 5 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 6 | 7 | /** 8 | * Basic widget that has a simplified tick() function. Mostly useful for debugging (so that you don't have to override the long tick function every time). 9 | */ 10 | public abstract class TickerWidget extends Widget { 11 | 12 | @Override 13 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 14 | tick(); 15 | } 16 | 17 | /** 18 | * A simplified tick() function that's called from the more complex default widget tick() function. 19 | */ 20 | public abstract void tick(); 21 | 22 | @Override 23 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 24 | 25 | @Override 26 | public void dispose() {} 27 | 28 | } 29 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/assembly/DraggableWidgetAssembly.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.assembly; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.event.MouseButtonEvent; 5 | import nokori.clear.windows.event.MouseMotionEvent; 6 | import org.joml.Vector2f; 7 | import org.lwjgl.glfw.GLFW; 8 | 9 | import static nokori.clear.vg.ClearStaticResources.*; 10 | 11 | /** 12 | * DraggableWidgetAssemblies are exactly what the name implies. It's a WidgetAssembly that can be dragged with the mouse. 13 | * With the right settings, you can put a bunch of DraggableWidgetAssembly objects in one DraggableWidgetAssembly and create a system 14 | * where nodes are draggable inside of a canvas that is pannable. 15 | *

16 | * Check out ClearDraggableWidgetDemo.java to see an example of this in action. 17 | */ 18 | public class DraggableWidgetAssembly extends WidgetAssembly { 19 | 20 | private Vector2f anchor = new Vector2f(); 21 | private boolean dragging = false; 22 | private Vector2f startPos = new Vector2f(); 23 | 24 | private boolean requiresMouseToBeWithinWidgetToDrag = true; 25 | private boolean ignoreChildrenWidgets = false; 26 | 27 | public DraggableWidgetAssembly() { 28 | this(0f, 0f, 0f, 0f); 29 | } 30 | 31 | public DraggableWidgetAssembly(float x, float y, float width, float height) { 32 | super(x, y, width, height); 33 | 34 | setOnInternalMouseButtonEvent(e -> { 35 | boolean bDragging = dragging; 36 | dragging = ((dragging || canDrag(e.getWindow(), 1.0f)) && isFocusedOrCanFocus(this) && isDragButtonEvent(e)); 37 | startPos.set(getClippedX(), getClippedY()); 38 | 39 | /*System.err.println(this + ": " + canDrag(e.getWindow()) + " " + isFocusedOrCanFocus(this) + " " + isDragButtonEvent(e) 40 | + " = " + dragging 41 | + " | Dimensions: " + DraggableWidgetAssembly.this.getWidth() + "/" + DraggableWidgetAssembly.this.getHeight());*/ 42 | 43 | //If we start dragging, focus on this widget 44 | if (!bDragging && dragging) { 45 | //The anchor is a relative X/Y value as to where the mouse was inside of the Widget when we clicked. 46 | //That way we can factor this into dragging calculations by subtracting the anchor from the mouseX/Y 47 | //(Preventing the widget from snapping to the mouse coordinates from the upper-left) 48 | draggingAnchorCallback(e); 49 | setFocusedWidget(this); 50 | } 51 | 52 | //If dragging stops, unfocus if applicable 53 | if (!dragging) { 54 | clearFocusIfApplicable(this); 55 | draggingReleaseCallback(e, (startPos.x != getClippedX() || startPos.y != getClippedY())); 56 | } 57 | }); 58 | 59 | setOnInternalMouseMotionEvent(e -> { 60 | if (dragging && isFocusedOrCanFocus(this)) { 61 | //Calculating the coordinates like this instead of using the mouse event DX/DY means that the user will have more freedom in how they use move() 62 | //E.G. calculating it like this will allow the user to modify move to allow for grid snapping without any issues arising from the new X/Y values 63 | //System.err.println(this + " Dragging: " + getX() + "/" + getY() + " " + anchor.x() + "/" + anchor.y()); 64 | 65 | draggingCallback(e); 66 | } 67 | }); 68 | } 69 | 70 | /** 71 | * This is split into its own function so that it can be overriden separately from clipDraggingAnchor() 72 | * @param e 73 | */ 74 | protected void draggingAnchorCallback(MouseButtonEvent e) { 75 | clipDraggingAnchor((float) e.getScaledMouseX(scaler.getScale()), (float) e.getScaledMouseY(scaler.getScale())); 76 | } 77 | 78 | /** 79 | * This is split into its own function so that it can be overriden separately from move() 80 | * @param e 81 | */ 82 | protected void draggingCallback(MouseMotionEvent e) { 83 | move(getDragX(e.getScaledMouseX(scaler.getScale())), getDragY(e.getScaledMouseY(scaler.getScale()))); 84 | } 85 | 86 | protected void draggingReleaseCallback(MouseButtonEvent e, boolean wasMoved) { 87 | 88 | } 89 | 90 | public float getDragX(double mouseX) { 91 | return (float) (getX() + (mouseX - getX()) - anchor.x()); 92 | } 93 | 94 | public float getDragY(double mouseY) { 95 | return (float) (getY() + (mouseY - getY()) - anchor.y()); 96 | } 97 | 98 | /** 99 | * This function handles moving the widget assembly when the dragging controls are used. Extension and overriding of this function will allow for the 100 | * modification of the movement behavior. 101 | * 102 | * @param newX - the requested new X value for the widget 103 | * @param newY - the requested new Y value for the widget 104 | */ 105 | public void move(float newX, float newY) { 106 | setX(newX); 107 | setY(newY); 108 | } 109 | 110 | protected boolean canDrag(Window window, float scale) { 111 | //We won't let the user drag the widget if the mouse is hovering one of its draggable widget assembly children (normal children don't count) 112 | boolean hoveringChildren = false; 113 | 114 | if (!ignoreChildrenWidgets) { 115 | for (int i = 0; i < children.size(); i++) { 116 | Widget w = children.get(i); 117 | 118 | if (w.isMouseIntersectingThisWidget(window) && w.isInputEnabled()) { 119 | hoveringChildren = true; 120 | break; 121 | } 122 | } 123 | } 124 | 125 | //System.err.println("canDrag() -> " + hoveringChildren + " " + isMouseWithinThisWidget(window) + " " + requiresMouseToBeWithinWidgetToDrag); 126 | 127 | return ((isMouseIntersectingThisWidget(window) || !requiresMouseToBeWithinWidgetToDrag) && !hoveringChildren); 128 | } 129 | 130 | /** 131 | * @return true if the mouseButton is the button used for dragging. 132 | */ 133 | protected boolean isDragButtonEvent(MouseButtonEvent e) { 134 | return (e.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && e.isPressed()); 135 | } 136 | 137 | /** 138 | * This value determines whether or not the mouse has to actually be within the bounds of this Widget for the dragging functionality to be activated. 139 | * Disabling this can have some advantages, such as when you want to be able to drag an entire canvas around. 140 | */ 141 | public boolean requiresMouseToBeWithinWidgetToDrag() { 142 | return requiresMouseToBeWithinWidgetToDrag; 143 | } 144 | 145 | /** 146 | * This value determines whether or not the mouse has to actually be within the bounds of this Widget for the dragging functionality to be activated. 147 | * Disabling this can have some advantages, such as when you want to be able to drag an entire canvas (widget assembly containing multiple nodes) around 148 | * without worrying about the input not being recognized because you tried to drag outside of its bounds. 149 | * 150 | * @param requiresMouseToBeWithinWidgetToDrag 151 | */ 152 | public void setRequiresMouseToBeWithinWidgetToDrag(boolean requiresMouseToBeWithinWidgetToDrag) { 153 | this.requiresMouseToBeWithinWidgetToDrag = requiresMouseToBeWithinWidgetToDrag; 154 | } 155 | 156 | /** 157 | * If this is set to true, this widget won't take into consideration whether or not the mouse is hovering one of its children widgets before allowing 158 | * the user to drag it. Otherwise, if this is false, you won't be able to drag the widget if the mouse is hovering another widget within this widget. 159 | */ 160 | public boolean ignoreChildrenWidgets() { 161 | return ignoreChildrenWidgets; 162 | } 163 | 164 | /** 165 | * If this is set to true, this widget won't take into consideration whether or not the mouse is hovering one of its children widgets before allowing 166 | * the user to drag it. Otherwise, if this is false, you won't be able to drag the widget if the mouse is hovering another widget within this widget. 167 | * 168 | * @param ignoreChildrenWidgets 169 | */ 170 | public void setIgnoreChildrenWidgets(boolean ignoreChildrenWidgets) { 171 | this.ignoreChildrenWidgets = ignoreChildrenWidgets; 172 | } 173 | 174 | /** 175 | * @return true if this DraggableWidgetAssembly is being dragged via user input. 176 | */ 177 | public boolean isDragging() { 178 | return dragging; 179 | } 180 | 181 | /** 182 | * @return the current dragging anchor. This is a value of the relative X/Y coordinate of the mouse to the widget X/Y when it's clicked for the first time for dragging. 183 | */ 184 | public Vector2f getDraggingAnchor() { 185 | return anchor; 186 | } 187 | 188 | /** 189 | * Sets the dragging anchor for this DraggableWidgetAssembly (the relative X/Y coordinate of the mouse to the widget X/Y). Setting it manually may be useful for instances 190 | * where you need to drag multiple Widgets at the same time. 191 | * 192 | * @param mouseX 193 | * @param mouseY 194 | */ 195 | public void clipDraggingAnchor(float mouseX, float mouseY) { 196 | anchor.set(mouseX - getX(), mouseY - getY()); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/assembly/RootWidgetAssembly.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.assembly; 2 | 3 | /** 4 | * This is just a WidgetAssembly configured for the typical use-case of a root WidgetAssembly uses in a ClearApplication 5 | * @author Brayden 6 | * 7 | */ 8 | public class RootWidgetAssembly extends WidgetAssembly { 9 | public RootWidgetAssembly() { 10 | super(new WidgetSynch(WidgetSynch.Mode.WITH_FRAMEBUFFER)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/assembly/WidgetAssembly.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.assembly; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import org.lwjgl.nanovg.NanoVG; 6 | 7 | /** 8 | * This is an empty generic implementation of a Widget that can be used primarily as a container for other Widgets. 9 | */ 10 | public class WidgetAssembly extends Widget { 11 | 12 | private ClearColor backgroundFill = null; 13 | 14 | 15 | /** 16 | * Initializes the WidgetAssembly with the coordinates (0, 0) and the dimensions (0, 0). 17 | */ 18 | public WidgetAssembly() { 19 | super(0f, 0f, 0f, 0f); 20 | } 21 | 22 | /** 23 | * Creates a WidgetAssembly configured to be synchronized to its parent with a WidgetSynch. 24 | 25 | * @param widgetSynch - the WidgetSynch to add to this WidgetAssembly. 26 | */ 27 | public WidgetAssembly(WidgetSynch widgetSynch) { 28 | this(); 29 | addChild(widgetSynch); 30 | } 31 | 32 | /** 33 | * Creates a WidgetAssembly with the given dimensions. 34 | * 35 | * @param x 36 | * @param y 37 | * @param width 38 | * @param height 39 | */ 40 | public WidgetAssembly(float x, float y, float width, float height) { 41 | super(x, y, width, height); 42 | } 43 | 44 | /** 45 | * Creates a WidgetAssembly with the given width and height and configures it to use the given WidgetClip for positioning. 46 | * 47 | * @param width 48 | * @param height 49 | * @param widgetClip 50 | */ 51 | public WidgetAssembly(float width, float height, WidgetClip widgetClip) { 52 | super(0, 0, width, height); 53 | addChild(widgetClip); 54 | } 55 | 56 | @Override 57 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 58 | 59 | @Override 60 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 61 | if (backgroundFill != null) { 62 | backgroundFill.tallocNVG(fill -> { 63 | long vg = context.get(); 64 | float x = getClippedX(); 65 | float y = getClippedY(); 66 | float w = getWidth(); 67 | float h = getHeight(); 68 | 69 | NanoVG.nvgBeginPath(vg); 70 | NanoVG.nvgRoundedRect(vg, x, y, w, h, 0); 71 | NanoVG.nvgFillColor(vg, fill); 72 | NanoVG.nvgFill(vg); 73 | NanoVG.nvgClosePath(vg); 74 | }); 75 | } 76 | } 77 | 78 | public void setBackgroundFill(ClearColor backgroundFill) { 79 | this.backgroundFill = backgroundFill; 80 | } 81 | 82 | public ClearColor getBackgroundFill() { 83 | return backgroundFill; 84 | } 85 | 86 | @Override 87 | public void dispose() {} 88 | } 89 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/assembly/WidgetClip.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.assembly; 2 | 3 | import nokori.clear.vg.NanoVGContext; 4 | 5 | public class WidgetClip extends Widget { 6 | 7 | public enum Alignment { 8 | TOP_LEFT, TOP_CENTER, TOP_RIGHT, 9 | CENTER_LEFT, CENTER, CENTER_RIGHT, 10 | BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT 11 | }; 12 | 13 | private Alignment alignment; 14 | private float xPadding, yPadding; 15 | 16 | private boolean xModdingEnabled; 17 | private boolean yModdingEnabled; 18 | 19 | public WidgetClip(Alignment alignment) { 20 | this(alignment, 0f, 0f); 21 | } 22 | 23 | public WidgetClip(Alignment alignment, float xPadding, float yPadding) { 24 | this(alignment, xPadding, yPadding, true, true); 25 | } 26 | 27 | public WidgetClip(Alignment alignment, float xPadding, float yPadding, boolean xModdingEnabled, boolean yModdingEnabled) { 28 | this.alignment = alignment; 29 | this.xPadding = xPadding; 30 | this.yPadding = yPadding; 31 | this.xModdingEnabled = xModdingEnabled; 32 | this.yModdingEnabled = yModdingEnabled; 33 | } 34 | 35 | @Override 36 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 37 | if (parent == null || parent.parent == null) { 38 | System.err.println("WARNING: WidgetClip isn't attached to another Widget, or that Widget isn't attached to another Widget!" 39 | + "\nBoth must be present for a WidgetClip to work correctly! (Heirarchy: Widget -> Child Widget -> Widget Clip)"); 40 | return; 41 | } 42 | 43 | float containerWidth = parent.parent.getWidth(); 44 | float containerHeight = parent.parent.getHeight(); 45 | 46 | float centerX = containerWidth/2 - parent.getWidth()/2; 47 | float centerY = containerHeight/2 - parent.getHeight()/2; 48 | 49 | float leftX = 0f; 50 | float rightX = containerWidth - parent.getWidth(); 51 | 52 | float topY = 0f; 53 | float bottomY = containerHeight - parent.getHeight(); 54 | 55 | float clipX = parent.getX(); 56 | float clipY = parent.getY(); 57 | 58 | switch(alignment) { 59 | case BOTTOM_CENTER: 60 | clipX = centerX; 61 | clipY = bottomY; 62 | break; 63 | case BOTTOM_LEFT: 64 | clipX = leftX; 65 | clipY = bottomY; 66 | break; 67 | case BOTTOM_RIGHT: 68 | clipX = rightX; 69 | clipY = bottomY; 70 | break; 71 | case CENTER: 72 | clipX = centerX; 73 | clipY = centerY; 74 | break; 75 | case CENTER_LEFT: 76 | clipX = leftX; 77 | clipY = centerY; 78 | break; 79 | case CENTER_RIGHT: 80 | clipX = rightX; 81 | clipY = centerY; 82 | break; 83 | case TOP_CENTER: 84 | clipX = centerX; 85 | clipY = topY; 86 | break; 87 | case TOP_LEFT: 88 | clipX = leftX; 89 | clipY = topY; 90 | break; 91 | case TOP_RIGHT: 92 | clipX = rightX; 93 | clipY = topY; 94 | break; 95 | default: 96 | break; 97 | } 98 | 99 | clipX += xPadding; 100 | clipY += yPadding; 101 | 102 | if (xModdingEnabled) { 103 | parent.setX(clipX); 104 | } 105 | 106 | if (yModdingEnabled) { 107 | parent.setY(clipY); 108 | } 109 | } 110 | 111 | public Alignment getAlignment() { 112 | return alignment; 113 | } 114 | 115 | public void setAlignment(Alignment alignment) { 116 | this.alignment = alignment; 117 | } 118 | 119 | public float getxPadding() { 120 | return xPadding; 121 | } 122 | 123 | public void setxPadding(float xPadding) { 124 | this.xPadding = xPadding; 125 | } 126 | 127 | public float getyPadding() { 128 | return yPadding; 129 | } 130 | 131 | public void setyPadding(float yPadding) { 132 | this.yPadding = yPadding; 133 | } 134 | 135 | public boolean isxModdingEnabled() { 136 | return xModdingEnabled; 137 | } 138 | 139 | public void setxModdingEnabled(boolean xModdingEnabled) { 140 | this.xModdingEnabled = xModdingEnabled; 141 | } 142 | 143 | public boolean isyModdingEnabled() { 144 | return yModdingEnabled; 145 | } 146 | 147 | public void setyModdingEnabled(boolean yModdingEnabled) { 148 | this.yModdingEnabled = yModdingEnabled; 149 | } 150 | 151 | @Override 152 | public void render(NanoVGContext context, WidgetAssembly rootWidgetAssembly) {} 153 | 154 | @Override 155 | public void dispose() { 156 | 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/assembly/WidgetUtils.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.assembly; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.windows.Window; 6 | import org.lwjgl.nanovg.NVGColor; 7 | import org.lwjgl.nanovg.NanoVG; 8 | 9 | import static org.lwjgl.nanovg.NanoVG.*; 10 | 11 | public class WidgetUtils { 12 | 13 | /** 14 | * Shorthand way to render rectangles with NanoVG. 15 | * 16 | * @param context 17 | * @param fill 18 | * @param x 19 | * @param y 20 | * @param width 21 | * @param height 22 | */ 23 | public static void nvgRect(NanoVGContext context, NVGColor fill, float x, float y, float width, float height) { 24 | nvgRect(context.get(), fill, x, y, width, height); 25 | } 26 | 27 | public static void nvgRect(long vg, float x, float y, float width, float height) { 28 | nvgRect(vg, (NVGColor) null, x, y, width, height); 29 | } 30 | 31 | public static void nvgRect(long vg, ClearColor fill, float x, float y, float width, float height) { 32 | fill.tallocNVG(f -> { 33 | nvgRect(vg, f, x, y, width, height); 34 | }); 35 | } 36 | 37 | /** 38 | * Shorthand way to render rectangles with NanoVG. 39 | * 40 | * @param vg - handle for NanoVG context 41 | * @param fill 42 | * @param x 43 | * @param y 44 | * @param width 45 | * @param height 46 | */ 47 | public static void nvgRect(long vg, NVGColor fill, float x, float y, float width, float height) { 48 | nvgBeginPath(vg); 49 | 50 | if (fill != null) { 51 | nvgFillColor(vg, fill); 52 | } 53 | 54 | NanoVG.nvgRect(vg, x, y, width, height); 55 | nvgFill(vg); 56 | nvgClosePath(vg); 57 | } 58 | 59 | public static void nvgShape(long vg, NVGColor fill, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { 60 | nvgBeginPath(vg); 61 | 62 | if (fill != null) { 63 | nvgFillColor(vg, fill); 64 | } 65 | 66 | nvgMoveTo(vg, x1, y1); 67 | nvgLineTo(vg, x2, y2); 68 | nvgLineTo(vg, x3, y3); 69 | nvgLineTo(vg, x4, y4); 70 | nvgFill(vg); 71 | nvgClosePath(vg); 72 | } 73 | 74 | /** 75 | * Checks if the two given rectangles are intersecting. 76 | *

77 | * Adapted from the Java AWT Rectangle implementation. 78 | */ 79 | public static boolean rectanglesIntersect(double x1, double y1, double w1, double h1, double x2, double y2, double w2, double h2){ 80 | double tw = w1; 81 | double th = h1; 82 | 83 | double rw = w2; 84 | double rh = h2; 85 | 86 | if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) { 87 | return false; 88 | } 89 | 90 | double tx = x1; 91 | double ty = y1; 92 | tw += tx; 93 | th += ty; 94 | 95 | double rx = x2; 96 | double ry = y2; 97 | rw += rx; 98 | rh += ry; 99 | 100 | // overflow || intersect 101 | return ((rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry)); 102 | } 103 | 104 | /** 105 | * @return true if the given point is inside the given rectangle. 106 | */ 107 | public static boolean pointWithinRectangle(double px, double py, double x, double y, double w, double h){ 108 | if (px >= x && px <= x + w){ 109 | if (py >= y && py <= y + h){ 110 | return true; 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | 117 | public static boolean mouseWithinRectangle(Widget widget, Window window, double x, double y, double w, double h) { 118 | return pointWithinRectangle(window.getScaledMouseX(widget.getScaler().getScale()), window.getScaledMouseY(widget.getScaler().getScale()), x, y, w, h); 119 | } 120 | 121 | /* 122 | * CLAMP functions from NOKORI ENGINE 123 | */ 124 | 125 | public static float clamp(float f, float min, float max){ 126 | return f > min ? (f < max ? f : max) : min; 127 | } 128 | 129 | public static double clamp(double f, double min, double max){ 130 | return f > min ? (f < max ? f : max) : min; 131 | } 132 | 133 | public static int clamp(int i, int min, int max){ 134 | return i > min ? (i < max ? i : max) : min; 135 | } 136 | 137 | public static long clamp(long i, long min, long max){ 138 | return i > min ? (i < max ? i : max) : min; 139 | } 140 | 141 | /* 142 | * MIX functions from NOKORI ENGINE 143 | */ 144 | 145 | public static float mix(float x, float y, float a) { 146 | return x + (y - x) * a; 147 | } 148 | 149 | public static double mix(double x, double y, double a) { 150 | return x + (y - x) * a; 151 | } 152 | 153 | /* 154 | * SMOOTHMIX functions from NOKORI ENGINE 155 | */ 156 | 157 | public static float smoothmix(float x, float y, float a) { 158 | a = a * a * (3.0f - 2.0f * a); 159 | return x + (y - x) * a; 160 | } 161 | 162 | public static float smoothermix(float x, float y, float a) { 163 | a = a * a * a * (a * (a * 6 - 15) + 10); 164 | return x + (y - x) * a; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/CharEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.CharEvent; 4 | 5 | public interface CharEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/EventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | public interface EventListener { 4 | public void listen(E e); 5 | } 6 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/KeyEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.KeyEvent; 4 | 5 | public interface KeyEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/MouseButtonEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.MouseButtonEvent; 4 | 5 | public interface MouseButtonEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/MouseEnteredEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.vg.MouseEnteredEvent; 4 | 5 | public interface MouseEnteredEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/MouseExitedEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.vg.MouseExitedEvent; 4 | 5 | public interface MouseExitedEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/MouseMotionEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.MouseMotionEvent; 4 | 5 | public interface MouseMotionEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/listener/MouseScrollEventListener.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.listener; 2 | 3 | import nokori.clear.windows.event.MouseScrollEvent; 4 | 5 | public interface MouseScrollEventListener extends EventListener{ 6 | 7 | } 8 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/DefaultTextAreaContentInputHandler.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | import nokori.clear.vg.ClearStaticResources; 4 | import nokori.clear.vg.util.NanoVGScaler; 5 | import nokori.clear.vg.widget.assembly.Widget; 6 | import nokori.clear.windows.Window; 7 | import nokori.clear.windows.event.CharEvent; 8 | import nokori.clear.windows.event.KeyEvent; 9 | import nokori.clear.windows.event.MouseButtonEvent; 10 | import nokori.clear.windows.event.MouseMotionEvent; 11 | import org.lwjgl.glfw.GLFW; 12 | 13 | public class DefaultTextAreaContentInputHandler extends TextAreaContentInputHandler { 14 | 15 | private boolean mousePressed = false; 16 | 17 | private NanoVGScaler scaler; 18 | 19 | public DefaultTextAreaContentInputHandler(TextAreaWidget textAreaWidget, TextAreaContentHandler textAreaContentHandler) { 20 | super(textAreaWidget, textAreaContentHandler); 21 | this.scaler = textAreaWidget.getScaler(); 22 | } 23 | 24 | public void charEvent(Window window, CharEvent event) { 25 | if (ClearStaticResources.isFocused(widget) && settings().isEditingEnabled()) { 26 | contentHandler.insertCharacterAtCaret(event.getCharString()); 27 | } 28 | } 29 | 30 | public void mouseMotionEvent(Window window, MouseMotionEvent event) { 31 | if (mousePressed && !widget.isScrollbarSelected() && ClearStaticResources.isFocused(widget)) { 32 | contentHandler.queueCaret((float) event.getScaledMouseX(scaler.getScale()), (float) event.getScaledMouseY(scaler.getScale())); 33 | } else { 34 | reset(widget.isScrollbarSelected()); 35 | } 36 | } 37 | 38 | public void mouseButtonEvent(Window window, MouseButtonEvent event) { 39 | if (widget.isScrollbarSelected()) return; 40 | 41 | if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && settings().isCaretEnabled()) { 42 | 43 | /* 44 | * Re-focusing for other Text Areas 45 | * 46 | * Allows us to seamlessly move the caret between text areas 47 | */ 48 | 49 | Widget focused = ClearStaticResources.getFocusedWidget(); 50 | 51 | //Cancel editing if the mouse is hovering this text field but its currently focused on another text field 52 | if (event.isPressed() && widget.isMouseIntersectingThisWidget(window) 53 | && (focused instanceof TextAreaWidget && focused != widget)) { 54 | 55 | ((TextAreaWidget) focused).endEditing(); 56 | } 57 | 58 | //Reset this input handler if its not currently possible to focus on it 59 | if (!ClearStaticResources.isFocusedOrCanFocus(widget)) { 60 | reset(true); 61 | return; 62 | } 63 | 64 | /* 65 | * Caret Placement & Highlighting 66 | */ 67 | boolean bMousePressed = mousePressed; 68 | mousePressed = event.isPressed(); 69 | 70 | if (mousePressed) { 71 | //This queues up caret repositioning based on the mouse coordinates 72 | //Update the caret positioning on next render when we have the character 73 | //locations available 74 | contentHandler.queueCaret((float) event.getScaledMouseX(scaler.getScale()), (float) event.getScaledMouseY(scaler.getScale())); 75 | 76 | // If the mouse wasn't previously pressed, reset the highlighting. 77 | if (!bMousePressed) { 78 | contentHandler.resetHighlighting(); 79 | } 80 | } 81 | } else { 82 | reset(true); 83 | } 84 | } 85 | 86 | /** 87 | * Resets this input handler (sets mousePressed to false, resets the content handlers highlighting) 88 | * 89 | * @param resetHighlighting 90 | */ 91 | public void reset(boolean resetHighlighting) { 92 | mousePressed = false; 93 | 94 | if (resetHighlighting) { 95 | contentHandler.resetHighlighting(); 96 | } 97 | } 98 | 99 | public void keyEvent(Window window, KeyEvent event) { 100 | if (!ClearStaticResources.isFocused(widget) || !event.isPressed()) return; 101 | 102 | int key = event.getKey(); 103 | TextAreaInputSettings config = settings(); 104 | 105 | /* 106 | * Backspace 107 | */ 108 | 109 | if (config.isBackspaceEnabled() && key == GLFW.GLFW_KEY_BACKSPACE) { 110 | 111 | if (contentHandler.isContentHighlighted()) { 112 | contentHandler.deleteHighlightedContent(); 113 | } else { 114 | contentHandler.backspaceAtCaret(); 115 | } 116 | 117 | return; 118 | } 119 | 120 | /* 121 | * Tab 122 | */ 123 | 124 | if (config.isTabEnabled() && key == GLFW.GLFW_KEY_TAB) { 125 | contentHandler.tabAtCaret(); 126 | return; 127 | } 128 | 129 | /* 130 | * Return 131 | */ 132 | 133 | if (key == GLFW.GLFW_KEY_ENTER) { 134 | if (config.returnEndsEditing()) { 135 | contentHandler.endEditing(); 136 | return; 137 | } 138 | 139 | if (config.isReturnEnabled()) { 140 | contentHandler.newLineAtCaret(); 141 | return; 142 | } 143 | } 144 | 145 | /* 146 | * CTRL-[button] Commands 147 | */ 148 | 149 | if (window.isKeyDown(GLFW.GLFW_KEY_LEFT_CONTROL)) { 150 | /* 151 | * Copy 152 | */ 153 | 154 | if (config.isCopyEnabled() && event.getKey() == GLFW.GLFW_KEY_C) { 155 | contentHandler.copySelectionToClipboard(window); 156 | return; 157 | } 158 | 159 | /* 160 | * Cut 161 | */ 162 | 163 | if (config.isCutEnabled() && event.getKey() == GLFW.GLFW_KEY_X) { 164 | contentHandler.cutSelectionToClipboard(window); 165 | return; 166 | } 167 | 168 | /* 169 | * Paste 170 | */ 171 | 172 | if (config.isPasteEnabled() && event.getKey() == GLFW.GLFW_KEY_V) { 173 | contentHandler.pasteClipboardAtCaret(window); 174 | return; 175 | } 176 | 177 | /* 178 | * Bold 179 | */ 180 | 181 | if (config.isBoldEnabled() && event.getKey() == GLFW.GLFW_KEY_B) { 182 | contentHandler.boldenSelection(); 183 | return; 184 | } 185 | 186 | /* 187 | * Italic 188 | */ 189 | 190 | if (config.isItalicEnabled() && event.getKey() == GLFW.GLFW_KEY_I) { 191 | contentHandler.italicizeSelection(); 192 | return; 193 | } 194 | 195 | /* 196 | * Undo 197 | */ 198 | 199 | if (config.isUndoEnabled() && event.getKey() == GLFW.GLFW_KEY_Z) { 200 | contentHandler.undo(); 201 | return; 202 | } 203 | 204 | /* 205 | * Redo 206 | */ 207 | 208 | if (config.isRedoEnabled() && event.getKey() == GLFW.GLFW_KEY_Y) { 209 | contentHandler.redo(); 210 | return; 211 | } 212 | } 213 | 214 | /* 215 | * Move Caret 216 | */ 217 | 218 | if (config.isArrowKeysEnabled()) { 219 | //Move caret right 220 | if (key == GLFW.GLFW_KEY_RIGHT) { 221 | contentHandler.moveCaretRight(); 222 | return; 223 | } 224 | 225 | //Move caret left 226 | if (key == GLFW.GLFW_KEY_LEFT) { 227 | contentHandler.moveCaretLeft(); 228 | return; 229 | } 230 | } 231 | 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/EditingEndedCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | public interface EditingEndedCallback { 4 | public void process(); 5 | } 6 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/ObservableTextAreaWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.font.Font; 5 | import nokori.clear.vg.util.NanoVGScaler; 6 | 7 | /** 8 | * This is an extension of TextAreaWidget that creates a TextAreaWidget that is viewable, but not editable in anyway. This is for cases where you want rich text labels that have formatting, 9 | * but you don't want to allow the users to edit the area. 10 | */ 11 | public class ObservableTextAreaWidget extends TextAreaWidget { 12 | 13 | public ObservableTextAreaWidget(ClearColor fill, String text, Font font, float fontSize) { 14 | super(fill, text, font, fontSize); 15 | makeImmutable(); 16 | } 17 | 18 | public ObservableTextAreaWidget(float width, float height, ClearColor fill, String text, Font font, float fontSize) { 19 | super(width, height, fill, text, font, fontSize); 20 | makeImmutable(); 21 | } 22 | public ObservableTextAreaWidget(float x, float y, float width, float height, ClearColor fill, String text, Font font, float fontSize) { 23 | this(x, y, width, height, new NanoVGScaler(), fill, text, font, fontSize); 24 | } 25 | 26 | public ObservableTextAreaWidget(float x, float y, float width, float height, NanoVGScaler scaler, ClearColor fill, String text, Font font, float fontSize) { 27 | super(x, y, width, height, scaler, fill, text, font, fontSize); 28 | makeImmutable(); 29 | } 30 | 31 | private void makeImmutable() { 32 | getInputSettings().setEditingEnabled(false); 33 | getInputSettings().setHighlightingEnabled(false); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/TextAreaContentInputHandler.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.event.CharEvent; 5 | import nokori.clear.windows.event.KeyEvent; 6 | import nokori.clear.windows.event.MouseButtonEvent; 7 | import nokori.clear.windows.event.MouseMotionEvent; 8 | 9 | /** 10 | * This class handles input for the TextAreaContentHandler (anything pertaining to editing text content). If you want to change the controls, you can override this class and 11 | * call setTextAreaContentInputHandler() on TextAreaWidget to do so however you wish. 12 | */ 13 | public abstract class TextAreaContentInputHandler { 14 | 15 | protected TextAreaWidget widget; 16 | protected TextAreaContentHandler contentHandler; 17 | 18 | public TextAreaContentInputHandler(TextAreaWidget textAreaWidget, TextAreaContentHandler textAreaContentHandler) { 19 | this.widget = textAreaWidget; 20 | this.contentHandler = textAreaContentHandler; 21 | } 22 | 23 | /** 24 | * This is a shortcut function for getting the TextAreaInputSettings in the linked TextAreaWidget. It's recommended that these settings be considered 25 | * when handling inputs so that the user can easily enable/disable features. 26 | */ 27 | protected TextAreaInputSettings settings() { 28 | return widget.getInputSettings(); 29 | } 30 | 31 | public abstract void charEvent(Window window, CharEvent event); 32 | 33 | public abstract void mouseMotionEvent(Window window, MouseMotionEvent event); 34 | 35 | public abstract void mouseButtonEvent(Window window, MouseButtonEvent event); 36 | 37 | public abstract void keyEvent(Window window, KeyEvent event); 38 | } 39 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/TextAreaHistory.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | import java.util.Stack; 4 | 5 | public class TextAreaHistory { 6 | 7 | private Stack undoStack = new Stack(); 8 | private Stack redoStack = new Stack(); 9 | 10 | private static final long STATE_SAVE_TIME = 2000L; //Two seconds 11 | 12 | private long lastEditTime = -1; 13 | 14 | private static class TextState { 15 | StringBuilder textBuilder; 16 | int caret; 17 | 18 | public TextState(StringBuilder textBuilder, int caret) { 19 | this.textBuilder = textBuilder; 20 | this.caret = caret; 21 | } 22 | 23 | public void apply(TextAreaWidget widget, TextAreaContentHandler textAreaContentHandler) { 24 | StringBuilder s = null; 25 | int characterLimit = widget.getInputSettings().getCharacterLimit(); 26 | 27 | if (textBuilder.length() > characterLimit) { 28 | s = new StringBuilder(widget.getTextBuilder().substring(0, characterLimit)); 29 | } else { 30 | s = new StringBuilder(textBuilder); 31 | } 32 | 33 | 34 | widget.setTextBuilder(s); 35 | 36 | textAreaContentHandler.setCaretPosition(caret); 37 | } 38 | } 39 | 40 | public void notifyEditing(TextAreaWidget widget, TextAreaContentHandler contentHandler) { 41 | long currentTime = System.currentTimeMillis(); 42 | 43 | if (lastEditTime == -1 || undoStack.isEmpty() || (currentTime - lastEditTime) >= STATE_SAVE_TIME) { 44 | pushState(undoStack, widget, contentHandler); 45 | } 46 | 47 | lastEditTime = currentTime; 48 | } 49 | 50 | private void pushState(Stack stack, TextAreaWidget widget, TextAreaContentHandler contentHandler) { 51 | stack.push(new TextState(new StringBuilder(widget.getTextBuilder()), contentHandler.getCaretPosition())); 52 | } 53 | 54 | public void undo(TextAreaWidget widget, TextAreaContentHandler textAreaContentHandler) { 55 | if (!undoStack.isEmpty()) { 56 | pushState(redoStack, widget, textAreaContentHandler); 57 | TextState state = undoStack.pop(); 58 | state.apply(widget, textAreaContentHandler); 59 | } 60 | } 61 | 62 | public void redo(TextAreaWidget widget, TextAreaContentHandler textAreaContentHandler) { 63 | if (!redoStack.isEmpty()) { 64 | pushState(undoStack, widget, textAreaContentHandler); 65 | TextState state = redoStack.pop(); 66 | state.apply(widget, textAreaContentHandler); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/TextAreaInputSettings.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | /** 4 | * Contains all input settings for a text area. This class allows you to customize all inputs for a text area in-depth and switch them in and out as needed. 5 | */ 6 | public class TextAreaInputSettings { 7 | 8 | private TextAreaWidget widget; 9 | 10 | //This is a general toggle variable for all inputs. 11 | private boolean inputEnabled = true; 12 | 13 | private int characterLimit = Integer.MAX_VALUE; 14 | 15 | private boolean caretEnabled = true; 16 | private boolean highlightingEnabled = true; 17 | 18 | private boolean editingEnabled = true; 19 | 20 | //If true, users can edit the formatting of the text (setting bold, italics, etc). This is disabled automatically by widgets like the Auto-Formatter (for syntax highlighting). 21 | //Spacing commands like \n and \t aren't affected by this, however. 22 | private boolean manualFormattingEnabled = true; 23 | 24 | private boolean backspaceEnabled = true; 25 | private boolean tabEnabled = true; 26 | 27 | private boolean returnEnabled = true; 28 | private boolean returnEndsEditing = false; 29 | 30 | private boolean copyEnabled = true; 31 | private boolean cutEnabled = true; 32 | private boolean pasteEnabled = true; 33 | 34 | private boolean boldEnabled = true; 35 | private boolean italicEnabled = true; 36 | 37 | private boolean undoEnabled = true; 38 | private boolean redoEnabled = true; 39 | 40 | private boolean arrowKeysEnabled = true; 41 | 42 | private boolean verticalScrollbarEnabled = true; 43 | private boolean horizontalScrollbarEnabled = true; 44 | 45 | /* 46 | * 47 | * 48 | * GETTERS / SETTERS START 49 | * 50 | * 51 | */ 52 | 53 | public TextAreaInputSettings(TextAreaWidget widget) { 54 | this.widget = widget; 55 | } 56 | 57 | public boolean isInputEnabled() { 58 | return inputEnabled; 59 | } 60 | 61 | public void setInputEnabled(boolean inputEnabled) { 62 | this.inputEnabled = inputEnabled; 63 | 64 | if (!inputEnabled) { 65 | widget.getTextContentHandler().endEditing(); 66 | } 67 | } 68 | 69 | public int getCharacterLimit() { 70 | return characterLimit; 71 | } 72 | 73 | public void setCharacterLimit(int characterLimit) { 74 | this.characterLimit = characterLimit; 75 | } 76 | 77 | public boolean isCaretEnabled() { 78 | return (inputEnabled && caretEnabled); 79 | } 80 | 81 | public void setCaretEnabled(boolean caretEnabled) { 82 | this.caretEnabled = caretEnabled; 83 | } 84 | 85 | public boolean isHighlightingEnabled() { 86 | return (inputEnabled && highlightingEnabled); 87 | } 88 | 89 | public void setHighlightingEnabled(boolean highlightingEnabled) { 90 | this.highlightingEnabled = highlightingEnabled; 91 | } 92 | 93 | public boolean isEditingEnabled() { 94 | return (inputEnabled && editingEnabled); 95 | } 96 | 97 | public void setEditingEnabled(boolean editingEnabled) { 98 | this.editingEnabled = editingEnabled; 99 | 100 | if (!editingEnabled) { 101 | widget.getTextContentHandler().endEditing(); 102 | } 103 | } 104 | 105 | public boolean isManualFormattingEnabled() { 106 | return manualFormattingEnabled; 107 | } 108 | 109 | public void setManualFormattingEnabled(boolean manualFormattingEnabled) { 110 | this.manualFormattingEnabled = manualFormattingEnabled; 111 | } 112 | 113 | public boolean isBackspaceEnabled() { 114 | return (inputEnabled && backspaceEnabled); 115 | } 116 | 117 | public void setBackspaceEnabled(boolean backspaceEnabled) { 118 | this.backspaceEnabled = backspaceEnabled; 119 | } 120 | 121 | public boolean isTabEnabled() { 122 | return (inputEnabled && tabEnabled); 123 | } 124 | 125 | public void setTabEnabled(boolean tabEnabled) { 126 | this.tabEnabled = tabEnabled; 127 | } 128 | 129 | public boolean isReturnEnabled() { 130 | return (inputEnabled && returnEnabled); 131 | } 132 | 133 | public void setReturnEnabled(boolean returnEnabled) { 134 | this.returnEnabled = returnEnabled; 135 | } 136 | 137 | public boolean returnEndsEditing() { 138 | return returnEndsEditing; 139 | } 140 | 141 | public void setReturnEndsEditing(boolean returnExitsEditing) { 142 | this.returnEndsEditing = returnExitsEditing; 143 | } 144 | 145 | public boolean isCopyEnabled() { 146 | return (inputEnabled && copyEnabled); 147 | } 148 | 149 | public void setCopyEnabled(boolean copyEnabled) { 150 | this.copyEnabled = copyEnabled; 151 | } 152 | 153 | public boolean isCutEnabled() { 154 | return (inputEnabled && cutEnabled); 155 | } 156 | 157 | public void setCutEnabled(boolean cutEnabled) { 158 | this.cutEnabled = cutEnabled; 159 | } 160 | 161 | public boolean isPasteEnabled() { 162 | return (inputEnabled && pasteEnabled); 163 | } 164 | 165 | public void setPasteEnabled(boolean pasteEnabled) { 166 | this.pasteEnabled = pasteEnabled; 167 | } 168 | 169 | public boolean isBoldEnabled() { 170 | return (inputEnabled && boldEnabled); 171 | } 172 | 173 | public void setBoldEnabled(boolean boldEnabled) { 174 | this.boldEnabled = boldEnabled; 175 | } 176 | 177 | public boolean isItalicEnabled() { 178 | return (inputEnabled && italicEnabled); 179 | } 180 | 181 | public void setItalicEnabled(boolean italicEnabled) { 182 | this.italicEnabled = italicEnabled; 183 | } 184 | 185 | public boolean isUndoEnabled() { 186 | return undoEnabled; 187 | } 188 | 189 | public void setUndoEnabled(boolean undoEnabled) { 190 | this.undoEnabled = undoEnabled; 191 | } 192 | 193 | public boolean isRedoEnabled() { 194 | return redoEnabled; 195 | } 196 | 197 | public void setRedoEnabled(boolean redoEnabled) { 198 | this.redoEnabled = redoEnabled; 199 | } 200 | 201 | public boolean isArrowKeysEnabled() { 202 | return (inputEnabled && arrowKeysEnabled); 203 | } 204 | 205 | public void setArrowKeysEnabled(boolean arrowKeysEnabled) { 206 | this.arrowKeysEnabled = arrowKeysEnabled; 207 | } 208 | 209 | public boolean isVerticalScrollbarEnabled() { 210 | return (inputEnabled && verticalScrollbarEnabled); 211 | } 212 | 213 | public void setVerticalScrollbarEnabled(boolean verticalScrollbarEnabled) { 214 | this.verticalScrollbarEnabled = verticalScrollbarEnabled; 215 | } 216 | 217 | public boolean isHorizontalScrollbarEnabled() { 218 | return (inputEnabled && horizontalScrollbarEnabled); 219 | } 220 | 221 | public void setHorizontalScrollbarEnabled(boolean horizontalScrollbarEnabled) { 222 | this.horizontalScrollbarEnabled = horizontalScrollbarEnabled; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/vg/widget/text/TextFieldWidget.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.vg.widget.text; 2 | 3 | import nokori.clear.vg.ClearColor; 4 | import nokori.clear.vg.NanoVGContext; 5 | import nokori.clear.vg.font.Font; 6 | import nokori.clear.vg.util.NanoVGScaler; 7 | import nokori.clear.vg.widget.assembly.WidgetAssembly; 8 | 9 | /** 10 | * This class is an extension of TextAreaWidget that simplifies its functionality down into just being a one-line field input. 11 | */ 12 | public class TextFieldWidget extends TextAreaWidget { 13 | 14 | /** 15 | * This constructor allows the widget to be initialized without the NanoVGContext but comes at the cost of 16 | * not initializing the widget height right away (it will be zero until tick() is called for the first time). 17 | */ 18 | public TextFieldWidget(float width, ClearColor fill, String text, Font font, float fontSize) { 19 | this(0, 0, width, fill, text, font, fontSize); 20 | } 21 | 22 | /** 23 | * This constructor allows the widget to be initialized without the NanoVGContext but comes at the cost of 24 | * not initializing the widget height right away (it will be zero until tick() is called for the first time). 25 | */ 26 | public TextFieldWidget(float x, float y, float width, ClearColor fill, String text, Font font, float fontSize) { 27 | this(null, x, y, width, fill, text, font, fontSize); 28 | } 29 | 30 | /** 31 | * This shortened version of the primary constructor will calculate the widget height automatically by using the NanoVGContext. 32 | */ 33 | public TextFieldWidget(NanoVGContext context, float width, ClearColor fill, String text, Font font, float fontSize) { 34 | this(context, width, new NanoVGScaler(), fill, text, font, fontSize); 35 | } 36 | 37 | public TextFieldWidget(NanoVGContext context, float width, NanoVGScaler scaler, ClearColor fill, String text, Font font, float fontSize) { 38 | this(context, 0, 0, width, scaler, fill, text, font, fontSize); 39 | } 40 | 41 | public TextFieldWidget(NanoVGContext context, float x, float y, float width, ClearColor fill, String text, Font font, float fontSize) { 42 | this(context, x, y, width, new NanoVGScaler(), fill, text, font, fontSize); 43 | } 44 | 45 | /** 46 | * This is the primary constructor which allows full and complete customization of the TextFieldWidget. Additionally, the widget's height will be 47 | * calculated at initialization thanks to the inclusion of a NanoVGContext. 48 | */ 49 | public TextFieldWidget(NanoVGContext context, float x, float y, float width, NanoVGScaler scaler, ClearColor fill, String text, Font font, float fontSize) { 50 | super(x, y, width, 0f, scaler, fill, text, font, fontSize); 51 | 52 | if (context != null) { 53 | calculateHeight(context); 54 | } 55 | 56 | setWordWrappingEnabled(false); 57 | setLineNumbersEnabled(false); 58 | 59 | getInputSettings().setVerticalScrollbarEnabled(false); 60 | getInputSettings().setHorizontalScrollbarEnabled(false); 61 | getInputSettings().setReturnEnabled(false); 62 | getInputSettings().setReturnEndsEditing(true); 63 | getInputSettings().setTabEnabled(false); 64 | } 65 | 66 | 67 | @Override 68 | public void tick(NanoVGContext context, WidgetAssembly rootWidgetAssembly) { 69 | super.tick(context, rootWidgetAssembly); 70 | calculateHeight(context); 71 | } 72 | 73 | /** 74 | * Calculates the height for this TextFieldWidget by getting the height of the font configured to this widget. 75 | * This is called automatically in the tick() function, however it can be called manually in instances 76 | * where you're initializing it in a constructor and want the most up-to-date data immediately. 77 | */ 78 | public void calculateHeight(NanoVGContext context) { 79 | setHeight(getFont().getHeight(context, getFontSize(), TEXT_AREA_ALIGNMENT, getDefaultFontStyle())); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/windows/event/vg/MouseEnteredEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event.vg; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.event.EventImpl; 5 | import nokori.clear.windows.pool.Pool; 6 | 7 | public class MouseEnteredEvent extends EventImpl { 8 | 9 | private static final Pool POOL = new Pool() { 10 | @Override 11 | protected MouseEnteredEvent create() { 12 | return new MouseEnteredEvent(); 13 | } 14 | }; 15 | 16 | private Window window; 17 | private double mouseX; 18 | private double mouseY; 19 | 20 | private MouseEnteredEvent() {} 21 | 22 | public static MouseEnteredEvent fire(Window window, long timestamp, double mouseX, double mouseY) { 23 | 24 | MouseEnteredEvent e = POOL.get(); 25 | 26 | e.window = window; 27 | e.timestamp = timestamp; 28 | e.mouseX = mouseX; 29 | e.mouseY = mouseY; 30 | 31 | return e; 32 | } 33 | 34 | @Override 35 | public void reset() { 36 | window = null; 37 | } 38 | 39 | public Window getWindow() { 40 | return window; 41 | } 42 | 43 | public double getMouseX() { 44 | return mouseX; 45 | } 46 | 47 | public double getMouseY() { 48 | return mouseY; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ClearVG/src/main/java/nokori/clear/windows/event/vg/MouseExitedEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event.vg; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.event.EventImpl; 5 | import nokori.clear.windows.pool.Pool; 6 | 7 | public class MouseExitedEvent extends EventImpl { 8 | 9 | private static final Pool POOL = new Pool() { 10 | @Override 11 | protected MouseExitedEvent create() { 12 | return new MouseExitedEvent(); 13 | } 14 | }; 15 | 16 | private Window window; 17 | private double mouseX; 18 | private double mouseY; 19 | 20 | private MouseExitedEvent() {} 21 | 22 | public static MouseExitedEvent fire(Window window, long timestamp, double mouseX, double mouseY) { 23 | 24 | MouseExitedEvent e = POOL.get(); 25 | 26 | e.window = window; 27 | e.timestamp = timestamp; 28 | e.mouseX = mouseX; 29 | e.mouseY = mouseY; 30 | 31 | return e; 32 | } 33 | @Override 34 | public void reset() { 35 | window = null; 36 | } 37 | 38 | public Window getWindow() { 39 | return window; 40 | } 41 | 42 | public double getMouseX() { 43 | return mouseX; 44 | } 45 | 46 | public double getMouseY() { 47 | return mouseY; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ClearWindows/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | /.classpath 4 | /.settings/ 5 | -------------------------------------------------------------------------------- /ClearWindows/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ClearWindows 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /ClearWindows/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | nokori.clear 4 | ClearWindows 5 | 1.0.0 6 | 7 | 8 | 9 | maven-compiler-plugin 10 | 3.7.0 11 | 12 | 8 13 | 8 14 | 15 | 16 | 17 | 18 | 19 | 20 | 3.2.1 21 | 1.9.12 22 | natives-windows 23 | 24 | 25 | 26 | 27 | org.lwjgl 28 | lwjgl 29 | ${lwjgl.version} 30 | 31 | 32 | org.lwjgl 33 | lwjgl-egl 34 | ${lwjgl.version} 35 | 36 | 37 | org.lwjgl 38 | lwjgl-glfw 39 | ${lwjgl.version} 40 | 41 | 42 | org.lwjgl 43 | lwjgl-jemalloc 44 | ${lwjgl.version} 45 | 46 | 47 | org.lwjgl 48 | lwjgl-nanovg 49 | ${lwjgl.version} 50 | 51 | 52 | org.lwjgl 53 | lwjgl-nfd 54 | ${lwjgl.version} 55 | 56 | 57 | org.lwjgl 58 | lwjgl-openal 59 | ${lwjgl.version} 60 | 61 | 62 | org.lwjgl 63 | lwjgl-opencl 64 | ${lwjgl.version} 65 | 66 | 67 | org.lwjgl 68 | lwjgl-opengl 69 | ${lwjgl.version} 70 | 71 | 72 | org.lwjgl 73 | lwjgl-opengles 74 | ${lwjgl.version} 75 | 76 | 77 | org.lwjgl 78 | lwjgl-stb 79 | ${lwjgl.version} 80 | 81 | 82 | org.lwjgl 83 | lwjgl-tinyfd 84 | ${lwjgl.version} 85 | 86 | 87 | org.lwjgl 88 | lwjgl 89 | ${lwjgl.version} 90 | ${lwjgl.natives} 91 | 92 | 93 | org.lwjgl 94 | lwjgl-glfw 95 | ${lwjgl.version} 96 | ${lwjgl.natives} 97 | 98 | 99 | org.lwjgl 100 | lwjgl-jemalloc 101 | ${lwjgl.version} 102 | ${lwjgl.natives} 103 | 104 | 105 | org.lwjgl 106 | lwjgl-nanovg 107 | ${lwjgl.version} 108 | ${lwjgl.natives} 109 | 110 | 111 | org.lwjgl 112 | lwjgl-nfd 113 | ${lwjgl.version} 114 | ${lwjgl.natives} 115 | 116 | 117 | org.lwjgl 118 | lwjgl-openal 119 | ${lwjgl.version} 120 | ${lwjgl.natives} 121 | 122 | 123 | org.lwjgl 124 | lwjgl-opengl 125 | ${lwjgl.version} 126 | ${lwjgl.natives} 127 | 128 | 129 | org.lwjgl 130 | lwjgl-opengles 131 | ${lwjgl.version} 132 | ${lwjgl.natives} 133 | 134 | 135 | org.lwjgl 136 | lwjgl-stb 137 | ${lwjgl.version} 138 | ${lwjgl.natives} 139 | 140 | 141 | org.lwjgl 142 | lwjgl-tinyfd 143 | ${lwjgl.version} 144 | ${lwjgl.natives} 145 | 146 | 147 | org.joml 148 | joml 149 | ${joml.version} 150 | 151 | 152 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/ContextParams.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import org.lwjgl.glfw.GLFW; 4 | 5 | public class ContextParams { 6 | 7 | private int versionMajor, versionMinor; 8 | private int profile; 9 | private boolean forwardCompatible; 10 | 11 | private boolean debugContext; 12 | 13 | public ContextParams() { 14 | this(1, 1); 15 | } 16 | 17 | public ContextParams(int versionMajor, int versionMinor) { 18 | this(versionMajor, versionMinor, GLFW.GLFW_OPENGL_ANY_PROFILE); 19 | } 20 | 21 | public ContextParams(int versionMajor, int versionMinor, int profile) { 22 | this(versionMajor, versionMinor, profile, false); 23 | } 24 | 25 | public ContextParams(int versionMajor, int versionMinor, int profile, boolean forwardCompatible) { 26 | this(versionMajor, versionMinor, profile, forwardCompatible, false); 27 | } 28 | 29 | public ContextParams(int versionMajor, int versionMinor, int profile, boolean forwardCompatible, boolean debugContext) { 30 | this.versionMajor = versionMajor; 31 | this.versionMinor = versionMinor; 32 | this.profile = profile; 33 | this.forwardCompatible = forwardCompatible; 34 | 35 | this.debugContext = debugContext; 36 | } 37 | 38 | public int getVersionMajor() { 39 | return versionMajor; 40 | } 41 | 42 | public int getVersionMinor() { 43 | return versionMinor; 44 | } 45 | 46 | public int getProfile() { 47 | return profile; 48 | } 49 | 50 | public boolean isForwardCompatible() { 51 | return forwardCompatible; 52 | } 53 | 54 | public boolean isDebugContext() { 55 | return debugContext; 56 | } 57 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/Cursor.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import org.lwjgl.glfw.GLFWImage; 4 | import org.lwjgl.stb.STBImage; 5 | 6 | import java.io.File; 7 | import java.nio.ByteBuffer; 8 | 9 | import static org.lwjgl.glfw.GLFW.*; 10 | 11 | public class Cursor { 12 | 13 | private long handle; 14 | private Type type = null; 15 | 16 | public Cursor(Type type) { 17 | this.type = type; 18 | handle = glfwCreateStandardCursor(type.shape); 19 | } 20 | 21 | public Cursor(File file, int hotX, int hotY) { 22 | int[] w = new int[1]; 23 | int[] h = new int[1]; 24 | int[] c = new int[1]; 25 | 26 | ByteBuffer buffer = STBImage.stbi_load(file.getAbsolutePath(), w, h, c, 4); 27 | 28 | GLFWImage glfwImg = GLFWImage.malloc(); 29 | glfwImg.set(w[0], h[0], buffer); 30 | 31 | handle = glfwCreateCursor(glfwImg, hotX, hotY); 32 | 33 | glfwImg.free(); 34 | STBImage.stbi_image_free(buffer); 35 | } 36 | 37 | Cursor(long handle) { 38 | this.handle = handle; 39 | } 40 | 41 | public void apply(Window window) { 42 | apply(window.getHandle()); 43 | } 44 | 45 | public void apply(long windowHandle) { 46 | glfwSetCursor(windowHandle, handle); 47 | } 48 | 49 | public long getHandle() { 50 | return handle; 51 | } 52 | 53 | public Type getType() { 54 | return type; 55 | } 56 | 57 | public void destroy() { 58 | glfwDestroyCursor(handle); 59 | } 60 | 61 | public enum Type { 62 | /** 63 | * The default arrow-type mouse icon. 64 | */ 65 | ARROW(GLFW_ARROW_CURSOR), 66 | 67 | /** 68 | * The I cursor used frequently for typing interfaces. 69 | */ 70 | I_BEAM(GLFW_IBEAM_CURSOR), 71 | 72 | /** 73 | * A cross-shaped crosshair cursor. 74 | */ 75 | CROSSHAIR(GLFW_CROSSHAIR_CURSOR), 76 | 77 | /** 78 | * A pointing finger icon. 79 | */ 80 | HAND(GLFW_HAND_CURSOR), 81 | 82 | /** 83 | * A cursor used for resizing an element horizontally. 84 | */ 85 | HORIZONTAL_RESIZE(GLFW_HRESIZE_CURSOR), 86 | 87 | /** 88 | * A cursor used for resizing an element vertically. 89 | */ 90 | VERTICAL_RESIZE(GLFW_VRESIZE_CURSOR); 91 | 92 | private int shape; 93 | 94 | private Type(int shape) { 95 | this.shape = shape; 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/FrameRateLimiter.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import org.lwjgl.glfw.GLFW; 4 | 5 | public class FrameRateLimiter { 6 | /** 7 | * number of nano seconds in a second 8 | */ 9 | private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L; 10 | 11 | /** 12 | * The time to sleep/yield until the next frame 13 | */ 14 | private static long nextFrame = 0; 15 | 16 | /** 17 | * whether the initialisation code has run 18 | */ 19 | private static boolean initialised = false; 20 | 21 | /** 22 | * for calculating the averages the previous sleep/yield times are stored 23 | */ 24 | private static RunningAvg sleepDurations = new RunningAvg(10); 25 | private static RunningAvg yieldDurations = new RunningAvg(10); 26 | 27 | 28 | /** 29 | * An accurate sync method that will attempt to run at a constant frame rate. 30 | * It should be called once every frame. 31 | * 32 | * @param fps - the desired frame rate, in frames per second 33 | */ 34 | public static void sync(float fps) { 35 | if (fps <= 0) return; 36 | if (!initialised) initialise(); 37 | 38 | try { 39 | // sleep until the average sleep time is greater than the time remaining till nextFrame 40 | for (long t0 = getTime(), t1; (nextFrame - t0) > sleepDurations.avg(); t0 = t1) { 41 | Thread.sleep(1); 42 | sleepDurations.add((t1 = getTime()) - t0); // update average sleep time 43 | } 44 | 45 | // slowly dampen sleep average if too high to avoid yielding too much 46 | sleepDurations.dampenForLowResTicker(); 47 | 48 | // yield until the average yield time is greater than the time remaining till nextFrame 49 | for (long t0 = getTime(), t1; (nextFrame - t0) > yieldDurations.avg(); t0 = t1) { 50 | Thread.yield(); 51 | yieldDurations.add((t1 = getTime()) - t0); // update average yield time 52 | } 53 | } catch (InterruptedException e) { 54 | 55 | } 56 | 57 | // schedule next frame, drop frame(s) if already too late for next frame 58 | nextFrame = Math.max(nextFrame + (int) (NANOS_IN_SECOND / fps), getTime()); 59 | } 60 | 61 | /** 62 | * This method will initialise the sync method by setting initial 63 | * values for sleepDurations/yieldDurations and nextFrame. 64 | *

65 | * If running on windows it will start the sleep timer fix. 66 | */ 67 | private static void initialise() { 68 | initialised = true; 69 | 70 | sleepDurations.init(1000 * 1000); 71 | yieldDurations.init((int) (-(getTime() - getTime()) * 1.333)); 72 | 73 | nextFrame = getTime(); 74 | 75 | String osName = System.getProperty("os.name"); 76 | 77 | if (osName.startsWith("Win")) { 78 | // On windows the sleep functions can be highly inaccurate by 79 | // over 10ms making in unusable. However it can be forced to 80 | // be a bit more accurate by running a separate sleeping daemon 81 | // thread. 82 | Thread timerAccuracyThread = new Thread(new Runnable() { 83 | public void run() { 84 | try { 85 | Thread.sleep(Long.MAX_VALUE); 86 | } catch (Exception e) { 87 | } 88 | } 89 | }); 90 | 91 | timerAccuracyThread.setName("LWJGL Timer"); 92 | timerAccuracyThread.setDaemon(true); 93 | timerAccuracyThread.start(); 94 | } 95 | } 96 | 97 | /** 98 | * Get the system time in nano seconds 99 | * 100 | * @return will return the current time in nano's 101 | */ 102 | private static long getTime() { 103 | return (long) (GLFW.glfwGetTime() * NANOS_IN_SECOND); 104 | } 105 | 106 | private static class RunningAvg { 107 | private final long[] slots; 108 | private int offset; 109 | 110 | private static final long DAMPEN_THRESHOLD = 10 * 1000L * 1000L; // 10ms 111 | private static final float DAMPEN_FACTOR = 0.9f; // don't change: 0.9f is exactly right! 112 | 113 | public RunningAvg(int slotCount) { 114 | this.slots = new long[slotCount]; 115 | this.offset = 0; 116 | } 117 | 118 | public void init(long value) { 119 | while (this.offset < this.slots.length) { 120 | this.slots[this.offset++] = value; 121 | } 122 | } 123 | 124 | public void add(long value) { 125 | this.slots[this.offset++ % this.slots.length] = value; 126 | this.offset %= this.slots.length; 127 | } 128 | 129 | public long avg() { 130 | long sum = 0; 131 | for (int i = 0; i < this.slots.length; i++) { 132 | sum += this.slots[i]; 133 | } 134 | return sum / this.slots.length; 135 | } 136 | 137 | public void dampenForLowResTicker() { 138 | if (this.avg() > DAMPEN_THRESHOLD) { 139 | for (int i = 0; i < this.slots.length; i++) { 140 | this.slots[i] *= DAMPEN_FACTOR; 141 | } 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/GLFWException.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | public class GLFWException extends Exception { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public GLFWException(String message) { 8 | super(message); 9 | } 10 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/Joystick.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import nokori.clear.windows.callback.JoystickCallback; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.FloatBuffer; 7 | 8 | import static org.lwjgl.glfw.GLFW.*; 9 | 10 | public class Joystick { 11 | 12 | private int index; 13 | private String name; 14 | 15 | private float[] axes; 16 | private boolean[] buttons; 17 | 18 | private JoystickCallback callback; 19 | 20 | public Joystick(int index) { 21 | 22 | this.index = index; 23 | 24 | name = glfwGetJoystickName(index); 25 | 26 | FloatBuffer axisData = glfwGetJoystickAxes(index); 27 | axes = new float[axisData.remaining()]; 28 | for (int i = 0; i < axes.length; i++) { 29 | axes[i] = axisData.get(i); 30 | } 31 | 32 | ByteBuffer buttonData = glfwGetJoystickButtons(index); 33 | buttons = new boolean[buttonData.remaining()]; 34 | for (int i = 0; i < buttons.length; i++) { 35 | buttons[i] = buttonData.get(i) == GLFW_PRESS; 36 | } 37 | 38 | callback = null; 39 | } 40 | 41 | public void setCallback(JoystickCallback callback) { 42 | this.callback = callback; 43 | } 44 | 45 | void poll(long timestamp) { 46 | 47 | FloatBuffer axisData = glfwGetJoystickAxes(index); 48 | if (axisData != null) { 49 | for (int i = 0; i < axes.length; i++) { 50 | 51 | float newValue = axisData.get(i); 52 | if (callback != null && axes[i] != newValue) { 53 | callback.axisMoved(this, timestamp, i, newValue); 54 | } 55 | axes[i] = newValue; 56 | } 57 | } 58 | 59 | ByteBuffer buttonData = glfwGetJoystickButtons(index); 60 | 61 | if (buttonData != null) { 62 | for (int i = 0; i < buttons.length; i++) { 63 | boolean newValue = buttonData.get(i) == GLFW_PRESS; 64 | 65 | if (callback != null && buttons[i] != newValue) { 66 | callback.buttonStateChanged(this, timestamp, i, newValue); 67 | } 68 | buttons[i] = newValue; 69 | } 70 | } 71 | } 72 | 73 | public int getIndex() { 74 | return index; 75 | } 76 | 77 | public String getName() { 78 | return name; 79 | } 80 | 81 | public int getAxisCount() { 82 | return axes.length; 83 | } 84 | 85 | public int getButtonCount() { 86 | return buttons.length; 87 | } 88 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/Monitor.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import org.lwjgl.glfw.GLFWVidMode; 4 | 5 | import java.nio.IntBuffer; 6 | import java.util.ArrayList; 7 | 8 | import static org.lwjgl.glfw.GLFW.*; 9 | import static org.lwjgl.system.MemoryStack.*; 10 | 11 | public class Monitor { 12 | 13 | private static final double MM_TO_INCHES = 0.0393700787; 14 | 15 | private long pointer; 16 | private boolean primaryMonitor; 17 | 18 | private String name; 19 | 20 | private int virtualX, virtualY; 21 | 22 | private int physicalWidth, physicalHeight; 23 | private double physicalDiagonalInches; 24 | 25 | private ArrayList videoModes; 26 | private VideoMode desktopVideoMode; 27 | 28 | public Monitor(long pointer, boolean primaryMonitor) { 29 | 30 | this.pointer = pointer; 31 | this.primaryMonitor = primaryMonitor; 32 | 33 | name = glfwGetMonitorName(pointer); 34 | 35 | stackPush(); 36 | IntBuffer buf1 = stackMallocInt(1); 37 | IntBuffer buf2 = stackMallocInt(1); 38 | 39 | glfwGetMonitorPos(pointer, buf1, buf2); 40 | virtualX = buf1.get(0); 41 | virtualY = buf2.get(0); 42 | 43 | glfwGetMonitorPhysicalSize(pointer, buf1, buf2); 44 | physicalWidth = buf1.get(0); 45 | physicalHeight = buf2.get(0); 46 | physicalDiagonalInches = MM_TO_INCHES * Math.sqrt(physicalWidth * physicalWidth + physicalHeight * physicalHeight); 47 | 48 | stackPop(); 49 | 50 | 51 | GLFWVidMode.Buffer videoModePointer = glfwGetVideoModes(pointer); 52 | 53 | int count = videoModePointer.remaining(); 54 | videoModes = new ArrayList<>(count + 1); 55 | for (int i = 0; i < count; i++) { 56 | videoModes.add(new VideoMode(videoModePointer.get(i))); 57 | } 58 | 59 | desktopVideoMode = new VideoMode(glfwGetVideoMode(pointer)); 60 | if (!videoModes.contains(desktopVideoMode)) { 61 | //System.out.println("The desktop video mode " + desktopVideoMode + " of monitor '" + name + "' is not in the list of available display modes. Adding it."); 62 | videoModes.add(desktopVideoMode); 63 | } 64 | } 65 | 66 | public long getPointer() { 67 | return pointer; 68 | } 69 | 70 | public String getName() { 71 | return name; 72 | } 73 | 74 | public boolean isPrimaryMonitor() { 75 | return primaryMonitor; 76 | } 77 | 78 | public int getVirtualX() { 79 | return virtualX; 80 | } 81 | 82 | public int getVirtualY() { 83 | return virtualY; 84 | } 85 | 86 | public int getPhysicalWidth() { 87 | return physicalWidth; 88 | } 89 | 90 | public int getPhysicalHeight() { 91 | return physicalHeight; 92 | } 93 | 94 | public double getPhysicalDiagonalInches() { 95 | return physicalDiagonalInches; 96 | } 97 | 98 | public ArrayList getVideoModes() { 99 | return videoModes; 100 | } 101 | 102 | public VideoMode getDesktopVideoMode() { 103 | return desktopVideoMode; 104 | } 105 | 106 | public VideoMode getBestVideoMode(int width, int height) { 107 | VideoMode result = null; 108 | for (int i = 0; i < videoModes.size(); i++) { 109 | VideoMode vm = videoModes.get(i); 110 | //System.out.println(vm); 111 | if (vm.getWidth() == width && vm.getHeight() == height) { 112 | 113 | if ( 114 | result == null || 115 | vm.getColorBitCount() > result.getColorBitCount() || 116 | (vm.getColorBitCount() == result.getColorBitCount() && vm.getRefreshRate() > result.getRefreshRate()) 117 | ) { 118 | result = vm; 119 | } 120 | } 121 | } 122 | return result; 123 | } 124 | 125 | 126 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/PixelFormat.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | public class PixelFormat { 4 | 5 | 6 | private int redBits; 7 | private int greenBits; 8 | private int blueBits; 9 | 10 | private int alphaBits; 11 | 12 | private int depthBits; 13 | private int stencilBits; 14 | 15 | private int msaaSamples; 16 | 17 | 18 | public PixelFormat() { 19 | this(8, 8, 8); 20 | } 21 | 22 | 23 | public PixelFormat(int redBits, int greenBits, int blueBits) { 24 | this(redBits, greenBits, blueBits, 0); 25 | } 26 | 27 | 28 | public PixelFormat(int redBits, int greenBits, int blueBits, int alphaBits) { 29 | this(redBits, greenBits, blueBits, alphaBits, 0); 30 | } 31 | 32 | public PixelFormat(int redBits, int greenBits, int blueBits, int alphaBits, int depthBits) { 33 | this(redBits, greenBits, blueBits, alphaBits, depthBits, 0); 34 | } 35 | 36 | public PixelFormat(int redBits, int greenBits, int blueBits, int alphaBits, int depthBits, int stencilBits) { 37 | this(redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, 0); 38 | } 39 | 40 | public PixelFormat(int redBits, int greenBits, int blueBits, int alphaBits, int depthBits, int stencilBits, int msaaSamples) { 41 | 42 | this.redBits = redBits; 43 | this.greenBits = greenBits; 44 | this.blueBits = blueBits; 45 | 46 | this.alphaBits = alphaBits; 47 | 48 | this.depthBits = depthBits; 49 | this.stencilBits = stencilBits; 50 | 51 | this.msaaSamples = msaaSamples; 52 | } 53 | 54 | public int getRedBits() { 55 | return redBits; 56 | } 57 | 58 | public int getGreenBits() { 59 | return greenBits; 60 | } 61 | 62 | public int getBlueBits() { 63 | return blueBits; 64 | } 65 | 66 | public int getColorBitCount() { 67 | return redBits + greenBits + blueBits; 68 | } 69 | 70 | public int getAlphaBits() { 71 | return alphaBits; 72 | } 73 | 74 | public int getDepthBits() { 75 | return depthBits; 76 | } 77 | 78 | public int getStencilBits() { 79 | return stencilBits; 80 | } 81 | 82 | public int getMsaaSamples() { 83 | return msaaSamples; 84 | } 85 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/VideoMode.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows; 2 | 3 | import org.lwjgl.glfw.GLFWVidMode; 4 | 5 | public class VideoMode { 6 | 7 | private int width; 8 | private int height; 9 | 10 | private int refreshRate; 11 | 12 | private int redBits; 13 | private int greenBits; 14 | private int blueBits; 15 | 16 | private boolean fullscreenAllowed; 17 | 18 | public VideoMode(int width, int height) { 19 | this(width, height, 0, 0, 0); 20 | } 21 | 22 | public VideoMode(int width, int height, int redBits, int greenBits, int blueBits) { 23 | 24 | this.width = width; 25 | this.height = height; 26 | 27 | this.redBits = redBits; 28 | this.greenBits = greenBits; 29 | this.blueBits = blueBits; 30 | 31 | fullscreenAllowed = false; 32 | } 33 | 34 | public VideoMode(GLFWVidMode vidMode) { 35 | 36 | width = vidMode.width(); 37 | height = vidMode.height(); 38 | 39 | refreshRate = vidMode.refreshRate(); 40 | 41 | redBits = vidMode.redBits(); 42 | greenBits = vidMode.greenBits(); 43 | blueBits = vidMode.blueBits(); 44 | 45 | 46 | fullscreenAllowed = true; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | VideoMode vm = (VideoMode) obj; 52 | return 53 | width == vm.width && 54 | height == vm.height && 55 | refreshRate == vm.refreshRate && 56 | redBits == vm.redBits && 57 | greenBits == vm.greenBits && 58 | blueBits == vm.blueBits; 59 | } 60 | 61 | public int getWidth() { 62 | return width; 63 | } 64 | 65 | public int getHeight() { 66 | return height; 67 | } 68 | 69 | public int getRefreshRate() { 70 | return refreshRate; 71 | } 72 | 73 | public int getRedBits() { 74 | return redBits; 75 | } 76 | 77 | public int getGreenBits() { 78 | return greenBits; 79 | } 80 | 81 | public int getBlueBits() { 82 | return blueBits; 83 | } 84 | 85 | public int getColorBitCount() { 86 | return redBits + greenBits + blueBits; 87 | } 88 | 89 | public boolean fullscreenAllowed() { 90 | return fullscreenAllowed; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "VideoMode{" + width + "x" + height + "x" + getColorBitCount() + " (" + redBits + "/" + greenBits + "/" + blueBits + ")" + "@" + refreshRate + "}"; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/CharCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public interface CharCallback extends InputCallback { 6 | public void charEvent(Window window, long timestamp, int codepoint, String c, int mods); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/InputCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | public interface InputCallback { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/JoystickCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Joystick; 4 | 5 | public interface JoystickCallback extends InputCallback { 6 | public void axisMoved(Joystick joystick, long timestamp, int axis, float newValue); 7 | 8 | public void buttonStateChanged(Joystick joystick, long timestamp, int button, boolean pressed); 9 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/JoystickStateCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Joystick; 4 | 5 | public interface JoystickStateCallback extends InputCallback { 6 | public void joystickStateChanged(Joystick joystick, long timestamp, boolean connected); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/KeyCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public interface KeyCallback extends InputCallback { 6 | public void keyEvent(Window window, long timestamp, int key, int scanCode, boolean pressed, boolean repeat, int mods); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/MouseButtonCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public interface MouseButtonCallback extends InputCallback { 6 | public void mouseButtonEvent(Window window, long timestamp, double mouseX, double mouseY, int button, boolean pressed, int mods); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/MouseMotionCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public interface MouseMotionCallback extends InputCallback { 6 | public void mouseMotionEvent(Window window, long timestamp, double mouseX, double mouseY, double dx, double dy); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/MouseScrollCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public interface MouseScrollCallback extends InputCallback { 6 | public void scrollEvent(Window window, long timestamp, double mouseX, double mouseY, double xoffset, double yoffset); 7 | } 8 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/WindowFramebufferSizeCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | /** 6 | * This is a callback for the window's internal rendering pixel width and height (not necessarily the actual window dimensions - that's handled by WindowSizeCallback) 7 | */ 8 | public interface WindowFramebufferSizeCallback extends InputCallback { 9 | public void windowSizeEvent(Window window, long timestamp, int width, int height); 10 | } 11 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/WindowPosCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | /** 6 | * This is a callback for the Window's pixel position on the desktop. 7 | */ 8 | public interface WindowPosCallback extends InputCallback { 9 | public void windowPositionEvent(Window window, long timestamp, int x, int y); 10 | } 11 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/callback/WindowSizeCallback.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.callback; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | /** 6 | * This is a callback for the window's actual pixel width and height (not the internal rendering dimensions - that's handled by WindowFramebufferSizeCallback) 7 | */ 8 | public interface WindowSizeCallback extends InputCallback { 9 | public void windowSizeEvent(Window window, long timestamp, int width, int height); 10 | } 11 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/CharEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class CharEvent extends EventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected CharEvent create() { 11 | return new CharEvent(); 12 | } 13 | }; 14 | 15 | private int codepoint; 16 | private String c; 17 | private int mods; 18 | 19 | public static CharEvent fire(Window window, long timestamp, int codepoint, String c, int mods) { 20 | 21 | CharEvent e = POOL.get(); 22 | 23 | e.window = window; 24 | e.timestamp = timestamp; 25 | e.codepoint = codepoint; 26 | e.c = c; 27 | e.mods = mods; 28 | 29 | return e; 30 | } 31 | 32 | public int getCodepoint() { 33 | return codepoint; 34 | } 35 | 36 | public String getCharString() { 37 | return c; 38 | } 39 | 40 | public int getMods() { 41 | return mods; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/Event.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Poolable; 5 | 6 | public interface Event extends Poolable { 7 | 8 | public long getTimestamp(); 9 | 10 | public Window getWindow(); 11 | 12 | public void setConsumed(boolean consumed); 13 | 14 | public boolean isConsumed(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/EventImpl.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | 5 | public abstract class EventImpl implements Event { 6 | 7 | protected long timestamp; 8 | protected Window window = null; 9 | protected boolean consumed = false; 10 | 11 | @Override 12 | public void reset() { 13 | window = null; 14 | consumed = false; 15 | } 16 | 17 | @Override 18 | public long getTimestamp() { 19 | return timestamp; 20 | } 21 | 22 | @Override 23 | public Window getWindow() { 24 | return window; 25 | } 26 | 27 | @Override 28 | public void setConsumed(boolean consumed) { 29 | this.consumed = consumed; 30 | } 31 | 32 | @Override 33 | public boolean isConsumed() { 34 | return consumed; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/JoystickAxisEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Joystick; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class JoystickAxisEvent extends EventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected JoystickAxisEvent create() { 11 | return new JoystickAxisEvent(); 12 | } 13 | }; 14 | 15 | private Joystick joystick; 16 | private int axis; 17 | private float newValue; 18 | 19 | private JoystickAxisEvent() { 20 | } 21 | 22 | public static JoystickAxisEvent fire(Joystick joystick, long timestamp, int axis, float newValue) { 23 | 24 | JoystickAxisEvent e = POOL.get(); 25 | 26 | e.joystick = joystick; 27 | e.timestamp = timestamp; 28 | e.axis = axis; 29 | e.newValue = newValue; 30 | 31 | return e; 32 | } 33 | 34 | @Override 35 | public void reset() { 36 | super.reset(); 37 | joystick = null; 38 | } 39 | 40 | public Joystick getJoystick() { 41 | return joystick; 42 | } 43 | 44 | public int getAxis() { 45 | return axis; 46 | } 47 | 48 | public float getNewValue() { 49 | return newValue; 50 | } 51 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/JoystickButtonEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Joystick; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class JoystickButtonEvent extends EventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected JoystickButtonEvent create() { 11 | return new JoystickButtonEvent(); 12 | } 13 | }; 14 | 15 | private Joystick joystick; 16 | private int button; 17 | private boolean pressed; 18 | 19 | private JoystickButtonEvent() { 20 | } 21 | 22 | public static JoystickButtonEvent fire(Joystick joystick, long timestamp, int button, boolean pressed) { 23 | 24 | JoystickButtonEvent e = POOL.get(); 25 | 26 | e.joystick = joystick; 27 | e.timestamp = timestamp; 28 | e.button = button; 29 | e.pressed = pressed; 30 | 31 | return e; 32 | } 33 | 34 | @Override 35 | public void reset() { 36 | super.reset(); 37 | joystick = null; 38 | } 39 | 40 | public Joystick getJoystick() { 41 | return joystick; 42 | } 43 | 44 | public int getButton() { 45 | return button; 46 | } 47 | 48 | public boolean isPressed() { 49 | return pressed; 50 | } 51 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/JoystickStateEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Joystick; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class JoystickStateEvent extends EventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected JoystickStateEvent create() { 11 | return new JoystickStateEvent(); 12 | } 13 | }; 14 | 15 | private Joystick joystick; 16 | private boolean connected; 17 | 18 | private JoystickStateEvent() { 19 | } 20 | 21 | public static JoystickStateEvent fire(Joystick joystick, long timestamp, boolean connected) { 22 | 23 | JoystickStateEvent e = POOL.get(); 24 | 25 | e.joystick = joystick; 26 | e.timestamp = timestamp; 27 | e.connected = connected; 28 | 29 | return e; 30 | } 31 | 32 | @Override 33 | public void reset() { 34 | super.reset(); 35 | joystick = null; 36 | } 37 | 38 | public Joystick getJoystick() { 39 | return joystick; 40 | } 41 | 42 | public boolean isConnected() { 43 | return connected; 44 | } 45 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/KeyEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class KeyEvent extends EventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected KeyEvent create() { 11 | return new KeyEvent(); 12 | } 13 | }; 14 | 15 | private int key; 16 | private int scanCode; 17 | private boolean pressed; 18 | private boolean repeat; 19 | private int mods; 20 | 21 | private KeyEvent() { 22 | } 23 | 24 | public static KeyEvent fire(Window window, long timestamp, int key, int scanCode, boolean pressed, boolean repeat, int mods) { 25 | 26 | KeyEvent e = POOL.get(); 27 | 28 | e.window = window; 29 | e.timestamp = timestamp; 30 | e.key = key; 31 | e.scanCode = scanCode; 32 | e.pressed = pressed; 33 | e.repeat = repeat; 34 | e.mods = mods; 35 | 36 | return e; 37 | } 38 | 39 | public int getKey() { 40 | return key; 41 | } 42 | 43 | public int getScanCode() { 44 | return scanCode; 45 | } 46 | 47 | public boolean isPressed() { 48 | return pressed; 49 | } 50 | 51 | public boolean isRepeat() { 52 | return repeat; 53 | } 54 | 55 | public int getMods() { 56 | return mods; 57 | } 58 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/MouseButtonEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class MouseButtonEvent extends MouseEventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected MouseButtonEvent create() { 11 | return new MouseButtonEvent(); 12 | } 13 | }; 14 | 15 | private int button; 16 | private boolean pressed; 17 | private int mods; 18 | 19 | private MouseButtonEvent() { 20 | } 21 | 22 | public static MouseButtonEvent fire(Window window, long timestamp, double mouseX, double mouseY, int button, boolean pressed, int mods) { 23 | 24 | MouseButtonEvent e = POOL.get(); 25 | 26 | e.window = window; 27 | e.timestamp = timestamp; 28 | e.mouseX = mouseX; 29 | e.mouseY = mouseY; 30 | e.button = button; 31 | e.pressed = pressed; 32 | e.mods = mods; 33 | 34 | return e; 35 | } 36 | 37 | public int getButton() { 38 | return button; 39 | } 40 | 41 | public boolean isPressed() { 42 | return pressed; 43 | } 44 | 45 | public int getMods() { 46 | return mods; 47 | } 48 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/MouseEventImpl.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | public abstract class MouseEventImpl extends EventImpl { 4 | protected double mouseX; 5 | protected double mouseY; 6 | 7 | public double getMouseX() { 8 | return mouseX; 9 | } 10 | 11 | public double getMouseY() { 12 | return mouseY; 13 | } 14 | 15 | public double getScaledMouseX(double scale) { 16 | return getScaledMouseCoordinate(mouseX, scale); 17 | } 18 | 19 | public double getScaledMouseY(double scale) { 20 | return getScaledMouseCoordinate(mouseY, scale); 21 | } 22 | 23 | public static double getScaledMouseCoordinate(double coordinate, double scale) { 24 | return (coordinate / scale); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/MouseMotionEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class MouseMotionEvent extends MouseEventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected MouseMotionEvent create() { 11 | return new MouseMotionEvent(); 12 | } 13 | }; 14 | 15 | private double dx; 16 | private double dy; 17 | 18 | private MouseMotionEvent() { 19 | } 20 | 21 | public static MouseMotionEvent fire(Window window, long timestamp, double mouseX, double mouseY, double dx, double dy) { 22 | 23 | MouseMotionEvent e = POOL.get(); 24 | 25 | e.window = window; 26 | e.timestamp = timestamp; 27 | e.mouseX = mouseX; 28 | e.mouseY = mouseY; 29 | e.dx = dx; 30 | e.dy = dy; 31 | 32 | return e; 33 | } 34 | 35 | public double getDX() { 36 | return dx; 37 | } 38 | 39 | public double getDY() { 40 | return dy; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/event/MouseScrollEvent.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.event; 2 | 3 | import nokori.clear.windows.Window; 4 | import nokori.clear.windows.pool.Pool; 5 | 6 | public class MouseScrollEvent extends MouseEventImpl { 7 | 8 | private static final Pool POOL = new Pool() { 9 | @Override 10 | protected MouseScrollEvent create() { 11 | return new MouseScrollEvent(); 12 | } 13 | }; 14 | 15 | private double xoffset; 16 | private double yoffset; 17 | 18 | private MouseScrollEvent() { 19 | } 20 | 21 | public static MouseScrollEvent fire(Window window, long timestamp, double mouseX, double mouseY, double xoffset, double yoffset) { 22 | 23 | MouseScrollEvent e = POOL.get(); 24 | 25 | e.window = window; 26 | e.timestamp = timestamp; 27 | e.mouseX = mouseX; 28 | e.mouseY = mouseY; 29 | e.xoffset = xoffset; 30 | e.yoffset = yoffset; 31 | 32 | return e; 33 | } 34 | 35 | public double getXOffset() { 36 | return xoffset; 37 | } 38 | 39 | public double getYOffset() { 40 | return yoffset; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/pool/Pool.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.pool; 2 | 3 | import nokori.clear.windows.util.FastArrayList; 4 | 5 | 6 | public abstract class Pool { 7 | 8 | protected FastArrayList pool; 9 | private int allocationCount; 10 | 11 | public Pool() { 12 | pool = new FastArrayList<>(); 13 | } 14 | 15 | public E get() { 16 | if (pool.isEmpty()) { 17 | allocationCount++; 18 | return create(); 19 | } else { 20 | return pool.removeLast(); 21 | } 22 | } 23 | 24 | protected abstract E create(); 25 | 26 | public void recycle(E poolable) { 27 | poolable.reset(); //Reset immediately so we don't get a lot of systems using a lot of memory in the pool 28 | pool.add(poolable); 29 | } 30 | 31 | public int getAllocationCount() { 32 | return allocationCount; 33 | } 34 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/pool/Poolable.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.pool; 2 | 3 | public interface Poolable { 4 | public void reset(); 5 | } 6 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/FastArrayList.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import java.io.Serializable; 4 | 5 | public class FastArrayList implements Serializable { 6 | 7 | private static final long serialVersionUID = -3570017818041447411L; 8 | 9 | private static final int MIN_FULL_CLEAR_INTERVAL = 100, MAX_FULL_CLEAR_INTERVAL = 200; 10 | 11 | private int fullClearInterval; 12 | 13 | private int capacity; 14 | private E[] elements; 15 | 16 | private int size, maxSize; 17 | //@SerializeLength 18 | private int fullClearTimer; 19 | 20 | public FastArrayList() { 21 | this(10); 22 | } 23 | 24 | @SuppressWarnings("unchecked") 25 | public FastArrayList(int initialCapacity) { 26 | 27 | fullClearInterval = MIN_FULL_CLEAR_INTERVAL + (int) ((MAX_FULL_CLEAR_INTERVAL + 1 - MIN_FULL_CLEAR_INTERVAL) * Math.random()); 28 | 29 | capacity = initialCapacity; 30 | elements = (E[]) new Object[capacity]; 31 | 32 | size = 0; 33 | fullClearTimer = fullClearInterval; 34 | } 35 | 36 | public void add(E element) { 37 | if (size == capacity) { 38 | expand(capacity + 1); 39 | } 40 | elements[size++] = element; 41 | } 42 | 43 | public void add(FastArrayList list) { 44 | 45 | if (list.isEmpty()) { 46 | return; 47 | } 48 | 49 | int newSize = size + list.size; 50 | if (newSize > capacity) { 51 | expand(newSize); 52 | } 53 | 54 | System.arraycopy(list.elements, 0, elements, size, list.size); 55 | size = newSize; 56 | } 57 | 58 | public int indexOf(E e) { 59 | for (int i = 0; i < elements.length; i++) { 60 | if (elements[i] == e) return i; 61 | } 62 | return -1; 63 | } 64 | 65 | public boolean contains(E e) { 66 | for (int i = 0; i < elements.length; i++) { 67 | if (elements[i] == e) return true; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | public boolean containsEquals(E e) { 74 | for (int i = 0; i < elements.length; i++) { 75 | if (elements[i].equals(e)) return true; 76 | } 77 | 78 | return false; 79 | } 80 | 81 | public void trimToSize() { 82 | if (capacity > size) { 83 | 84 | capacity = size; 85 | 86 | @SuppressWarnings("unchecked") 87 | E[] newElements = (E[]) new Object[capacity]; 88 | System.arraycopy(elements, 0, newElements, 0, capacity); 89 | elements = newElements; 90 | } 91 | } 92 | 93 | public boolean isEmpty() { 94 | return size == 0; 95 | } 96 | 97 | public int size() { 98 | return size; 99 | } 100 | 101 | public E get(int i) { 102 | return elements[i]; 103 | } 104 | 105 | public E removeLast() { 106 | return elements[--size]; 107 | } 108 | 109 | public E replace(int index, E newElement) { 110 | E old = elements[index]; 111 | elements[index] = newElement; 112 | return old; 113 | } 114 | 115 | public int takeFrom(FastArrayList list, int count) { 116 | count = Math.min(count, list.size); 117 | ensureCapacity(count); 118 | System.arraycopy(list.elements, list.size - count, elements, size, count); 119 | 120 | list.size -= count; 121 | size += count; 122 | 123 | return count; 124 | } 125 | 126 | public E removeSwap(int index) { 127 | E result = elements[index]; 128 | elements[index] = elements[--size]; 129 | return result; 130 | } 131 | 132 | public void forceClear() { 133 | fullClearTimer = 0; 134 | clear(); 135 | } 136 | 137 | public void clear() { 138 | if (--fullClearTimer < 0) { 139 | for (int i = 0; i < maxSize; i++) { 140 | elements[i] = null; 141 | } 142 | fullClearTimer = fullClearInterval; 143 | maxSize = 0; 144 | } else { 145 | maxSize = Math.max(size, maxSize); 146 | } 147 | size = 0; 148 | } 149 | 150 | public void reset() { 151 | size = 0; 152 | } 153 | 154 | public E[] getUnderlyingArray() { 155 | return elements; 156 | } 157 | 158 | public void ensureCapacity(int minCapacity) { 159 | if (capacity < minCapacity) { 160 | expand(minCapacity); 161 | } 162 | } 163 | 164 | @SuppressWarnings("unchecked") 165 | private void expand(int minCapacity) { 166 | E[] oldElements = elements; 167 | capacity = Math.max(minCapacity, capacity * 2); 168 | /*if(capacity > 1024*16){ 169 | System.out.println("FAL resized to " + capacity); 170 | }*/ 171 | elements = (E[]) new Object[capacity]; 172 | System.arraycopy(oldElements, 0, elements, 0, size); 173 | } 174 | 175 | @Override 176 | public String toString() { 177 | StringBuilder b = new StringBuilder("["); 178 | for (int i = 0; i < size; i++) { 179 | b.append(elements[i]); 180 | if (i != size - 1) { 181 | b.append(", "); 182 | } 183 | } 184 | return b.append(']').toString(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/ImmutableList.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class ImmutableList { 6 | private ArrayList internalList; 7 | 8 | public ImmutableList(ArrayList internalList) { 9 | this.internalList = internalList; 10 | } 11 | 12 | public E get(int index) { 13 | return internalList.get(index); 14 | } 15 | 16 | public int size() { 17 | return internalList.size(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/JVMUtil.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.lang.management.ManagementFactory; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class JVMUtil { 12 | /** 13 | * Restarts the Java virtual machine and forces it to run on the first thread IF and only IF it is not currently on the first thread. This allows your LWJGL3 program to run on Mac properly. 14 | *
15 | * Your java program must return after calling this method if it returns true as to prevent your application from running twice. 16 | *

17 | * To implement this method, simply put it on the first line of your main(args) function. 18 | *

19 | * Credit goes to Spasi on JGO for making this utility. 20 | *

21 | * Example of usage (first line in main() method):
22 | * if (LWJGUIUtil.restartJVMOnFirstThread(true, args)) {
23 | * return;
24 | * }
25 | * 26 | * @param needsOutput - Whether or not the JVM should print to System.out.println 27 | * @param args - the usual String[] args used in the main method 28 | * @return true if the JVM was successfully restarted. 29 | */ 30 | public static boolean restartJVMOnFirstThread(boolean needsOutput, String... args) { 31 | // Figure out the right class to call 32 | StackTraceElement[] cause = Thread.currentThread().getStackTrace(); 33 | 34 | boolean foundThisMethod = false; 35 | String callingClassName = null; 36 | for (StackTraceElement se : cause) { 37 | // Skip entries until we get to the entry for this class 38 | String className = se.getClassName(); 39 | String methodName = se.getMethodName(); 40 | if (foundThisMethod) { 41 | callingClassName = className; 42 | break; 43 | } else if (JVMUtil.class.getName().equals(className) && "restartJVMOnFirstThread".equals(methodName)) { 44 | foundThisMethod = true; 45 | } 46 | } 47 | 48 | if (callingClassName == null) { 49 | throw new RuntimeException("Error: unable to determine main class"); 50 | } 51 | 52 | try { 53 | Class theClass = Class.forName(callingClassName, true, Thread.currentThread().getContextClassLoader()); 54 | 55 | return restartJVMOnFirstThread(needsOutput, theClass, args); 56 | } catch (ClassNotFoundException e) { 57 | e.printStackTrace(); 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /** 64 | * Restarts the Java virtual machine and forces it to run on the first thread IF and only IF it is not currently on the first thread. This allows your LWJGL3 program to run on Mac properly. 65 | *

66 | * To implement this method, simply put it on the first line of your main(args) function. 67 | *

68 | * Credit goes to Spasi on JGO for making this utility. 69 | *

70 | * Example of usage (first line in main() method):
71 | * if (LWJGUIUtil.restartJVMOnFirstThread(true, class, args)) {
72 | * return;
73 | * }
74 | * 75 | * @param needsOutput - Whether or not the JVM should print to System.out.println 76 | * @param customClass - Class where the main method is stored 77 | * @param args - the usual String[] args used in the main method 78 | * @return true if the JVM was successfully restarted. 79 | */ 80 | public static boolean restartJVMOnFirstThread(boolean needsOutput, Class customClass, String... args) { 81 | 82 | // If we're already on the first thread, return 83 | String startOnFirstThread = System.getProperty("XstartOnFirstThread"); 84 | if (startOnFirstThread != null && startOnFirstThread.equals("true")) 85 | return false; 86 | 87 | // if not a mac then we're already on first thread, return. 88 | String osName = System.getProperty("os.name"); 89 | if (!osName.startsWith("Mac") && !osName.startsWith("Darwin")) { 90 | return false; 91 | } 92 | 93 | // get current jvm process pid 94 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; 95 | // get environment variable on whether XstartOnFirstThread is enabled 96 | String env = System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + pid); 97 | 98 | // if environment variable is "1" then XstartOnFirstThread is enabled 99 | if (env != null && env.equals("1")) { 100 | return false; 101 | } 102 | 103 | // restart jvm with -XstartOnFirstThread 104 | String separator = System.getProperty("file.separator"); 105 | String classpath = System.getProperty("java.class.path"); 106 | String mainClass = System.getenv("JAVA_MAIN_CLASS_" + pid); 107 | String jvmPath = System.getProperty("java.home") + separator + "bin" + separator + "java"; 108 | 109 | List inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); 110 | 111 | ArrayList jvmArgs = new ArrayList(); 112 | 113 | 114 | jvmArgs.add(jvmPath); 115 | jvmArgs.add("-XstartOnFirstThread"); 116 | jvmArgs.addAll(inputArguments); 117 | jvmArgs.add("-cp"); 118 | jvmArgs.add(classpath); 119 | 120 | if (customClass == null) { 121 | jvmArgs.add(mainClass); 122 | } else { 123 | jvmArgs.add(customClass.getName()); 124 | } 125 | for (int i = 0; i < args.length; i++) { 126 | jvmArgs.add(args[i]); 127 | } 128 | 129 | // if you don't need console output, just enable these two lines 130 | // and delete bits after it. This JVM will then terminate. 131 | if (!needsOutput) { 132 | try { 133 | ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs); 134 | processBuilder.start(); 135 | } catch (IOException e) { 136 | e.printStackTrace(); 137 | } 138 | } else { 139 | try { 140 | ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs); 141 | processBuilder.redirectErrorStream(true); 142 | Process process = processBuilder.start(); 143 | 144 | InputStream is = process.getInputStream(); 145 | InputStreamReader isr = new InputStreamReader(is); 146 | BufferedReader br = new BufferedReader(isr); 147 | String line; 148 | 149 | while ((line = br.readLine()) != null) { 150 | System.out.println(line); 151 | } 152 | System.exit(0); 153 | process.waitFor(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | 159 | return true; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/OperatingSystemUtil.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.MappedByteBuffer; 7 | import java.nio.channels.FileChannel; 8 | 9 | /** 10 | * This class contains some utilities for working around LWJGL's incompatibility with AWT, such as opening URL's in the users browser. 11 | */ 12 | public class OperatingSystemUtil { 13 | 14 | public MappedByteBuffer getMappedByteBuffer(File file) throws Exception { 15 | try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 16 | // Get file channel in read-only mode 17 | FileChannel fileChannel = randomAccessFile.getChannel(); 18 | 19 | // Get direct byte buffer access using channel.map() operation 20 | return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); 21 | } 22 | } 23 | 24 | public enum OperatingSystem { 25 | WINDOWS, 26 | MAC, 27 | LINUX, 28 | OTHER; 29 | } 30 | 31 | /** 32 | * This function uses Java to detect the OS that this program is running on and will return the corresponding OperatingSystem enum. 33 | */ 34 | public static OperatingSystem detectOperatingSystem() { 35 | String os = System.getProperty("os.name").toLowerCase(); 36 | 37 | if (os.indexOf("win") >= 0) { 38 | return OperatingSystem.WINDOWS; 39 | } 40 | 41 | if (os.indexOf("mac") >= 0) { 42 | return OperatingSystem.MAC; 43 | } 44 | 45 | if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) { 46 | return OperatingSystem.LINUX; 47 | } 48 | 49 | return OperatingSystem.OTHER; 50 | } 51 | 52 | 53 | /** 54 | * Opens the given URL string in the user's default browser. 55 | * This is a LWJGL3 alternative to Desktop.browse(), since LWJGL crashes on Mac machines if AWT is referenced at the same time. 56 | * 57 | * @param url 58 | * @throws IOException 59 | */ 60 | public static void openURLInBrowser(String url) throws IOException { 61 | Runtime rt = Runtime.getRuntime(); 62 | 63 | switch (detectOperatingSystem()) { 64 | case LINUX: 65 | String[] browsers = {"epiphany", "firefox", "mozilla", "konqueror", "netscape", "opera", "links", "lynx"}; 66 | 67 | StringBuffer cmd = new StringBuffer(); 68 | for (int i = 0; i < browsers.length; i++) { 69 | // If the first didn't work, try the next browser and so on 70 | if (i != 0) { 71 | cmd.append(" || "); 72 | } 73 | 74 | cmd.append(String.format("%s \"%s\"", browsers[i], url)); 75 | } 76 | 77 | rt.exec(new String[]{"sh", "-c", cmd.toString()}); 78 | break; 79 | case MAC: 80 | rt.exec("open " + url); 81 | break; 82 | case WINDOWS: 83 | rt.exec("rundll32 url.dll,FileProtocolHandler " + url); 84 | break; 85 | case OTHER: 86 | default: 87 | System.err.println("This function will not work on this operating system."); 88 | break; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/Stopwatch.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * This is a basic utility class that can be used to measure time. This can be useful for setting up delays and transitions. 7 | * 8 | * @author Brayden 9 | */ 10 | public class Stopwatch { 11 | private boolean started = false; 12 | private long startTimeInNanoseconds, endTimeInNanoseconds; 13 | 14 | public void timeInMilliseconds(long endTimeDistanceInMilliseconds) { 15 | timeInNanoseconds(TimeUnit.MILLISECONDS.toNanos(endTimeDistanceInMilliseconds)); 16 | } 17 | 18 | /** 19 | * Sets a starting time for the stopwatch, enabling the use of all of the other utilities in this class. 20 | * 21 | * @param endTimeDistance - sets an end time time stamp relative to the start time 22 | */ 23 | public void timeInNanoseconds(long endTimeDistanceInNanoseconds) { 24 | started = true; 25 | startTimeInNanoseconds = System.nanoTime(); 26 | endTimeInNanoseconds = startTimeInNanoseconds + endTimeDistanceInNanoseconds; 27 | } 28 | 29 | public boolean hasTimePassedInMillis(long timeInMillis) { 30 | long currentTime = System.nanoTime(); 31 | return (started && TimeUnit.NANOSECONDS.toMillis(currentTime - startTimeInNanoseconds) > timeInMillis); 32 | } 33 | 34 | public boolean hasTimePassedInNanoseconds(long timeInNanoseconds) { 35 | long currentTime = System.nanoTime(); 36 | return (started && (currentTime - startTimeInNanoseconds) > timeInNanoseconds); 37 | } 38 | 39 | public long getStartTimeInMilliseconds() { 40 | return TimeUnit.NANOSECONDS.toMillis(startTimeInNanoseconds); 41 | } 42 | 43 | public long getStartTimeInNanoseconds() { 44 | return startTimeInNanoseconds; 45 | } 46 | 47 | public long getEndTimeInMilliseconds() { 48 | return TimeUnit.NANOSECONDS.toMillis(endTimeInNanoseconds); 49 | } 50 | 51 | public long getEndTimeInNanoseconds() { 52 | return endTimeInNanoseconds; 53 | } 54 | 55 | public boolean isCurrentTimePassedEndTime() { 56 | return (System.nanoTime() > getEndTimeInNanoseconds()); 57 | } 58 | 59 | /** 60 | * @param endTimeInMilliseconds - the set end time in milliseconds 61 | * @return a value between 0 and 1 indicating the time passed between this Stopwatch's start time and the given end time. 62 | */ 63 | public float getNormalizedDistanceBetweenTimeInMilliseconds(long endTimeInMilliseconds) { 64 | return getNormalizedDistanceBetweenTimeInNanoseconds(TimeUnit.MILLISECONDS.toNanos(endTimeInMilliseconds)); 65 | } 66 | 67 | /** 68 | * @param endTimeInNanoseconds - the set end time in nanoseconds 69 | * @return a value between 0 and 1 indicating the time passed between this Stopwatch's start time and the given end time. 70 | */ 71 | public float getNormalizedDistanceBetweenTimeInNanoseconds(long endTimeInNanoseconds) { 72 | if (!started) { 73 | return 0f; 74 | } 75 | 76 | long currentTime = System.nanoTime(); 77 | 78 | double maxDistance = endTimeInNanoseconds - startTimeInNanoseconds; 79 | double distance = Math.max(endTimeInNanoseconds - currentTime, 0); 80 | 81 | float progress = 1f - (float) (distance / maxDistance); 82 | 83 | return progress; 84 | } 85 | 86 | /** 87 | * @return a value between 0 and 1 indicating the time passed between this Stopwatch's start time and end time. 88 | */ 89 | public float getNormalizedDistanceBetweenTime() { 90 | return getNormalizedDistanceBetweenTimeInNanoseconds(endTimeInNanoseconds); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/TinyFileDialog.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import org.lwjgl.PointerBuffer; 4 | import org.lwjgl.system.MemoryStack; 5 | import org.lwjgl.util.tinyfd.TinyFileDialogs; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * This class is a wrapper for various TinyFileDialog utilities. 11 | */ 12 | public class TinyFileDialog { 13 | 14 | public enum InputType { 15 | OK("ok"), 16 | OK_CANCEL("okcancel"), 17 | YES_NO("yesno"); 18 | 19 | String key; 20 | 21 | private InputType(String key) { 22 | this.key = key; 23 | } 24 | } 25 | 26 | public enum Icon { 27 | INFORMATION("info"), 28 | WARNING("warning"), 29 | ERROR("error"), 30 | QUESTION("question"); 31 | 32 | String key; 33 | 34 | private Icon(String key) { 35 | this.key = key; 36 | } 37 | } 38 | 39 | ; 40 | 41 | /** 42 | * Shows a message dialog with the given window title and message. 43 | * 44 | * @param title 45 | * @param message 46 | */ 47 | public static void showMessageDialog(String title, String message, Icon icon) { 48 | TinyFileDialogs.tinyfd_messageBox(title, message, InputType.OK.key, icon.key, true); 49 | } 50 | 51 | /** 52 | * Show a confirm dialog with the given settings. 53 | * 54 | * @param title - window title 55 | * @param message - window message 56 | * @param type - confirm type (buttons used) 57 | * @param icon - window icon 58 | * @param defaultButtonIsOK - if true, the default button highlighted will be the "yes" button. 59 | * @return true if "yes" or an equivalent is selected. 60 | */ 61 | public static boolean showConfirmDialog(String title, String message, InputType type, Icon icon, boolean defaultButtonIsOK) { 62 | return TinyFileDialogs.tinyfd_messageBox(title, message, type.key, icon.key, defaultButtonIsOK); 63 | } 64 | 65 | /** 66 | * Show an input dialog with the given settings. 67 | * 68 | * @param title - window title 69 | * @param message - window message 70 | * @param defaultInput - the default input to be displayed 71 | * @return - the final string inputted into the dialog 72 | */ 73 | public static String showInputDialog(String title, String message, String defaultInput) { 74 | return TinyFileDialogs.tinyfd_inputBox(title, message, defaultInput); 75 | } 76 | 77 | /** 78 | * Shows a dialog for selecting a folder. 79 | * 80 | * @param title - window title 81 | * @param defaultPath - default filepath to start from 82 | * @return the selected folder path in a File object 83 | */ 84 | public static File showOpenFolderDialog(String title, File defaultPath) { 85 | String result = TinyFileDialogs.tinyfd_selectFolderDialog(title, defaultPath.getAbsolutePath()); 86 | return result != null ? new File(result) : null; 87 | } 88 | 89 | /** 90 | * Opens a file open dialog. 91 | * 92 | * @param title window title 93 | * @param defaultPath default file path 94 | * @param filterDescription description of the accepted file extension(s) 95 | * @param acceptedFileExtension the first accepted file extension (example: "txt", use * for all) 96 | * @param additionalAcceptedFileExtensions any additional accepted file extensions 97 | * @return the selected file 98 | */ 99 | public static File showOpenFileDialog(String title, File defaultPath, String filterDescription, String... acceptedFileExtensions){ 100 | 101 | PointerBuffer filters = MemoryStack.stackMallocPointer(acceptedFileExtensions.length); 102 | 103 | filters.put(MemoryStack.stackUTF8("*." + acceptedFileExtensions[0])); 104 | 105 | if (acceptedFileExtensions.length > 1) { 106 | for(int i = 1; i < acceptedFileExtensions.length; i++){ 107 | filters.put(MemoryStack.stackUTF8("*." + acceptedFileExtensions[i])); 108 | } 109 | } 110 | 111 | filters.flip(); 112 | 113 | defaultPath = defaultPath.getAbsoluteFile(); 114 | String defaultString = defaultPath.getAbsolutePath(); 115 | if(defaultPath.isDirectory() && !defaultString.endsWith(File.separator)){ 116 | defaultString += File.separator; 117 | } 118 | 119 | //System.out.println(defaultString + " : exists: " + new File(defaultString).exists()); 120 | 121 | String result = TinyFileDialogs.tinyfd_openFileDialog(title, defaultString, filters, filterDescription, false); 122 | 123 | return result != null ? new File(result) : null; 124 | } 125 | 126 | /** 127 | * Opens a file save dialog. 128 | * 129 | * @param title window title 130 | * @param defaultPath default file path 131 | * @param filterDescription description of the accepted file extension(s) 132 | * @param fileExtension the file extension (example: "txt") 133 | * @param forceExtension the user can select any file regardless of extension. If this is set to true, then the given extension will be automatically added if the extension is wrong. 134 | * @return the selected file 135 | */ 136 | public static File showSaveFileDialog(String title, File defaultPath, String filterDescription, String fileExtension, boolean forceExtension) { 137 | 138 | PointerBuffer filters = MemoryStack.stackMallocPointer(1); 139 | 140 | filters.put(MemoryStack.stackUTF8("*." + fileExtension)).flip(); 141 | 142 | defaultPath = defaultPath.getAbsoluteFile(); 143 | 144 | String defaultString = defaultPath.getAbsolutePath(); 145 | 146 | if (defaultPath.isDirectory() && !defaultString.endsWith(File.separator)) { 147 | defaultString += File.separator; 148 | } 149 | 150 | //System.out.println(defaultString + " : exists: " + new File(defaultString).exists()); 151 | 152 | String result = TinyFileDialogs.tinyfd_saveFileDialog(title, defaultString, filters, filterDescription); 153 | 154 | if (result == null) { 155 | return null; 156 | } 157 | 158 | if (forceExtension && !result.endsWith("." + fileExtension)) { 159 | result += "." + fileExtension; 160 | } 161 | return new File(result); 162 | } 163 | } -------------------------------------------------------------------------------- /ClearWindows/src/main/java/nokori/clear/windows/util/WindowedApplication.java: -------------------------------------------------------------------------------- 1 | package nokori.clear.windows.util; 2 | 3 | import nokori.clear.windows.GLFWException; 4 | import nokori.clear.windows.Window; 5 | import nokori.clear.windows.WindowManager; 6 | 7 | 8 | public abstract class WindowedApplication { 9 | 10 | protected WindowManager windowManager; 11 | protected Window window; 12 | 13 | public WindowedApplication(WindowManager windowManager) { 14 | this.windowManager = windowManager; 15 | } 16 | 17 | public WindowedApplication() { 18 | try { 19 | windowManager = new WindowManager(); 20 | } catch (GLFWException e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | 25 | /** 26 | * Starts a Simple Window-based program. 27 | * 28 | * @param program - a class that extends this one, meant to be the root of the program 29 | * @param args - the args passed through the main method 30 | */ 31 | public static void launch(WindowedApplication program, String[] args) { 32 | launch(program, args, true); 33 | } 34 | 35 | /** 36 | * Starts a Simple Window-based program. 37 | * 38 | * @param program - a class that extends this one, meant to be the root of the program 39 | * @param args - the args passed through the main method 40 | * @param restartJVMOnFirstThread - if true, the JVM will be restarted on the first thread if not already. This ensures LWJGL3 compatibility on Mac. 41 | */ 42 | public static void launch(WindowedApplication program, String[] args, boolean restartJVMOnFirstThread) { 43 | //Restarts the JVM if necessary on the first thread to ensure Mac compatibility 44 | if (restartJVMOnFirstThread && JVMUtil.restartJVMOnFirstThread(true, args)) { 45 | return; 46 | } 47 | 48 | //Create Window and NuklearContext 49 | try { 50 | Window window = program.createWindow(program.windowManager); 51 | window.makeContextCurrent(); 52 | 53 | program.window = window; 54 | 55 | //Initialize the program 56 | program.init(program.windowManager, window, args); 57 | 58 | //Run the program 59 | program.loop(); 60 | 61 | } catch (GLFWException e) { 62 | e.printStackTrace(); 63 | return; 64 | } 65 | } 66 | 67 | protected void loop() { 68 | //Software loop 69 | while (!window.isCloseRequested()) { 70 | run(); 71 | windowManager.update(true); 72 | } 73 | 74 | endOfApplicationCallback(); 75 | 76 | if (exitProgramOnEndOfApplication()) { 77 | windowManager.dispose(); 78 | System.exit(0); 79 | } else { 80 | window.dispose(); 81 | } 82 | } 83 | 84 | /** 85 | * Called after the basic GLFW initialization is completed and before the program loop is started. 86 | * 87 | * @param args - the args passed through the main method 88 | * @param window - the newly created GLFW window 89 | */ 90 | public abstract void init(WindowManager windowManager, Window window, String[] args); 91 | 92 | /** 93 | * Called from the program loop. 94 | */ 95 | public abstract void run(); 96 | 97 | 98 | /** 99 | * Creates the GLFW Window. Allows the user to customize it to their specific use-case. 100 | * 101 | * @param windowManager 102 | * @return 103 | */ 104 | public abstract Window createWindow(WindowManager windowManager) throws GLFWException; 105 | 106 | public WindowManager getWindowManager() { 107 | return windowManager; 108 | } 109 | 110 | public Window getWindow() { 111 | return window; 112 | } 113 | 114 | /** 115 | * This overridable function allows you to change the end-of-life behavior for this WindowedApplication. 116 | * 117 | * @return true if this WindowApplication should shutdown the JVM and dispose the WindowManager at the end of its life. 118 | */ 119 | protected boolean exitProgramOnEndOfApplication() { 120 | return true; 121 | } 122 | 123 | /** 124 | * This is called at the end of this program's life, right before WindowManager is disposed. 125 | */ 126 | protected abstract void endOfApplicationCallback(); 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clear 2 | Clear is a wrapper for LWJGL3 that makes it simple to build basic programs that require a user-interface. The windowing and UI wrappers are separated into two projects in case you want to use one and not the other. The goal of the project is to provide a very sound foundation for creating custom user-interfaces that aren't limited by foundational design decisions (such as forcing certain layout types to be used, or so on). This is for GUI creators who want something "free-form" to build their interfaces in. 3 | 4 |

5 | 6 |

7 | 8 | ## General Features 9 | - Heavily customizable: focus on user extendability 10 | - High performance: system performance was kept in mind when creating the system, and as far as I know, it doesn't have any sort of resources leak or the like. Tools are provided to help keep resource management sweet and simple. 11 | - Documentation: the project has carefully been documented with numerous comments so far to allow users to quickly learn how to begin adding their own functionality to the base systems 12 | - Detailed Examples: demo programs are included that will show users how to use all of the currently implemented systems. The default LWJGL3 NanoVG demos are included as well for additional support options. 13 | 14 | ## ClearWindows 15 | - Create OpenGL-capable windows in two lines 16 | - Contains utilities for creating windowed programs with simple looping functionality 17 | - Has classes for making input callbacks for windows 18 | - Has framework for opening multiple windows at once and exerting fine control over them 19 | - Has a TinyFileDialog wrapper class that allows you to open various message/open/save/input dialogs in one line each 20 | 21 | ## ClearVG 22 | - Create a NanoVG context within one line 23 | - Works independent of ClearWindows (can be used with your own personal windowing solution) 24 | - Create a NanoVG-capable application within 100 lines 25 | - Has basic tools for making pretty user-interfaces 26 | - Reliable and customizable 27 | - Contains basic Widgets such as shapes, buttons, draggables, and systems for assembling Widgets into complex UI elements (WidgetAssembly) 28 | - Contains a fully realized TextAreaWidget that has text input systems, extendability, custom formatting, automatic formatting (for creating code areas), and much more 29 | 30 | # Examples 31 | 32 | ### [Hello World (basic clickable button)](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearHelloWorldDemo.java) 33 | ![clear_helloworld](https://user-images.githubusercontent.com/6147299/53410619-f24dd280-3989-11e9-91dd-5c653870fc59.png) 34 | 35 | 36 | ### [Text Area Support (formatting, line numbers, editing, in-depth customization)](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearTextAreaDemo.java) 37 | ![clear_textarea](https://user-images.githubusercontent.com/6147299/53695030-3a029e80-3d7c-11e9-9375-ff3f71f0b5db.png) 38 | 39 | ### [Code Area Support (syntax-highlighting, line numbers, tabbing)](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearTextAreaCodeEditorDemo.java) 40 | ![Clear CodeArea](https://user-images.githubusercontent.com/6147299/56476929-2367f200-6465-11e9-8a25-67481a251ae5.png) 41 | 42 | ### [Input Windows & Sub-Windows (Configured specially for LWJGL3)](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearTextFieldDemo.java) 43 | ![Clear InputWindow](https://user-images.githubusercontent.com/6147299/56476930-24991f00-6465-11e9-8e79-0dbc3ca54b1a.jpg) 44 | 45 | #### Other Examples: 46 | - [Draggable Widget Demo](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearDraggableWidgetDemo.java) 47 | - [TextAreaWidget Text Field Demo](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearTextFieldDemo.java) 48 | - [Circle Rendering Demo](https://github.com/SkyAphid/Clear/blob/master/ClearVG/demo/nokori/clear/vg/ClearCircleDemo.java) 49 | 50 | # Recommended Projects (See Also) 51 | - [LWJGUI - LWJGL3 JavaFX Alternative](https://github.com/orange451/LWJGUI) 52 | I contributed a bit to this project, of which some of those contributions helped form the basis of Clear. If you're wanting more in-depth functionality closer to JavaFX, I highly recommend this project. Clear is meant to be somewhat more minimalistic and straight-forward, whereas LWJGUI will give you a close approximation of JavaFX's general structure. It's well-made and concise, I highly recommend this UI solution if Clear doesn't have the features you're looking for. 53 | -------------------------------------------------------------------------------- /fonts/NotoSans/NotoSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSans/NotoSans-Bold.ttf -------------------------------------------------------------------------------- /fonts/NotoSans/NotoSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSans/NotoSans-Italic.ttf -------------------------------------------------------------------------------- /fonts/NotoSans/NotoSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSans/NotoSans-Light.ttf -------------------------------------------------------------------------------- /fonts/NotoSans/NotoSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSans/NotoSans-Regular.ttf -------------------------------------------------------------------------------- /fonts/NotoSerif/NotoSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSerif/NotoSerif-Bold.ttf -------------------------------------------------------------------------------- /fonts/NotoSerif/NotoSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSerif/NotoSerif-Italic.ttf -------------------------------------------------------------------------------- /fonts/NotoSerif/NotoSerif-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSerif/NotoSerif-Light.ttf -------------------------------------------------------------------------------- /fonts/NotoSerif/NotoSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkyAphid/Clear/2c553ad948010a1259f9d5136f5079883ec7d31c/fonts/NotoSerif/NotoSerif-Regular.ttf --------------------------------------------------------------------------------