Skip to content

Commit 29f5775

Browse files
authored
Panel drag resize (#160)
* resizeable panel prototype * cursor icon change on resize area * stuff * test lwjgl3
1 parent b8efc40 commit 29f5775

File tree

18 files changed

+411
-46
lines changed

18 files changed

+411
-46
lines changed

build.gradle

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//version: 1723428048
1+
//version: 1753288091
22
/*
33
* DO NOT CHANGE THIS FILE!
44
* Also, you may replace this file at any time if there is an update available.
@@ -50,6 +50,8 @@ def out = services.get(StyledTextOutputFactory).create('an-output')
5050

5151

5252
// Project properties
53+
loadProjectProperties()
54+
5355

5456
// Required properties: we don't know how to handle these being missing gracefully
5557
checkPropertyExists("modName")
@@ -80,6 +82,7 @@ propertyDefaultIfUnset("includeWellKnownRepositories", true)
8082
propertyDefaultIfUnset("includeCommonDevEnvMods", true)
8183
propertyDefaultIfUnset("stripForgeRequirements", false)
8284
propertyDefaultIfUnset("noPublishedSources", false)
85+
propertyDefaultIfUnset("mixinProviderSpec", "zone.rong:mixinbooter:10.6")
8386
propertyDefaultIfUnset("forceEnableMixins", false)
8487
propertyDefaultIfUnset("mixinConfigRefmap", "mixins.${project.modId}.refmap.json")
8588
propertyDefaultIfUnsetWithEnvVar("enableCoreModDebug", false, "CORE_MOD_DEBUG")
@@ -518,7 +521,6 @@ configurations {
518521
testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable)
519522
}
520523

521-
String mixinProviderSpec = 'zone.rong:mixinbooter:9.1'
522524
dependencies {
523525
if (usesMixins.toBoolean()) {
524526
annotationProcessor 'org.ow2.asm:asm-debug-all:5.2'
@@ -870,10 +872,10 @@ if (enableJava17RunTasks.toBoolean()) {
870872

871873
dependencies {
872874
if (modId != 'lwjgl3ify') {
873-
java17Dependencies("io.github.twilightflower:lwjgl3ify:1.0.0")
874-
}
875-
java17PatchDependencies("io.github.twilightflower:lwjgl3ify:1.0.0:forgePatches") {
875+
java17Dependencies("io.github.twilightflower:lwjgl3ify:1.0.1")
876+
java17PatchDependencies("io.github.twilightflower:lwjgl3ify:1.0.1:forgePatches") {
876877
transitive = false
878+
}
877879
}
878880
}
879881

@@ -1475,14 +1477,28 @@ tasks.register('faq') {
14751477
"To add new dependencies to your project, place them in 'dependencies.gradle', NOT in 'build.gradle' as they would be replaced when the script updates.\n" +
14761478
"To add new repositories to your project, place them in 'repositories.gradle'.\n" +
14771479
"If you need additional gradle code to run, you can place it in a file named 'addon.gradle' (or either of the above, up to you for organization).\n\n" +
1478-
"If your build fails to recognize the syntax of newer Java versions, enable Jabel in your 'gradle.properties' under the option name 'enableModernJavaSyntax'.\n" +
1480+
"If your build fails to recognize the syntax of newer Java versions, enable Jabel in your 'buildscript.properties' under the option name 'enableModernJavaSyntax'.\n" +
14791481
"To see information on how to configure your IDE properly for Java 17, see https://github.com/GregTechCEu/Buildscripts/blob/master/docs/jabel.md\n\n" +
14801482
"Report any issues or feature requests you have for this build script to https://github.com/GregTechCEu/Buildscripts/issues\n")
14811483
}
14821484
}
14831485

14841486

14851487
// Helpers
1488+
def loadProjectProperties() {
1489+
def configFile = file("buildscript.properties")
1490+
if (configFile.exists()) {
1491+
configFile.withReader {
1492+
def prop = new Properties()
1493+
prop.load(it)
1494+
new ConfigSlurper().parse(prop).forEach { String k, def v ->
1495+
project.ext.setProperty(k, v)
1496+
}
1497+
}
1498+
} else {
1499+
print("Failed to read from buildscript.properties, as it did not exist!")
1500+
}
1501+
}
14861502

14871503
def getDefaultArtifactGroup() {
14881504
def lastIndex = project.modGroup.lastIndexOf('.')
@@ -1495,7 +1511,7 @@ def getFile(String relativePath) {
14951511

14961512
def checkPropertyExists(String propertyName) {
14971513
if (!project.hasProperty(propertyName)) {
1498-
throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GregTechCEu/Buildscripts/blob/main/gradle.properties")
1514+
throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"buildscript.properties\" or \"gradle.properties\". You can find all properties and their description here: https://github.com/GregTechCEu/Buildscripts/blob/main/buildscript.properties and https://github.com/GregTechCEu/Buildscripts/blob/main/gradle.properties")
14991515
}
15001516
}
15011517

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ enableModernJavaSyntax = true
4242
# This is primarily used to test if your mod is compatible with platforms running
4343
# Minecraft 1.12.2 on modern versions of Java and LWJGL, and assist in fixing any problems with it.
4444
# Using this requires that you use a Java 17/Java 21 JDK for development.
45-
enableJava17RunTasks = false
45+
enableJava17RunTasks = true
4646

4747
# Generate a class with String fields for the mod id, name and version named with the fields below
4848
generateGradleTokenClass = com.cleanroommc.modularui.MuiTags

src/main/java/com/cleanroommc/modularui/ClientProxy.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.cleanroommc.modularui;
22

33
import com.cleanroommc.modularui.animation.AnimatorManager;
4+
import com.cleanroommc.modularui.api.widget.ResizeDragArea;
45
import com.cleanroommc.modularui.drawable.DrawableSerialization;
56
import com.cleanroommc.modularui.factory.GuiFactories;
67
import com.cleanroommc.modularui.factory.inventory.InventoryTypes;
78
import com.cleanroommc.modularui.holoui.HoloScreenEntity;
89
import com.cleanroommc.modularui.holoui.ScreenEntityRender;
910
import com.cleanroommc.modularui.keybind.KeyBindHandler;
10-
import com.cleanroommc.modularui.overlay.OverlayManager;
1111
import com.cleanroommc.modularui.screen.ClientScreenHandler;
1212
import com.cleanroommc.modularui.test.EventHandler;
1313
import com.cleanroommc.modularui.test.OverlayTest;
@@ -31,7 +31,15 @@
3131
import net.minecraftforge.fml.relauncher.Side;
3232
import net.minecraftforge.fml.relauncher.SideOnly;
3333

34+
import org.lwjgl.LWJGLException;
35+
import org.lwjgl.input.Cursor;
3436
import org.lwjgl.input.Keyboard;
37+
import org.lwjgl.input.Mouse;
38+
39+
import java.awt.image.BufferedImage;
40+
import java.io.IOException;
41+
import java.nio.IntBuffer;
42+
import javax.imageio.ImageIO;
3543

3644
@SideOnly(Side.CLIENT)
3745
@SuppressWarnings("unused")
@@ -40,6 +48,12 @@ public class ClientProxy extends CommonProxy {
4048
private final Timer timer60Fps = new Timer(60f);
4149
public static KeyBinding testKey;
4250

51+
public static Cursor resizeCursorDiag;
52+
public static Cursor resizeCursorDiagInverse;
53+
public static Cursor resizeCursorH;
54+
public static Cursor resizeCursorV;
55+
private static Cursor currentCursor;
56+
4357
@Override
4458
void preInit(FMLPreInitializationEvent event) {
4559
super.preInit(event);
@@ -64,6 +78,83 @@ void preInit(FMLPreInitializationEvent event) {
6478
if (!Minecraft.getMinecraft().getFramebuffer().isStencilEnabled()) {
6579
Minecraft.getMinecraft().getFramebuffer().enableStencil();
6680
}
81+
82+
// Create resize window cursors
83+
try {
84+
BufferedImage img = ImageIO.read(getClass().getClassLoader().getResourceAsStream("assets/modularui/textures/gui/icons/cursor_resize_diag.png"));
85+
int size = img.getHeight();
86+
resizeCursorDiagInverse = new Cursor(size, size, size / 2, size / 2, 1, readPixel(img, true, false), null);
87+
resizeCursorDiag = new Cursor(size, size, size / 2, size / 2, 1, readPixel(img, false, false), null);
88+
89+
img = ImageIO.read(getClass().getClassLoader().getResourceAsStream("assets/modularui/textures/gui/icons/cursor_resize.png"));
90+
size = img.getHeight();
91+
resizeCursorH = new Cursor(size, size, size / 2, size / 2, 1, readPixel(img, false, false), null);
92+
resizeCursorV = new Cursor(size, size, size / 2, size / 2, 1, readPixel(img, false, true), null);
93+
} catch (IOException | LWJGLException e) {
94+
throw new RuntimeException(e);
95+
} catch (Throwable e) {
96+
ModularUI.LOGGER.info("Custom Cursors failed to load. This is likely because an incompatible LWJGL version was used like with CleanroomLoader.");
97+
// TODO: proper lwjgl 3 support
98+
// currently it seems this is not even triggered and the cursors are created correctly, but when the cursors are set nothing happens
99+
}
100+
}
101+
102+
public static IntBuffer readPixel(BufferedImage img, boolean inverse, boolean transpose) {
103+
int size = img.getHeight();
104+
IntBuffer buffer = IntBuffer.allocate(size * size);
105+
int y = inverse ? 0 : size - 1;
106+
while (inverse ? y < size : y >= 0) {
107+
for (int x = 0; x < size; x++) {
108+
int a, b;
109+
if (transpose) {
110+
a = y;
111+
b = x;
112+
} else {
113+
a = x;
114+
b = y;
115+
}
116+
buffer.put(img.getRGB(a, b));
117+
}
118+
if (inverse) {
119+
y++;
120+
} else {
121+
y--;
122+
}
123+
}
124+
buffer.flip();
125+
return buffer;
126+
}
127+
128+
public static void setCursorResizeIcon(ResizeDragArea dragArea) {
129+
if (resizeCursorV == null) return; // cursors failed to initialized
130+
try {
131+
if (dragArea == null) {
132+
resetCursorIcon();
133+
return;
134+
}
135+
Cursor cursor = switch (dragArea) {
136+
case TOP_LEFT, BOTTOM_RIGHT -> resizeCursorDiagInverse;
137+
case TOP_RIGHT, BOTTOM_LEFT -> resizeCursorDiag;
138+
case TOP, BOTTOM -> resizeCursorV;
139+
case LEFT, RIGHT -> resizeCursorH;
140+
};
141+
currentCursor = Mouse.setNativeCursor(cursor);
142+
} catch (LWJGLException e) {
143+
throw new RuntimeException(e);
144+
}
145+
}
146+
147+
public static void resetCursorIcon() {
148+
if (resizeCursorV == null) return; // cursors failed to initialized
149+
try {
150+
if (currentCursor == resizeCursorDiag || currentCursor == resizeCursorDiagInverse || currentCursor == resizeCursorH || currentCursor == resizeCursorV) {
151+
currentCursor = null;
152+
}
153+
Mouse.setNativeCursor(currentCursor);
154+
currentCursor = null;
155+
} catch (LWJGLException e) {
156+
throw new RuntimeException(e);
157+
}
67158
}
68159

69160
@Override

src/main/java/com/cleanroommc/modularui/api/layout/IViewport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ static void getChildrenAt(IWidget parent, IViewportStack stack, HoveredWidgetLis
7171
stack.pushMatrix();
7272
child.transform(stack);
7373
if (child.isInside(stack, x, y)) {
74-
widgetList.add(child, stack.peek());
74+
widgetList.add(child, stack.peek(), child.getAdditionalHoverInfo(stack, x, y));
7575
}
7676
if (child.hasChildren()) {
7777
getChildrenAt(child, stack, widgetList, x, y);
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.cleanroommc.modularui.api.widget;
2+
3+
import com.cleanroommc.modularui.api.layout.IViewportStack;
4+
import com.cleanroommc.modularui.widget.sizer.Area;
5+
6+
import net.minecraft.client.gui.GuiScreen;
7+
8+
/**
9+
* Implement this interface on a {@link IWidget} to allow it being resized by dragging the edges similar to windows.
10+
*/
11+
public interface IDragResizeable {
12+
13+
/**
14+
* @return if this widget can currently be resized by dragging an edge
15+
*/
16+
default boolean isCurrentlyResizable() {
17+
return true;
18+
}
19+
20+
/**
21+
* @return if the center position of this widget should be retained, by also resizing the opposite edge
22+
*/
23+
default boolean keepPosOnDragResize() {
24+
return true;
25+
}
26+
27+
/**
28+
* Called every time the mouse moves one or more pixel while this widget is resized by dragging an edge.
29+
*/
30+
default void onDragResize() {
31+
((IWidget) this).scheduleResize();
32+
}
33+
34+
/**
35+
* @return The border size in witch to allow drag resizing in pixels.
36+
*/
37+
default int getDragAreaSize() {
38+
return 3;
39+
}
40+
41+
/**
42+
* @return The minimum width this widget can be dragged to.
43+
*/
44+
default int getMinDragWidth() {
45+
return 18;
46+
}
47+
48+
/**
49+
* @return The minimum height this widget can be dragged to.
50+
*/
51+
default int getMinDragHeight() {
52+
return 18;
53+
}
54+
55+
/**
56+
* An internal method to detect if the mouse is currently hovering an area where a drag resize can be started.
57+
*/
58+
static ResizeDragArea getDragResizeCorner(IDragResizeable widget, Area area, IViewportStack stack, int x, int y) {
59+
if (!widget.isCurrentlyResizable()) return null;
60+
61+
int mx = stack.unTransformX(x, y);
62+
int my = stack.unTransformY(x, y);
63+
64+
if (mx < 0 || my < 0 || mx > area.w() || my > area.h()) return null;
65+
66+
int ras = widget.getDragAreaSize();
67+
if (mx < ras) {
68+
if (my < ras) return ResizeDragArea.TOP_LEFT;
69+
if (my > area.h() - ras) return ResizeDragArea.BOTTOM_LEFT;
70+
return ResizeDragArea.LEFT;
71+
}
72+
if (mx > area.w() - ras) {
73+
if (my < ras) return ResizeDragArea.TOP_RIGHT;
74+
if (my > area.h() - ras) return ResizeDragArea.BOTTOM_RIGHT;
75+
return ResizeDragArea.RIGHT;
76+
}
77+
if (my < ras) return ResizeDragArea.TOP;
78+
if (my > area.h() - ras) return ResizeDragArea.BOTTOM;
79+
return null;
80+
}
81+
82+
/**
83+
* An internal method to actually resize the widget while an edge is being dragged.
84+
*/
85+
static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea dragArea, Area startArea, int dx, int dy) {
86+
int keepPosFactor = resizeable.keepPosOnDragResize() || GuiScreen.isShiftKeyDown() ? 2 : 1;
87+
if (dx != 0) {
88+
if (dragArea.left) {
89+
int s = startArea.width - dx * keepPosFactor;
90+
if (s >= resizeable.getMinDragWidth()) {
91+
widget.flex().left(startArea.rx + dx);
92+
widget.flex().width(s);
93+
}
94+
} else if (dragArea.right) {
95+
int s = startArea.width + dx * keepPosFactor;
96+
if (s >= resizeable.getMinDragWidth()) {
97+
widget.flex().left(startArea.rx - dx * (keepPosFactor - 1));
98+
widget.flex().width(s);
99+
}
100+
}
101+
}
102+
if (dy != 0) {
103+
if (dragArea.top) {
104+
int s = startArea.height - dy * keepPosFactor;
105+
if (s >= resizeable.getMinDragHeight()) {
106+
widget.flex().top(startArea.ry + dy);
107+
widget.flex().height(s);
108+
}
109+
} else if (dragArea.bottom) {
110+
int s = startArea.height + dy * keepPosFactor;
111+
if (s >= resizeable.getMinDragHeight()) {
112+
widget.flex().top(startArea.ry - dy * (keepPosFactor - 1));
113+
widget.flex().height(s);
114+
}
115+
}
116+
}
117+
resizeable.onDragResize();
118+
}
119+
120+
}

src/main/java/com/cleanroommc/modularui/api/widget/IWidget.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public interface IWidget extends IGuiElement {
2727
* This element now becomes valid
2828
*
2929
* @param parent the parent this element belongs to
30-
* @param late true if this is called some time after the widget tree of the parent has been initialised
30+
* @param late true if this is called some time after the widget tree of the parent has been initialised
3131
*/
3232
void initialise(@NotNull IWidget parent, boolean late);
3333

@@ -94,6 +94,10 @@ default void transform(IViewportStack stack) {
9494
stack.translate(getArea().rx, getArea().ry, getArea().getPanelLayer() * 20);
9595
}
9696

97+
default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) {
98+
return null;
99+
}
100+
97101
default WidgetTheme getWidgetTheme(ITheme theme) {
98102
return theme.getFallback();
99103
}
@@ -119,8 +123,26 @@ default WidgetTheme getWidgetTheme(ITheme theme) {
119123
* @return if pos is inside this widgets area
120124
*/
121125
default boolean isInside(IViewportStack stack, int mx, int my) {
122-
int x = stack.unTransformX(mx, my);
123-
int y = stack.unTransformY(mx, my);
126+
return isInside(stack, mx, my, true);
127+
}
128+
129+
/**
130+
* Calculates if a given pos is inside this widgets area.
131+
* This should be used over {@link Area#isInside(int, int)}, since this accounts for transformations.
132+
*
133+
* @param stack viewport stack
134+
* @param mx x pos
135+
* @param my y pos
136+
* @param absolute true if the position is absolute or relative to the current stack transform otherwise
137+
* @return if pos is inside this widgets area
138+
*/
139+
default boolean isInside(IViewportStack stack, int mx, int my, boolean absolute) {
140+
int x = mx;
141+
int y = my;
142+
if (absolute) {
143+
x = stack.unTransformX(mx, my);
144+
y = stack.unTransformY(mx, my);
145+
}
124146
return x >= 0 && x < getArea().w() && y >= 0 && y < getArea().h();
125147
}
126148

0 commit comments

Comments
 (0)