diff --git a/dependencies.gradle b/dependencies.gradle index 263c4110bb0..15a9f452b95 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -10,6 +10,10 @@ dependencies { // Configuration jarJar(modApi(forge.configuration.get())) + // Math Parser + jarJar(implementation(libs.evalEx.get())) + additionalRuntimeClasspath(libs.evalEx.get()) + // Mixin (& Extras) annotationProcessor(variantOf(libs.mixin) { classifier("processor") }) compileOnly(annotationProcessor(forge.mixinExtras.common.get())) @@ -118,4 +122,6 @@ dependencies { modExtraLocalRuntime(forge.ponder) modExtraLocalRuntime(variantOf(forge.create) { classifier("slim") }) modExtraLocalRuntime(forge.flywheel.forge) + + modLocalRuntime(forge.curios) } diff --git a/docs/content/Development/MUI2/Layout_MainAxisAlignments.png b/docs/content/Development/MUI2/Layout_MainAxisAlignments.png new file mode 100644 index 00000000000..308916e9eb6 Binary files /dev/null and b/docs/content/Development/MUI2/Layout_MainAxisAlignments.png differ diff --git a/docs/content/Development/MUI2/Syncing/Sync-Basics.md b/docs/content/Development/MUI2/Syncing/Sync-Basics.md new file mode 100644 index 00000000000..12cd19e6337 --- /dev/null +++ b/docs/content/Development/MUI2/Syncing/Sync-Basics.md @@ -0,0 +1,268 @@ +# Sync Basics + +## Basics of Syncing +To display dynamic values on the client, you have to send the data from the server to the client. When making UIs, it is very important to keep track of if and how your data is being synced to the client. There are a few ways to do so, which will be discussed below. + +When opening a UI, a copy of the UI is created both on the server and on the client. It is important to note here that the server's copy will have access to most everything about e.g. the machine, block state, the world etc., but the client's copy will not. + +To sync this data back and forth, you need to use `SyncHandler`s. These will send your data from the server to the client when it updates. + +Because of this, you cannot just use values you create SyncHandlers for directly in the client. An example: + +```java + +var tickSyncValue = new IntSyncValue(() -> this.ticks, (newValue) -> this.ticks = newValue); +for(int i=0;i)` - Queries the supplier every frame to retrieve the component to display +- `new DynamicDrawable(Supplier)` - Queries the supplier every frame to retrieve the drawable to display + + +!!! Note + To convert IKeys or Drawables to Widgets, you need to chain `.asWidget()` + +```java +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public int ticks = 0; + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + this.subscribeServerTick(() -> ticks++); + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176, 168); + var tickSyncValue = new IntSyncValue(() -> this.ticks, (newValue) -> this.ticks = newValue); + syncManager.syncValue("tickSyncValue", tickSyncValue); + + var column = Flow.column(); + + column.child(IKey.dynamic(() -> Component.literal("Ticks: " + this.ticks)) // note that this is a Supplier instead of a Component + .asWidget() + .margin(4)); + + column.child( + new DynamicDrawable(() -> { // note that this is a Supplier instead of an IDrawable + if (ticks % 40 < 20) { // flip every second + return GTGuiTextures.BUTTON_FLUID_OUTPUT; + } else { + return GTGuiTextures.BUTTON_ITEM_OUTPUT; + } + }) + .asWidget() + .background(GTGuiTextures.BACKGROUND_STEEL) + ); + + panel.child(column); + + return panel; + } +} +``` + +Here, we create a basic `SyncValue` for an integer. This takes a `Supplier` and a `Consumer`, more commonly known as a getter and a setter. Generally speaking, `SyncValue`s will take a `Supplier` and `Consumer` of the type of value they are syncing. + +If the value returned by the getter changed on the server, the value gets serialized and sent to the client by the `SyncManager`. The `SyncHandler`'s value can always be manually updated, for example to do client-to-server syncing. + +Then, the value on the client (being set every time the server sends an update) is retrieved every frame by the lambdas used in the dynamic widgets. + +If you want to update the value from the client, you can call `syncValue.setValue()` on the client. This will also update the value on the server side. + +## Method 2: DynamicLinkedSyncHandler + +This method is great for widgets whose structure and layout can change depending on your synced values. + +```java +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public int ticks = 0; + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + this.subscribeServerTick(() -> ticks++); + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176, 168); + var tickSyncValue = new IntSyncValue(() -> this.ticks, (newValue) -> this.ticks = newValue); + syncManager.syncValue("tickSyncValue", tickSyncValue); + + DynamicLinkedSyncHandler dynamicLinkedSyncHandler = new DynamicLinkedSyncHandler<>(tickSyncValue) + .widgetProvider((widgetSyncManager, intSyncHandler) -> { + var list = new ListWidget<>() + .widthRel(1) + .coverChildrenHeight() + .crossAxisAlignment(Alignment.CrossAxis.START); + int tickValue = intSyncHandler.getValue(); // It is also possible to just reference this.ticks directly + int amountOfItems = 1 + (tickValue % 200) / 20; + for (int i = 0; i < amountOfItems; i++) { + list.child(IKey.str("Value nr. " + (i + 1)).asWidget()); // No need for IKey.dynamic since we have the value as a variable here, inside the lambda + } + return list; + }); + + panel.child(new DynamicSyncedWidget<>() + .widthRel(1) + .coverChildrenHeight() + .syncHandler(dynamicLinkedSyncHandler) + .padding(3)); + + return panel; + } +} +``` + +This method works in three steps: + +The first step is creating a `SyncHandler` and registering it to the `PanelSyncManager`. +The second step is creating a `DynamicLinkedSyncHandler` based on the first `SyncHandler`. This is effectively a wrapper class to provide your widget whenever your initial `SyncHandler` updates. +The third step is creating a `DynamicSyncedWidget` with that `DynamicLinkedSyncHandler` as its `SyncHandler`. + +This effectively lets us create a new "version" of the widget whenever our value (in this case the `ticks` int) changes. Furthermore, in this example we have the actual values of the things we want to sync when constructing our widget tree on the client, allowing for much greater customization. + + +!!! note + For even more complex systems where you need to dynamically register sync handlers within the `DynamicLinkedSyncHandler`'s `.widgetProvider(...)`, this can be done by calling `.getOrCreateSyncHandler(...)` on the `widgetSyncManager` parameter of the lambda. + +## Method 3: Types that take SyncHandlers +There are some widgets that have built in support for working directly with SyncHandlers. + +```java +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public boolean buttonPressed = false; + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176, 168); + + var column = Flow.column().paddingTop(3); + + column.child( + IKey.dynamic(() -> Component.literal("Pressed: " + this.buttonPressed)) + .asWidget()); + + var buttonSyncValue = new BooleanSyncValue(() -> this.buttonPressed, (newValue) -> this.buttonPressed = newValue); + column.child(new ToggleButton().value(buttonSyncValue)); + + panel.child(column); + + return panel; + } +} +``` + +Note that in this case, the ToggleButton takes care of registering the SyncValue for us, so we do not register it to the syncManager ourselves. This method is great for simple functions using widgets that support it. + +A few examples of this would be: + +- `new ToggleButton().value(BooleanSyncValue)` - A ToggleButton that affects the Boolean sync value +- `new TextFieldWidget().value(StringSyncValue)` - A TextField that updates the String sync value +- `new ProgressWidget().value(DoubleSyncValue)` - A ProgressWidget (e.g. bar) that shows progress, can also be constructed with `new ProgressWidget().progress(() -> this.progress)` +- `new SliderWidget().value(DoubleSyncValue)` - A SliderWidget that updates the Double sync value + +## Method 4: Manually notifying DynamicSyncHandlers +This method is useful when your custom widget needs complex data coming in, like through multiple sync handlers. + +```java +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public int rows = 0; + public int columns = 0; + public int counter = 0; + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + this.subscribeServerTick(() -> { + counter += 1; + if (counter % 20 == 0) { + rows = (rows + 1) % 10; + } + if (counter % 15 == 0) { + columns = (columns + 1) % 10; + } + }); + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 200, 200); + + var rowsSyncValue = new IntSyncValue(() -> this.rows, (newValue) -> this.rows = newValue); + syncManager.syncValue("rows", rowsSyncValue); + + var columnsSyncValue = new IntSyncValue(() -> this.columns, (newValue) -> this.columns = newValue); + syncManager.syncValue("columns", columnsSyncValue); + + DynamicSyncHandler gridWidgetHandler = new DynamicSyncHandler().widgetProvider((slotsSyncManger, buffer) -> { + + Flow grid = Flow.column().width(200); + for (int rowNr = 0; rowNr < this.rows; rowNr++) { + Flow row = Flow.row(); + for (int columnNr = 0; columnNr < this.columns; columnNr++) { + row.child(IKey.str(rowNr + ", " + columnNr).asWidget().width(20)); + } + grid.child(row); + } + return grid; + }); + + rowsSyncValue.setChangeListener(() -> { + gridWidgetHandler.notifyUpdate(buffer -> {}); + }); + columnsSyncValue.setChangeListener(() -> { + gridWidgetHandler.notifyUpdate(buffer -> {}); + }); + + panel.child(new DynamicSyncedWidget<>().syncHandler(gridWidgetHandler)); + return panel; + } +} +``` + +This is very similar to method 2, but instead of a `DynamicLinkedSyncHandler` we use a normal `DynamicSyncHandler` where we have to manually let it know when to update. We do this in the change listener of our two `SyncValue`s by calling notifyUpdate. + +Do note there's also a buffer where you can serialize data to, to be consumed in the `.widgetProvider(...)` in the spot where in a `DynamicLinkedSyncHandler` our syncValue would be. It is usually not needed to put anything in this buffer. + +## Other sync information + +### SyncHandler panel separation + +SyncValues are separated across panels. So if you do `mainPanelSyncManager.syncValue("rows", rowsSyncValue);` in one panel, you can't just call `popupSyncManager.getSyncHandlerFromMapKey("rows:0")`. +You can, however, call `syncManager.getModularSyncManager().getPanelSyncManager("panel name here").getSyncHandlerFromMapKey("rows:0");`. + +### Value.Dynamic +Sometimes you need to quickly create a Value for something that already exists client side. For this you can use `new [Type]Value.Dynamic(...)`. +For example, if you have a client-only value that's affected by a button, you could do +`panel.child(new ToggleButton().value(new BoolValue.Dynamic(() -> this.toggled, (newValue) -> this.toggled = newValue)));` + +Another reason to use a dynamic value is if you want to change the type of a variable, e.g. `.value(new DoubleValue.Dynamic(() -> (double) this.x, val -> this.x = (double) val)` where x is an int. + +The third reason is if you want to use a `SyncHandler` in two separate widgets that would both auto-register it. +``` + var boolSyncValue = new BooleanSyncValue(() -> this.toggled, (newValue) -> this.toggled = newValue); + panel.child(new ToggleButton().value(boolSyncValue)); + panel.child(new ToggleButton().value(BoolValue.wrap(boolSyncValue)).left(32)); +``` +Without the wrap around the second boolSyncValue call, it would complain of registration of an already registered SyncValue. + diff --git a/docs/content/Development/MUI2/Syncing/Synced-Actions.md b/docs/content/Development/MUI2/Syncing/Synced-Actions.md new file mode 100644 index 00000000000..f774ffd201a --- /dev/null +++ b/docs/content/Development/MUI2/Syncing/Synced-Actions.md @@ -0,0 +1,48 @@ +# Synced Actions + +```java +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + } + + public int number = 0; + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176, 168); + + var numberSyncValue = new IntSyncValue(() -> this.number, (newValue) -> this.number = newValue); + syncManager.syncValue("number", numberSyncValue); + + syncManager.registerServerSyncedAction("randomButtonPressed", (packet) -> { + this.number = getLevel().getRandom().nextInt(); + }); + + Column contents = new Column(); + contents.child(IKey.dynamic(() -> Component.literal("Number: " + number)) + .asWidget() + .width(100) + .height(16) + .margin(4)); + + contents.child(new ButtonWidget<>() + .onMousePressed((x, y, button) -> { + if (button == 0) { + syncManager.callSyncedAction("randomButtonPressed"); + } + return true; + }) + .size(16)); + return panel.child(contents); + } +} + +``` + +Synced actions are useful when you need a client-side input (e.g. a button press) to trigger an action on the server. + +In this case we create a synced action called `randomButtonPressed`, and when the button is pressed on the client, the `randomButtonPressed` signal is sent to the server and the relevant action is executed. + +If you need to pass other data with your action, you can pass a `(packet) -> {...}` as second argument to `callSyncedAction` and serialize your data there, and then deserialize it from the `packet` argument in the synced action definition. \ No newline at end of file diff --git a/docs/content/Development/MUI2/Syncing/index.md b/docs/content/Development/MUI2/Syncing/index.md new file mode 100644 index 00000000000..ed0a1fb6e5f --- /dev/null +++ b/docs/content/Development/MUI2/Syncing/index.md @@ -0,0 +1 @@ +Syncing is the concept of synchronising data between client and server. This folder will contain files relevant to setting up syncing on your UIs. \ No newline at end of file diff --git a/docs/content/Development/MUI2/Test-Machine.md b/docs/content/Development/MUI2/Test-Machine.md new file mode 100644 index 00000000000..63d3a9135c5 --- /dev/null +++ b/docs/content/Development/MUI2/Test-Machine.md @@ -0,0 +1,35 @@ +# Making a MUI2 Test Machine +To make a basic machine to test your UI, simply create the following class: + +```java title="MultiMachines.java" +public class MuiTestMachine extends MetaMachine implements IMuiMachine { + + public MuiTestMachine(BlockEntityCreationInfo info) { + super(info); + } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176, 168); + // Do stuff with your panel here, add children, etc. + // For example: + panel.child(IKey.str("Test machine") + .asWidget() + .margin(4)); + + return panel; + } +} +``` + +and in your machines definitions class, add the following entry: + +```java + public static final MachineDefinition MUI_TEST_MACHINE = REGISTRATE + .machine("mui_test", MuiTestMachine::new) + .model(createOverlayCasingMachineModel(GTCEu.id("block/casings/solid/machine_casing_clean_stainless_steel"), + GTCEu.id("block/machine/part/computer_monitor"))) + .register(); +``` + +Make sure to run datagen after making the initial machine to register the lang keys, model, etc. Running datagen afterward for UI changes is not required. \ No newline at end of file diff --git a/docs/content/Development/MUI2/Widget-Layout.md b/docs/content/Development/MUI2/Widget-Layout.md new file mode 100644 index 00000000000..822ac6d82ca --- /dev/null +++ b/docs/content/Development/MUI2/Widget-Layout.md @@ -0,0 +1,190 @@ +# Widget Layout, Sizing and Positioning + +Each widget has several builder setter methods for position and size. They all come from the `IPositioned` interface. + + +## Coordinate system +- Widget coordinates are local to the widget; (0, 0) is the top-left corner of the widget. +- Positions are relative to the parent by default. + +## Sizing + +- `width(int)` sets the widget width in pixels +- `widthRel(float)` sets the widget width relative to its parent (e.g. if the parent is 120 px wide and we + call `widthRel(0.5f)`, our widget will be 60 px wide) +- `height(int)` and `heightRel(float)` work analogous to previous methods +- `size(int width, int height)` is equivalent to `.width(width).height(height)` +- `size(int val)` is equivalent to `.width(val).height(val)` +- `sizeRel(float width, float height)` and `sizeRel(float val)` work analogous to previous methods +- `fullWidth()` and `fullHeight()` are shortcuts for `widthRel(1f)` and `heightRel(1f)` respectively +- `full()` combines the previous two methods +- `coverChildrenWidth()` makes the widget width wrap tightly around its children +- `coverChildrenHeight()` works analogous to previous method +- `coverChildren()` wraps width and height tightly +- `expanded()` is only useful for children of `Row` and `Column` widgets. It will make the widget expand as much as + possible in the widgets axis (width in row and height in column) + +## Positioning + +We can set position on four different points, two for each axis. `left()`, `right()`, `top()` and `bottom()`. +To understand what they are doing take a look at the following picture: + +![grafik](https://github.com/CleanroomMC/ModularUI/assets/45517902/ab173431-1509-414c-8db4-4545c985a9bd) + +As you can see the methods are fairly self-explanatory. Each of those methods has multiple variants much like `width()` +and `widthRel()`. Only methods for `left()` will be listed here. + +- `left(int x)` sets the x position in pixels relative to its parent +- `leftRel(float x)` sets the x position relative to its parent (f.e. 0.5f will center the widget) +- `leftRelOffset(float val, int offset)` is the same as `leftRel(float x)`, but also adds an `offset` in pixels after the calculation +- `leftRelAnchor(float val, float anchor)` is the same as `leftRel(float x)`, but with a different anchor + (see [Anchor](#anchor)) +- `leftRel(float val, int offset, float anchor)` combines `leftRelOffset()` and `leftRelAnchor()` +- `left(DoubleSupplier val, Measure measure)` is like `left()` and `leftRel()`, but with a dynamic value. Note that the + supplier is only evaluated during resizing. You can't use it for animating widgets. +- `leftRelOffset(DoubleSupplier val, int offset)` is like `leftRelOffset(float val, int offset)` with a dynamic value +- `leftRelAnchor(DoubleSupplier val, float anchor)` is like `leftRelAnchor(float val, float anchor)` with a dynamic + value +- `leftRel(DoubleSupplier val, int offset, float anchor)` combines the two methods above + +All the above variants also exist for `right()`, `top()` and `bottom()`. +Additionally, there is + +- `pos(int x, int y)` combines `left(int x)` and `top(int y)` +- `posRel(float x, float y)` combines `leftRel(float x)` and `topRel(float y)` +- `alignX(float x)` is short for `leftRelAnchor(x, x)` +- `alignY(float y)` is short for `topRelAnchor(y, y)` +- `align(Alignment a)` combines `alignX(float x)` and `alignY(float y)` +- `center()` is short for `align(Alignment.Center)` + +## Anchor + +The anchor is the point of the widget at which the widget will be positioned with the relative value. The following +picture should make this clear. In the picture `leftRelAnchor(0.5f, 0.3f)` is called. + +![anchor.png](https://raw.githubusercontent.com/CleanroomMC/Website/refs/heads/main/docs/wiki/modularui/anchor.png) + +Here the anchor is placed at `0.3f`, which is about a third of the widget. +And that anchor is positioned at `0.5f` of the parent widget (the center). +Try imagining what happens with different anchor values and play around with it by yourself. + +If we had called `leftRel(float val, int offset, float anchor)`, then the offset would be added after the anchor +and relative position calculation. + +## Combining Size and Position + +You can call multiple different position and size methods, but you should be aware of its effects and limitations. + +Each axis (x and y) has three setters (x has `left()`, `right()` and `width()`, y has `top()`, `bottom()` and `height()`) +without including all the variations. + +!!! Note + You can call at most two setters for each axis, since with two properties set the last one can always be calculated. + +For example of you call `left()` and `right()` then the width can be calculated with `right - left`. +Setting all three properties for an axis will remove one of the other properties and log an info message. + +!!! Note + By default, the position is (0, 0) and the size is 18x18 in pixels for most widgets. + +## Changing the relative Parent + +By default, the size and position are calculated relative to the widget's parent, but that can be changed with +`relative(Area)`, `relative(IGuiElement)` and `relativeToScreen()`. +The parent of all panels is by default the screen. + +!!! Warning + Changing the relative widget might cause some unexpected results in some edge cases. Please notify us if you run + into one of those. + + +## Flow layout (Row / Column) +`Flow` is the core layout widget for arranging children along one axis. + +- `Flow.row()` lays out children **left-to-right** (X axis). +- `Flow.column()` lays out children **top-to-bottom** (Y axis). +- `mainAxisAlignment(...)` controls how children are distributed on the main axis: + - `START`, `CENTER`, `END`, `SPACE_BETWEEN`, `SPACE_AROUND` + - e.g. `Flow.row().mainAxisAlignment(Alignment.MainAxis.CENTER)` centers the row's children horizontally, given that it has a set width +- `crossAxisAlignment(...)` controls alignment on the perpendicular axis: + - `START`, `CENTER`, `END` + - e.g. `Flow.row().crossAxisAlignment(Alignment.CrossAxis.CENTER)` centers the row's children vertically, given that it has a set height +- `childPadding(int)` adds fixed spacing between children. +- `coverChildren()` / `coverChildrenWidth()` / `coverChildrenHeight()` sizes the flow to fit its children. +- `collapseDisabledChild()` does not consider disabled children during position calculations. + - This is useful when the enabled state of children changes dynamically. +- `reverseLayout(bool)` reverses the order that children are drawn. + +Notes: + +- Centering (main or cross axis) requires the flow to have a known size on that axis. If you want a row to center its children horizontally, give it a width (e.g. `widthRel(1f)` or `width(120)`). +- By default, a `Flow` is `full()`, which means they take up as much space as their parents size. + +Here is how all the MainAxisAlignments apply to widgets: + +![MainAxisAlignments](./Layout_MainAxisAlignments.png) + + +## Centering widgets +There are two common ways to center things: + +1) Center a widget within its parent (positioning) + +- `widget.center()` or `widget.align(Alignment.Center)` +- `widget.horizontalCenter()` or `widget.verticalCenter()` + +2) Center children inside a Row/Column (layout) + +- `Flow.row().mainAxisAlignment(Alignment.MainAxis.CENTER)` centers children along the flow's direction. +- `Flow.column().crossAxisAlignment(Alignment.CrossAxis.CENTER)` centers children perpendicular to the flow's direction. +- Remember to give the row/column a size on that axis (e.g. `widthRel(1f)` for a row). + +## Margin vs padding +Spacing is handled via two different concepts: + +- Margin: space outside a widget. Layouts (like `Flow`) include margins when positioning children. + - `marginTop(px)`, `marginBottom(px)`, `marginLeft(px)`, `marginRight(px)` set the padding in pixels for the directions + - `margin(all)`, `margin(horizontal, vertical)`, `margin(left, right, top, bottom)` are shortcuts for the respective methods +- Padding: space inside a widget. It reduces the content area and affects how children are placed. + - `paddingTop(px)`, `paddingBottom(px)`, `paddingLeft(px)`, `paddingRight(px)` sets the margin in pixels for the directions + - `padding(all)`, `padding(horizontal, vertical)`, `padding(left, right, top, bottom)` are shortcuts for the respective methods + +## Examples + +### Simple centered panel with a row of buttons +```java +ModularPanel panel = new ModularPanel("example") + .size(176, 168); + +panel.child(new ParentWidget<>() + .size(90, 63) + .align(Alignment.CENTER) + .child(Flow.row() + .coverChildren() + .childPadding(4) + .child(new ButtonWidget<>().size(16)) + .child(new ButtonWidget<>().size(16)) + .child(new ButtonWidget<>().size(16)))); +``` + +### Column with padding and left-aligned content +```java +Flow column = Flow.column() + .widthRel(1f) + .padding(10) + .crossAxisAlignment(Alignment.CrossAxis.START) + .child(new TextWidget<>(IKey.str("Title")).marginBottom(4)) + .child(new TextWidget<>(IKey.str("Body"))); +``` + +### Slot grid using absolute positioning +```java +ParentWidget slots = new ParentWidget<>(); +for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + slots.child(new ItemSlot() + .left(18 * x) + .top(18 * y)); + } +} +``` diff --git a/docs/content/Development/MUI2/index.md b/docs/content/Development/MUI2/index.md new file mode 100644 index 00000000000..f06372b41a0 --- /dev/null +++ b/docs/content/Development/MUI2/index.md @@ -0,0 +1,5 @@ +# ModularUI 2 +ModularUI 2 is the UI library used in GTM starting from 8.0.0, and is the successor to LDLib UI. +From ModularUI 1.12's github: + +With ModularUI you can build GUIs fast by adding Widgets to panels with layout widgets, so you don't have to calculate positions and sizes yourself. ModularUI is very dynamic and allows for very complicated client-server synced GUIs. \ No newline at end of file diff --git a/docs/content/Modpacks/Changes/v8.0.0.md b/docs/content/Modpacks/Changes/v8.0.0.md index 9e9aae3f31a..fdeb7e18ec6 100644 --- a/docs/content/Modpacks/Changes/v8.0.0.md +++ b/docs/content/Modpacks/Changes/v8.0.0.md @@ -143,4 +143,6 @@ A large number of machine feature interfaces have been removed, and have had the ## Other Changes - `BlastingRecipeBuilder`, `CampfireRecipeBuilder`, `SmeltingRecipeBuilder` and `SmokingRecipeBuilder` have been merged into `SimpleCookingRecipeBuilder` - - Example usage: `SimpleCookingRecipeBuilder.campfireCooking("cooking_chicken").input(new ItemStack(Items.CHICKEN)).output(new ItemStacks(Items.COOKED_CHICKEN)).cookingTime(100).experience(100).save(provider);` \ No newline at end of file + - Example usage: `SimpleCookingRecipeBuilder.campfireCooking("cooking_chicken").input(new ItemStack(Items.CHICKEN)).output(new ItemStacks(Items.COOKED_CHICKEN)).cookingTime(100).experience(100).save(provider);` +- `GTFluidImpl` has been merged into `GTFluid`, use `GTFluid.Flowing` and `GTFluid.Source` instead of `GTFluidImpl.Flowing` and `GTFluidImpl.Source`. +- Item behaviors have been moved to `common/item/behavior`, and some items have been moved from `api/item` to `common/item` diff --git a/docs/content/Modpacks/Examples/Example_Coil_Multiblock.md b/docs/content/Modpacks/Examples/Example_Coil_Multiblock.md new file mode 100644 index 00000000000..09fa577b996 --- /dev/null +++ b/docs/content/Modpacks/Examples/Example_Coil_Multiblock.md @@ -0,0 +1,102 @@ +--- +title: "Example Coil Multiblock" +--- + +### Superheated Pyrolyzing Oven Multiblock (by Phoenixvine) + + +Below is an example of a multiblock using the CoilWorkableElectricMultiblockMachine class and the pyrolyseOvenOverclock machine logic. + +### Multiblock +=== "JavaScript" + ```js title="superheated_pyrolyzing_oven_multiblock.js" + // In order to use multiblock logic extending beyond the normal WorkableElectricMultiblockMachine, (This is the multiblock type used by default for kubejs) you need to load a class. Coil multiblocks such as the Electric Blast Furnace, Pyrolyse Oven, and the Cracker use this class. + const CoilWorkableElectricMultiblockMachine = Java.loadClass("com.gregtechceu.gtceu.api.machine.multiblock.CoilWorkableElectricMultiblockMachine") + + GTCEuStartupEvents.registry('gtceu:machine', event => { + event.create("superheated_pyrolyzing_oven", "multiblock") + .machine((holder) => new CoilWorkableElectricMultiblockMachine(holder)) + .rotationState(RotationState.NON_Y_AXIS) + .recipeTypes('pyrolyse_oven') + .recipeModifiers( + [ + GTRecipeModifiers.PARALLEL_HATCH, + (machine, recipe) => GTRecipeModifiers.pyrolyseOvenOverclock(machine, recipe) + ] + ) + .appearanceBlock(GTBlocks.CASING_STEEL_SOLID) + .pattern(definition => FactoryBlockPattern.start() + .aisle("BBCCCBB", "BBCDCBB", "BBCCCBB", "BBCCCBB", "BBEEEBB", "BBEEEBB") + .aisle("BCCCCCB", "BCAFACB", "BCAFACB", "BCGGGCB", "BEAAAEB", "BEHHHEB") + .aisle("CCCCCCC", "CAAFAAC", "CAAFAAC", "CGGGGGC", "EAAAAAE", "EHHHHHE") + .aisle("CCCCCCC", "DFFFFFD", "CFFFFFC", "CGGGGGC", "EAAAAAE", "EHHMHHE") + .aisle("CCCCCCC", "CAAFAAC", "CAAFAAC", "CGGGGGC", "EAAAAAE", "EHHHHHE") + .aisle("BCCCCCB", "BCAFACB", "BCAFACB", "BCGGGCB", "BEAAAEB", "BEHHHEB") + .aisle("BBCCCBB", "BBCICBB", "BBCCCBB", "BBCCCBB", "BBEEEBB", "BBEEEBB") + .where('A', Predicates.blocks("minecraft:air")) + .where('B', Predicates.any()) + .where('C', Predicates.blocks('gtceu:solid_machine_casing').setMinGlobalLimited(10) + .or(Predicates.abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(Predicates.abilities(PartAbility.PARALLEL_HATCH).setMaxGlobalLimited(1)) + .or(Predicates.autoAbilities(definition.getRecipeTypes()))) + .where('D', Predicates.blocks("gtceu:steel_firebox_casing")) + .where('E', Predicates.blocks("gtceu:laminated_glass")) + .where('F', Predicates.blocks("gtceu:ptfe_pipe_casing")) + .where('G', Predicates.heatingCoils()) + .where('H', Predicates.blocks("gtceu:high_temperature_smelting_casing")) + .where('M', Predicates.abilities(PartAbility.MUFFLER).setExactLimit(1)) + .where('I', Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel("gtceu:block/casings/solid/machine_casing_solid_steel", + "gtceu:block/multiblock/pyrolyse_oven"); + + }) + ``` + + +=== "Java" + ```java title="MultiMachines.java" + + public static final MultiblockMachineDefinition SUPERHEATED_PYROLYZING_OVEN = REGISTRATE + .multiblock("superheated_pyrolyzing_oven", (holder) -> new CoilWorkableElectricMultiblockMachine(holder)) + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(GTRecipeTypes.PYROLYSE_RECIPES) + .recipeModifiers(GTRecipeModifiers.PARALLEL_HATCH, + (machine, recipe) -> GTRecipeModifiers.pyrolyseOvenOverclock(machine, recipe)) + .appearanceBlock(GTBlocks.CASING_STEEL_SOLID) + .pattern(definition -> FactoryBlockPattern.start() + .aisle("BBCCCBB", "BBCDCBB", "BBCCCBB", "BBCCCBB", "BBEEEBB", "BBEEEBB") + .aisle("BCCCCCB", "BCAFACB", "BCAFACB", "BCGGGCB", "BEAAAEB", "BEHHHEB") + .aisle("CCCCCCC", "CAAFAAC", "CAAFAAC", "CGGGGGC", "EAAAAAE", "EHHHHHE") + .aisle("CCCCCCC", "DFFFFFD", "CFFFFFC", "CGGGGGC", "EAAAAAE", "EHHMHHE") + .aisle("CCCCCCC", "CAAFAAC", "CAAFAAC", "CGGGGGC", "EAAAAAE", "EHHHHHE") + .aisle("BCCCCCB", "BCAFACB", "BCAFACB", "BCGGGCB", "BEAAAEB", "BEHHHEB") + .aisle("BBCCCBB", "BBCICBB", "BBCCCBB", "BBCCCBB", "BBEEEBB", "BBEEEBB") + .where('A', Predicates.air()) + .where('B', Predicates.any()) + .where('C', Predicates.blocks(GTBlocks.CASING_STEEL_SOLID.get()).setMinGlobalLimited(10) + .or(Predicates.abilities(PartAbility.MAINTENANCE).setExactLimit(1)) + .or(Predicates.abilities(PartAbility.PARALLEL_HATCH).setMaxGlobalLimited(1)) + .or(Predicates.autoAbilities(definition.getRecipeTypes()))) + .where('D', Predicates.blocks(GTBlocks.FIREBOX_STEEL.get())) + .where('E', Predicates.blocks(CASING_LAMINATED_GLASS.get())) + .where('F', Predicates.blocks(GTBlocks.CASING_POLYTETRAFLUOROETHYLENE_PIPE.get())) + .where('G', Predicates.heatingCoils()) + .where('H', Predicates.blocks(GCYMBlocks.CASING_HIGH_TEMPERATURE_SMELTING.get())) + .where('M', Predicates.abilities(PartAbility.MUFFLER).setExactLimit(1)) + .where('I', Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel(GTCEu.id("block/casings/solid/machine_casing_solid_steel"), + GTCEu.id("block/multiblock/pyrolyse_oven")) + .register(); + ``` + +### Lang + +```json title="en_us.json" +{ + "block.gtceu.superheated_pyrolyzing_oven": "Superheated Pyrolyzing Oven", +} +``` + + diff --git a/docs/content/Modpacks/Examples/Example_Steam_Multiblock.md b/docs/content/Modpacks/Examples/Example_Steam_Multiblock.md new file mode 100644 index 00000000000..57b06f47885 --- /dev/null +++ b/docs/content/Modpacks/Examples/Example_Steam_Multiblock.md @@ -0,0 +1,87 @@ +--- +title: "Example Steam Multiblock" +--- + +### Large Steam Compressor Multiblock + +Below is an example of a multiblock using the SteamParallelMultiblockMachine class. +Steam multiblocks such as the Steam Grinder and Steam Oven use this class. + +### Multiblock + +=== "JavaScript" + ```js title="example_steam_multiblock_multiblock.js" + + // In order to use multiblock logic extending beyond the default multiblock type for KJS (WorkableElectricMultiblockMachine), you need to load a class. + const $SteamMulti = Java.loadClass('com.gregtechceu.gtceu.common.machine.multiblock.steam.SteamParallelMultiblockMachine'); + + GTCEuStartupEvents.registry('gtceu:machine', event => { + event.create('large_steam_compressor', 'multiblock') + .machine((holder) => new $SteamMulti(holder, 4)) + // The number in holder is the max amount of parallel it can use. + .rotationState(RotationState.NON_Y_AXIS) + .recipeType('compressor') + .recipeModifier((machine, recipe) => $SteamMulti.recipeModifier(machine, recipe), true) + .pattern(definition => FactoryBlockPattern.start() + .aisle("BCCCB", "BBCBB", "BBCBB", "BBBBB", "BBBBB") + .aisle("CDDDC", "BDBDB", "BDEDB", "BBDBB", "BBBBB") + .aisle("CDDDC", "CBBBC", "CEFEC", "BDDDB", "BBGBB") + .aisle("CDDDC", "BDBDB", "BDEDB", "BBDBB", "BBBBB") + .aisle("BCCCB", "BBHBB", "BBCBB", "BBBBB", "BBBBB") + .where('B', Predicates.any()) + .where('C', Predicates.blocks('gtceu:steam_machine_casing').setMinGlobalLimited(10) + .or(Predicates.abilities(PartAbility.STEAM_IMPORT_ITEMS).setMaxGlobalLimited(1)) + .or(Predicates.abilities(PartAbility.STEAM).setMaxGlobalLimited(1)) + .or(Predicates.abilities(PartAbility.STEAM_EXPORT_ITEMS).setMaxGlobalLimited(1))) + .where('D', Predicates.blocks("gtceu:industrial_steam_casing")) + .where('E', Predicates.blocks("gtceu:bronze_brick_casing")) + .where('F', Predicates.blocks("gtceu:bronze_firebox_casing")) + .where('G', Predicates.blocks("gtceu:bronze_machine_casing")) + .where('H', Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel("gtceu:block/casings/steam/bronze/bottom", + "gtceu:block/machines/compressor") + }) + ``` + +=== "Java" + ```java title="MultiMachines.java" + public static final MultiblockMachineDefinition LARGE_STEAM_COMPRESSOR = REGISTRATE + .multiblock("large_steam_compressor", (holder) -> new SteamParallelMultiblockMachine(holder, 4)) + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(COMPRESSOR_RECIPES) + .recipeModifier((machine, recipe) -> SteamParallelMultiblockMachine.recipeModifier(machine, recipe), true) + .pattern(definition -> FactoryBlockPattern.start() + .aisle("BCCCB", "BBCBB", "BBCBB", "BBBBB", "BBBBB") + .aisle("CDDDC", "BDBDB", "BDEDB", "BBDBB", "BBBBB") + .aisle("CDDDC", "CBBBC", "CEFEC", "BDDDB", "BBGBB") + .aisle("CDDDC", "BDBDB", "BDEDB", "BBDBB", "BBBBB") + .aisle("BCCCB", "BBHBB", "BBCBB", "BBBBB", "BBBBB") + .where('B', Predicates.any()) + .where('C', Predicates.blocks(GTBlocks.CASING_BRONZE_BRICKS.get()).setMinGlobalLimited(10) + .or(Predicates.abilities(PartAbility.STEAM_IMPORT_ITEMS).setMaxGlobalLimited(1)) + .or(Predicates.abilities(PartAbility.STEAM).setMaxGlobalLimited(1)) + .or(Predicates.abilities(PartAbility.STEAM_EXPORT_ITEMS).setMaxGlobalLimited(1))) + .where('D', Predicates.blocks(GCYMBlocks.CASING_INDUSTRIAL_STEAM.get())) + .where('E', Predicates.blocks(GTBlocks.BRONZE_BRICKS_HULL.get())) + .where('F', Predicates.blocks(GTBlocks.FIREBOX_BRONZE.get())) + .where('G', Predicates.blocks(GTBlocks.BRONZE_HULL.get())) + .where('H', Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel(GTCEu.id("block/casings/steam/bronze/bottom"), + GTCEu.id("block/machines/compressor")) + .register(); + ``` + +### Lang + +```json title="en_us.json" +{ + "block.gtceu.large_steam_compressor": "Large Steam Compressor", +} +``` + + + + + diff --git a/docs/content/Modpacks/Examples/Example_Turbine.md b/docs/content/Modpacks/Examples/Example_Turbine.md new file mode 100644 index 00000000000..4abdeefe1d5 --- /dev/null +++ b/docs/content/Modpacks/Examples/Example_Turbine.md @@ -0,0 +1,94 @@ +--- +title: "Example Turbine" +--- + +### Example Turbine + +Below is an example of a multiblock using the LargeTurbineMachine class for making custom large turbines. + +### Multiblock + +=== "JavaScript" + ```js title="hyper_gas_turbine.js" + // In order to use multiblock logic extending beyond the normal WorkableElectricMultiblockMachine, (This is the multiblock type used by default for kubejs) you need to load a class. LargeTurbineMachines such as the gas, steam, and plasma turbines use this class. + const $LargeTurbineMachine = Java.loadClass("com.gregtechceu.gtceu.common.machine.multiblock.generator.LargeTurbineMachine") + + GTCEuStartupEvents.registry('gtceu:machine', event => { + event.create('hyper_gas_turbine', 'multiblock') + .machine((holder) => new $LargeTurbineMachine(holder, GTValues.LuV)) // The value shows one rotor holder tier above the recommended minimum rotor holder. The tier of rotor holder provides a boost based on the efficiency stat. + .rotationState(RotationState.NON_Y_AXIS) + .recipeTypes("gas_turbine") + .recipeModifiers([GTRecipeModifiers.OC_NON_PERFECT_SUBTICK, GTRecipeModifiers.BATCH_MODE, (machine, recipe) => GTRecipeModifiers.LargeTurbineMachine(machine, recipe)]) + .appearanceBlock(GTBlocks.CASING_TITANIUM_STABLE) + .pattern(definition => FactoryBlockPattern.start() + .aisle("BBBBBBB", "BBBCBBB", "BBBDBBB", "BBBCBBB", "BBBBBBB") + .aisle("BBBCBBB", "BBCACBB", "BBCFCBB", "BBCACBB", "BBBCBBB") + .aisle("BBCCCBB", "BCAAACB", "BCAFACB", "BCAFACB", "BBCCCBB") + .aisle("BCCCCCB", "CAAFAAC", "CFFFFFC", "CAFFFAC", "BCCECCB") + .aisle("BBCCCBB", "BCAAACB", "BCAFACB", "BCAFACB", "BBCCCBB") + .aisle("BBBCBBB", "BBCACBB", "BBCFCBB", "BBCACBB", "BBBCBBB") + .aisle("BBBBBBB", "BBBCBBB", "BBBGBBB", "BBBCBBB", "BBBBBBB") + .where("A", Predicates.blocks("minecraft:air")) + .where("B", Predicates.any()) + .where("C", Predicates.blocks("gtceu:stainless_steel_turbine_casing") + .or(Predicates.autoAbilities(definition.getRecipeTypes())) + .or(Predicates.abilities(PartAbility.MAINTENANCE).setExactLimit(1))) + .where("D", Predicates.ability(PartAbility.MUFFLER).setExactLimit(1)) + .where("E", Predicates.ability(PartAbility.ROTOR_HOLDER).setExactLimit(1)) + .where("F", Predicates.blocks("gtceu:stainless_steel_frame")) + .where("G", Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel("gtceu:block/casings/mechanic/machine_casing_turbine_stainless_steel", + "gtceu:block/multiblock/generator/large_gas_turbine") + }); + ``` + +=== "Java" + ```java title="MultiMachines.java" + public static final MultiblockMachineDefinition HYPER_GAS_TURBINE = REGISTRATE + .multiblock("hyper_gas_turbine", (holder) -> new LargeTurbineMachine(holder, GTValues.LuV, 4)) // The value shows one rotor holder tier above the recommended minimum rotor holder. The tier of rotor holder provides a boost based on the efficiency stat. + .rotationState(RotationState.NON_Y_AXIS) + .recipeType(GTRecipeTypes.GAS_TURBINE_FUELS) + .recipeModifiers(GTRecipeModifiers.OC_NON_PERFECT_SUBTICK, GTRecipeModifiers.BATCH_MODE, LargeTurbineMachine::recipeModifier) + .pattern(definition -> FactoryBlockPattern.start() + .aisle("BBBBBBB", "BBBCBBB", "BBBDBBB", "BBBCBBB", "BBBBBBB") + .aisle("BBBCBBB", "BBCACBB", "BBCECBB", "BBCACBB", "BBBCBBB") + .aisle("BBCCCBB", "BCAAACB", "BCAEACB", "BCAEACB", "BBCCCBB") + .aisle("BCCCCCB", "CAAEAAC", "CEEEEEC", "CAEEEAC", "BCCFCCB") + .aisle("BBCCCBB", "BCAAACB", "BCAEACB", "BCAEACB", "BBCCCBB") + .aisle("BBBCBBB", "BBCACBB", "BBCECBB", "BBCACBB", "BBBCBBB") + .aisle("BBBBBBB", "BBBCBBB", "BBBGBBB", "BBBCBBB", "BBBBBBB") + .where("A", Predicates.blocks("minecraft:air")) + .where("B", Predicates.any()) + .where("C", Predicates.blocks("gtceu:stainless_steel_turbine_casing") + .or(Predicates.autoAbilities(definition.getRecipeTypes())) + .or(Predicates.abilities(PartAbility.MAINTENANCE).setExactLimit(1))) + .where("D", Predicates.ability(PartAbility.MUFFLER).setExactLimit(1)) + .where("F", Predicates.ability(PartAbility.ROTOR_HOLDER).setExactLimit(1)) + .where("E", Predicates.blocks("gtceu:stainless_steel_frame")) + .where("G", Predicates.controller(Predicates.blocks(definition.get()))) + .build()) + .workableCasingModel(GTCEu.id("block/casings/steam/bronze/bottom"), + GTCEu.id("block/machines/compressor")) + .register(); + ``` + + +### Lang + +```json title="en_us.json" +{ + "block.gtceu.hyper_gas_turbine": "Hyper Gas Turbine", +} +``` + + + + + + + + + + + diff --git a/docs/content/Modpacks/Examples/Greenhouse.md b/docs/content/Modpacks/Examples/Greenhouse.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/content/Modpacks/Materials-and-Elements/Material-Properties.md b/docs/content/Modpacks/Materials-and-Elements/Material-Properties.md index 0108ec3c6f5..793950efaf2 100644 --- a/docs/content/Modpacks/Materials-and-Elements/Material-Properties.md +++ b/docs/content/Modpacks/Materials-and-Elements/Material-Properties.md @@ -102,7 +102,7 @@ Properties can be applied to a material to decide how they behave. An example of - `.washedIn(string fluid)` - `string fluid` Is what fluid it uses for if it has the ore prop and is making crushed->refined. For example, the sodium persulfate and mercury ore washing recipes. - `.separatedInto(list material)` - - `list material` Is the list of materials that are obtained when processing purified dusts in the centrifuge. + - `list material` Is the list of materials that are obtained when processing purified dusts in the electromagnetic seperator. There is a max of two materials. - `.oreSmeltInto(string material)` - `string material` Is what is obtained through directly smelting the ore. diff --git a/gradle/forge.versions.toml b/gradle/forge.versions.toml index 1f203aa06af..1d0db77b89f 100644 --- a/gradle/forge.versions.toml +++ b/gradle/forge.versions.toml @@ -11,6 +11,7 @@ ae2 = "15.0.18" kubejs = "2001.6.5-build.16" rhino = "2001.2.3-build.10" architectury = "9.2.14" +clothmath = "+" clothconfig = "11.1.136" curios = "5.9.1+1.20.1" kotlinforforge = "4.11.0" @@ -75,6 +76,7 @@ flywheel-forge = { module = "dev.engine-room.flywheel:flywheel-forge-1.20.1 kubejs = { module = "dev.latvian.mods:kubejs-forge", version.ref = "kubejs" } rhino = { module = "dev.latvian.mods:rhino-forge", version.ref = "rhino" } architectury = { module = "dev.architectury:architectury-forge", version.ref = "architectury" } +clothmath = { module = "me.shedaniel.cloth:basic-math", version.ref = "clothmath" } clothconfig = { module = "me.shedaniel.cloth:cloth-config-forge", version.ref = "clothconfig" } curios = { module = "top.theillusivec4.curios:curios-forge", version.ref = "curios" } kotlinforforge = { module = "thedarkcolour:kotlinforforge", version.ref = "kotlinforforge" } @@ -116,7 +118,7 @@ ftbchunks-cm = { module = "curse.maven:ftb-chunks-forge-314906", version. [bundles] jei = ["jei-common-api", "jei-forge-api", "jei-forge-impl"] -rei = ["rei-plugin", "rei-forge"] +rei = ["rei-plugin", "rei-forge", "clothmath"] rei-runtime = ["rei-forge", "architectury", "clothconfig"] kjs = ["kubejs", "rhino", "architectury"] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96029a58b06..880fc1a6e45 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ lombok = "8.14" jetbrains-annotations = "26.0.1" renderNurse = "0.0.12" mixin = "0.8.7" +evalEx = "3.6.0" [libraries] minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } @@ -17,6 +18,7 @@ minecraftForge = { module = "net.minecraftforge:forge", version.ref = " jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } renderNurse = { module = "net.neoforged:render-nurse", version.ref = "renderNurse" } mixin = { module = "org.spongepowered:mixin", version.ref = "mixin" } +evalEx = { module = "com.ezylang:EvalEx", version.ref = "evalEx" } [plugins] modDevGradle = { id = "net.neoforged.moddev.legacyforge", version.ref = "modDevGradle" } diff --git a/gradle/scripts/repositories.gradle b/gradle/scripts/repositories.gradle index 167432e271f..4ce761a0199 100644 --- a/gradle/scripts/repositories.gradle +++ b/gradle/scripts/repositories.gradle @@ -1,12 +1,13 @@ repositories { mavenLocal() mavenCentral() - // force gradle to download FastUtil & LWJGL from the central maven so we can get source artifacts + // force gradle to download FastUtil and LWJGL from the central maven so we can get source artifacts exclusiveContent { // Force forRepository { mavenCentral() } filter { includeGroup("it.unimi.dsi") includeGroup("org.lwjgl") + includeGroupAndSubgroups("io.netty") } } diff --git a/src/generated/resources/assets/gtceu/blockstates/test_mui.json b/src/generated/resources/assets/gtceu/blockstates/test_mui.json new file mode 100644 index 00000000000..c96e32638d9 --- /dev/null +++ b/src/generated/resources/assets/gtceu/blockstates/test_mui.json @@ -0,0 +1,28 @@ +{ + "variants": { + "facing=down": { + "model": "gtceu:block/machine/test_mui", + "x": 90 + }, + "facing=east": { + "model": "gtceu:block/machine/test_mui", + "y": 90 + }, + "facing=north": { + "model": "gtceu:block/machine/test_mui" + }, + "facing=south": { + "model": "gtceu:block/machine/test_mui", + "y": 180 + }, + "facing=up": { + "gtceu:z": 180, + "model": "gtceu:block/machine/test_mui", + "x": 270 + }, + "facing=west": { + "model": "gtceu:block/machine/test_mui", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/blockstates/test_mui_new.json b/src/generated/resources/assets/gtceu/blockstates/test_mui_new.json new file mode 100644 index 00000000000..41ab9b075f2 --- /dev/null +++ b/src/generated/resources/assets/gtceu/blockstates/test_mui_new.json @@ -0,0 +1,28 @@ +{ + "variants": { + "facing=down": { + "model": "gtceu:block/machine/test_mui_new", + "x": 90 + }, + "facing=east": { + "model": "gtceu:block/machine/test_mui_new", + "y": 90 + }, + "facing=north": { + "model": "gtceu:block/machine/test_mui_new" + }, + "facing=south": { + "model": "gtceu:block/machine/test_mui_new", + "y": 180 + }, + "facing=up": { + "gtceu:z": 180, + "model": "gtceu:block/machine/test_mui_new", + "x": 270 + }, + "facing=west": { + "model": "gtceu:block/machine/test_mui_new", + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/lang/en_ud.json b/src/generated/resources/assets/gtceu/lang/en_ud.json index 892d9e3836c..3189bc754e3 100644 --- a/src/generated/resources/assets/gtceu/lang/en_ud.json +++ b/src/generated/resources/assets/gtceu/lang/en_ud.json @@ -1134,6 +1134,8 @@ "block.gtceu.substation_capacitor.tooltip_filled": "∩Ǝ %dɟ§ :ʎʇıɔɐdɐƆ ʎbɹǝuƎɔ§", "block.gtceu.superconducting_coil": "ʞɔoןᗺ ןıoƆ buıʇɔnpuoɔɹǝdnS", "block.gtceu.tempered_glass": "ssɐן⅁ pǝɹǝdɯǝ⟘", + "block.gtceu.test_mui": "ınW ʇsǝ⟘", + "block.gtceu.test_mui_new": "ʍǝN ınW ʇsǝ⟘", "block.gtceu.the_end_marker": "puƎ ǝɥ⟘", "block.gtceu.the_nether_marker": "ɹǝɥʇǝN ǝɥ⟘", "block.gtceu.titanium_crate": "ǝʇɐɹƆ ɯnıuɐʇı⟘", @@ -1746,11 +1748,11 @@ "command.gtceu.share_prospection_data.notification": "¡noʎ ɥʇıʍ ɐʇɐp buıʇɔǝdsoɹd buıɹɐɥs sı %s", "config.gtceu.option.addLoot": "ʇooꞀppɐ", "config.gtceu.option.ae2": "ᄅǝɐ", - "config.gtceu.option.allowDrumsInputFluidsFromOutputSide": "ǝpıSʇndʇnOɯoɹℲspınןℲʇnduIsɯnɹᗡʍoןןɐ", "config.gtceu.option.allowedImageDomains": "suıɐɯoᗡǝbɐɯIpǝʍoןןɐ", "config.gtceu.option.animationTime": "ǝɯı⟘uoıʇɐɯıuɐ", "config.gtceu.option.arcRecyclingYield": "pןǝıʎbuıןɔʎɔǝᴚɔɹɐ", "config.gtceu.option.armorHud": "pnHɹoɯɹɐ", + "config.gtceu.option.autoRebuildResources": "sǝɔɹnosǝᴚpןınqǝᴚoʇnɐ", "config.gtceu.option.batchDuration": "uoıʇɐɹnᗡɥɔʇɐq", "config.gtceu.option.bedrockOreDistance": "ǝɔuɐʇsıᗡǝɹOʞɔoɹpǝq", "config.gtceu.option.bedrockOreDropTagPrefix": "xıɟǝɹԀbɐ⟘doɹᗡǝɹOʞɔoɹpǝq", @@ -1766,7 +1768,9 @@ "config.gtceu.option.coloredWireOutline": "ǝuıןʇnOǝɹıMpǝɹoןoɔ", "config.gtceu.option.compat": "ʇɐdɯoɔ", "config.gtceu.option.createCompat": "ʇɐdɯoƆǝʇɐǝɹɔ", + "config.gtceu.option.cursorColor": "ɹoןoƆɹosɹnɔ", "config.gtceu.option.debug": "bnqǝp", + "config.gtceu.option.debugUI": "I∩bnqǝp", "config.gtceu.option.debugWorldgen": "uǝbpןɹoMbnqǝp", "config.gtceu.option.defaultPaintingColor": "ɹoןoƆbuıʇuıɐԀʇןnɐɟǝp", "config.gtceu.option.defaultUIColor": "ɹoןoƆI∩ʇןnɐɟǝp", @@ -1794,6 +1798,7 @@ "config.gtceu.option.energyUsageMultiplier": "ɹǝıןdıʇןnWǝbɐs∩ʎbɹǝuǝ", "config.gtceu.option.environmentalHazardDecayRate": "ǝʇɐᴚʎɐɔǝᗡpɹɐzɐHןɐʇuǝɯuoɹıʌuǝ", "config.gtceu.option.environmentalHazards": "spɹɐzɐHןɐʇuǝɯuoɹıʌuǝ", + "config.gtceu.option.escRestoresLastText": "ʇxǝ⟘ʇsɐꞀsǝɹoʇsǝᴚɔsǝ", "config.gtceu.option.euToFeRatio": "oıʇɐᴚǝℲo⟘nǝ", "config.gtceu.option.extractorRecyclingYield": "pןǝıʎbuıןɔʎɔǝᴚɹoʇɔɐɹʇxǝ", "config.gtceu.option.feToEuRatio": "oıʇɐᴚnƎo⟘ǝɟ", @@ -1845,6 +1850,7 @@ "config.gtceu.option.meHatchEnergyUsage": "ǝbɐs∩ʎbɹǝuƎɥɔʇɐHǝɯ", "config.gtceu.option.minerSpeed": "pǝǝdSɹǝuıɯ", "config.gtceu.option.minimap": "dɐɯıuıɯ", + "config.gtceu.option.mui": "ınɯ", "config.gtceu.option.nanoSaber": "ɹǝqɐSouɐu", "config.gtceu.option.nanoSaberBaseDamage": "ǝbɐɯɐᗡǝsɐᗺɹǝqɐSouɐu", "config.gtceu.option.nanoSaberDamageBoost": "ʇsooᗺǝbɐɯɐᗡɹǝqɐSouɐu", @@ -1865,6 +1871,7 @@ "config.gtceu.option.oreVeinGridSize": "ǝzıSpıɹ⅁uıǝΛǝɹo", "config.gtceu.option.oreVeinRandomOffset": "ʇǝsɟɟOɯopuɐᴚuıǝΛǝɹo", "config.gtceu.option.oreVeins": "suıǝΛǝɹo", + "config.gtceu.option.outlineColor": "ɹoןoƆǝuıןʇno", "config.gtceu.option.ownerOPBypass": "ssɐdʎᗺԀOɹǝuʍo", "config.gtceu.option.prospectorEnergyUseMultiplier": "ɹǝıןdıʇןnWǝs∩ʎbɹǝuƎɹoʇɔǝdsoɹd", "config.gtceu.option.quantumTank": "ʞuɐ⟘ɯnʇuɐnb", @@ -1878,14 +1885,28 @@ "config.gtceu.option.renderGrowingPlants": "sʇuɐןԀbuıʍoɹ⅁ɹǝpuǝɹ", "config.gtceu.option.renderer": "ɹǝɹǝpuǝɹ", "config.gtceu.option.replaceMinedBlocksWith": "ɥʇıMsʞɔoןᗺpǝuıWǝɔɐןdǝɹ", + "config.gtceu.option.replaceVanillaTooltips": "sdıʇןoo⟘ɐןןıuɐΛǝɔɐןdǝɹ", "config.gtceu.option.replaceWithCobbleVersion": "uoısɹǝΛǝןqqoƆɥʇıMǝɔɐןdǝɹ", "config.gtceu.option.requireGTToolsForBlocks": "sʞɔoןᗺɹoℲsןoo⟘⟘⅁ǝɹınbǝɹ", "config.gtceu.option.rngDamageElectricTools": "sןoo⟘ɔıɹʇɔǝןƎǝbɐɯɐᗡbuɹ", "config.gtceu.option.rubberTreeSpawnChance": "ǝɔuɐɥƆuʍɐdSǝǝɹ⟘ɹǝqqnɹ", "config.gtceu.option.sandOresFall": "ןןɐℲsǝɹOpuɐs", + "config.gtceu.option.scale": "ǝןɐɔs", "config.gtceu.option.shouldWeatherOrTerrainExplosion": "uoısoןdxƎuıɐɹɹǝ⟘ɹOɹǝɥʇɐǝMpןnoɥs", "config.gtceu.option.showDimensionTier": "ɹǝı⟘uoısuǝɯıᗡʍoɥs", + "config.gtceu.option.showExtra": "ɐɹʇxƎʍoɥs", + "config.gtceu.option.showHovered": "pǝɹǝʌoHʍoɥs", + "config.gtceu.option.showOutline": "ǝuıןʇnOʍoɥs", + "config.gtceu.option.showParent": "ʇuǝɹɐԀʍoɥs", + "config.gtceu.option.showParentOutline": "ǝuıןʇnOʇuǝɹɐԀʍoɥs", + "config.gtceu.option.showParentPos": "soԀʇuǝɹɐԀʍoɥs", + "config.gtceu.option.showParentSize": "ǝzıSʇuǝɹɐԀʍoɥs", + "config.gtceu.option.showParentWidgetTheme": "ǝɯǝɥ⟘ʇǝbpıMʇuǝɹɐԀʍoɥs", + "config.gtceu.option.showPos": "soԀʍoɥs", + "config.gtceu.option.showSize": "ǝzıSʍoɥs", + "config.gtceu.option.showWidgetTheme": "ǝɯǝɥ⟘ʇǝbpıMʍoɥs", "config.gtceu.option.smallBoilers": "sɹǝןıoᗺןןɐɯs", + "config.gtceu.option.smoothProgressBar": "ɹɐᗺssǝɹboɹԀɥʇooɯs", "config.gtceu.option.solarBoilerBaseOutput": "ʇndʇnOǝsɐᗺɹǝןıoᗺɹɐןos", "config.gtceu.option.solidBoilerBaseOutput": "ʇndʇnOǝsɐᗺɹǝןıoᗺpıןos", "config.gtceu.option.sprayCanChainLength": "ɥʇbuǝꞀuıɐɥƆuɐƆʎɐɹds", @@ -1897,17 +1918,21 @@ "config.gtceu.option.surfaceRockProspectRange": "ǝbuɐᴚʇɔǝdsoɹԀʞɔoᴚǝɔɐɟɹns", "config.gtceu.option.tankItemFluidPreview": "ʍǝıʌǝɹԀpınןℲɯǝʇIʞuɐʇ", "config.gtceu.option.temperaturesInCelsius": "snısןǝƆuIsǝɹnʇɐɹǝdɯǝʇ", + "config.gtceu.option.textColor": "ɹoןoƆʇxǝʇ", "config.gtceu.option.titaniumBoilerHeatSpeed": "pǝǝdSʇɐǝHɹǝןıoᗺɯnıuɐʇıʇ", "config.gtceu.option.titaniumBoilerMaxTemperature": "ǝɹnʇɐɹǝdɯǝ⟘xɐWɹǝןıoᗺɯnıuɐʇıʇ", "config.gtceu.option.toggle": "ǝןbboʇ", "config.gtceu.option.toolCraftingSounds": "spunoSbuıʇɟɐɹƆןooʇ", "config.gtceu.option.toolUseSounds": "spunoSǝs∩ןooʇ", "config.gtceu.option.tools": "sןooʇ", + "config.gtceu.option.tooltipPos": "soԀdıʇןooʇ", "config.gtceu.option.treeFellingDelay": "ʎɐןǝᗡbuıןןǝℲǝǝɹʇ", "config.gtceu.option.tungstensteelBoilerHeatSpeed": "pǝǝdSʇɐǝHɹǝןıoᗺןǝǝʇsuǝʇsbunʇ", "config.gtceu.option.tungstensteelBoilerMaxTemperature": "ǝɹnʇɐɹǝdɯǝ⟘xɐWɹǝןıoᗺןǝǝʇsuǝʇsbunʇ", + "config.gtceu.option.ui": "ın", "config.gtceu.option.universalHazards": "spɹɐzɐHןɐsɹǝʌıun", "config.gtceu.option.updateIntervals": "sןɐʌɹǝʇuIǝʇɐpdn", + "config.gtceu.option.useDarkThemeByDefault": "ʇןnɐɟǝᗡʎᗺǝɯǝɥ⟘ʞɹɐᗡǝsn", "config.gtceu.option.useVBO": "OᗺΛǝsn", "config.gtceu.option.voltageTierAdvImpeller": "ɹǝןןǝdɯIʌpⱯɹǝı⟘ǝbɐʇןoʌ", "config.gtceu.option.voltageTierAdvNanoSuit": "ʇınSouɐNʌpⱯɹǝı⟘ǝbɐʇןoʌ", @@ -2018,20 +2043,18 @@ "cover.conveyor.transfer_rate": "ɔǝs/sɯǝʇıㄥ§", "cover.detector_base.message_inverted_state": "pǝʇɹǝʌuI :snʇɐʇS buıɹoʇıuoW", "cover.detector_base.message_normal_state": "ןɐɯɹoN :snʇɐʇS buıɹoʇıuoW", - "cover.ender_fluid_link.incomplete_hex.0": "¡ǝʇǝןdɯoɔuı sı ɹoןoɔ pǝʇʇnduI", - "cover.ender_fluid_link.incomplete_hex.1": ")sʇıbıp xǝɥ 8 ןןɐ( ǝʇǝןdɯoɔ ǝɔuo pǝıןddɐ ǝq ןןıʍ ʇI", - "cover.ender_fluid_link.incomplete_hex.2": "¡sʇıpǝ ǝsoן ןןıʍ ınb ǝɥʇ buısoןƆ", "cover.ender_fluid_link.iomode.disabled": "pǝןqɐsıᗡ O/I", "cover.ender_fluid_link.iomode.enabled": "pǝןqɐuƎ O/I", - "cover.ender_fluid_link.private.tooltip.disabled.0": "ǝpoɯ ʞuɐʇ ǝʇɐʌıɹd oʇ ɥɔʇıʍS", - "cover.ender_fluid_link.private.tooltip.disabled.1": "ɹǝʌoɔ ǝɥʇ pǝɔɐןd ʎןןɐuıbıɹo oɥʍ ɹǝʎɐןd ǝɥʇ sǝsn ǝpoɯ ǝʇɐʌıɹԀ", - "cover.ender_fluid_link.private.tooltip.enabled": "ǝpoɯ ʞuɐʇ ɔıןqnd oʇ ɥɔʇıʍS", "cover.ender_fluid_link.title": "ʞuıꞀ pınןℲ ɹǝpuƎ", - "cover.ender_fluid_link.tooltip.channel_description": "ʇxǝʇ ʇnduı ɥʇıʍ uoıʇdıɹɔsǝp ןǝuuɐɥɔ ʇǝS", - "cover.ender_fluid_link.tooltip.channel_name": "ʇxǝʇ ʇnduı ɥʇıʍ ǝɯɐu ןǝuuɐɥɔ ʇǝS", - "cover.ender_fluid_link.tooltip.clear_button": "uoıʇdıɹɔsǝp ןǝuuɐɥɔ ɹɐǝןƆ", - "cover.ender_fluid_link.tooltip.list_button": "ʇsıן ןǝuuɐɥɔ ʍoɥS", "cover.ender_item_link.title": "ʞuıꞀ ɯǝʇI ɹǝpuƎ", + "cover.ender_link.description_empty": "uoıʇdıɹɔsǝᗡ ʎʇdɯƎ", + "cover.ender_link.private.tooltip": "ɹǝʌoɔ sıɥʇ pǝɔɐןd oɥʍ ɹǝʎɐןd ǝɥʇ oʇ ǝןqıssǝɔɔɐ ʎןuO :ǝpoɯ ǝʇɐʌıɹԀ", + "cover.ender_link.protected.tooltip": "˙ɯɐǝʇ ǝɯɐs ǝɥʇ uo sɹǝʎɐןd oʇ ǝןqıssǝɔɔⱯ :ǝpoW pǝʇɔǝʇoɹԀ", + "cover.ender_link.public.tooltip": "sɹǝʎɐןd ןןɐ oʇ ǝןqıssǝɔɔⱯ :ǝpoɯ ɔıןqnԀ", + "cover.ender_link.tooltip.channel_description": "uoıʇdıɹɔsǝp ןǝuuɐɥƆ", + "cover.ender_link.tooltip.channel_name": ")ǝnןɐʌ ןɐɯıɔǝpɐxǝɥ ʇıq-ᄅƐ( ᗡI ןǝuuɐɥƆ", + "cover.ender_link.tooltip.clear_button": "uoıʇdıɹɔsǝp ןǝuuɐɥɔ ɹɐǝןƆ", + "cover.ender_link.tooltip.list_button": "ʇsıן ןǝuuɐɥɔ ʍoɥS", "cover.ender_redstone_link.title": "ʞuıꞀ ǝuoʇspǝᴚ ɹǝpuƎ", "cover.filter.blacklist.disabled": "ʇsıןǝʇıɥM", "cover.filter.blacklist.enabled": "ʇsıןʞɔɐןᗺ", @@ -2069,6 +2092,7 @@ "cover.item_smart_filter.filtering_mode.electrolyzer": "ɹǝzʎןoɹʇɔǝןƎ", "cover.item_smart_filter.filtering_mode.sifter": "ɹǝʇɟıS", "cover.item_smart_filter.title": "ɹǝʇןıℲ ɯǝʇI ʇɹɐɯS", + "cover.machine_controller.control": "ʇǝbɹɐ⟘ ɹǝןןoɹʇuoƆ", "cover.machine_controller.invert.disabled.0": "unɹ oʇ ןǝʌǝן ǝuoʇspǝɹ ʇǝs ǝɥʇ uɐɥʇ ɹǝʞɐǝʍ ןɐubıs ɐ ǝɹınbǝɹ ןןıʍ ɹǝʌoɔ ǝɥʇ 'ǝpoɯ sıɥʇ uı - ɹ§ןɐɯɹoNǝ§", "cover.machine_controller.invert.enabled.0": "unɹ oʇ ןǝʌǝן ǝuoʇspǝɹ ʇǝs ǝɥʇ uɐɥʇ ɹǝbuoɹʇs ןɐubıs ɐ ǝɹınbǝɹ ןןıʍ ɹǝʌoɔ ǝɥʇ 'ǝpoɯ sıɥʇ uı - ɹ§pǝʇɹǝʌuIǝ§", "cover.machine_controller.inverted": "pǝʇɹǝʌuI", @@ -2226,6 +2250,10 @@ "gtceu.canner": "ɹǝuuɐƆ", "gtceu.central_monitor.gui.create_group": "dnoɹb ǝʇɐǝɹƆ", "gtceu.central_monitor.gui.currently_editing": "%s :buıʇıpǝ ʎןʇuǝɹɹnƆ", + "gtceu.central_monitor.gui.data_slot": "ʞɔıʇs ɐʇɐp ɟo ɹǝqɯnu ʇoןs ʇǝS", + "gtceu.central_monitor.gui.group_editor": "dnoɹb ɹoʇıuoɯ buıʇıpƎ", + "gtceu.central_monitor.gui.group_name": ":ǝɯɐN", + "gtceu.central_monitor.gui.monitor_groups": "sdnoɹb ɹoʇıuoW", "gtceu.central_monitor.gui.remove_from_group": "dnoɹb ɯoɹɟ ǝʌoɯǝᴚ", "gtceu.central_monitor.gui.set_target": "ʇǝbɹɐʇ ʇǝS", "gtceu.central_monitor.info_tooltip.0": "˙ʇı uı ǝןnpoɯ Ɩ ǝʌɐɥ ʎןuo ʎɐɯ dnoɹb Ɐ ˙ʇsɹıɟ sdnoɹb oʇuı ɯǝɥʇ ʇıןds oʇ ǝʌɐɥ noʎ 'sɹoʇıuoɯ ǝsn oʇ ɹǝpɹo uI", @@ -2282,7 +2310,8 @@ "gtceu.creative.chest.ipc": "ǝןɔʎƆ ɹǝd sɯǝʇI", "gtceu.creative.chest.item": "ɯǝʇI", "gtceu.creative.chest.tpc": "ǝןɔʎƆ ɹǝd sʞɔı⟘", - "gtceu.creative.computation.average": "ʇ∩MƆ pǝʇsǝnbǝᴚ ǝbɐɹǝʌⱯ", + "gtceu.creative.computation.average": "%d :ʞɔıʇ/∩MƆ ǝbɐɹǝʌⱯ", + "gtceu.creative.computation.max_usage": ":ʞɔıʇ/∩MƆ xɐW", "gtceu.creative.energy.amperage": "ǝbɐɹǝdɯⱯ", "gtceu.creative.energy.sink": "ʞuıS", "gtceu.creative.energy.source": "ǝɔɹnoS", @@ -2295,6 +2324,8 @@ "gtceu.creative_tooltip.3": "sıɥʇ ǝsn oʇ ㄥ§", "gtceu.cutter": "ɹǝʇʇnƆ", "gtceu.debug.f3_h.enabled": "ǝɹoɯ ǝǝs oʇ ǝןıɟ bıɟuoɔ ɥɔǝ⟘bǝɹ⅁ ǝɥʇ uı uoıʇdo bıɟuoɔ bnqǝp:ɔsıɯ ǝɥʇ ǝןqɐuǝ :sɹǝdoןǝʌǝᗡ ɹoℲ ¡oɟuı bnqǝp ǝɥʇ pǝıɟıpoɯ sɐɥ ɥɔǝ⟘bǝɹ⅁", + "gtceu.debug.resource_rebuild.done": "%s uı ǝuop pןınqǝɹ ǝɔɹnosǝɹ ǝןpɐɹ⅁", + "gtceu.debug.resource_rebuild.start": ")sǝɔɹnosǝᴚssǝɔoɹd: ʍǝןpɐɹb/˙( pןınqǝɹ ǝɔɹnosǝɹ ǝןpɐɹb buıʞoʌuI", "gtceu.direction.tooltip.back": "ʞɔɐᗺ", "gtceu.direction.tooltip.down": "uʍoᗡ", "gtceu.direction.tooltip.front": "ʇuoɹℲ", @@ -2360,10 +2391,24 @@ "gtceu.gui.adv_stocking_config.title": "buıʞɔoʇS ɔıʇɐɯoʇnⱯ ǝɹnbıɟuoƆ", "gtceu.gui.all_voiding": "ןןⱯɔ§ buıpıoΛㄥ§", "gtceu.gui.auto_output.name": "oʇnɐ", + "gtceu.gui.central_monitor.data_hatch_target": "˙pɐǝʇsuı ʇǝbɹɐʇ ǝɥʇ pǝɹǝpısuoɔ ǝq ןןıʍ )ɹǝʌoɔ ɹǝʇʇıɯsuɐɹʇ ssǝןǝɹıʍ ɐ buıʞɔıןɔ-ʇɥbıɹ ɐıʌ( oʇ pǝʞuıן sı ʇoןs pǝıɟıɔǝds ǝɥʇ uı ʞɔıʇs ɐʇɐp ǝɥʇ ʇɐɥʇ ʞɔoןq ǝɥʇ uǝɥ⟘ ˙ɹǝqɯnu ʇoןs ɐ ɹǝʇuǝ oʇ pǝʇdɯoɹd ǝq ןןıʍ noʎ 'ʇǝbɹɐʇ ɐ sɐ ɥɔʇɐɥ ɐʇɐp ɐ ʇɔǝןǝs noʎ ɟI", "gtceu.gui.central_monitor.group": "%s :dnoɹ⅁", "gtceu.gui.central_monitor.group_default_name": "%d# dnoɹ⅁", - "gtceu.gui.central_monitor.none": "ǝuou", + "gtceu.gui.central_monitor.gui_module_info": "˙ɹoʇıpǝ dnoɹb ɹoʇıuoɯ ǝɥʇ uı ʇǝbɹɐʇ ɐ ʇɔǝןǝs 'ɯoɹɟ I∩⅁ ǝɥʇ ʇǝb oʇ ʞɔoןq ɐ ʇɔǝןǝs o⟘", + "gtceu.gui.central_monitor.help": ":suoıʇɔǝןǝs ɟo sǝdʎʇ ǝɹɐ ǝɹǝɥ 'xoqʇxǝʇ ǝɯɐu dnoɹb ǝɥʇ ʍoןǝq pıɹb ǝɥʇ uı ɹoʇıuoɯ Ɩ ʇsɐǝן ʇɐ ʇɔǝןǝs oʇ ǝʌɐɥ noʎ uǝɥ⟘\n)ʎɐןdsıp oʇ ʇxǝʇ ǝɥʇ ɹǝʇuǝ ʇsnظ ǝןdɯɐxǝ ɹoɟ( ǝןnpoɯ ǝɥʇ ǝɹnbıɟuoɔ oʇ ɹɐǝddɐ ןןıʍ uoʇʇnq ɐ 'ʇı ʇɹǝsuı noʎ uǝɥM\n˙xoqʇxǝʇ ǝɯɐu dnoɹb ǝɥʇ ɟo ʇɥbıɹ ǝɥʇ oʇ ʇoןs ǝɥʇ uı pǝʇɹǝsuı ǝq ʇsnɯ ǝןnpoɯ Ɐ\nǝןnpoɯ ɹoʇıuoɯ pǝɹnbıɟuoɔ puɐ pǝʇɹǝsuı uɐ - \nuo buıɥʇǝɯos ʎɐןdsıp oʇ ʇuɐʍ noʎ ʇɐɥʇ sɹoʇıuoɯ ǝɥʇ ʇɔǝןǝs oʇ noʎ - \n:sbuıɥʇ ᄅ spǝǝu ʇı 'buıɥʇʎuɐ ʎɐןdsıp oʇ dnoɹb ɐ ɹoℲ\n˙ʞɔoןqıʇןnɯ ǝɥʇ uı sɹoʇıuoɯ ɟo ʇunoɯɐ ʎuɐ ɟo uoıʇɔǝןןoɔ ɐ sı dnoɹb ɹoʇıuoɯ Ɐ", + "gtceu.gui.central_monitor.in_group": "ɟɟnʇs ʎɐןdsıp oʇ pǝsn ǝq ןןıʍ puɐ 'dnoɹb ǝɥʇ oʇ pǝppɐ ǝɹɐ ʎɐʍ sıɥʇ pǝʇɔǝןǝs sɹoʇıuoW", + "gtceu.gui.central_monitor.in_group_and_target": "˙ǝɯıʇ ǝɯɐs ǝɥʇ ʇɐ ʇǝbɹɐʇ ɐ sɐ puɐ buıʎɐןdsıp ɹoɟ pǝsn sı ʎɐʍ sıɥʇ pǝʇɔǝןǝs ɹoʇıuoɯ ǝɥ⟘", + "gtceu.gui.central_monitor.left_click": "ʇı buıʞɔıןɔ-ʇɟǝן ʎq ɹoʇıuoɯ ɐ ʇɔǝןǝsun/ʇɔǝןǝS", + "gtceu.gui.central_monitor.module_editor_button": "ǝןnpoɯ ʇıpƎ", + "gtceu.gui.central_monitor.module_editor_disabled": "ʇı ʇıpǝ oʇ I∩⅁ sıɥʇ uǝdo-ǝɹ ǝsɐǝןd 'ǝןnpoɯ ǝɥʇ pǝbuɐɥɔ ʎןʇuǝɔǝɹ noʎ", + "gtceu.gui.central_monitor.pause": "˙unɹ ǝq ʇ,uoʍ ǝpoɔ puɐ 'pǝʇɐpdn ǝq ʇ,uoʍ ʇxǝ⟘\n˙uoıʇnɔǝxǝ ɹǝpןoɥǝɔɐןd ǝsnɐԀ", + "gtceu.gui.central_monitor.resume": "˙uoıʇnɔǝxǝ ɹǝpןoɥǝɔɐןd ǝsnɐdu∩", + "gtceu.gui.central_monitor.right_click": "˙ʇǝbɹɐʇ ɐ ǝq uɐɔ ʞɔoןq Ɩ ʎןuO ˙ʇı buıʞɔıןɔ-ʇɥbıɹ ʎq ʞɔoןq ɐ ʇɔǝןǝS", + "gtceu.gui.central_monitor.target": "˙ǝןnpoɯ ʇxǝʇ ɐ uı sɹǝpןoɥǝɔɐןd ɥʇıʍ pǝsn ǝq oʇ 'ʇǝbɹɐʇ ɐ pǝɹǝpısuoɔ sı ʎɐʍ sıɥʇ pǝʇɔǝןǝs ʞɔoןq ǝɥ⟘", + "gtceu.gui.central_monitor.text_module_help": "\n:%0ϛ< sı ʎbɹǝuǝ uǝɥʍ ןɐubıs ǝuoʇspǝɹ ɐ spuǝs puɐ oɟuı ʎbɹǝuǝ ǝɯos sʎɐןdsıp ʇɐɥʇ 'sɹǝpןoɥǝɔɐןd ɟo ǝbɐsn ǝןdɯɐxǝ uɐ s,ǝɹǝH\n˙oɟuı ǝɹoɯ ɹoɟ ǝɹǝɥʇ ǝbɐd dןǝɥ ǝɥʇ ʇno ʞɔǝɥɔ 'ʍopuıʍ buıʇıpǝ dnoɹb ɹoʇıuoɯ ǝɥʇ uı ʇǝbɹɐʇ ɐ ʇɔǝןǝS\n˙SSԀ ɐ ɹo ɹǝɟɟnq ʎɹǝʇʇɐq ɐ ʎןןɐnsn 'ʞɔoןq ʇǝbɹɐʇ sʇı uı ʎbɹǝuǝ ɟo ʇunoɯɐ ǝɥʇ sʎɐןdsıp }ʎbɹǝuǝ{ ǝןdɯɐxǝ ɹoℲ ˙ʞɔoןqıʇןnɯ ɹoʇıuoɯ ןɐɹʇuǝɔ\nǝɥʇ ɟo ʇɹɐd sı ɹo sɹǝʌoɔ ʇdǝɔɔɐ uɐɔ ʇɐɥʇ ʞɔoןq ʎuɐ ʎןןɐɔısɐq sı ʇǝbɹɐʇ Ɐ ˙uoıʇɔunɟ oʇ ʇǝbɹɐʇ ɐ ǝʌɐɥ ʇsnɯ }ʎbɹǝuǝ{ ǝʞıן 'sɹǝpןoɥǝɔɐןd ǝɯoS\n˙ɔʇǝ 'suoıʇɐןnɔןɐɔ unɹ 'ʇnduı ǝuoʇspǝɹ pɐǝɹ/ʇndʇno ǝuoʇspǝɹ ɐ ʇǝs sɐ ɥɔns 'sbuıɥʇ ɹǝɥʇo ǝɯos op osןɐ uɐɔ ʎǝɥ⟘\n˙)ʎbɹǝuǝ ɟo ʇunoɯɐ ǝɥʇ ǝsɐɔ sıɥʇ uı( pɐǝʇsuı ǝnןɐʌ ɹıǝɥʇ ʎɐןdsıp ןןıʍ 'pǝʎɐןdsıp uǝɥʍ ʇɐɥʇ \"}ʎbɹǝuǝ{\" ǝʞıן sbuıɹʇs ǝɹɐ sɹǝpןoɥǝɔɐןԀ\n˙sɹǝpןoɥǝɔɐןd ǝʌɐɥ uɐɔ ʇɐɥʇ ʇxǝʇ sʎɐןdsıp ǝןnpoɯ sıɥ⟘", "gtceu.gui.central_monitor.text_scale": "ǝןɐɔs ʇxǝ⟘", + "gtceu.gui.central_monitor.update_once": "˙ǝɔuo ʎןʇɔɐxǝ ʇı uı sɹǝpןoɥǝɔɐןd ןןɐ unɹ puɐ ʇxǝʇ ǝʇɐpd∩", + "gtceu.gui.central_monitor.url": ":Ꞁᴚ∩ ǝbɐɯı ʇnduI", "gtceu.gui.charger_slot.tooltip.0": "ɹ§ʇoןS ɹǝbɹɐɥƆɟ§", "gtceu.gui.charger_slot.tooltip.1": "ɹ§sǝıɹǝʇʇɐq %s ɯoɹɟ ɹǝʍod sʍɐɹᗡㄥ§", "gtceu.gui.charger_slot.tooltip.2": "sǝıɹǝʇʇɐq puɐ sןooʇ %s sǝbɹɐɥƆㄥ§", @@ -2381,8 +2426,7 @@ "gtceu.gui.computer_monitor_cover.placeholder_reference.1": ")oɟuı ǝɹoɯ ɹoɟ ɹǝʌoɥ(", "gtceu.gui.computer_monitor_cover.second_page_textbox_tooltip.0": "˙ǝɹǝɥ ,}{, %s ɟo ǝɔɐןd uı pǝsn ǝq oʇ ɹǝpןoɥǝɔɐןd ʇnduI", "gtceu.gui.computer_monitor_cover.second_page_textbox_tooltip.1": "˙sǝxoq ʇxǝʇ ǝsǝɥʇ uı ,ʎʇıɔɐdɐƆʎbɹǝuǝ, puɐ ,ʎbɹǝuǝ, puɐ ,∩Ǝ }{/}{ :ʎbɹǝuƎ, buıɹʇs ɐ ǝʌɐɥ uɐɔ noʎ 'ǝןdɯɐxǝ ɹoℲ", - "gtceu.gui.computer_monitor_cover.slot_tooltip.0": "ǝɔuǝɹǝɟǝɹ uɐɔ sɹǝpןoɥǝɔɐןd ǝɯos ʇɐɥʇ sɯǝʇı ɹoɟ ʇoןs Ɐ", - "gtceu.gui.computer_monitor_cover.slot_tooltip.1": "%d :ɹǝqɯnu ʇoןS", + "gtceu.gui.computer_monitor_cover.slot_tooltip": "\n%d :ɹǝqɯnu ʇoןS\nǝɔuǝɹǝɟǝɹ uɐɔ sɹǝpןoɥǝɔɐןd ǝɯos ʇɐɥʇ sɯǝʇı ɹoɟ ʇoןs Ɐ", "gtceu.gui.computer_monitor_cover.update_interval": ")sʞɔıʇ uı( ןɐʌɹǝʇuı ǝʇɐpd∩", "gtceu.gui.config_slot": "ɹ§ʇoןS bıɟuoƆɟ§", "gtceu.gui.config_slot.auto_pull_managed": "ןןnԀ-oʇnⱯ ʎq pǝbɐuɐW ㄥ§:pǝןqɐsıᗡㄣ§", @@ -2424,6 +2468,7 @@ "gtceu.gui.fluid_amount": ":ʇunoɯⱯ pınןℲ", "gtceu.gui.fluid_auto_input.tooltip.disabled": "pǝןqɐsıᗡ ʇnduI-oʇnⱯ pınןℲ", "gtceu.gui.fluid_auto_input.tooltip.enabled": "pǝןqɐuƎ ʇnduI-oʇnⱯ pınןℲ", + "gtceu.gui.fluid_auto_output": "%s :ʇndʇnO pınןℲ", "gtceu.gui.fluid_auto_output.allow_input.disabled": "ǝpıs ʇndʇno ǝɥʇ ɯoɹɟ ʇnduı spınןɟ ǝןqɐsıp", "gtceu.gui.fluid_auto_output.allow_input.enabled": "ǝpıs ʇndʇno ǝɥʇ ɯoɹɟ ʇnduı spınןɟ ʍoןןɐ", "gtceu.gui.fluid_auto_output.disabled": "pǝןqɐsıᗡɔ§ :ʇndʇnO oʇnⱯ pınןℲ", @@ -2435,6 +2480,7 @@ "gtceu.gui.fluid_auto_output.tooltip.enabled": "pǝןqɐuƎ ʇndʇnO-oʇnⱯ pınןℲ", "gtceu.gui.fluid_auto_output.unselected.0": "ʇndʇnO oʇnⱯ pınןℲ", "gtceu.gui.fluid_auto_output.unselected.1": "˙ʇndʇno sʇı ǝɹnbıɟuoɔ oʇ ǝuıɥɔɐɯ ǝɥʇ ɟo ǝpıs ɐ ʇɔǝןǝSㄥ§", + "gtceu.gui.fluid_input_from_output": "%s :ʇndʇnO ɯoɹɟ ʇnduI pınןℲ", "gtceu.gui.fluid_lock.tooltip.disabled": "pǝןqɐsıᗡ buıʞɔoꞀ pınןℲ", "gtceu.gui.fluid_lock.tooltip.enabled": "pǝןqɐuƎ buıʞɔoꞀ pınןℲ", "gtceu.gui.fluid_voiding": "spınןℲ6§ buıpıoΛㄥ§", @@ -2443,6 +2489,7 @@ "gtceu.gui.fuel_amount": ":ʇunoɯⱯ ןǝnℲ", "gtceu.gui.item_auto_input.tooltip.disabled": "pǝןqɐsıᗡ ʇnduI-oʇnⱯ ɯǝʇI", "gtceu.gui.item_auto_input.tooltip.enabled": "pǝןqɐuƎ ʇnduI-oʇnⱯ ɯǝʇI", + "gtceu.gui.item_auto_output": "%s :ʇndʇnO ɯǝʇI", "gtceu.gui.item_auto_output.allow_input.disabled": "ǝpıs ʇndʇno ǝɥʇ ɯoɹɟ ʇnduı sɯǝʇı ǝןqɐsıp", "gtceu.gui.item_auto_output.allow_input.enabled": "ǝpıs ʇndʇno ǝɥʇ ɯoɹɟ ʇnduı sɯǝʇı ʍoןןɐ", "gtceu.gui.item_auto_output.disabled": "pǝןqɐsıᗡɔ§ :ʇndʇnO oʇnⱯ ɯǝʇI", @@ -2454,6 +2501,8 @@ "gtceu.gui.item_auto_output.tooltip.enabled": "pǝןqɐuƎ ʇndʇnO-oʇnⱯ ɯǝʇI", "gtceu.gui.item_auto_output.unselected.0": "ʇndʇnO oʇnⱯ ɯǝʇI", "gtceu.gui.item_auto_output.unselected.1": "˙ʇndʇno sʇı ǝɹnbıɟuoɔ oʇ ǝuıɥɔɐɯ ǝɥʇ ɟo ǝpıs ɐ ʇɔǝןǝSㄥ§", + "gtceu.gui.item_collector.range": " :ǝbuɐᴚ", + "gtceu.gui.item_input_from_output": "%s :ʇndʇnO ɯoɹɟ ʇnduI ɯǝʇI", "gtceu.gui.item_lock.tooltip.disabled": "pǝןqɐsıᗡ buıʞɔoꞀ ɯǝʇI", "gtceu.gui.item_lock.tooltip.enabled": "pǝןqɐuƎ buıʞɔoꞀ ɯǝʇI", "gtceu.gui.item_voiding": "sɯǝʇI9§ buıpıoΛㄥ§", @@ -2463,8 +2512,13 @@ "gtceu.gui.machinemode.tab_tooltip": "ǝpoW ǝuıɥɔɐW ǝʌıʇɔɐ ǝbuɐɥƆ", "gtceu.gui.machinemode.title": "ǝpoW ǝuıɥɔɐW ǝʌıʇɔⱯ", "gtceu.gui.me_bus.auto_pull_button": "ƎW ɯoɹɟ buıןןnd ɯǝʇı ɔıʇɐɯoʇnɐ ǝןbboʇ oʇ ʞɔıןƆ", + "gtceu.gui.me_network.auto_pull_toggle": "ןןnԀ-oʇnⱯ ǝןbbo⟘", + "gtceu.gui.me_network.empty": "ʎʇdɯƎ", + "gtceu.gui.me_network.min_stack_size": ":ǝzıS ʞɔɐʇS uıW", "gtceu.gui.me_network.offline": "ɹ§ǝuıןɟɟOㄣ§ :snʇɐʇS ʞɹoʍʇǝN", "gtceu.gui.me_network.online": "ɹ§ǝuıןuOᄅ§ :snʇɐʇS ʞɹoʍʇǝN", + "gtceu.gui.me_network.stocking_settings": "sbuıʇʇǝS buıʞɔoʇS", + "gtceu.gui.me_network.ticks_per_cycle": ":ǝןɔʎƆ ɹǝԀ sʞɔı⟘", "gtceu.gui.multiblock.voiding_mode": ":ǝpoW buıpıoΛ", "gtceu.gui.no_voiding": "buıɥʇoN buıpıoΛㄥ§", "gtceu.gui.output_setting.title": "sbuıʇʇǝS ʇndʇnO", @@ -2479,6 +2533,7 @@ "gtceu.gui.overclock.off": "X", "gtceu.gui.overclock.range": "]%s '%s[ sɹǝı⟘ ǝןqɐןıɐʌⱯ", "gtceu.gui.overclock.title": "ɹǝı⟘ ʞɔoןɔɹǝʌO", + "gtceu.gui.pattern_buffer.set_custom_name": " :ǝɯɐN ɯoʇsnƆ ʇǝS", "gtceu.gui.silktouch.disabled.0": "˙ǝןqɐuƎ oʇ ʞɔıןƆ :pǝןqɐsıᗡ ɥɔno⟘ ʞןıS", "gtceu.gui.silktouch.disabled.1": "˙ǝuıɥɔɐɯ ǝןpı uɐ sǝɹınbǝɹ buıɥɔʇıʍSㄥ§", "gtceu.gui.silktouch.enabled.0": "˙ǝןqɐsıᗡ oʇ ʞɔıןƆ :pǝןqɐuƎ ɥɔno⟘ ʞןıS", @@ -2503,6 +2558,7 @@ "gtceu.io.export": "ʇɹodxƎ", "gtceu.io.import": "ʇɹodɯI", "gtceu.io.none": "ǝuoN", + "gtceu.item.tooltip.image_url": "%s :Ꞁᴚ∩ ǝbɐɯI", "gtceu.item_filter.empty_item": ")ɯǝʇI oN( ʎʇdɯƎ", "gtceu.item_filter.footer": "ǝpıɹɹǝʌo oʇ ɯǝʇı ɥʇıʍ ʞɔıןƆǝ§", "gtceu.item_list.item_stored": "%d :pǝɹoʇSㄥ§", @@ -3029,19 +3085,16 @@ "gtceu.machine.me_import_item_hatch.configs.tooltip": "ʞɔoʇs uı sǝdʎʇ ɯǝʇı 9Ɩ sdǝǝʞ", "gtceu.machine.miner.chunkradius": "%d :snıpɐᴚ ʞunɥƆ", "gtceu.machine.miner.fluid_usage": "˙ʞɔoןɔɹǝʌo ɹǝd pǝןqnop 'ㄥ§%sɟ§ ɟoㄥ§ ʇ/ᗺɯ %dɟ§ sǝs∩", - "gtceu.machine.miner.minex": "%d :Xɯ", - "gtceu.machine.miner.miney": "%d :ʎɯ", - "gtceu.machine.miner.minez": "%d :Zɯ", "gtceu.machine.miner.multi.description": "˙ǝɹo ɟo ʎʇıʇuɐnb ǝbnɥ sǝɔnpoɹd puɐ ɐǝɹɐ ǝbɹɐן ɐ sɹǝʌoɔ ʇɐɥʇ ǝuıɥɔɐɯ buıuıɯ ʞɔoןqıʇןnɯ Ɐ", "gtceu.machine.miner.multi.modes": "˙sǝpoW pǝubıןⱯ ʞunɥƆ puɐ ɥɔno⟘ ʞןıS sɐH", "gtceu.machine.miner.multi.production": "˙ㄥ§ɹoʇɐɹǝɔɐWɟ§ ɐ uɐɥʇ ǝɹo pǝɥsnɹɔ ǝɹoɯ ㄥ§xƐɟ§ sǝɔnpoɹԀ", "gtceu.machine.miner.per_block": "ʞɔoןᗺ ɹǝdㄥ§ %dsɟ§ sǝʞɐʇㄥ§", "gtceu.machine.miner.progress": "%d/%d :ssǝɹboɹԀ", "gtceu.machine.miner.radius": "%d :snıpɐᴚ", - "gtceu.machine.miner.startx": "%d :Xs", - "gtceu.machine.miner.starty": "%d :ʎs", - "gtceu.machine.miner.startz": "%d :Zs", "gtceu.machine.miner.tooltip": "ɐǝɹɐㄥ§ %sx%sɟ§ sɐ sʇɹɐʇS ¡ɹǝuıW ǝɥʇ ʍoןǝq sǝɹo sǝuıWㄥ§", + "gtceu.machine.miner.x": "%d :Xɯ '%d :Xs", + "gtceu.machine.miner.y": "%d :ʎɯ '%d :ʎs", + "gtceu.machine.miner.z": "%d :Zɯ '%d :Zs", "gtceu.machine.muffle.off": "pǝןqɐsıᗡ :buıןɟɟnW punoS", "gtceu.machine.muffle.on": "pǝןqɐuƎ :buıןɟɟnW punoS", "gtceu.machine.muffler_hatch.tooltip.0": "sǝuıɥɔɐɯ ɯoɹɟ ǝʇsɐʍ sɹǝʌoɔǝᴚ", @@ -3096,6 +3149,7 @@ "gtceu.machine.opv_gas_collector.tooltip": "uoısuǝɯıp ǝɥʇ uo buıpuǝdǝp ǝsɹǝʌıun ǝɥʇ ɯoɹɟ sɐ⅁ sʇɔǝןןoƆㄥ§", "gtceu.machine.opv_rock_crusher.tooltip": "ɹǝqɯɐɥƆ uoıʇɐɯɹoℲ ɔıuɐɔןoΛㄥ§", "gtceu.machine.parallel_hatch.display": "ʞɔoןqıʇןnɯ ǝɥʇ ɟo ןǝןןɐɹɐd ɯnɯıxɐɯ ǝɥʇ ʇsnظpⱯ", + "gtceu.machine.parallel_hatch.parallel_ui": "sןǝןןɐɹɐԀ", "gtceu.machine.parallel_hatch_mk5.tooltip": "˙ןǝןןɐɹɐd uı sǝdıɔǝɹ ㄣ oʇ dn unɹ oʇ sʍoןןⱯ", "gtceu.machine.parallel_hatch_mk6.tooltip": "˙ןǝןןɐɹɐd uı sǝdıɔǝɹ 9Ɩ oʇ dn unɹ oʇ sʍoןןⱯ", "gtceu.machine.parallel_hatch_mk7.tooltip": "˙ןǝןןɐɹɐd uı sǝdıɔǝɹ ㄣ9 oʇ dn unɹ oʇ sʍoןןⱯ", @@ -3338,10 +3392,9 @@ "gtceu.multiblock.hpca.info_bridging_disabled": "pǝןqɐsıᗡ buıbpıɹᗺ", "gtceu.multiblock.hpca.info_bridging_enabled": "pǝןqɐuƎ buıbpıɹᗺ", "gtceu.multiblock.hpca.info_coolant_name": "ʇuɐןooƆ ᗺƆԀ", + "gtceu.multiblock.hpca.info_cooling_demand": "%d / %s :puɐɯǝᗡ buıןooƆ", "gtceu.multiblock.hpca.info_max_computation": "%s :ʇ/∩MƆ xɐW", "gtceu.multiblock.hpca.info_max_coolant_required": "%s :pǝpǝǝN ʇuɐןooƆ", - "gtceu.multiblock.hpca.info_max_cooling_available": "%s :ǝןqɐןıɐʌⱯ buıןooƆ", - "gtceu.multiblock.hpca.info_max_cooling_demand": "%s :puɐɯǝᗡ buıןooƆ", "gtceu.multiblock.hpca.temperature": "%s :ǝɹnʇɐɹǝdɯǝ⟘", "gtceu.multiblock.hpca.warning_low_cooling": "buıןooɔ ɥbnouǝ ʇoN -", "gtceu.multiblock.hpca.warning_multiple_bridges": ")ʇıɟǝuǝq ןɐuoıʇıppɐ ou sǝpıʌoɹd( ǝɹnʇɔnɹʇs uı sǝbpıɹq ǝןdıʇןnW -", @@ -3527,6 +3580,14 @@ "gtceu.packer": "ɹǝʞɔɐԀ", "gtceu.part_sharing.disabled": "pǝןqɐsıᗡㄣ§ buıɹɐɥS ʞɔoןqıʇןnW", "gtceu.part_sharing.enabled": "pǝןqɐuƎɐ§ buıɹɐɥS ʞɔoןqıʇןnW", + "gtceu.placeholder_editor.constant_value": "˙ʇuɐʇsuoɔ ɐ ɥʇıʍ ʇı buıɔɐןdǝɹ ɹǝpısuoƆ\n˙ʇןnsǝɹ ǝɯɐs ǝɥʇ oʇ sǝʇɐnןɐʌǝ sʎɐʍןɐ uoıssǝɹdxǝ sıɥ⟘", + "gtceu.placeholder_editor.extra_closing_bracket": "ʇǝʞɔɐɹq buısoןɔ ɐɹʇxƎ", + "gtceu.placeholder_editor.no_placeholder": "ʇsıxǝ ʇou sǝop ,%s, ǝɯɐu ɥʇıʍ ɹǝpןoɥǝɔɐןԀ", + "gtceu.placeholder_editor.unclosed_bracket": ")\"}\"( ʇǝʞɔɐɹq pǝsoןɔu∩", + "gtceu.placeholder_editor.unclosed_brackets": "pǝsoןɔun ǝɹɐ )\"}\"( sʇǝʞɔɐɹq %d", + "gtceu.placeholder_editor.unclosed_escape": ")\"],\"( ǝdɐɔsǝ pǝsoןɔu∩", + "gtceu.placeholder_editor.unclosed_escapes": "pǝsoןɔun ǝɹɐ )\"],\"( sǝdɐɔsǝ %d", + "gtceu.placeholder_editor.write_in_if": "˙\"}}\">ǝpoɔ‾ǝsןǝ<\" \">ǝpoɔ<\" >uoıʇıpuoɔ< ɟı{ ןɐʌǝ{\" ǝsn 'sıɥʇ pıoʌɐ o⟘\n˙ʇndʇno ǝuoʇspǝɹ ǝɔnpoɹd ןןıʍ \"}}ϛƖ ʇǝs ǝuoʇspǝɹ{ 0 ɟı{\" 'ǝןdɯɐxǝ ɹoɟ 'ʇɐɥʇ suɐǝɯ sıɥ⟘\n˙uoıʇıpuoɔ ǝɥʇ ɟo ssǝןpɹɐbǝɹ pǝʇnɔǝxǝ ǝɹɐ }ɟı{ ǝpısuı sɹǝpןoɥǝɔɐןԀ", "gtceu.placeholder_info.active.0": "˙ǝsıʍɹǝɥʇo 0 'ǝdıɔǝɹ ɐ buıuunɹ ʎןʇuǝɹɹnɔ sı oʇ pǝɥɔɐʇʇɐ sı ɹǝʌoɔ ǝɥʇ ʞɔoןq ǝɥʇ ɟı Ɩ ɐ suɹnʇǝᴚ", "gtceu.placeholder_info.active.1": ":ǝbɐs∩", "gtceu.placeholder_info.active.2": "ǝdıɔǝɹ buıuunɹ ʎןʇuǝɹɹnɔ ɐ s,ǝɹǝɥʇ ɹǝɥʇǝɥʍ >- }ǝʌıʇɔɐ{ ", @@ -3655,6 +3716,9 @@ "gtceu.placeholder_info.if.0": "˙0 oʇ ןɐnbǝ ʇou sı puɐ buıɹʇs ʎʇdɯǝ uɐ ʇou sı ʇı ɟı ǝnɹʇ pǝɹǝpısuoɔ sı uoıʇıpuoɔ ǝɥ⟘ ˙uoıʇıpuoɔ ǝɥʇ uo buıpuǝdǝp sʇuǝɯnbɹɐ ǝɥʇ ɟo ǝuo suɹnʇǝᴚ", "gtceu.placeholder_info.if.1": ":ǝbɐs∩", "gtceu.placeholder_info.if.2": "}]ǝsןɐɟ‾ɟı‾pǝuɹnʇǝɹ[ >ǝnɹʇ‾ɟı‾pǝuɹnʇǝɹ< >uoıʇıpuoɔ< ɟı{ ", + "gtceu.placeholder_info.item.0": "ʇoןs pǝıɟıɔǝds ɐ uı ɯǝʇı ǝɥʇ ɟo pı puɐ ʇunoɯɐ ǝɥʇ suɹnʇǝᴚ", + "gtceu.placeholder_info.item.1": ":ǝbɐs∩", + "gtceu.placeholder_info.item.2": ")ǝןdɯɐxǝ ɹoɟ( \"puoɯɐıp:ʇɟɐɹɔǝuıɯ ƖƐ\" >- }>ʇoןs< ɯǝʇı{ ", "gtceu.placeholder_info.itemCount.0": "˙)pǝɹǝʇןıɟ ǝq uɐɔ( sɯǝʇı ɟo ʇunoɯɐ ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.itemCount.1": ":ǝbɐs∩", "gtceu.placeholder_info.itemCount.2": "ʇunoɯɐ ɯǝʇı ןɐʇoʇ >- }ʇunoƆɯǝʇı{ ", @@ -3668,6 +3732,9 @@ "gtceu.placeholder_info.maxProgress.1": ",%}00Ɩ * }}ssǝɹboɹԀxɐɯ{ / }ssǝɹboɹd{ ɔןɐɔ{ ɔןɐɔ{ :ssǝɹboɹԀ, :ǝןdɯɐxƎ", "gtceu.placeholder_info.maxProgress.2": ":ǝbɐs∩", "gtceu.placeholder_info.maxProgress.3": "ǝdıɔǝɹ buıuunɹ ʎןʇuǝɹɹnɔ ǝɥʇ ɟo ssǝɹboɹd xɐɯ ǝɥʇ >- }ssǝɹboɹԀxɐɯ{ ", + "gtceu.placeholder_info.module.0": ")ɹǝʌoɔ ɐ uı ʞɹoʍ ʇou sǝop( ɹoʇıuoɯ ןɐɹʇuǝɔ ǝɥʇ oʇuo ʇoןs pǝıɟıɔǝds ǝɥʇ uı ǝןnpoɯ ǝɥʇ sɹǝpuǝᴚ", + "gtceu.placeholder_info.module.1": ":ǝbɐs∩", + "gtceu.placeholder_info.module.2": "buıɹʇs ʎʇdɯǝ >- }>ʎ< >x< >ʇoןs< ǝןnpoɯ{ ", "gtceu.placeholder_info.nbt.0": "ʇoןs pǝıɟıɔǝds ǝɥʇ uı ɯǝʇı ǝɥʇ ɟo ɐʇɐp ʇqu ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.nbt.1": ":ǝbɐs∩", "gtceu.placeholder_info.nbt.2": "]˙˙˙[]Ɛʎǝʞ[]ᄅʎǝʞ[]Ɩʎǝʞ[ʇqu‾ɯǝʇı >- }˙˙˙ ]Ɛʎǝʞ[ ]ᄅʎǝʞ[ ]Ɩʎǝʞ[ >ʇoןs< ʇqu{ ", @@ -3681,9 +3748,16 @@ "gtceu.placeholder_info.progress.1": "}ssǝɹboɹԀxɐɯ{ puɐ 0 uǝǝʍʇǝq ɹǝbǝʇuı uɐ sı ssǝɹboɹd ʇɐɥʇ ǝʇoN", "gtceu.placeholder_info.progress.2": ":ǝbɐs∩", "gtceu.placeholder_info.progress.3": "ǝdıɔǝɹ buıuunɹ ʎןʇuǝɹɹnɔ ǝɥʇ ɟo ssǝɹboɹd ǝɥʇ >- }ssǝɹboɹd{ ", + "gtceu.placeholder_info.quad.0": ")sǝɔıʇɹǝʌ ㄣ ןןɐ ɹoɟ sɹǝʇǝɯɐɹɐd ʎɟıɔǝds ʇsnɯ( pɐnb ɐ sʍɐɹᗡ", + "gtceu.placeholder_info.quad.1": ":ǝbɐs∩", + "gtceu.placeholder_info.quad.2": "buıɹʇs ʎʇdɯǝ >- }>ㄣɹoןoɔ< >Ɛɹoןoɔ< >ᄅɹoןoɔ< >Ɩɹoןoɔ< >ㄣʎ< >ㄣx< >Ɛʎ< >Ɛx< >ᄅʎ< >ᄅx< >Ɩʎ< >Ɩx< pɐnb{ ", "gtceu.placeholder_info.random.0": "˙)ǝʌısnןɔuı( ןɐʌɹǝʇuı pǝıɟıɔǝds ǝɥʇ uı ɹǝqɯnu ɯopuɐɹ ɐ suɹnʇǝᴚ", "gtceu.placeholder_info.random.1": ":ǝbɐs∩", "gtceu.placeholder_info.random.2": ")ǝʌısnןɔuı( xɐɯ puɐ uıɯ uǝǝʍʇǝq ɹǝqɯnu ɯopuɐɹ ɐ >- }>xɐɯ< >uıɯ< ɯopuɐɹ{ ", + "gtceu.placeholder_info.rect.0": "ǝzıs puɐ sǝʇɐuıpɹooɔ pǝıɟıɔǝds ǝɥʇ ɥʇıʍ uoıʇısod pǝıɟıɔǝds ǝɥʇ ʇɐ ǝןbuɐʇɔǝɹ ɐ sʍɐɹᗡ", + "gtceu.placeholder_info.rect.1": ":ǝbɐs∩", + "gtceu.placeholder_info.rect.2": "buıɹʇs ʎʇdɯǝ >- }>ᗺ⅁ᴚⱯɹoןoɔ< >ʇɥbıǝɥ< >ɥʇpıʍ< >ʎ< >x< ʇɔǝɹ{ ", + "gtceu.placeholder_info.rect.3": ")Ɩ 'ᄅ( ǝzıs ǝɥʇ ɥʇıʍ )ϛᄅ˙0 'ϛ˙0( ʇɐ ǝןbuɐʇɔǝɹ ǝʇıɥʍ ɐ sʍɐɹp >- }ℲℲℲℲℲℲℲℲx0 Ɩ ᄅ ϛᄅ˙0 ϛ˙0 ʇɔǝɹ{ ", "gtceu.placeholder_info.redstone.0": "ɥʇbuǝɹʇs ʇndʇno ǝuoʇspǝɹ ǝɥʇ sʇǝs ɹo ɥʇbuǝɹʇs ןɐubıs ǝuoʇspǝɹ ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.redstone.1": ":ǝbɐs∩", "gtceu.placeholder_info.redstone.2": "ǝpıs pǝıɟıɔǝds ǝɥʇ ʇɐ )ϛƖ-0( ɥʇbuǝɹʇs ןɐubıs ǝuoʇspǝɹ >- }>ʇsǝʍ|ʇsɐǝ|ɥʇnos|ɥʇɹou|uʍop|dn< ʇǝb ǝuoʇspǝɹ{ ", @@ -3696,6 +3770,9 @@ "gtceu.placeholder_info.select.0": ")0 ɯoɹɟ buıʇɹɐʇs( xǝpuı pǝıɟıɔǝds ǝɥʇ ʇɐ ʇuǝɯnbɹɐ ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.select.1": ":ǝbɐs∩", "gtceu.placeholder_info.select.2": "xǝpuı pǝıɟıɔǝds ǝɥʇ ʇɐ ʇuǝɯnbɹɐ >- ˙˙˙ ]Ɛbɹɐ[ ]ᄅbɹɐ[ ]Ɩbɹɐ[ >xǝpuı< ʇɔǝןǝs{ ", + "gtceu.placeholder_info.setImage.0": "ʇoןs pǝıɟıɔǝds ǝɥʇ uı ǝןnpoɯ ǝbɐɯı uɐ uı Ꞁᴚ∩ ǝbɐɯı ǝɥʇ sʇǝS", + "gtceu.placeholder_info.setImage.1": ":ǝbɐs∩", + "gtceu.placeholder_info.setImage.2": "buıɹʇs ʎʇdɯǝ >- }>ןɹn< >ʇoןs< ǝbɐɯIʇǝs{ ", "gtceu.placeholder_info.strike.0": "ʇno pǝssoɹɔ sɐʍ ʇı ɟı sɐ ʇı buıʎɐןdsıp 'ʇxǝʇ ʇsɹıɟ ǝɥʇ ɯoɹɟ ʇxǝʇ ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.strike.1": ":ǝbɐs∩", "gtceu.placeholder_info.strike.2": "ʇxǝʇ ʇno-pǝssoɹɔ >- }>ʇxǝʇ< ǝʞıɹʇs{ ", @@ -4259,6 +4336,7 @@ "item.gtceu.gray_glass_lens": ")ʎɐɹ⅁( suǝꞀ ssɐן⅁", "item.gtceu.green_dye_spray_can": ")uǝǝɹ⅁( uɐƆ ʎɐɹdS", "item.gtceu.green_glass_lens": ")uǝǝɹ⅁( suǝꞀ ssɐן⅁", + "item.gtceu.gui_module": "ǝןnpoW ın⅁", "item.gtceu.hammer_extruder_mold.tooltip": "sɹǝɯɯɐH buıʞɐɯ ɹoɟ ǝdɐɥS ɹǝpnɹʇxƎㄥ§", "item.gtceu.hazmat_boots": "sʇooᗺ ʇınS sןɐıɹǝʇɐW snopɹɐzɐH", "item.gtceu.hazmat_chestpiece": "ǝɔǝıdʇsǝɥƆ ʇınS sןɐıɹǝʇɐW snopɹɐzɐH", diff --git a/src/generated/resources/assets/gtceu/lang/en_us.json b/src/generated/resources/assets/gtceu/lang/en_us.json index 35d0223ccab..c64082ced62 100644 --- a/src/generated/resources/assets/gtceu/lang/en_us.json +++ b/src/generated/resources/assets/gtceu/lang/en_us.json @@ -1134,6 +1134,8 @@ "block.gtceu.substation_capacitor.tooltip_filled": "§cEnergy Capacity: §f%d EU", "block.gtceu.superconducting_coil": "Superconducting Coil Block", "block.gtceu.tempered_glass": "Tempered Glass", + "block.gtceu.test_mui": "Test Mui", + "block.gtceu.test_mui_new": "Test Mui New", "block.gtceu.the_end_marker": "The End", "block.gtceu.the_nether_marker": "The Nether", "block.gtceu.titanium_crate": "Titanium Crate", @@ -1746,11 +1748,11 @@ "command.gtceu.share_prospection_data.notification": "%s is sharing prospecting data with you!", "config.gtceu.option.addLoot": "addLoot", "config.gtceu.option.ae2": "ae2", - "config.gtceu.option.allowDrumsInputFluidsFromOutputSide": "allowDrumsInputFluidsFromOutputSide", "config.gtceu.option.allowedImageDomains": "allowedImageDomains", "config.gtceu.option.animationTime": "animationTime", "config.gtceu.option.arcRecyclingYield": "arcRecyclingYield", "config.gtceu.option.armorHud": "armorHud", + "config.gtceu.option.autoRebuildResources": "autoRebuildResources", "config.gtceu.option.batchDuration": "batchDuration", "config.gtceu.option.bedrockOreDistance": "bedrockOreDistance", "config.gtceu.option.bedrockOreDropTagPrefix": "bedrockOreDropTagPrefix", @@ -1766,7 +1768,9 @@ "config.gtceu.option.coloredWireOutline": "coloredWireOutline", "config.gtceu.option.compat": "compat", "config.gtceu.option.createCompat": "createCompat", + "config.gtceu.option.cursorColor": "cursorColor", "config.gtceu.option.debug": "debug", + "config.gtceu.option.debugUI": "debugUI", "config.gtceu.option.debugWorldgen": "debugWorldgen", "config.gtceu.option.defaultPaintingColor": "defaultPaintingColor", "config.gtceu.option.defaultUIColor": "defaultUIColor", @@ -1794,6 +1798,7 @@ "config.gtceu.option.energyUsageMultiplier": "energyUsageMultiplier", "config.gtceu.option.environmentalHazardDecayRate": "environmentalHazardDecayRate", "config.gtceu.option.environmentalHazards": "environmentalHazards", + "config.gtceu.option.escRestoresLastText": "escRestoresLastText", "config.gtceu.option.euToFeRatio": "euToFeRatio", "config.gtceu.option.extractorRecyclingYield": "extractorRecyclingYield", "config.gtceu.option.feToEuRatio": "feToEuRatio", @@ -1845,6 +1850,7 @@ "config.gtceu.option.meHatchEnergyUsage": "meHatchEnergyUsage", "config.gtceu.option.minerSpeed": "minerSpeed", "config.gtceu.option.minimap": "minimap", + "config.gtceu.option.mui": "mui", "config.gtceu.option.nanoSaber": "nanoSaber", "config.gtceu.option.nanoSaberBaseDamage": "nanoSaberBaseDamage", "config.gtceu.option.nanoSaberDamageBoost": "nanoSaberDamageBoost", @@ -1865,6 +1871,7 @@ "config.gtceu.option.oreVeinGridSize": "oreVeinGridSize", "config.gtceu.option.oreVeinRandomOffset": "oreVeinRandomOffset", "config.gtceu.option.oreVeins": "oreVeins", + "config.gtceu.option.outlineColor": "outlineColor", "config.gtceu.option.ownerOPBypass": "ownerOPBypass", "config.gtceu.option.prospectorEnergyUseMultiplier": "prospectorEnergyUseMultiplier", "config.gtceu.option.quantumTank": "quantumTank", @@ -1878,14 +1885,28 @@ "config.gtceu.option.renderGrowingPlants": "renderGrowingPlants", "config.gtceu.option.renderer": "renderer", "config.gtceu.option.replaceMinedBlocksWith": "replaceMinedBlocksWith", + "config.gtceu.option.replaceVanillaTooltips": "replaceVanillaTooltips", "config.gtceu.option.replaceWithCobbleVersion": "replaceWithCobbleVersion", "config.gtceu.option.requireGTToolsForBlocks": "requireGTToolsForBlocks", "config.gtceu.option.rngDamageElectricTools": "rngDamageElectricTools", "config.gtceu.option.rubberTreeSpawnChance": "rubberTreeSpawnChance", "config.gtceu.option.sandOresFall": "sandOresFall", + "config.gtceu.option.scale": "scale", "config.gtceu.option.shouldWeatherOrTerrainExplosion": "shouldWeatherOrTerrainExplosion", "config.gtceu.option.showDimensionTier": "showDimensionTier", + "config.gtceu.option.showExtra": "showExtra", + "config.gtceu.option.showHovered": "showHovered", + "config.gtceu.option.showOutline": "showOutline", + "config.gtceu.option.showParent": "showParent", + "config.gtceu.option.showParentOutline": "showParentOutline", + "config.gtceu.option.showParentPos": "showParentPos", + "config.gtceu.option.showParentSize": "showParentSize", + "config.gtceu.option.showParentWidgetTheme": "showParentWidgetTheme", + "config.gtceu.option.showPos": "showPos", + "config.gtceu.option.showSize": "showSize", + "config.gtceu.option.showWidgetTheme": "showWidgetTheme", "config.gtceu.option.smallBoilers": "smallBoilers", + "config.gtceu.option.smoothProgressBar": "smoothProgressBar", "config.gtceu.option.solarBoilerBaseOutput": "solarBoilerBaseOutput", "config.gtceu.option.solidBoilerBaseOutput": "solidBoilerBaseOutput", "config.gtceu.option.sprayCanChainLength": "sprayCanChainLength", @@ -1897,17 +1918,21 @@ "config.gtceu.option.surfaceRockProspectRange": "surfaceRockProspectRange", "config.gtceu.option.tankItemFluidPreview": "tankItemFluidPreview", "config.gtceu.option.temperaturesInCelsius": "temperaturesInCelsius", + "config.gtceu.option.textColor": "textColor", "config.gtceu.option.titaniumBoilerHeatSpeed": "titaniumBoilerHeatSpeed", "config.gtceu.option.titaniumBoilerMaxTemperature": "titaniumBoilerMaxTemperature", "config.gtceu.option.toggle": "toggle", "config.gtceu.option.toolCraftingSounds": "toolCraftingSounds", "config.gtceu.option.toolUseSounds": "toolUseSounds", "config.gtceu.option.tools": "tools", + "config.gtceu.option.tooltipPos": "tooltipPos", "config.gtceu.option.treeFellingDelay": "treeFellingDelay", "config.gtceu.option.tungstensteelBoilerHeatSpeed": "tungstensteelBoilerHeatSpeed", "config.gtceu.option.tungstensteelBoilerMaxTemperature": "tungstensteelBoilerMaxTemperature", + "config.gtceu.option.ui": "ui", "config.gtceu.option.universalHazards": "universalHazards", "config.gtceu.option.updateIntervals": "updateIntervals", + "config.gtceu.option.useDarkThemeByDefault": "useDarkThemeByDefault", "config.gtceu.option.useVBO": "useVBO", "config.gtceu.option.voltageTierAdvImpeller": "voltageTierAdvImpeller", "config.gtceu.option.voltageTierAdvNanoSuit": "voltageTierAdvNanoSuit", @@ -2018,20 +2043,18 @@ "cover.conveyor.transfer_rate": "§7items/sec", "cover.detector_base.message_inverted_state": "Monitoring Status: Inverted", "cover.detector_base.message_normal_state": "Monitoring Status: Normal", - "cover.ender_fluid_link.incomplete_hex.0": "Inputted color is incomplete!", - "cover.ender_fluid_link.incomplete_hex.1": "It will be applied once complete (all 8 hex digits)", - "cover.ender_fluid_link.incomplete_hex.2": "Closing the gui will lose edits!", "cover.ender_fluid_link.iomode.disabled": "I/O Disabled", "cover.ender_fluid_link.iomode.enabled": "I/O Enabled", - "cover.ender_fluid_link.private.tooltip.disabled.0": "Switch to private tank mode", - "cover.ender_fluid_link.private.tooltip.disabled.1": "Private mode uses the player who originally placed the cover", - "cover.ender_fluid_link.private.tooltip.enabled": "Switch to public tank mode", "cover.ender_fluid_link.title": "Ender Fluid Link", - "cover.ender_fluid_link.tooltip.channel_description": "Set channel description with input text", - "cover.ender_fluid_link.tooltip.channel_name": "Set channel name with input text", - "cover.ender_fluid_link.tooltip.clear_button": "Clear channel description", - "cover.ender_fluid_link.tooltip.list_button": "Show channel list", "cover.ender_item_link.title": "Ender Item Link", + "cover.ender_link.description_empty": "Empty Description", + "cover.ender_link.private.tooltip": "Private mode: Only accessible to the player who placed this cover", + "cover.ender_link.protected.tooltip": "Protected Mode: Accessible to players on the same team.", + "cover.ender_link.public.tooltip": "Public mode: Accessible to all players", + "cover.ender_link.tooltip.channel_description": "Channel description", + "cover.ender_link.tooltip.channel_name": "Channel ID (32-bit hexadecimal value)", + "cover.ender_link.tooltip.clear_button": "Clear channel description", + "cover.ender_link.tooltip.list_button": "Show channel list", "cover.ender_redstone_link.title": "Ender Redstone Link", "cover.filter.blacklist.disabled": "Whitelist", "cover.filter.blacklist.enabled": "Blacklist", @@ -2069,6 +2092,7 @@ "cover.item_smart_filter.filtering_mode.electrolyzer": "Electrolyzer", "cover.item_smart_filter.filtering_mode.sifter": "Sifter", "cover.item_smart_filter.title": "Smart Item Filter", + "cover.machine_controller.control": "Controller Target", "cover.machine_controller.invert.disabled.0": "§eNormal§r - in this mode, the cover will require a signal weaker than the set redstone level to run", "cover.machine_controller.invert.enabled.0": "§eInverted§r - in this mode, the cover will require a signal stronger than the set redstone level to run", "cover.machine_controller.inverted": "Inverted", @@ -2226,6 +2250,10 @@ "gtceu.canner": "Canner", "gtceu.central_monitor.gui.create_group": "Create group", "gtceu.central_monitor.gui.currently_editing": "Currently editing: %s", + "gtceu.central_monitor.gui.data_slot": "Set slot number of data stick", + "gtceu.central_monitor.gui.group_editor": "Editing monitor group", + "gtceu.central_monitor.gui.group_name": "Name:", + "gtceu.central_monitor.gui.monitor_groups": "Monitor groups", "gtceu.central_monitor.gui.remove_from_group": "Remove from group", "gtceu.central_monitor.gui.set_target": "Set target", "gtceu.central_monitor.info_tooltip.0": "In order to use monitors, you have to split them into groups first. A group may only have 1 module in it.", @@ -2282,7 +2310,8 @@ "gtceu.creative.chest.ipc": "Items per Cycle", "gtceu.creative.chest.item": "Item", "gtceu.creative.chest.tpc": "Ticks per Cycle", - "gtceu.creative.computation.average": "Average Requested CWUt", + "gtceu.creative.computation.average": "Average CWU/tick: %d", + "gtceu.creative.computation.max_usage": "Max CWU/tick:", "gtceu.creative.energy.amperage": "Amperage", "gtceu.creative.energy.sink": "Sink", "gtceu.creative.energy.source": "Source", @@ -2295,6 +2324,8 @@ "gtceu.creative_tooltip.3": "§7 to use this", "gtceu.cutter": "Cutter", "gtceu.debug.f3_h.enabled": "GregTech has modified the debug info! For Developers: enable the misc:debug config option in the GregTech config file to see more", + "gtceu.debug.resource_rebuild.done": "Gradle resource rebuild done in %s", + "gtceu.debug.resource_rebuild.start": "Invoking gradle resource rebuild (./gradlew :processResources)", "gtceu.direction.tooltip.back": "Back", "gtceu.direction.tooltip.down": "Down", "gtceu.direction.tooltip.front": "Front", @@ -2360,10 +2391,24 @@ "gtceu.gui.adv_stocking_config.title": "Configure Automatic Stocking", "gtceu.gui.all_voiding": "§7Voiding §cAll", "gtceu.gui.auto_output.name": "auto", + "gtceu.gui.central_monitor.data_hatch_target": "If you select a data hatch as a target, you will be prompted to enter a slot number. Then the block that the data stick in the specified slot is linked to (via right-clicking a wireless transmitter cover) will be considered the target instead.", "gtceu.gui.central_monitor.group": "Group: %s", "gtceu.gui.central_monitor.group_default_name": "Group #%d", - "gtceu.gui.central_monitor.none": "none", + "gtceu.gui.central_monitor.gui_module_info": "To select a block to get the GUI from, select a target in the monitor group editor.", + "gtceu.gui.central_monitor.help": "A monitor group is a collection of any amount of monitors in the multiblock.\nFor a group to display anything, it needs 2 things:\n - you to select the monitors that you want to display something on\n - an inserted and configured monitor module\nA module must be inserted in the slot to the right of the group name textbox.\nWhen you insert it, a button will appear to configure the module (for example just enter the text to display)\nThen you have to select at least 1 monitor in the grid below the group name textbox, here are types of selections:", + "gtceu.gui.central_monitor.in_group": "Monitors selected this way are added to the group, and will be used to display stuff", + "gtceu.gui.central_monitor.in_group_and_target": "The monitor selected this way is used for displaying and as a target at the same time.", + "gtceu.gui.central_monitor.left_click": "Select/unselect a monitor by left-clicking it", + "gtceu.gui.central_monitor.module_editor_button": "Edit module", + "gtceu.gui.central_monitor.module_editor_disabled": "You recently changed the module, please re-open this GUI to edit it", + "gtceu.gui.central_monitor.pause": "Pause placeholder execution.\nText won't be updated, and code won't be run.", + "gtceu.gui.central_monitor.resume": "Unpause placeholder execution.", + "gtceu.gui.central_monitor.right_click": "Select a block by right-clicking it. Only 1 block can be a target.", + "gtceu.gui.central_monitor.target": "The block selected this way is considered a target, to be used with placeholders in a text module.", + "gtceu.gui.central_monitor.text_module_help": "This module displays text that can have placeholders.\nPlaceholders are strings like \"{energy}\" that when displayed, will display their value instead (in this case the amount of energy).\nThey can also do some other things, such as set a redstone output/read redstone input, run calculations, etc.\nSome placeholders, like {energy} must have a target to function. A target is basically any block that can accept covers or is part of the\ncentral monitor multiblock. For example {energy} displays the amount of energy in its target block, usually a battery buffer or a PSS.\nSelect a target in the monitor group editing window, check out the help page there for more info.\nHere's an example usage of placeholders, that displays some energy info and sends a redstone signal when energy is <50%:\n", "gtceu.gui.central_monitor.text_scale": "Text scale", + "gtceu.gui.central_monitor.update_once": "Update text and run all placeholders in it exactly once.", + "gtceu.gui.central_monitor.url": "Input image URL:", "gtceu.gui.charger_slot.tooltip.0": "§fCharger Slot§r", "gtceu.gui.charger_slot.tooltip.1": "§7Draws power from %s batteries§r", "gtceu.gui.charger_slot.tooltip.2": "§7Charges %s tools and batteries", @@ -2381,8 +2426,7 @@ "gtceu.gui.computer_monitor_cover.placeholder_reference.1": "(hover for more info)", "gtceu.gui.computer_monitor_cover.second_page_textbox_tooltip.0": "Input placeholder to be used in place of %s '{}' here.", "gtceu.gui.computer_monitor_cover.second_page_textbox_tooltip.1": "For example, you can have a string 'Energy: {}/{} EU' and 'energy' and 'energyCapacity' in these text boxes.", - "gtceu.gui.computer_monitor_cover.slot_tooltip.0": "A slot for items that some placeholders can reference", - "gtceu.gui.computer_monitor_cover.slot_tooltip.1": "Slot number: %d", + "gtceu.gui.computer_monitor_cover.slot_tooltip": "A slot for items that some placeholders can reference\nSlot number: %d\n", "gtceu.gui.computer_monitor_cover.update_interval": "Update interval (in ticks)", "gtceu.gui.config_slot": "§fConfig Slot§r", "gtceu.gui.config_slot.auto_pull_managed": "§4Disabled:§7 Managed by Auto-Pull", @@ -2424,6 +2468,7 @@ "gtceu.gui.fluid_amount": "Fluid Amount:", "gtceu.gui.fluid_auto_input.tooltip.disabled": "Fluid Auto-Input Disabled", "gtceu.gui.fluid_auto_input.tooltip.enabled": "Fluid Auto-Input Enabled", + "gtceu.gui.fluid_auto_output": "Fluid Output: %s", "gtceu.gui.fluid_auto_output.allow_input.disabled": "disable fluids input from the output side", "gtceu.gui.fluid_auto_output.allow_input.enabled": "allow fluids input from the output side", "gtceu.gui.fluid_auto_output.disabled": "Fluid Auto Output: §cDisabled", @@ -2435,6 +2480,7 @@ "gtceu.gui.fluid_auto_output.tooltip.enabled": "Fluid Auto-Output Enabled", "gtceu.gui.fluid_auto_output.unselected.0": "Fluid Auto Output", "gtceu.gui.fluid_auto_output.unselected.1": "§7Select a side of the machine to configure its output.", + "gtceu.gui.fluid_input_from_output": "Fluid Input from Output: %s", "gtceu.gui.fluid_lock.tooltip.disabled": "Fluid Locking Disabled", "gtceu.gui.fluid_lock.tooltip.enabled": "Fluid Locking Enabled", "gtceu.gui.fluid_voiding": "§7Voiding §9Fluids", @@ -2443,6 +2489,7 @@ "gtceu.gui.fuel_amount": "Fuel Amount:", "gtceu.gui.item_auto_input.tooltip.disabled": "Item Auto-Input Disabled", "gtceu.gui.item_auto_input.tooltip.enabled": "Item Auto-Input Enabled", + "gtceu.gui.item_auto_output": "Item Output: %s", "gtceu.gui.item_auto_output.allow_input.disabled": "disable items input from the output side", "gtceu.gui.item_auto_output.allow_input.enabled": "allow items input from the output side", "gtceu.gui.item_auto_output.disabled": "Item Auto Output: §cDisabled", @@ -2454,6 +2501,8 @@ "gtceu.gui.item_auto_output.tooltip.enabled": "Item Auto-Output Enabled", "gtceu.gui.item_auto_output.unselected.0": "Item Auto Output", "gtceu.gui.item_auto_output.unselected.1": "§7Select a side of the machine to configure its output.", + "gtceu.gui.item_collector.range": "Range: ", + "gtceu.gui.item_input_from_output": "Item Input from Output: %s", "gtceu.gui.item_lock.tooltip.disabled": "Item Locking Disabled", "gtceu.gui.item_lock.tooltip.enabled": "Item Locking Enabled", "gtceu.gui.item_voiding": "§7Voiding §6Items", @@ -2463,8 +2512,13 @@ "gtceu.gui.machinemode.tab_tooltip": "Change active Machine Mode", "gtceu.gui.machinemode.title": "Active Machine Mode", "gtceu.gui.me_bus.auto_pull_button": "Click to toggle automatic item pulling from ME", + "gtceu.gui.me_network.auto_pull_toggle": "Toggle Auto-Pull", + "gtceu.gui.me_network.empty": "Empty", + "gtceu.gui.me_network.min_stack_size": "Min Stack Size:", "gtceu.gui.me_network.offline": "Network Status: §4Offline§r", "gtceu.gui.me_network.online": "Network Status: §2Online§r", + "gtceu.gui.me_network.stocking_settings": "Stocking Settings", + "gtceu.gui.me_network.ticks_per_cycle": "Ticks Per Cycle:", "gtceu.gui.multiblock.voiding_mode": "Voiding Mode:", "gtceu.gui.no_voiding": "§7Voiding Nothing", "gtceu.gui.output_setting.title": "Output Settings", @@ -2479,6 +2533,7 @@ "gtceu.gui.overclock.off": "X", "gtceu.gui.overclock.range": "Available Tiers [%s, %s]", "gtceu.gui.overclock.title": "Overclock Tier", + "gtceu.gui.pattern_buffer.set_custom_name": "Set Custom Name: ", "gtceu.gui.silktouch.disabled.0": "Silk Touch Disabled: Click to Enable.", "gtceu.gui.silktouch.disabled.1": "§7Switching requires an idle machine.", "gtceu.gui.silktouch.enabled.0": "Silk Touch Enabled: Click to Disable.", @@ -2503,6 +2558,7 @@ "gtceu.io.export": "Export", "gtceu.io.import": "Import", "gtceu.io.none": "None", + "gtceu.item.tooltip.image_url": "Image URL: %s", "gtceu.item_filter.empty_item": "Empty (No Item)", "gtceu.item_filter.footer": "§eClick with item to override", "gtceu.item_list.item_stored": "§7Stored: %d", @@ -3029,19 +3085,16 @@ "gtceu.machine.me_import_item_hatch.configs.tooltip": "Keeps 16 item types in stock", "gtceu.machine.miner.chunkradius": "Chunk Radius: %d", "gtceu.machine.miner.fluid_usage": "Uses §f%d mB/t §7of §f%s§7, doubled per overclock.", - "gtceu.machine.miner.minex": "mX: %d", - "gtceu.machine.miner.miney": "mY: %d", - "gtceu.machine.miner.minez": "mZ: %d", "gtceu.machine.miner.multi.description": "A multiblock mining machine that covers a large area and produces huge quantity of ore.", "gtceu.machine.miner.multi.modes": "Has Silk Touch and Chunk Aligned Modes.", "gtceu.machine.miner.multi.production": "Produces §f3x§7 more crushed ore than a §fMacerator§7.", "gtceu.machine.miner.per_block": "§7takes §f%ds §7per Block", "gtceu.machine.miner.progress": "Progress: %d/%d", "gtceu.machine.miner.radius": "Radius: %d", - "gtceu.machine.miner.startx": "sX: %d", - "gtceu.machine.miner.starty": "sY: %d", - "gtceu.machine.miner.startz": "sZ: %d", "gtceu.machine.miner.tooltip": "§7Mines ores below the Miner! Starts as §f%sx%s §7area", + "gtceu.machine.miner.x": "sX: %d, mX: %d", + "gtceu.machine.miner.y": "sY: %d, mY: %d", + "gtceu.machine.miner.z": "sZ: %d, mZ: %d", "gtceu.machine.muffle.off": "Sound Muffling: Disabled", "gtceu.machine.muffle.on": "Sound Muffling: Enabled", "gtceu.machine.muffler_hatch.tooltip.0": "Recovers waste from machines", @@ -3096,6 +3149,7 @@ "gtceu.machine.opv_gas_collector.tooltip": "§7Collects Gas from the universe depending on the dimension", "gtceu.machine.opv_rock_crusher.tooltip": "§7Volcanic Formation Chamber", "gtceu.machine.parallel_hatch.display": "Adjust the maximum parallel of the multiblock", + "gtceu.machine.parallel_hatch.parallel_ui": "Parallels", "gtceu.machine.parallel_hatch_mk5.tooltip": "Allows to run up to 4 recipes in parallel.", "gtceu.machine.parallel_hatch_mk6.tooltip": "Allows to run up to 16 recipes in parallel.", "gtceu.machine.parallel_hatch_mk7.tooltip": "Allows to run up to 64 recipes in parallel.", @@ -3338,10 +3392,9 @@ "gtceu.multiblock.hpca.info_bridging_disabled": "Bridging Disabled", "gtceu.multiblock.hpca.info_bridging_enabled": "Bridging Enabled", "gtceu.multiblock.hpca.info_coolant_name": "PCB Coolant", + "gtceu.multiblock.hpca.info_cooling_demand": "Cooling Demand: %s / %d", "gtceu.multiblock.hpca.info_max_computation": "Max CWU/t: %s", "gtceu.multiblock.hpca.info_max_coolant_required": "Coolant Needed: %s", - "gtceu.multiblock.hpca.info_max_cooling_available": "Cooling Available: %s", - "gtceu.multiblock.hpca.info_max_cooling_demand": "Cooling Demand: %s", "gtceu.multiblock.hpca.temperature": "Temperature: %s", "gtceu.multiblock.hpca.warning_low_cooling": "- Not enough cooling", "gtceu.multiblock.hpca.warning_multiple_bridges": "- Multiple bridges in structure (provides no additional benefit)", @@ -3527,6 +3580,14 @@ "gtceu.packer": "Packer", "gtceu.part_sharing.disabled": "Multiblock Sharing §4Disabled", "gtceu.part_sharing.enabled": "Multiblock Sharing §aEnabled", + "gtceu.placeholder_editor.constant_value": "This expression always evaluates to the same result.\nConsider replacing it with a constant.", + "gtceu.placeholder_editor.extra_closing_bracket": "Extra closing bracket", + "gtceu.placeholder_editor.no_placeholder": "Placeholder with name '%s' does not exist", + "gtceu.placeholder_editor.unclosed_bracket": "Unclosed bracket (\"}\")", + "gtceu.placeholder_editor.unclosed_brackets": "%d brackets (\"}\") are unclosed", + "gtceu.placeholder_editor.unclosed_escape": "Unclosed escape (\"']\")", + "gtceu.placeholder_editor.unclosed_escapes": "%d escapes (\"']\") are unclosed", + "gtceu.placeholder_editor.write_in_if": "Placeholders inside {if} are executed regardless of the condition.\nThis means that, for example, \"{if 0 {redstone set 15}}\" will produce redstone output.\nTo avoid this, use \"{eval {if \"\" \"\"}}\".", "gtceu.placeholder_info.active.0": "Returns a 1 if the block the cover is attached to is currently running a recipe, 0 otherwise.", "gtceu.placeholder_info.active.1": "Usage:", "gtceu.placeholder_info.active.2": " {active} -> whether there's a currently running recipe", @@ -3655,6 +3716,9 @@ "gtceu.placeholder_info.if.0": "Returns one of the arguments depending on the condition. The condition is considered true if it is not an empty string and is not equal to 0.", "gtceu.placeholder_info.if.1": "Usage:", "gtceu.placeholder_info.if.2": " {if [returned_if_false]}", + "gtceu.placeholder_info.item.0": "Returns the amount and id of the item in a specified slot", + "gtceu.placeholder_info.item.1": "Usage:", + "gtceu.placeholder_info.item.2": " {item } -> \"31 minecraft:diamond\" (for example)", "gtceu.placeholder_info.itemCount.0": "Returns the amount of items (can be filtered).", "gtceu.placeholder_info.itemCount.1": "Usage:", "gtceu.placeholder_info.itemCount.2": " {itemCount} -> total item amount", @@ -3668,6 +3732,9 @@ "gtceu.placeholder_info.maxProgress.1": "Example: 'Progress: {calc {calc {progress} / {maxProgress}} * 100}%'", "gtceu.placeholder_info.maxProgress.2": "Usage:", "gtceu.placeholder_info.maxProgress.3": " {maxProgress} -> the max progress of the currently running recipe", + "gtceu.placeholder_info.module.0": "Renders the module in the specified slot onto the central monitor (does not work in a cover)", + "gtceu.placeholder_info.module.1": "Usage:", + "gtceu.placeholder_info.module.2": " {module } -> empty string", "gtceu.placeholder_info.nbt.0": "Returns the nbt data of the item in the specified slot", "gtceu.placeholder_info.nbt.1": "Usage:", "gtceu.placeholder_info.nbt.2": " {nbt [key1] [key2] [key3] ...} -> item_nbt[key1][key2][key3][...]", @@ -3681,9 +3748,16 @@ "gtceu.placeholder_info.progress.1": "Note that progress is an integer between 0 and {maxProgress}", "gtceu.placeholder_info.progress.2": "Usage:", "gtceu.placeholder_info.progress.3": " {progress} -> the progress of the currently running recipe", + "gtceu.placeholder_info.quad.0": "Draws a quad (must specify parameters for all 4 vertices)", + "gtceu.placeholder_info.quad.1": "Usage:", + "gtceu.placeholder_info.quad.2": " {quad } -> empty string", "gtceu.placeholder_info.random.0": "Returns a random number in the specified interval (inclusive).", "gtceu.placeholder_info.random.1": "Usage:", "gtceu.placeholder_info.random.2": " {random } -> a random number between min and max (inclusive)", + "gtceu.placeholder_info.rect.0": "Draws a rectangle at the specified position with the specified coordinates and size", + "gtceu.placeholder_info.rect.1": "Usage:", + "gtceu.placeholder_info.rect.2": " {rect } -> empty string", + "gtceu.placeholder_info.rect.3": " {rect 0.5 0.25 2 1 0xFFFFFFFF} -> draws a white rectangle at (0.5, 0.25) with the size (2, 1)", "gtceu.placeholder_info.redstone.0": "Returns the redstone signal strength or sets the redstone output strength", "gtceu.placeholder_info.redstone.1": "Usage:", "gtceu.placeholder_info.redstone.2": " {redstone get } -> redstone signal strength (0-15) at the specified side", @@ -3696,6 +3770,9 @@ "gtceu.placeholder_info.select.0": "Returns the argument at the specified index (starting from 0)", "gtceu.placeholder_info.select.1": "Usage:", "gtceu.placeholder_info.select.2": " {select [arg1] [arg2] [arg3] ... -> argument at the specified index", + "gtceu.placeholder_info.setImage.0": "Sets the image URL in an image module in the specified slot", + "gtceu.placeholder_info.setImage.1": "Usage:", + "gtceu.placeholder_info.setImage.2": " {setImage } -> empty string", "gtceu.placeholder_info.strike.0": "Returns the text from the first text, displaying it as if it was crossed out", "gtceu.placeholder_info.strike.1": "Usage:", "gtceu.placeholder_info.strike.2": " {strike } -> crossed-out text", @@ -4259,6 +4336,7 @@ "item.gtceu.gray_glass_lens": "Glass Lens (Gray)", "item.gtceu.green_dye_spray_can": "Spray Can (Green)", "item.gtceu.green_glass_lens": "Glass Lens (Green)", + "item.gtceu.gui_module": "Gui Module", "item.gtceu.hammer_extruder_mold.tooltip": "§7Extruder Shape for making Hammers", "item.gtceu.hazmat_boots": "Hazardous Materials Suit Boots", "item.gtceu.hazmat_chestpiece": "Hazardous Materials Suit Chestpiece", diff --git a/src/generated/resources/assets/gtceu/models/block/machine/test_mui.json b/src/generated/resources/assets/gtceu/models/block/machine/test_mui.json new file mode 100644 index 00000000000..f86f60d96f9 --- /dev/null +++ b/src/generated/resources/assets/gtceu/models/block/machine/test_mui.json @@ -0,0 +1,18 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "gtceu:test_mui", + "replaceable_textures": [ + "all" + ], + "variants": { + "": { + "model": { + "parent": "gtceu:block/machine/part/computer_monitor", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_clean_stainless_steel" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/models/block/machine/test_mui_new.json b/src/generated/resources/assets/gtceu/models/block/machine/test_mui_new.json new file mode 100644 index 00000000000..12fd1d59a84 --- /dev/null +++ b/src/generated/resources/assets/gtceu/models/block/machine/test_mui_new.json @@ -0,0 +1,42 @@ +{ + "parent": "minecraft:block/block", + "loader": "gtceu:machine", + "machine": "gtceu:test_mui_new", + "replaceable_textures": [ + "all" + ], + "variants": { + "recipe_logic_status=idle": { + "model": { + "parent": "gtceu:block/machine/part/computer_monitor", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_clean_stainless_steel" + } + } + }, + "recipe_logic_status=suspend": { + "model": { + "parent": "gtceu:block/machine/part/computer_monitor", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_clean_stainless_steel" + } + } + }, + "recipe_logic_status=waiting": { + "model": { + "parent": "gtceu:block/machine/part/computer_monitor", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_clean_stainless_steel" + } + } + }, + "recipe_logic_status=working": { + "model": { + "parent": "gtceu:block/machine/part/computer_monitor", + "textures": { + "all": "gtceu:block/casings/solid/machine_casing_clean_stainless_steel" + } + } + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/models/item/gui_module.json b/src/generated/resources/assets/gtceu/models/item/gui_module.json new file mode 100644 index 00000000000..5c1152837c4 --- /dev/null +++ b/src/generated/resources/assets/gtceu/models/item/gui_module.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "gtceu:item/gui_module" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/models/item/test_mui.json b/src/generated/resources/assets/gtceu/models/item/test_mui.json new file mode 100644 index 00000000000..9ae958ad11c --- /dev/null +++ b/src/generated/resources/assets/gtceu/models/item/test_mui.json @@ -0,0 +1,3 @@ +{ + "parent": "gtceu:block/machine/test_mui" +} \ No newline at end of file diff --git a/src/generated/resources/assets/gtceu/models/item/test_mui_new.json b/src/generated/resources/assets/gtceu/models/item/test_mui_new.json new file mode 100644 index 00000000000..e741c72dc8d --- /dev/null +++ b/src/generated/resources/assets/gtceu/models/item/test_mui_new.json @@ -0,0 +1,3 @@ +{ + "parent": "gtceu:block/machine/test_mui_new" +} \ No newline at end of file diff --git a/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java b/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java index 35c816d2bc4..58aaac61653 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java +++ b/src/main/java/com/gregtechceu/gtceu/api/block/MetaMachineBlock.java @@ -284,9 +284,14 @@ public InteractionResult use(BlockState state, Level world, BlockPos pos, Player shouldOpenUi = gtToolItem.definition$shouldOpenUIAfterUse(new UseOnContext(player, hand, hit)); } - if (shouldOpenUi && machine instanceof IUIMachine uiMachine && - MachineOwner.canOpenOwnerMachine(player, machine)) { - return uiMachine.tryToOpenUI(player, hand, hit); + if (shouldOpenUi && MachineOwner.canOpenOwnerMachine(player, machine)) { + if (machine.getDefinition().getUI() != null) { + return machine.getDefinition().getUI().tryToOpenUI(player, hand, hit); + } else if (machine instanceof IMuiMachine muiMachine) { + return muiMachine.tryToOpenUI(player, hand, hit); + } else if (machine instanceof IUIMachine uiMachine) { + return uiMachine.tryToOpenUI(player, hand, hit); + } } return shouldOpenUi ? InteractionResult.PASS : InteractionResult.CONSUME; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java b/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java index c16c3d6a2c3..965831da9f8 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java +++ b/src/main/java/com/gregtechceu/gtceu/api/block/PipeBlock.java @@ -18,7 +18,7 @@ import com.gregtechceu.gtceu.client.model.pipe.PipeModel; import com.gregtechceu.gtceu.common.data.GTItems; import com.gregtechceu.gtceu.common.data.GTMaterialBlocks; -import com.gregtechceu.gtceu.common.item.CoverPlaceBehavior; +import com.gregtechceu.gtceu.common.item.behavior.CoverPlaceBehavior; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.recipe.VanillaRecipeHelper; import com.gregtechceu.gtceu.utils.GTMath; @@ -271,6 +271,7 @@ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block b pipeTile.setConnection(facing, false, false); updateActiveNodeStatus(level, pos, pipeTile); } + pipeTile.getCoverContainer().onNeighborChanged(block, fromPos, isMoving); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java b/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java index b4fce4cded1..de39df64b52 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java +++ b/src/main/java/com/gregtechceu/gtceu/api/blockentity/PipeBlockEntity.java @@ -7,10 +7,10 @@ import com.gregtechceu.gtceu.api.cover.CoverBehavior; import com.gregtechceu.gtceu.api.data.chemical.material.Material; import com.gregtechceu.gtceu.api.data.tag.TagPrefix; -import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.item.tool.GTToolType; import com.gregtechceu.gtceu.api.item.tool.IToolGridHighlight; import com.gregtechceu.gtceu.api.machine.TickableSubscription; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.pipenet.*; import com.gregtechceu.gtceu.api.sync_system.ManagedSyncBlockEntity; import com.gregtechceu.gtceu.api.sync_system.annotations.RerenderOnChanged; @@ -18,10 +18,9 @@ import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.common.data.GTMaterialBlocks; import com.gregtechceu.gtceu.common.data.GTMaterials; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.GTUtil; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; - import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -329,13 +328,13 @@ public boolean shouldRenderGrid(Player player, BlockPos pos, BlockState state, I return false; } - public ResourceTexture getPipeTexture(boolean isBlock) { - return isBlock ? GuiTextures.TOOL_PIPE_CONNECT : GuiTextures.TOOL_PIPE_BLOCK; + public UITexture getPipeTexture(boolean isBlock) { + return isBlock ? GTGuiTextures.TOOL_PIPE_CONNECT : GTGuiTextures.TOOL_PIPE_BLOCK; } @Override - public @Nullable ResourceTexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, - Direction side) { + public @Nullable UITexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, + Direction side) { if (toolTypes.contains(getPipeTuneTool())) { if (player.isShiftKeyDown() && this.canHaveBlockedFaces()) { return getPipeTexture(isBlocked(side)); diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/forge/GTCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapability.java similarity index 96% rename from src/main/java/com/gregtechceu/gtceu/api/capability/forge/GTCapability.java rename to src/main/java/com/gregtechceu/gtceu/api/capability/GTCapability.java index f26b143cd3f..66a0aa5c6b3 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/forge/GTCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapability.java @@ -1,6 +1,5 @@ -package com.gregtechceu.gtceu.api.capability.forge; +package com.gregtechceu.gtceu.api.capability; -import com.gregtechceu.gtceu.api.capability.*; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMaintenanceMachine; import net.minecraftforge.common.capabilities.Capability; diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapabilityHelper.java b/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapabilityHelper.java index 1bf8262ac7e..ddd096e5fab 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapabilityHelper.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/GTCapabilityHelper.java @@ -1,6 +1,5 @@ package com.gregtechceu.gtceu.api.capability; -import com.gregtechceu.gtceu.api.capability.forge.GTCapability; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMaintenanceMachine; import net.minecraft.core.BlockPos; diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/IMonitorComponent.java b/src/main/java/com/gregtechceu/gtceu/api/capability/IMonitorComponent.java index 002c650a187..89289292be3 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/IMonitorComponent.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/IMonitorComponent.java @@ -1,6 +1,6 @@ package com.gregtechceu.gtceu.api.capability; -import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; import net.minecraft.core.BlockPos; import net.minecraftforge.items.IItemHandler; @@ -13,7 +13,7 @@ default boolean isMonitor() { return false; } - IGuiTexture getComponentIcon(); + IDrawable getIcon(); BlockPos getBlockPos(); diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/compat/EUToFEProvider.java b/src/main/java/com/gregtechceu/gtceu/api/capability/compat/EUToFEProvider.java index 619d7904dec..b3b025c8f1a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/compat/EUToFEProvider.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/compat/EUToFEProvider.java @@ -1,8 +1,8 @@ package com.gregtechceu.gtceu.api.capability.compat; import com.gregtechceu.gtceu.api.GTValues; +import com.gregtechceu.gtceu.api.capability.GTCapability; import com.gregtechceu.gtceu.api.capability.IEnergyContainer; -import com.gregtechceu.gtceu.api.capability.forge.GTCapability; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.utils.GTMath; import com.gregtechceu.gtceu.utils.GTUtil; diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java index 8f2e15d80e7..755a9c88955 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java @@ -17,11 +17,11 @@ import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; import com.gregtechceu.gtceu.client.TooltipsHandler; import com.gregtechceu.gtceu.common.valueprovider.*; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidEntryList; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidStackList; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidTagList; -import com.gregtechceu.gtceu.integration.xei.handlers.fluid.CycleFluidEntryHandler; -import com.gregtechceu.gtceu.integration.xei.widgets.GTRecipeWidget; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidEntryList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidStackList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidTagList; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.fluid.CycleFluidEntryHandler; +import com.gregtechceu.gtceu.integration.recipeviewer.widgets.GTRecipeWidget; import com.gregtechceu.gtceu.utils.GTMath; import com.lowdragmc.lowdraglib.gui.texture.ProgressTexture; diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IO.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IO.java index 40a905e48e0..e1ffe4dbb08 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IO.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IO.java @@ -1,6 +1,7 @@ package com.gregtechceu.gtceu.api.capability.recipe; import com.gregtechceu.gtceu.api.gui.widget.EnumSelectorWidget; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; @@ -21,10 +22,13 @@ public enum IO implements EnumSelectorWidget.SelectableEnum { public final String tooltip; @Getter public final IGuiTexture icon; + @Getter + public final UITexture uiTexture; IO(String tooltip, String textureName) { this.tooltip = tooltip; this.icon = new ResourceTexture("gtceu:textures/gui/icon/io_mode/" + textureName + ".png"); + this.uiTexture = UITexture.fullImage("gtceu:textures/gui/icon/io_mode/" + textureName + ".png"); } public boolean support(IO io) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java index 239d8a9377b..e38611320d5 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java @@ -23,11 +23,11 @@ import com.gregtechceu.gtceu.core.mixins.IngredientAccessor; import com.gregtechceu.gtceu.core.mixins.TagValueAccessor; import com.gregtechceu.gtceu.core.mixins.forge.IntersectionIngredientAccessor; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemEntryList; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemStackList; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemTagList; -import com.gregtechceu.gtceu.integration.xei.handlers.item.CycleItemEntryHandler; -import com.gregtechceu.gtceu.integration.xei.widgets.GTRecipeWidget; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemEntryList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemStackList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemTagList; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.item.CycleItemEntryHandler; +import com.gregtechceu.gtceu.integration.recipeviewer.widgets.GTRecipeWidget; import com.gregtechceu.gtceu.utils.*; import com.lowdragmc.lowdraglib.gui.widget.Widget; diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java index 28df149687e..6d2a54badc1 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java @@ -1,6 +1,5 @@ package com.gregtechceu.gtceu.api.capability.recipe; -import com.gregtechceu.gtceu.api.codec.DispatchedMapCodec; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.content.Content; @@ -9,6 +8,7 @@ import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; import com.gregtechceu.gtceu.api.registry.GTRegistries; +import com.gregtechceu.gtceu.utils.codec.DispatchedMapCodec; import com.lowdragmc.lowdraglib.gui.widget.Widget; import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java b/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java index 7da44721bec..6707d76e5e6 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/CoverBehavior.java @@ -2,22 +2,19 @@ import com.gregtechceu.gtceu.api.blockentity.ICopyable; import com.gregtechceu.gtceu.api.capability.ICoverable; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.factory.CoverUIFactory; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfigurator; import com.gregtechceu.gtceu.api.item.tool.GTToolType; import com.gregtechceu.gtceu.api.item.tool.IToolGridHighlight; import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.sync_system.ISyncManaged; -import com.gregtechceu.gtceu.api.sync_system.ManagedSyncBlockEntity; import com.gregtechceu.gtceu.api.sync_system.SyncDataHolder; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.api.transfer.fluid.IFluidHandlerModifiable; import com.gregtechceu.gtceu.client.renderer.cover.ICoverRenderer; import com.gregtechceu.gtceu.client.renderer.cover.IDynamicCoverRenderer; - -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; @@ -80,9 +77,7 @@ public void scheduleRenderUpdate() { @Override public void markAsChanged() { - if (coverHolder instanceof ManagedSyncBlockEntity syncEntity) { - syncEntity.markAsChanged(); - } + coverHolder.markAsChanged(); } /** @@ -109,6 +104,7 @@ public void onAttached(ItemStack itemStack, @Nullable ServerPlayer player) { attachItem = itemStack.copy(); attachItem.setCount(1); syncDataHolder.markClientSyncFieldDirty("attachItem"); + markAsChanged(); } public void onLoad() {} @@ -151,9 +147,9 @@ public boolean canConnectRedstone() { // ******* Interaction *******// ////////////////////////////////////// public InteractionResult onScrewdriverClick(Player playerIn, InteractionHand hand, BlockHitResult hitResult) { - if (this instanceof IUICover) { + if (this instanceof IMuiCover muiCover) { if (playerIn instanceof ServerPlayer serverPlayer) { - CoverUIFactory.INSTANCE.openUI(this, serverPlayer); + com.gregtechceu.gtceu.common.mui.factory.CoverUIFactory.INSTANCE.open(serverPlayer, muiCover); } return InteractionResult.sidedSuccess(playerIn.level().isClientSide); } @@ -190,17 +186,17 @@ public boolean shouldRenderPlate() { public boolean shouldRenderGrid(Player player, BlockPos pos, BlockState state, ItemStack held, Set toolTypes) { return toolTypes.contains(GTToolType.CROWBAR) || - ((toolTypes.isEmpty() || toolTypes.contains(GTToolType.SCREWDRIVER)) && this instanceof IUICover); + ((toolTypes.isEmpty() || toolTypes.contains(GTToolType.SCREWDRIVER)) && this instanceof IMuiCover); } @Override - public @Nullable ResourceTexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, - Direction side) { + public @Nullable UITexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, + Direction side) { if (toolTypes.contains(GTToolType.CROWBAR)) { - return GuiTextures.TOOL_REMOVE_COVER; + return GTGuiTextures.TOOL_REMOVE_COVER; } - if ((toolTypes.isEmpty() || toolTypes.contains(GTToolType.SCREWDRIVER)) && this instanceof IUICover) { - return GuiTextures.TOOL_COVER_SETTINGS; + if ((toolTypes.isEmpty() || toolTypes.contains(GTToolType.SCREWDRIVER)) && this instanceof IMuiCover) { + return GTGuiTextures.TOOL_COVER_SETTINGS; } return null; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/IMuiCover.java b/src/main/java/com/gregtechceu/gtceu/api/cover/IMuiCover.java new file mode 100644 index 00000000000..421813e3f09 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/IMuiCover.java @@ -0,0 +1,79 @@ +package com.gregtechceu.gtceu.api.cover; + +import com.gregtechceu.gtceu.api.mui.base.IUIHolder; +import com.gregtechceu.gtceu.api.mui.factory.SidedPosGuiData; +import com.gregtechceu.gtceu.api.mui.value.BoolValue; +import com.gregtechceu.gtceu.api.mui.value.sync.EnumSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.common.mui.GTGuiTheme; +import com.gregtechceu.gtceu.common.mui.GTGuis; + +public interface IMuiCover extends IUIHolder { + + default CoverBehavior self() { + return (CoverBehavior) this; + } + + default boolean isRemote() { + return self().coverHolder.isRemote(); + } + + default boolean isRemoved() { + return self().coverHolder.isRemoved() || self().coverHolder.getCoverAtSide(self().attachedSide) != self(); + } + + default GTGuiTheme getUITheme() { + return GTGuiTheme.COVER; + } + + @Override + default ModularPanel buildUI(SidedPosGuiData data, PanelSyncManager syncManager, UISettings settings) { + ModularPanel panel = GTGuis.createPanel(this.self(), 176, 192 + 18); + + panel.child(GTMuiWidgets.createTitleBar(this.self().getAttachItem(), 176, GTGuiTextures.BACKGROUND)); + + Flow column = Flow.column() + .top(7).margin(7, 0) + .childPadding(2) + .widthRel(1.0f).coverChildrenHeight(); + + createCoverUIRows(column, data, syncManager, settings); + return panel.child(column) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); + } + + /** + * The default cover UI panel builds a single column with rows added by each cover. + */ + default void createCoverUIRows(Flow column, SidedPosGuiData data, PanelSyncManager syncManager, + UISettings settings) {} + + /* Helper methods for UI creation with covers that are commonly used */ + + /** + * The color used for Cover UI text. Available for reference, but is + * handled automatically by the {@link GTGuiTheme#COVER} theme. + */ + int UI_TEXT_COLOR = 0xFF555555; + + default Flow coverUIRow() { + return Flow.row() + .coverChildrenHeight() + .widthRel(1f) + .childPadding(2); + } + + /** + * Get a BoolValue for use with toggle buttons which are "linked together," + * meaning only one of them can be pressed at a time. + */ + default > BoolValue.Dynamic boolValueOf(EnumSyncValue syncValue, T value) { + return new BoolValue.Dynamic(() -> syncValue.getValue() == value, $ -> syncValue.setValue(value)); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java b/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java deleted file mode 100644 index 93d85bd74f5..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/IUICover.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.gregtechceu.gtceu.api.cover; - -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.UITemplate; - -import com.lowdragmc.lowdraglib.gui.modular.IUIHolder; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; -import com.lowdragmc.lowdraglib.gui.widget.Widget; -import com.lowdragmc.lowdraglib.utils.Position; - -import net.minecraft.world.entity.player.Player; - -public interface IUICover extends IUIHolder { - - default CoverBehavior self() { - return (CoverBehavior) this; - } - - @Override - default boolean isInvalid() { - return self().coverHolder.isRemoved() || self().coverHolder.getCoverAtSide(self().attachedSide) != self(); - } - - @Override - default boolean isRemote() { - return self().coverHolder.isRemote(); - } - - @Override - default ModularUI createUI(Player entityPlayer) { - var widget = createUIWidget(); - var size = widget.getSize(); - widget.setSelfPosition(new Position((176 - size.width) / 2, 0)); - var modularUI = new ModularUI(176, size.height + 82, this, entityPlayer) - .background(GuiTextures.BACKGROUND) - .widget(widget) - .widget(UITemplate.bindPlayerInventory(entityPlayer.getInventory(), GuiTextures.SLOT, 7, size.height, - true)); - modularUI.registerCloseListener(this::onUIClosed); - return modularUI; - } - - default void onUIClosed() {} - - Widget createUIWidget(); - - @Override - default void markAsDirty() {} -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/Filter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/Filter.java index 304aef62395..ebfa18add97 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/Filter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/Filter.java @@ -1,5 +1,10 @@ package com.gregtechceu.gtceu.api.cover.filter; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; + import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; import net.minecraft.nbt.CompoundTag; @@ -9,7 +14,14 @@ public interface Filter> extends Predicate { - WidgetGroup openConfigurator(int x, int y); + default WidgetGroup openConfigurator(int x, int y) { + return null; + } + + /** + * @return Filter panel when opened by itself (including the player inventory) + */ + ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings); CompoundTag saveFilter(); diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java index ea051ee971f..7518f95f5e1 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandler.java @@ -53,16 +53,16 @@ public FilterHandler(ISyncManaged container) { this.container = container; } - protected abstract F loadFilter(ItemStack filterItem); + public abstract F loadFilter(ItemStack filterItem); protected abstract F getEmptyFilter(); - protected abstract boolean canInsertFilterItem(ItemStack itemStack); - ////////////////////////////////// // ***** PUBLIC API ******// ////////////////////////////////// + public abstract boolean canInsertFilterItem(ItemStack itemStack); + public Widget createFilterSlotUI(int xPos, int yPos) { return new SlotWidget(getFilterSlot(), 0, xPos, yPos) .setChangeListener(this::updateFilter) @@ -117,7 +117,7 @@ public FilterHandler onFilterUpdated(Consumer onFilterUpdated) { // ***** FILTER HANDLING ******// /////////////////////////////////////// - private CustomItemStackHandler getFilterSlot() { + public CustomItemStackHandler getFilterSlot() { if (this.filterSlot == null) { this.filterSlot = new CustomItemStackHandler(this.filterItem) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandlers.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandlers.java index cb269e0aa23..c48c9f4d0e8 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandlers.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FilterHandlers.java @@ -2,16 +2,21 @@ import com.gregtechceu.gtceu.api.sync_system.ISyncManaged; +import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault public interface FilterHandlers { static FilterHandler item(ISyncManaged container) { return new FilterHandler<>(container) { @Override - protected ItemFilter loadFilter(ItemStack filterItem) { + public ItemFilter loadFilter(ItemStack filterItem) { return ItemFilter.loadFilter(filterItem); } @@ -21,7 +26,7 @@ protected ItemFilter getEmptyFilter() { } @Override - protected boolean canInsertFilterItem(ItemStack itemStack) { + public boolean canInsertFilterItem(ItemStack itemStack) { return ItemFilter.FILTERS.containsKey(itemStack.getItem()); } }; @@ -31,7 +36,7 @@ static FilterHandler fluid(ISyncManaged container) { return new FilterHandler<>(container) { @Override - protected FluidFilter loadFilter(ItemStack filterItem) { + public FluidFilter loadFilter(ItemStack filterItem) { return FluidFilter.loadFilter(filterItem); } @@ -41,7 +46,7 @@ protected FluidFilter getEmptyFilter() { } @Override - protected boolean canInsertFilterItem(ItemStack itemStack) { + public boolean canInsertFilterItem(ItemStack itemStack) { return FluidFilter.FILTERS.containsKey(itemStack.getItem()); } }; diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FluidFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FluidFilter.java index 9a8a09fb68e..a5d3798d362 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FluidFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/FluidFilter.java @@ -1,6 +1,9 @@ package com.gregtechceu.gtceu.api.cover.filter; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; @@ -54,8 +57,8 @@ public int testFluidAmount(FluidStack fluidStack) { } @Override - public WidgetGroup openConfigurator(int x, int y) { - throw new NotImplementedException("Not available for empty fluid filter"); + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + return null; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/ItemFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/ItemFilter.java index 73e24344dc7..695bfa8bf22 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/ItemFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/ItemFilter.java @@ -1,6 +1,9 @@ package com.gregtechceu.gtceu.api.cover.filter; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; @@ -53,8 +56,8 @@ public boolean test(ItemStack itemStack) { } @Override - public WidgetGroup openConfigurator(int x, int y) { - throw new NotImplementedException("Not available for empty item filter"); + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + return null; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleFluidFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleFluidFilter.java index d2c6610a644..0878f01e712 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleFluidFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleFluidFilter.java @@ -1,11 +1,21 @@ package com.gregtechceu.gtceu.api.cover.filter; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.widget.ScrollablePhantomFluidWidget; -import com.gregtechceu.gtceu.api.gui.widget.ToggleButtonWidget; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.BooleanSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.FluidSlotSyncHandler; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widgets.Dialog; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ToggleButton; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Grid; +import com.gregtechceu.gtceu.api.mui.widgets.slot.FluidSlot; import com.gregtechceu.gtceu.api.transfer.fluid.CustomFluidTank; - -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.GTItems; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag; @@ -38,9 +48,17 @@ public class SimpleFluidFilter implements FluidFilter { @Getter protected int maxStackSize = 1; - private CustomFluidTank[] fluidStorageSlots = new CustomFluidTank[9]; + private final CustomFluidTank[] fluidStorageSlots = new CustomFluidTank[9]; protected SimpleFluidFilter() { + for (int i = 0; i < 9; i++) { + int finalI = i; + fluidStorageSlots[i] = new CustomFluidTank(64000); + fluidStorageSlots[i].setOnContentsChanged(() -> { + matches[finalI] = fluidStorageSlots[finalI].getFluid(); + onUpdated.accept(this); + }); + } Arrays.fill(matches, FluidStack.EMPTY); } @@ -56,6 +74,7 @@ private static SimpleFluidFilter loadFilter(CompoundTag tag, Consumer fluidStorageSlots[index].getFluid(), - (fluid) -> fluidStorageSlots[index].setFluid(fluid)) { - - @Override - public void updateScreen() { - super.updateScreen(); - setShowAmount(maxStackSize > 1L); - } - - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - setShowAmount(maxStackSize > 1L); - } - }; - - tank.setChangeListener(() -> { - matches[index] = fluidStorageSlots[index].getFluidInTank(0); - onUpdated.accept(this); - }).setBackground(GuiTextures.SLOT); - - group.addWidget(tank); - } + @Override + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + for (int i = 0; i < 9; i++) { + syncManager.syncValue("filter_slot_" + i, + new FluidSlotSyncHandler(fluidStorageSlots[i]).controlsAmount(true).phantom(true)); } - group.addWidget(new ToggleButtonWidget(18 * 3 + 5, 0, 20, 20, - GuiTextures.BUTTON_BLACKLIST, this::isBlackList, this::setBlackList)); - group.addWidget(new ToggleButtonWidget(18 * 3 + 5, 20, 20, 20, - GuiTextures.BUTTON_FILTER_NBT, this::isIgnoreNbt, this::setIgnoreNbt)); - return group; + + Grid filterGrid = new Grid() + .coverChildren() + .mapTo(3, 9, i -> new FluidSlot().syncHandler("filter_slot_" + i)); + + BooleanSyncValue blacklist = new BooleanSyncValue(this::isBlackList, this::setBlackList); + syncManager.syncValue("blacklist", blacklist); + + BooleanSyncValue ignoreNBT = new BooleanSyncValue(this::isIgnoreNbt, this::setIgnoreNbt); + syncManager.syncValue("ignoreNBT", ignoreNBT); + + Flow filterConfigButtons = Flow.col() + .coverChildren() + .child(new ToggleButton().stateBackground(GTGuiTextures.BUTTON_BLACKLIST).syncHandler("blacklist")) + .child(new ToggleButton().stateBackground(GTGuiTextures.BUTTON_IGNORE_NBT).syncHandler("ignoreNBT")); + + return new Dialog<>("simple_fluid_filter") + .setDisablePanelsBelow(false) + .setDraggable(true) + .setCloseOnOutOfBoundsClick(true) + .child(GTMuiWidgets.createTitleBar(GTItems.FLUID_FILTER.asStack(), 176, GTGuiTextures.BACKGROUND)) + .child(Flow.row() + .top(10) + .coverChildrenHeight() + .child(filterGrid.horizontalCenter()) + .child(filterConfigButtons.marginLeft(118))) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleItemFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleItemFilter.java index 408a6656687..27cad35ac54 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleItemFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SimpleItemFilter.java @@ -1,13 +1,24 @@ package com.gregtechceu.gtceu.api.cover.filter; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.widget.PhantomSlotWidget; -import com.gregtechceu.gtceu.api.gui.widget.ToggleButtonWidget; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.BooleanSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.value.sync.PhantomItemSlotSyncHandler; +import com.gregtechceu.gtceu.api.mui.widgets.Dialog; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.ToggleButton; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Grid; +import com.gregtechceu.gtceu.api.mui.widgets.slot.ModularSlot; +import com.gregtechceu.gtceu.api.mui.widgets.slot.PhantomItemSlot; import com.gregtechceu.gtceu.api.transfer.item.CustomItemStackHandler; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.GTItems; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.GTUtil; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; - import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; @@ -15,6 +26,7 @@ import net.minecraft.world.item.ItemStack; import lombok.Getter; +import org.jetbrains.annotations.NotNull; import java.util.Arrays; import java.util.function.Consumer; @@ -97,42 +109,87 @@ public void setIgnoreNbt(boolean ingoreNbt) { onUpdated.accept(this); } - public WidgetGroup openConfigurator(int x, int y) { - WidgetGroup group = new WidgetGroup(x, y, 18 * 3 + 25, 18 * 3); // 80 55 - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - final int index = i * 3 + j; + @Override + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + FilterItemStackHandler handler = new FilterItemStackHandler(matches, this); + + Grid filterGrid = new Grid() + .coverChildren() + .mapTo(3, 9, i -> new PhantomItemSlot() + .size(16) + .syncHandler(new PhantomItemSlotSyncHandler(new ModularSlot(handler, i) + .changeListener((stack, amount, client, init) -> { + handler.setStackInSlot(i, stack); + }).ignoreMaxStackSize(true).accessibility(true, false)))); + + BooleanSyncValue blacklist = new BooleanSyncValue(this::isBlackList, this::setBlackList); + syncManager.syncValue("blacklist", blacklist); + + BooleanSyncValue ignoreNBT = new BooleanSyncValue(this::isIgnoreNbt, this::setIgnoreNbt); + syncManager.syncValue("ignoreNBT", ignoreNBT); + + Flow filterConfigButtons = Flow.col() + .coverChildren() + .child(new ToggleButton().stateBackground(GTGuiTextures.BUTTON_BLACKLIST).syncHandler("blacklist")) + .child(new ToggleButton().stateBackground(GTGuiTextures.BUTTON_IGNORE_NBT).syncHandler("ignoreNBT")); + + return new Dialog<>("simple_item_filter") + .setDisablePanelsBelow(false) + .setDraggable(true) + .setCloseOnOutOfBoundsClick(true) + .child(GTMuiWidgets.createTitleBar(GTItems.ITEM_FILTER.asStack(), 176, GTGuiTextures.BACKGROUND)) + .child(Flow.row() + .top(10) + .coverChildrenHeight() + .child(filterGrid.horizontalCenter()) + .child(filterConfigButtons.marginLeft(118))) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); + } + + public static class FilterItemStackHandler extends CustomItemStackHandler { - var handler = new CustomItemStackHandler(matches[index]); + private final ItemStack[] matches; + private final SimpleItemFilter filter; - var slot = new PhantomSlotWidget(handler, 0, i * 18, j * 18) { + public FilterItemStackHandler(SimpleItemFilter filter) { + this(filter.matches, filter); + } - @Override - public void updateScreen() { - super.updateScreen(); - setMaxStackSize(maxStackSize); - } + public FilterItemStackHandler(ItemStack[] matches, SimpleItemFilter simpleItemFilter) { + super(matches.length); + this.matches = matches; + this.filter = simpleItemFilter; + } - @Override - public void detectAndSendChanges() { - super.detectAndSendChanges(); - setMaxStackSize(maxStackSize); - } - }; + @Override + public @NotNull ItemStack getStackInSlot(int slot) { + return matches[slot]; + } - slot.setChangeListener(() -> { - matches[index] = handler.getStackInSlot(0); - onUpdated.accept(this); - }).setBackground(GuiTextures.SLOT); + @Override + protected int getStackLimit(int slot, @NotNull ItemStack stack) { + return 1; + } - group.addWidget(slot); + @Override + public @NotNull ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount >= matches[slot].getCount()) { + matches[slot] = ItemStack.EMPTY; } + return matches[slot]; + } + + @Override + public @NotNull ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public void setStackInSlot(int slot, @NotNull ItemStack stack) { + super.setStackInSlot(slot, stack); + matches[slot] = stack.copyWithCount(1); + filter.onUpdated.accept(filter); } - group.addWidget(new ToggleButtonWidget(18 * 3 + 5, 0, 20, 20, - GuiTextures.BUTTON_BLACKLIST, this::isBlackList, this::setBlackList)); - group.addWidget(new ToggleButtonWidget(18 * 3 + 5, 20, 20, 20, - GuiTextures.BUTTON_FILTER_NBT, this::isIgnoreNbt, this::setIgnoreNbt)); - return group; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java index c26624f1c2d..f8ad51ce5cc 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java @@ -1,22 +1,38 @@ package com.gregtechceu.gtceu.api.cover.filter; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; import com.gregtechceu.gtceu.api.gui.widget.EnumSelectorWidget; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.drawable.ColorType; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.EnumSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widgets.Dialog; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.content.Content; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.GTItems; import com.gregtechceu.gtceu.common.data.GTRecipeTypes; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.ItemStackHashStrategy; import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; +import lombok.Getter; +import java.util.Arrays; import java.util.Collections; import java.util.function.Consumer; @@ -25,6 +41,7 @@ public class SmartItemFilter implements ItemFilter { protected Consumer itemWriter = filter -> {}; protected Consumer onUpdated = filter -> itemWriter.accept(filter); + @Getter private SmartFilteringMode filterMode = SmartFilteringMode.ELECTROLYZER; protected SmartItemFilter() {} @@ -69,11 +86,23 @@ private void setFilterMode(SmartFilteringMode filterMode) { } @Override - public WidgetGroup openConfigurator(int x, int y) { - WidgetGroup group = new WidgetGroup(x, y, 18 * 3 + 25, 18 * 3); - group.addWidget(new EnumSelectorWidget<>(16, 8, 32, 32, - SmartFilteringMode.VALUES, filterMode, this::setFilterMode)); - return group; + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + EnumSyncValue mode = new EnumSyncValue<>(SmartFilteringMode.class, + this::getFilterMode, this::setFilterMode); + + syncManager.syncValue("mode", mode); + + return new Dialog<>("smart_item_filter") + .setDisablePanelsBelow(false) + .setDraggable(true) + .setCloseOnOutOfBoundsClick(true) + .child(GTMuiWidgets.createTitleBar(GTItems.SMART_ITEM_FILTER.asStack(), 176, GTGuiTextures.BACKGROUND)) + .child(new GTMuiWidgets.EnumRowBuilder<>(SmartFilteringMode.class) + .value(mode) + .overlay(16, SmartFilteringMode.getTextures()) + .lang(IKey.dynamic(() -> Component.translatable(filterMode.localeName))) + .build().margin(7)) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); } @Override @@ -134,6 +163,13 @@ public String getTooltip() { return "cover.item_smart_filter.filtering_mode." + localeName; } + public static UITexture[] getTextures() { + return Arrays.stream(VALUES) + .map(v -> UITexture.fullImage(GTCEu.MOD_ID, + "textures/block/machines/" + v.localeName + "/overlay_front.png", ColorType.DEFAULT)) + .toArray(UITexture[]::new); + } + @Override public IGuiTexture getIcon() { return new ResourceTexture("gtceu:textures/block/machines/" + localeName + "/overlay_front.png"); diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFilter.java index 712f8be4a57..2dce613ee5d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFilter.java @@ -1,32 +1,30 @@ package com.gregtechceu.gtceu.api.cover.filter; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.data.lang.LangHandler; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.value.sync.StringSyncValue; +import com.gregtechceu.gtceu.api.mui.widgets.Dialog; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; +import com.gregtechceu.gtceu.api.mui.widgets.textfield.TextFieldWidget; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.TagExprFilter; -import com.lowdragmc.lowdraglib.gui.widget.ImageWidget; -import com.lowdragmc.lowdraglib.gui.widget.TextFieldWidget; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; - import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.ItemStack; import lombok.Getter; import java.util.function.Consumer; -import java.util.regex.Pattern; public abstract class TagFilter> implements Filter { - private static final Pattern DOUBLE_WILDCARD = Pattern.compile("\\*{2,}"); - private static final Pattern DOUBLE_AND = Pattern.compile("&{2,}"); - private static final Pattern DOUBLE_OR = Pattern.compile("\\|{2,}"); - private static final Pattern DOUBLE_NOT = Pattern.compile("!{2,}"); - private static final Pattern DOUBLE_XOR = Pattern.compile("\\^{2,}"); - private static final Pattern DOUBLE_SPACE = Pattern.compile(" {2,}"); - @Getter - protected String oreDictFilterExpression = ""; + protected String filterString = ""; protected Consumer itemWriter = filter -> {}; protected Consumer onUpdated = filter -> itemWriter.accept(filter); @@ -37,7 +35,7 @@ protected TagFilter() {} @Override public boolean isBlank() { - return oreDictFilterExpression.isBlank(); + return filterString.isBlank(); } public CompoundTag saveFilter() { @@ -45,78 +43,34 @@ public CompoundTag saveFilter() { return null; } var tag = new CompoundTag(); - tag.putString("oreDict", oreDictFilterExpression); + tag.putString("oreDict", filterString); return tag; } - public void setOreDict(String oreDict) { - this.oreDictFilterExpression = oreDict; - matchExpr = TagExprFilter.parseExpression(oreDictFilterExpression); + public void setFilterString(String filterString) { + this.filterString = filterString; + matchExpr = TagExprFilter.parseExpression(filterString); onUpdated.accept((S) this); } - public WidgetGroup openConfigurator(int x, int y) { - WidgetGroup group = new WidgetGroup(x, y, 18 * 3 + 25, 18 * 3); // 80 55 - group.addWidget(new ImageWidget(0, 0, 20, 20, GuiTextures.INFO_ICON) - .setHoverTooltips( - LangHandler.getMultiLang("cover.tag_filter.info").toArray(new MutableComponent[0]))); - group.addWidget(new TextFieldWidget(0, 29, 18 * 3 + 25, 12, () -> oreDictFilterExpression, this::setOreDict) - .setMaxStringLength(64) - .setValidator(input -> { - // remove all operators that are double - input = DOUBLE_WILDCARD.matcher(input).replaceAll("*"); - input = DOUBLE_AND.matcher(input).replaceAll("&"); - input = DOUBLE_OR.matcher(input).replaceAll("|"); - input = DOUBLE_NOT.matcher(input).replaceAll("!"); - input = DOUBLE_XOR.matcher(input).replaceAll("^"); - input = DOUBLE_SPACE.matcher(input).replaceAll(" "); - // move ( and ) so it doesn't create invalid expressions f.e. xxx (& yyy) => xxx & (yyy) - // append or prepend ( and ) if the amount is not equal - StringBuilder builder = new StringBuilder(); - int unclosed = 0; - char last = ' '; - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - if (c == ' ') { - if (last != '(') - builder.append(" "); - continue; - } - if (c == '(') - unclosed++; - else if (c == ')') { - unclosed--; - if (last == '&' || last == '|' || last == '^') { - int l = builder.lastIndexOf(" " + last); - int l2 = builder.lastIndexOf(String.valueOf(last)); - builder.insert(l == l2 - 1 ? l : l2, ")"); - continue; - } - if (i > 0 && builder.charAt(builder.length() - 1) == ' ') { - builder.deleteCharAt(builder.length() - 1); - } - } else if ((c == '&' || c == '|' || c == '^') && last == '(') { - builder.deleteCharAt(builder.lastIndexOf("(")); - builder.append(c).append(" ("); - continue; - } - - builder.append(c); - last = c; - } - if (unclosed > 0) { - builder.append(")".repeat(unclosed)); - } else if (unclosed < 0) { - unclosed = -unclosed; - for (int i = 0; i < unclosed; i++) { - builder.insert(0, "("); - } - } - input = builder.toString(); - input = input.replaceAll(" {2,}", " "); - return input; - })); - return group; + protected abstract ItemStack getFilterItem(); + + @Override + public ModularPanel getPanel(GuiData data, PanelSyncManager syncManager, UISettings settings) { + StringSyncValue filterString = new StringSyncValue(this::getFilterString, this::setFilterString); + RichTooltip infoTooltip = new RichTooltip().addMultiLine("cover.tag_filter.info"); + + var inputRow = Flow.row().margin(7).coverChildren().horizontalCenter() + .child(new TextFieldWidget().width(140).value(filterString)) + .child(GTGuiTextures.INFO.asWidget().tooltip(infoTooltip)); + + return new Dialog<>("tag_filter") + .setDisablePanelsBelow(false) + .setDraggable(true) + .setCloseOnOutOfBoundsClick(true) + .child(GTMuiWidgets.createTitleBar(getFilterItem(), 176, GTGuiTextures.BACKGROUND)) + .child(inputRow) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFluidFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFluidFilter.java index ff54bd91eb0..014a14e0898 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFluidFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagFluidFilter.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.cover.filter; +import com.gregtechceu.gtceu.common.data.GTItems; import com.gregtechceu.gtceu.utils.TagExprFilter; import net.minecraft.nbt.CompoundTag; @@ -27,21 +28,26 @@ public static TagFluidFilter loadFilter(ItemStack itemStack) { private static TagFluidFilter loadFilter(CompoundTag tag, Consumer itemWriter) { var handler = new TagFluidFilter(); handler.itemWriter = itemWriter; - handler.oreDictFilterExpression = tag.getString("oreDict"); + handler.filterString = tag.getString("oreDict"); handler.matchExpr = null; handler.cache.clear(); - handler.matchExpr = TagExprFilter.parseExpression(handler.oreDictFilterExpression); + handler.matchExpr = TagExprFilter.parseExpression(handler.filterString); return handler; } - public void setOreDict(String oreDict) { + public void setFilterString(String oreDict) { cache.clear(); - super.setOreDict(oreDict); + super.setFilterString(oreDict); + } + + @Override + protected ItemStack getFilterItem() { + return GTItems.TAG_FLUID_FILTER.asStack(); } @Override public boolean test(FluidStack fluidStack) { - if (oreDictFilterExpression.isEmpty()) return false; + if (filterString.isEmpty()) return false; if (cache.containsKey(fluidStack.getFluid())) return cache.getOrDefault(fluidStack.getFluid(), false); if (TagExprFilter.tagsMatch(matchExpr, fluidStack)) { cache.put(fluidStack.getFluid(), true); diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagItemFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagItemFilter.java index 5018590726a..e33eca3e7df 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagItemFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/TagItemFilter.java @@ -1,5 +1,6 @@ package com.gregtechceu.gtceu.api.cover.filter; +import com.gregtechceu.gtceu.common.data.GTItems; import com.gregtechceu.gtceu.utils.TagExprFilter; import net.minecraft.nbt.CompoundTag; @@ -26,21 +27,26 @@ public static TagItemFilter loadFilter(ItemStack itemStack) { private static TagItemFilter loadFilter(CompoundTag tag, Consumer itemWriter) { var handler = new TagItemFilter(); handler.itemWriter = itemWriter; - handler.oreDictFilterExpression = tag.getString("oreDict"); + handler.filterString = tag.getString("oreDict"); handler.matchExpr = null; handler.cache.clear(); - handler.matchExpr = TagExprFilter.parseExpression(handler.oreDictFilterExpression); + handler.matchExpr = TagExprFilter.parseExpression(handler.filterString); return handler; } - public void setOreDict(String oreDict) { + public void setFilterString(String oreDict) { cache.clear(); - super.setOreDict(oreDict); + super.setFilterString(oreDict); + } + + @Override + protected ItemStack getFilterItem() { + return GTItems.TAG_FILTER.asStack(); } @Override public boolean test(ItemStack itemStack) { - if (oreDictFilterExpression.isEmpty()) return false; + if (filterString.isEmpty()) return false; if (cache.containsKey(itemStack.getItem())) return cache.getOrDefault(itemStack.getItem(), false); if (TagExprFilter.tagsMatch(matchExpr, itemStack)) { cache.put(itemStack.getItem(), true); diff --git a/src/main/java/com/gregtechceu/gtceu/api/data/chemical/material/properties/HazardProperty.java b/src/main/java/com/gregtechceu/gtceu/api/data/chemical/material/properties/HazardProperty.java index 64ce68c8eba..6f3a184a71a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/data/chemical/material/properties/HazardProperty.java +++ b/src/main/java/com/gregtechceu/gtceu/api/data/chemical/material/properties/HazardProperty.java @@ -6,10 +6,10 @@ import com.gregtechceu.gtceu.api.data.chemical.material.stack.MaterialEntry; import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; import com.gregtechceu.gtceu.api.data.tag.TagPrefix; -import com.gregtechceu.gtceu.api.item.GTBucketItem; import com.gregtechceu.gtceu.api.item.TagPrefixItem; import com.gregtechceu.gtceu.api.item.armor.ArmorComponentItem; import com.gregtechceu.gtceu.common.data.GTMaterials; +import com.gregtechceu.gtceu.common.item.GTBucketItem; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.recipe.CustomTags; diff --git a/src/main/java/com/gregtechceu/gtceu/api/data/tag/TagPrefix.java b/src/main/java/com/gregtechceu/gtceu/api/data/tag/TagPrefix.java index c438c7e0167..eccb723b760 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/data/tag/TagPrefix.java +++ b/src/main/java/com/gregtechceu/gtceu/api/data/tag/TagPrefix.java @@ -23,7 +23,7 @@ import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.recipe.CustomTags; import com.gregtechceu.gtceu.integration.kjs.GTRegistryInfo; -import com.gregtechceu.gtceu.integration.xei.widgets.GTOreByProduct; +import com.gregtechceu.gtceu.integration.recipeviewer.widgets.GTOreByProduct; import com.gregtechceu.gtceu.utils.FormattingUtil; import com.gregtechceu.gtceu.utils.memoization.GTMemoizer; diff --git a/src/main/java/com/gregtechceu/gtceu/api/fluids/GTFluid.java b/src/main/java/com/gregtechceu/gtceu/api/fluids/GTFluid.java index 05fbacab74c..d39b11ff44f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/fluids/GTFluid.java +++ b/src/main/java/com/gregtechceu/gtceu/api/fluids/GTFluid.java @@ -17,9 +17,12 @@ import net.minecraft.world.level.block.LiquidBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.material.FlowingFluid; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.common.extensions.IForgeFluid; +import net.minecraftforge.fluids.FluidType; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import lombok.Getter; @@ -32,7 +35,7 @@ @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -public abstract class GTFluid extends FlowingFluid implements IAttributedFluid { +public abstract class GTFluid extends FlowingFluid implements IAttributedFluid, IForgeFluid { @Getter private final Collection attributes = new ObjectLinkedOpenHashSet<>(); @@ -44,10 +47,11 @@ public abstract class GTFluid extends FlowingFluid implements IAttributedFluid { private final Supplier block; @Getter private final int burnTime; + private final Supplier fluidType; - public GTFluid(@NotNull FluidState state, Supplier stillFluid, + public GTFluid(FluidState state, Supplier stillFluid, Supplier flowingFluid, Supplier block, - Supplier bucket, int burnTime) { + Supplier bucket, int burnTime, Supplier fluidType) { super(); this.state = state; this.stillFluid = stillFluid; @@ -55,10 +59,11 @@ public GTFluid(@NotNull FluidState state, Supplier stillFluid, this.block = block; this.bucketItem = bucket; this.burnTime = burnTime; + this.fluidType = fluidType; } @Override - public void addAttribute(@NotNull FluidAttribute attribute) { + public void addAttribute(FluidAttribute attribute) { attributes.add(attribute); } @@ -127,4 +132,52 @@ public boolean isSame(Fluid fluid) { boolean flowing = this.getFlowing() == fluid; return still || flowing; } + + public FluidType getFluidType() { + return fluidType.get(); + } + + public static class Source extends GTFluid { + + public Source(FluidState state, Supplier stillFluid, + Supplier flowingFluid, Supplier block, + Supplier bucket, int burnTime, Supplier fluidType) { + super(state, stillFluid, flowingFluid, block, bucket, burnTime, fluidType); + } + + @Override + public int getAmount(net.minecraft.world.level.material.FluidState state) { + return 8; + } + + @Override + public boolean isSource(net.minecraft.world.level.material.FluidState state) { + return true; + } + } + + public static class Flowing extends GTFluid { + + public Flowing(FluidState state, Supplier stillFluid, + Supplier flowingFluid, Supplier block, + Supplier bucket, int burnTime, Supplier fluidType) { + super(state, stillFluid, flowingFluid, block, bucket, burnTime, fluidType); + // registerDefaultState(getStateDefinition().any().setValue(LEVEL, 7)); + } + + protected void createFluidStateDefinition(StateDefinition.@NotNull Builder builder) { + super.createFluidStateDefinition(builder); + builder.add(LEVEL); + } + + @Override + public int getAmount(net.minecraft.world.level.material.FluidState state) { + return state.getValue(LEVEL); + } + + @Override + public boolean isSource(net.minecraft.world.level.material.FluidState state) { + return false; + } + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/fluids/forge/GTFluidImpl.java b/src/main/java/com/gregtechceu/gtceu/api/fluids/forge/GTFluidImpl.java deleted file mode 100644 index cbc6e009b09..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/fluids/forge/GTFluidImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.gregtechceu.gtceu.api.fluids.forge; - -import com.gregtechceu.gtceu.api.fluids.FluidState; -import com.gregtechceu.gtceu.api.fluids.GTFluid; - -import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.LiquidBlock; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.material.Fluid; -import net.minecraftforge.common.SoundActions; -import net.minecraftforge.common.extensions.IForgeFluid; -import net.minecraftforge.fluids.FluidType; - -import org.jetbrains.annotations.NotNull; - -import java.util.Optional; -import java.util.function.Supplier; - -import javax.annotation.ParametersAreNonnullByDefault; - -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -public abstract class GTFluidImpl extends GTFluid implements IForgeFluid { - - private final Supplier fluidType; - - public GTFluidImpl(@NotNull FluidState state, Supplier stillFluid, - Supplier flowingFluid, Supplier block, - Supplier bucket, int burnTime, Supplier fluidType) { - super(state, stillFluid, flowingFluid, block, bucket, burnTime); - this.fluidType = fluidType; - } - - @Override - public FluidType getFluidType() { - return fluidType.get(); - } - - @Override - public Optional getPickupSound() { - return fluidType != null && fluidType.get() != null ? - Optional.ofNullable(fluidType.get().getSound(SoundActions.BUCKET_FILL)) : Optional.empty(); - } - - public static class Source extends GTFluidImpl { - - public Source(@NotNull FluidState state, Supplier stillFluid, - Supplier flowingFluid, Supplier block, - Supplier bucket, int burnTime, Supplier fluidType) { - super(state, stillFluid, flowingFluid, block, bucket, burnTime, fluidType); - } - - @Override - public int getAmount(net.minecraft.world.level.material.FluidState state) { - return 8; - } - - @Override - public boolean isSource(net.minecraft.world.level.material.FluidState state) { - return true; - } - } - - public static class Flowing extends GTFluidImpl { - - public Flowing(@NotNull FluidState state, Supplier stillFluid, - Supplier flowingFluid, Supplier block, - Supplier bucket, int burnTime, Supplier fluidType) { - super(state, stillFluid, flowingFluid, block, bucket, burnTime, fluidType); - // registerDefaultState(getStateDefinition().any().setValue(LEVEL, 7)); - } - - protected void createFluidStateDefinition(StateDefinition.@NotNull Builder builder) { - super.createFluidStateDefinition(builder); - builder.add(LEVEL); - } - - @Override - public int getAmount(net.minecraft.world.level.material.FluidState state) { - return state.getValue(LEVEL); - } - - @Override - public boolean isSource(net.minecraft.world.level.material.FluidState state) { - return false; - } - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/factory/CoverUIFactory.java b/src/main/java/com/gregtechceu/gtceu/api/gui/factory/CoverUIFactory.java deleted file mode 100644 index 62bdd2601ad..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/factory/CoverUIFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.gregtechceu.gtceu.api.gui.factory; - -import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; -import com.gregtechceu.gtceu.api.cover.CoverBehavior; -import com.gregtechceu.gtceu.api.cover.IUICover; - -import com.lowdragmc.lowdraglib.gui.factory.UIFactory; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; - -import net.minecraft.client.Minecraft; -import net.minecraft.core.Direction; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -public class CoverUIFactory extends UIFactory { - - public static final CoverUIFactory INSTANCE = new CoverUIFactory(); - - public CoverUIFactory() { - super(GTCEu.id("cover")); - } - - @Override - protected ModularUI createUITemplate(CoverBehavior holder, Player entityPlayer) { - if (holder instanceof IUICover cover) { - return cover.createUI(entityPlayer); - } - return null; - } - - @OnlyIn(Dist.CLIENT) - @Override - protected CoverBehavior readHolderFromSyncData(FriendlyByteBuf syncData) { - Level world = Minecraft.getInstance().level; - if (world == null) return null; - var pos = syncData.readBlockPos(); - var side = syncData.readEnum(Direction.class); - var coverable = GTCapabilityHelper.getCoverable(world, pos, side); - if (coverable != null) { - return coverable.getCoverAtSide(side); - } - return null; - } - - @Override - protected void writeHolderToSyncData(FriendlyByteBuf syncData, CoverBehavior holder) { - syncData.writeBlockPos(holder.coverHolder.getBlockPos()); - syncData.writeEnum(holder.attachedSide); - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/ConfiguratorPanel.java b/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/ConfiguratorPanel.java index 6ab93be46bc..4762dfeb273 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/ConfiguratorPanel.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/ConfiguratorPanel.java @@ -444,6 +444,6 @@ public void onClose(Runnable closeCallback) { } private static int getAnimationTime() { - return ConfigHolder.INSTANCE.client.animationTime; + return ConfigHolder.INSTANCE.client.ui.animationTime; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/FancyMachineUIWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/FancyMachineUIWidget.java index 5e62d3bf958..a9891226b20 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/FancyMachineUIWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/fancy/FancyMachineUIWidget.java @@ -71,7 +71,7 @@ public FancyMachineUIWidget(IFancyUIProvider mainPage, int width, int height) { this.pageSwitcher = new PageSwitcher(this::switchPage); setBackground(GuiTextures.BACKGROUND.copy() - .setColor(Long.decode(ConfigHolder.INSTANCE.client.defaultUIColor).intValue() | 0xFF000000)); + .setColor(Long.decode(ConfigHolder.INSTANCE.client.ui.defaultUIColor).intValue() | 0xFF000000)); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/CoverConfigurator.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/CoverConfigurator.java index a7253922ede..a2197e6d1ff 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/CoverConfigurator.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/CoverConfigurator.java @@ -2,8 +2,6 @@ import com.gregtechceu.gtceu.api.capability.ICoverable; import com.gregtechceu.gtceu.api.cover.CoverBehavior; -import com.gregtechceu.gtceu.api.cover.IUICover; -import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfigurator; import com.gregtechceu.gtceu.common.data.GTItems; @@ -12,7 +10,6 @@ import com.lowdragmc.lowdraglib.gui.widget.Widget; import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; import com.lowdragmc.lowdraglib.utils.Position; -import com.lowdragmc.lowdraglib.utils.Size; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; @@ -42,17 +39,6 @@ public IGuiTexture getIcon() { @Override public Widget createConfigurator() { - WidgetGroup group = new WidgetGroup(new Position(0, 0)); - if (side != null) { - if (coverable.getCoverAtSide(side) instanceof IUICover iuiCover) { - Widget coverConfigurator = iuiCover.createUIWidget(); - coverConfigurator.setBackground(GuiTextures.BACKGROUND); - coverConfigurator.setSelfPosition(new Position(4, -4)); - group.addWidget(coverConfigurator); - group.setSize(new Size(Math.max(120, coverConfigurator.getSize().width + 8), - Math.max(80, 80 + coverConfigurator.getSize().height))); - } - } - return group; + return new WidgetGroup(new Position(0, 0)); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/GhostCircuitSlotWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/GhostCircuitSlotWidget.java index c10f85179b8..26a37530d1c 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/GhostCircuitSlotWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/GhostCircuitSlotWidget.java @@ -1,7 +1,7 @@ package com.gregtechceu.gtceu.api.gui.widget; import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.common.item.IntCircuitBehaviour; +import com.gregtechceu.gtceu.common.item.behavior.IntCircuitBehaviour; import com.gregtechceu.gtceu.config.ConfigHolder; import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/PatternPreviewWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/PatternPreviewWidget.java index 182707f3530..087670b518f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/PatternPreviewWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/PatternPreviewWidget.java @@ -10,7 +10,7 @@ import com.gregtechceu.gtceu.api.pattern.TraceabilityPredicate; import com.gregtechceu.gtceu.api.pattern.predicates.SimplePredicate; import com.gregtechceu.gtceu.config.ConfigHolder; -import com.gregtechceu.gtceu.integration.xei.handlers.item.CycleItemEntryHandler; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.item.CycleItemEntryHandler; import com.lowdragmc.lowdraglib.client.scene.WorldSceneRenderer; import com.lowdragmc.lowdraglib.client.utils.RenderUtils; diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ProspectingMapWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ProspectingMapWidget.java index 69147b9a09b..56afaf9f62b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ProspectingMapWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/ProspectingMapWidget.java @@ -5,7 +5,7 @@ import com.gregtechceu.gtceu.api.gui.misc.ProspectorMode; import com.gregtechceu.gtceu.api.gui.texture.ProspectingTexture; import com.gregtechceu.gtceu.api.item.IComponentItem; -import com.gregtechceu.gtceu.common.item.ProspectorScannerBehavior; +import com.gregtechceu.gtceu.common.item.behavior.ProspectorScannerBehavior; import com.gregtechceu.gtceu.integration.map.WaypointManager; import com.gregtechceu.gtceu.integration.map.cache.client.GTClientCache; import com.gregtechceu.gtceu.integration.map.cache.server.ServerCache; diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/SlotWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/SlotWidget.java index ef7f6b23990..52b383b7ccc 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/SlotWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/SlotWidget.java @@ -1,10 +1,10 @@ package com.gregtechceu.gtceu.api.gui.widget; import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemEntryList; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemStackList; -import com.gregtechceu.gtceu.integration.xei.entry.item.ItemTagList; -import com.gregtechceu.gtceu.integration.xei.handlers.item.CycleItemEntryHandler; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemEntryList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemStackList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.item.ItemTagList; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.item.CycleItemEntryHandler; import com.lowdragmc.lowdraglib.gui.editor.annotation.LDLRegister; import com.lowdragmc.lowdraglib.gui.editor.configurator.ConfiguratorGroup; diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/TankWidget.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/TankWidget.java index 5230f99742d..2e474162c67 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/TankWidget.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/TankWidget.java @@ -4,10 +4,10 @@ import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; import com.gregtechceu.gtceu.api.transfer.fluid.IFluidHandlerModifiable; import com.gregtechceu.gtceu.client.TooltipsHandler; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidEntryList; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidStackList; -import com.gregtechceu.gtceu.integration.xei.entry.fluid.FluidTagList; -import com.gregtechceu.gtceu.integration.xei.handlers.fluid.CycleFluidEntryHandler; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidEntryList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidStackList; +import com.gregtechceu.gtceu.integration.recipeviewer.entry.fluid.FluidTagList; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.fluid.CycleFluidEntryHandler; import com.gregtechceu.gtceu.utils.FormattingUtil; import com.gregtechceu.gtceu.utils.GTUtil; diff --git a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/directional/handlers/CoverableConfigHandler.java b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/directional/handlers/CoverableConfigHandler.java index a6a30fd5190..a6afc7eebb4 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/gui/widget/directional/handlers/CoverableConfigHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/gui/widget/directional/handlers/CoverableConfigHandler.java @@ -2,18 +2,16 @@ import com.gregtechceu.gtceu.api.capability.ICoverable; import com.gregtechceu.gtceu.api.cover.CoverBehavior; -import com.gregtechceu.gtceu.api.cover.IUICover; import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.FancyMachineUIWidget; import com.gregtechceu.gtceu.api.gui.widget.CoverConfigurator; -import com.gregtechceu.gtceu.api.gui.widget.PredicatedButtonWidget; import com.gregtechceu.gtceu.api.gui.widget.SlotWidget; import com.gregtechceu.gtceu.api.gui.widget.directional.IDirectionalConfigHandler; import com.gregtechceu.gtceu.api.item.IComponentItem; import com.gregtechceu.gtceu.api.item.component.IItemComponent; import com.gregtechceu.gtceu.api.transfer.item.CustomItemStackHandler; -import com.gregtechceu.gtceu.common.item.CoverPlaceBehavior; +import com.gregtechceu.gtceu.common.item.behavior.CoverPlaceBehavior; import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; @@ -21,8 +19,6 @@ import com.lowdragmc.lowdraglib.gui.widget.SceneWidget; import com.lowdragmc.lowdraglib.gui.widget.Widget; import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; -import com.lowdragmc.lowdraglib.utils.Position; -import com.lowdragmc.lowdraglib.utils.Size; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; @@ -88,9 +84,6 @@ public boolean canPutStack(ItemStack stack) { } .setChangeListener(this::coverItemChanged) .setBackgroundTexture(new GuiTextureGroup(GuiTextures.SLOT, GuiTextures.IO_CONFIG_COVER_SLOT_OVERLAY))); - group.addWidget(new PredicatedButtonWidget(0, 0, 18, 18, CONFIG_BTN_TEXTURE, this::toggleConfigTab, - () -> side != null && coverBehavior != null && machine.getCoverAtSide(side) instanceof IUICover)); - checkCoverBehaviour(); return group; @@ -171,24 +164,6 @@ public Component getTitle() { public IGuiTexture getIcon() { return GuiTextures.CLOSE_ICON; } - - @Override - public Widget createConfigurator() { - WidgetGroup group = new WidgetGroup(new Position(0, 0)); - - if (side == null || !(coverable.getCoverAtSide(side) instanceof IUICover iuiCover)) - return group; - - Widget coverConfigurator = iuiCover.createUIWidget(); - coverConfigurator.addSelfPosition(-1, -20); - - group.addWidget(coverConfigurator); - group.setSize(new Size( - Math.max(120, coverConfigurator.getSize().width), - Math.max(80, coverConfigurator.getSize().height - 20))); - - return group; - } }; this.coverConfigurator = this.panel.createFloatingTab(configurator); diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/ComponentItem.java b/src/main/java/com/gregtechceu/gtceu/api/item/ComponentItem.java index b45213e753e..4c3b171a546 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/ComponentItem.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/ComponentItem.java @@ -1,9 +1,15 @@ package com.gregtechceu.gtceu.api.item; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; import com.gregtechceu.gtceu.api.capability.IElectricItem; import com.gregtechceu.gtceu.api.item.capability.ElectricItem; import com.gregtechceu.gtceu.api.item.component.*; +import com.gregtechceu.gtceu.api.mui.base.IItemUIHolder; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import com.lowdragmc.lowdraglib.client.renderer.IItemRendererProvider; import com.lowdragmc.lowdraglib.client.renderer.IRenderer; @@ -49,7 +55,8 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class ComponentItem extends Item - implements HeldItemUIFactory.IHeldItemUIHolder, IItemRendererProvider, IComponentItem { + implements HeldItemUIFactory.IHeldItemUIHolder, IItemRendererProvider, IComponentItem, + IItemUIHolder { protected int burnTime = -1; @@ -443,4 +450,24 @@ public ItemStack getInfiniteChargedStack() { electricItem.setInfiniteCharge(true); return itemStack; } + + @Override + public @Nullable ModularPanel buildUI(PlayerInventoryGuiData data, PanelSyncManager syncManager, + UISettings settings) { + for (IItemComponent component : getComponents()) { + if (component instanceof IItemUIHolder uiHolder) { + return uiHolder.buildUI(data, syncManager, settings); + } + } + GTCEu.LOGGER.error("Tried to get UI of {} item when it does not have one!", data.getUsedItemStack()); + return null; + } + + @Override + public boolean shouldOpenUI() { + for (IItemComponent component : getComponents()) { + if (component instanceof IItemUIHolder holder) return holder.shouldOpenUI(); + } + return false; + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/IGTTool.java b/src/main/java/com/gregtechceu/gtceu/api/item/IGTTool.java index 57dff0347c9..52cd3a1bd0f 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/IGTTool.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/IGTTool.java @@ -19,15 +19,17 @@ import com.gregtechceu.gtceu.api.item.tool.aoe.AoESymmetrical; import com.gregtechceu.gtceu.api.item.tool.behavior.IToolBehavior; import com.gregtechceu.gtceu.api.item.tool.behavior.IToolUIBehavior; +import com.gregtechceu.gtceu.api.mui.base.IUIHolder; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.sound.SoundEntry; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.recipe.VanillaRecipeHelper; import com.gregtechceu.gtceu.utils.FormattingUtil; import com.gregtechceu.gtceu.utils.GTUtil; -import com.lowdragmc.lowdraglib.gui.factory.HeldItemUIFactory; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; - import net.minecraft.Util; import net.minecraft.client.color.item.ItemColor; import net.minecraft.core.BlockPos; @@ -83,7 +85,7 @@ import static net.minecraft.world.item.Item.BASE_ATTACK_DAMAGE_UUID; import static net.minecraft.world.item.Item.BASE_ATTACK_SPEED_UUID; -public interface IGTTool extends HeldItemUIFactory.IHeldItemUIHolder, ItemLike, IForgeItem { +public interface IGTTool extends IUIHolder>, ItemLike, IForgeItem { GTToolType getToolType(); @@ -878,14 +880,15 @@ default void playSound(Player player) { } @Override - default ModularUI createUI(Player player, HeldItemUIFactory.HeldItemHolder holder) { + default ModularPanel buildUI(PlayerInventoryGuiData data, PanelSyncManager syncManager, UISettings settings) { for (var behavior : getToolStats().getBehaviors()) { - if (!(behavior instanceof IToolUIBehavior uiBehavior) || !uiBehavior.openUI(player, holder.getHand())) { + if (!(behavior instanceof IToolUIBehavior uiBehavior) || + !uiBehavior.shouldOpenUI(data.getPlayer(), data.getPlayer().getUsedItemHand())) { continue; } - return uiBehavior.createUI(player, holder); + return uiBehavior.buildUI(data, syncManager, settings); } - return new ModularUI(holder, player); + return null; } default Set getToolClasses(ItemStack stack) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/component/ElectricStats.java b/src/main/java/com/gregtechceu/gtceu/api/item/component/ElectricStats.java index 0d79562c552..1ec8ee3c7ce 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/component/ElectricStats.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/component/ElectricStats.java @@ -2,10 +2,10 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.GTValues; +import com.gregtechceu.gtceu.api.capability.GTCapability; import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; import com.gregtechceu.gtceu.api.capability.IElectricItem; import com.gregtechceu.gtceu.api.capability.compat.FeCompat; -import com.gregtechceu.gtceu.api.capability.forge.GTCapability; import com.gregtechceu.gtceu.api.item.capability.ElectricItem; import com.gregtechceu.gtceu.api.item.component.forge.IComponentCapability; import com.gregtechceu.gtceu.client.renderer.item.ToolChargeBarRenderer; diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/component/IMonitorModuleItem.java b/src/main/java/com/gregtechceu/gtceu/api/item/component/IMonitorModuleItem.java index 91a12e74343..2ee8ac3ab2b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/component/IMonitorModuleItem.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/component/IMonitorModuleItem.java @@ -1,12 +1,12 @@ package com.gregtechceu.gtceu.api.item.component; +import com.gregtechceu.gtceu.api.mui.base.IPanelHandler; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.placeholder.PlaceholderContext; import com.gregtechceu.gtceu.client.renderer.monitor.IMonitorRenderer; import com.gregtechceu.gtceu.common.machine.multiblock.electric.CentralMonitorMachine; import com.gregtechceu.gtceu.common.machine.multiblock.electric.monitor.MonitorGroup; -import com.lowdragmc.lowdraglib.gui.widget.Widget; - import net.minecraft.world.item.ItemStack; public interface IMonitorModuleItem extends IItemComponent { @@ -15,9 +15,10 @@ default void tick(ItemStack stack, CentralMonitorMachine machine, MonitorGroup g default void tickInPlaceholder(ItemStack stack, PlaceholderContext context) {} - IMonitorRenderer getRenderer(ItemStack stack); + IMonitorRenderer getRenderer(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group); - Widget createUIWidget(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group); + IPanelHandler createModularPanel(ItemStack stack, CentralMonitorMachine machine, MonitorGroup group, + PanelSyncManager syncManager); default String getType() { return "unknown"; diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/tool/IToolGridHighlight.java b/src/main/java/com/gregtechceu/gtceu/api/item/tool/IToolGridHighlight.java index a79ccf07b39..a37a8d35947 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/tool/IToolGridHighlight.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/tool/IToolGridHighlight.java @@ -1,6 +1,6 @@ package com.gregtechceu.gtceu.api.item.tool; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -19,8 +19,8 @@ default boolean shouldRenderGrid(Player player, BlockPos pos, BlockState state, return true; } - default @Nullable ResourceTexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, - Direction side) { + default @Nullable UITexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, + Direction side) { return null; } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/item/tool/behavior/IToolUIBehavior.java b/src/main/java/com/gregtechceu/gtceu/api/item/tool/behavior/IToolUIBehavior.java index e00fd5f658e..1349f8fcaff 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/item/tool/behavior/IToolUIBehavior.java +++ b/src/main/java/com/gregtechceu/gtceu/api/item/tool/behavior/IToolUIBehavior.java @@ -1,9 +1,9 @@ package com.gregtechceu.gtceu.api.item.tool.behavior; -import com.lowdragmc.lowdraglib.gui.factory.HeldItemUIFactory; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; +import com.gregtechceu.gtceu.api.mui.base.IUIHolder; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryGuiData; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryUIFactory; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.player.Player; @@ -12,20 +12,17 @@ import org.jetbrains.annotations.NotNull; -public interface IToolUIBehavior extends IToolBehavior { +public interface IToolUIBehavior extends IToolBehavior, IUIHolder> { @Override default @NotNull InteractionResultHolder onItemRightClick(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) { - var heldItem = player.getItemInHand(hand); - if (player instanceof ServerPlayer serverPlayer && openUI(serverPlayer, hand)) { - HeldItemUIFactory.INSTANCE.openUI(serverPlayer, hand); - return InteractionResultHolder.success(heldItem); + ItemStack heldItem = player.getItemInHand(hand); + if (level.isClientSide && shouldOpenUI(player, hand)) { + PlayerInventoryUIFactory.INSTANCE.openFromHandClient(hand); } return InteractionResultHolder.pass(heldItem); } - boolean openUI(@NotNull Player player, @NotNull InteractionHand hand); - - ModularUI createUI(Player player, HeldItemUIFactory.HeldItemHolder holder); + boolean shouldOpenUI(@NotNull Player player, @NotNull InteractionHand hand); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/MachineDefinition.java b/src/main/java/com/gregtechceu/gtceu/api/machine/MachineDefinition.java index 01b6204239c..a3f82df3984 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/MachineDefinition.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/MachineDefinition.java @@ -6,12 +6,13 @@ import com.gregtechceu.gtceu.api.gui.editor.EditableMachineUI; import com.gregtechceu.gtceu.api.item.MetaMachineItem; import com.gregtechceu.gtceu.api.machine.feature.IRecipeLogicMachine; +import com.gregtechceu.gtceu.api.mui.factory.PanelFactory; +import com.gregtechceu.gtceu.api.mui.theme.ThemeAPI; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.modifier.RecipeModifier; import com.gregtechceu.gtceu.client.model.machine.MachineRenderState; - -import com.lowdragmc.lowdraglib.utils.ShapeUtils; +import com.gregtechceu.gtceu.utils.GTUtil; import net.minecraft.core.Direction; import net.minecraft.core.IdMapper; @@ -125,6 +126,13 @@ public class MachineDefinition implements Supplier { private EditableMachineUI editableUI; @Getter @Setter + @Nullable + private PanelFactory UI; + @Getter + @Setter + private String themeId = ThemeAPI.DEFAULT_ID; + @Getter + @Setter private Reference2IntMap> recipeOutputLimits = new Reference2IntOpenHashMap<>(); @Getter @@ -164,7 +172,7 @@ public ItemStack asStack(int count) { public VoxelShape getShape(Direction direction) { if (shape.isEmpty() || shape == Shapes.block() || direction == Direction.NORTH) return shape; - return this.cache.computeIfAbsent(direction, dir -> ShapeUtils.rotate(shape, dir)); + return this.cache.computeIfAbsent(direction, dir -> GTUtil.rotateVoxelShape(shape, dir)); } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java index 7609d198079..eb34211707e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/MetaMachine.java @@ -9,7 +9,7 @@ import com.gregtechceu.gtceu.api.blockentity.IGregtechBlockEntity; import com.gregtechceu.gtceu.api.blockentity.IPaintable; import com.gregtechceu.gtceu.api.capability.*; -import com.gregtechceu.gtceu.api.capability.forge.GTCapability; +import com.gregtechceu.gtceu.api.capability.GTCapability; import com.gregtechceu.gtceu.api.capability.recipe.IO; import com.gregtechceu.gtceu.api.cover.CoverBehavior; import com.gregtechceu.gtceu.api.data.RotationState; @@ -29,6 +29,7 @@ import com.gregtechceu.gtceu.api.machine.trait.feature.IInteractionTrait; import com.gregtechceu.gtceu.api.machine.trait.feature.IRenderingTrait; import com.gregtechceu.gtceu.api.misc.*; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.pattern.util.RelativeDirection; import com.gregtechceu.gtceu.api.sync_system.ManagedSyncBlockEntity; import com.gregtechceu.gtceu.api.sync_system.SyncDataHolder; @@ -44,11 +45,11 @@ import com.gregtechceu.gtceu.common.cover.data.ManualIOMode; import com.gregtechceu.gtceu.common.machine.owner.MachineOwner; import com.gregtechceu.gtceu.common.machine.owner.PlayerOwner; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.GTUtil; import com.gregtechceu.gtceu.utils.data.TagCompatibilityFixer; import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; import com.lowdragmc.lowdraglib.utils.DummyWorld; import net.minecraft.ChatFormatting; @@ -80,7 +81,6 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.data.ModelData; -import net.minecraftforge.client.model.data.ModelProperty; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.common.util.LazyOptional; @@ -103,9 +103,6 @@ public class MetaMachine extends ManagedSyncBlockEntity implements IGregtechBlockEntity, IToolable, IToolGridHighlight, IFancyTooltip, IPaintable, IMachineFeature, ICopyable { - public static final ModelProperty MODEL_DATA_LEVEL = new ModelProperty<>(); - public static final ModelProperty MODEL_DATA_POS = new ModelProperty<>(); - @Getter protected final SyncDataHolder syncDataHolder = new SyncDataHolder(this); @@ -423,7 +420,7 @@ public InteractionResult onUse(BlockState state, Level world, BlockPos pos, Play /** * Called when a machine is left clicked. - * + * * @return true to cancel the click event, false to continue processing */ public boolean onLeftClick(Player player, Level world, InteractionHand hand, BlockPos pos, @@ -511,8 +508,8 @@ public boolean shouldRenderGrid(Player player, BlockPos pos, BlockState state, I } @Override - public @Nullable ResourceTexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, - Direction side) { + public @Nullable UITexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, + Direction side) { var cover = coverContainer.getCoverAtSide(side); if (cover != null) { var tips = cover.sideTips(player, pos, state, toolTypes, side); @@ -522,16 +519,16 @@ public boolean shouldRenderGrid(Player player, BlockPos pos, BlockState state, I if (toolTypes.contains(GTToolType.WRENCH)) { if (player.isShiftKeyDown()) { if (isFacingValid(side) || (allowExtendedFacing() && hasFrontFacing() && side == getFrontFacing())) { - return GuiTextures.TOOL_FRONT_FACING_ROTATION; + return GTGuiTextures.TOOL_FRONT_FACING_ROTATION; } } } else if (toolTypes.contains(GTToolType.SOFT_MALLET)) { if (this instanceof IControllable controllable) { - return controllable.isWorkingEnabled() ? GuiTextures.TOOL_START : GuiTextures.TOOL_PAUSE; + return controllable.isWorkingEnabled() ? GTGuiTextures.TOOL_START : GTGuiTextures.TOOL_PAUSE; } } else if (toolTypes.contains(GTToolType.HARD_HAMMER)) { if (this instanceof IMufflableMachine mufflableMachine) { - return mufflableMachine.isMuffled() ? GuiTextures.TOOL_SOUND : GuiTextures.TOOL_MUTE; + return mufflableMachine.isMuffled() ? GTGuiTextures.TOOL_SOUND : GTGuiTextures.TOOL_MUTE; } } @@ -630,7 +627,7 @@ public void setFrontFacing(Direction facing) { } @Override - public @NotNull ModelData getModelData() { + public ModelData getModelData() { ModelData.Builder data = super.getModelData().derive(); updateModelData(data); return data.build(); @@ -641,7 +638,7 @@ public Direction getUpwardsFacing() { Direction.NORTH; } - public void setUpwardsFacing(@NotNull Direction upwardsFacing) { + public void setUpwardsFacing(Direction upwardsFacing) { if (!getDefinition().isAllowExtendedFacing()) { return; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleGeneratorMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleGeneratorMachine.java index 1e23a3f118b..b8028072fa6 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleGeneratorMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleGeneratorMachine.java @@ -3,47 +3,29 @@ import com.gregtechceu.gtceu.api.GTValues; import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; import com.gregtechceu.gtceu.api.capability.recipe.*; -import com.gregtechceu.gtceu.api.gui.editor.EditableMachineUI; -import com.gregtechceu.gtceu.api.machine.feature.IEnvironmentalHazardEmitter; -import com.gregtechceu.gtceu.api.machine.feature.IFancyUIMachine; +import com.gregtechceu.gtceu.api.machine.trait.hazard.EnvironmentalHazardEmitterTrait; import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.content.ContentModifier; import com.gregtechceu.gtceu.api.recipe.modifier.ModifierFunction; import com.gregtechceu.gtceu.api.recipe.modifier.ParallelLogic; import com.gregtechceu.gtceu.api.recipe.modifier.RecipeModifier; -import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; +import com.gregtechceu.gtceu.common.data.GTMedicalConditions; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; -import com.lowdragmc.lowdraglib.utils.Position; -import com.lowdragmc.lowdraglib.utils.Size; - -import net.minecraft.Util; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.resources.ResourceLocation; - -import com.google.common.collect.Tables; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import java.util.Collections; -import java.util.EnumMap; -import java.util.LinkedHashMap; -import java.util.function.BiFunction; - -public class SimpleGeneratorMachine extends WorkableTieredMachine - implements IFancyUIMachine, IEnvironmentalHazardEmitter { +public class SimpleGeneratorMachine extends WorkableTieredMachine { @Getter - private final float hazardStrengthPerOperation; + private final EnvironmentalHazardEmitterTrait hazardEmitter; public SimpleGeneratorMachine(BlockEntityCreationInfo info, int tier, float hazardStrengthPerOperation, Int2IntFunction tankScalingFunction) { super(info, tier, tankScalingFunction); energyContainer.setSideOutputCondition(side -> !hasFrontFacing() || side == getFrontFacing()); - this.hazardStrengthPerOperation = hazardStrengthPerOperation; + this.hazardEmitter = new EnvironmentalHazardEmitterTrait(this, GTMedicalConditions.CARBON_MONOXIDE_POISONING, + hazardStrengthPerOperation); } public SimpleGeneratorMachine(BlockEntityCreationInfo info, int tier, Int2IntFunction tankScalingFunction) { @@ -81,7 +63,7 @@ public int tintColor(int index) { * @param recipe recipe * @return A {@link ModifierFunction} for the given Simple Generator */ - public static ModifierFunction recipeModifier(@NotNull MetaMachine machine, @NotNull GTRecipe recipe) { + public static ModifierFunction recipeModifier(MetaMachine machine, GTRecipe recipe) { if (!(machine instanceof SimpleGeneratorMachine generator)) { return RecipeModifier.nullWrongType(SimpleGeneratorMachine.class, machine); } @@ -112,47 +94,11 @@ public boolean canVoidRecipeOutputs(RecipeCapability capability) { @Override public void afterWorking() { super.afterWorking(); - spreadEnvironmentalHazard(); + hazardEmitter.emitHazard(); } @Override public long getDisplayRecipeVoltage() { return GTValues.V[this.tier]; } - - ////////////////////////////////////// - // *********** GUI ***********// - ////////////////////////////////////// - - @SuppressWarnings("UnstableApiUsage") - public static BiFunction EDITABLE_UI_CREATOR = Util - .memoize((path, recipeType) -> new EditableMachineUI("generator", path, () -> { - WidgetGroup template = recipeType.getRecipeUI().createEditableUITemplate(false, false).createDefault(); - WidgetGroup group = new WidgetGroup(0, 0, template.getSize().width + 4 + 8, - template.getSize().height + 8); - Size size = group.getSize(); - template.setSelfPosition(new Position( - (size.width - 4 - template.getSize().width) / 2 + 4, - (size.height - template.getSize().height) / 2)); - group.addWidget(template); - return group; - }, (template, machine) -> { - if (machine instanceof SimpleGeneratorMachine generatorMachine) { - var storages = Tables.newCustomTable(new EnumMap<>(IO.class), - LinkedHashMap, Object>::new); - storages.put(IO.IN, ItemRecipeCapability.CAP, generatorMachine.importItems.storage); - storages.put(IO.OUT, ItemRecipeCapability.CAP, generatorMachine.exportItems.storage); - storages.put(IO.IN, FluidRecipeCapability.CAP, generatorMachine.importFluids); - storages.put(IO.OUT, FluidRecipeCapability.CAP, generatorMachine.exportFluids); - - generatorMachine.getRecipeType().getRecipeUI().createEditableUITemplate(false, false).setupUI( - template, - new GTRecipeTypeUI.RecipeHolder(generatorMachine.recipeLogic::getProgressPercent, - storages, - new CompoundTag(), - Collections.emptyList(), - false, false)); - createEnergyBar().setupUI(template, generatorMachine); - } - })); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleTieredMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleTieredMachine.java index e2e3a655937..2e8fac0951b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleTieredMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/SimpleTieredMachine.java @@ -4,42 +4,16 @@ import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; import com.gregtechceu.gtceu.api.capability.recipe.*; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.editor.EditableMachineUI; -import com.gregtechceu.gtceu.api.gui.editor.EditableUI; -import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; -import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfigurator; -import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; -import com.gregtechceu.gtceu.api.gui.widget.GhostCircuitSlotWidget; -import com.gregtechceu.gtceu.api.gui.widget.SlotWidget; -import com.gregtechceu.gtceu.api.machine.fancyconfigurator.CircuitFancyConfigurator; -import com.gregtechceu.gtceu.api.machine.feature.IFancyUIMachine; import com.gregtechceu.gtceu.api.machine.feature.IHasCircuitSlot; import com.gregtechceu.gtceu.api.machine.trait.AutoOutputTrait; import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; -import com.gregtechceu.gtceu.api.recipe.GTRecipeType; -import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.api.transfer.item.CustomItemStackHandler; -import com.gregtechceu.gtceu.common.item.IntCircuitBehaviour; +import com.gregtechceu.gtceu.common.item.behavior.IntCircuitBehaviour; import com.gregtechceu.gtceu.config.ConfigHolder; -import com.gregtechceu.gtceu.data.lang.LangHandler; import com.gregtechceu.gtceu.utils.ISubscription; -import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; -import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; -import com.lowdragmc.lowdraglib.gui.util.ClickData; -import com.lowdragmc.lowdraglib.gui.widget.WidgetGroup; -import com.lowdragmc.lowdraglib.utils.Position; - -import net.minecraft.Util; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; - -import com.google.common.collect.Tables; import it.unimi.dsi.fastutil.ints.Int2IntFunction; import lombok.Getter; import org.jetbrains.annotations.Nullable; @@ -51,7 +25,7 @@ * All simple single machines are implemented here. */ public class SimpleTieredMachine extends WorkableTieredMachine - implements IFancyUIMachine, IHasCircuitSlot { + implements IHasCircuitSlot { @Getter @SaveField @@ -144,143 +118,4 @@ public void onMachineDestroyed() { public long getDisplayRecipeVoltage() { return GTValues.V[this.tier]; } - - ////////////////////////////////////// - // *********** GUI ***********// - ////////////////////////////////////// - - @Override - public void attachConfigurators(ConfiguratorPanel configuratorPanel) { - IFancyUIMachine.super.attachConfigurators(configuratorPanel); - - if (autoOutput.supportsAutoOutputFluids()) { - configuratorPanel.attachConfigurators(createAutoOutputFluidConfigurator()); - } - if (autoOutput.supportsAutoOutputItems()) { - configuratorPanel.attachConfigurators(createAutoOutputItemConfigurator()); - } - - if (isCircuitSlotEnabled()) { - configuratorPanel.attachConfigurators(new CircuitFancyConfigurator(circuitInventory.storage)); - } - } - - private IFancyConfigurator createAutoOutputFluidConfigurator() { - return createAutoOutputConfigurator( - GuiTextures.IO_CONFIG_FLUID_MODES_BUTTON, - "gtceu.gui.fluid_auto_output", - this.autoOutput::isAutoOutputFluids, - (cd, nextState) -> this.autoOutput.setAllowAutoOutputFluids(nextState)); - } - - private IFancyConfigurator createAutoOutputItemConfigurator() { - return createAutoOutputConfigurator( - GuiTextures.IO_CONFIG_ITEM_MODES_BUTTON, - "gtceu.gui.item_auto_output", - this.autoOutput::isAutoOutputItems, - (cd, nextState) -> this.autoOutput.setAllowAutoOutputItems(nextState)); - } - - private IFancyConfigurator createAutoOutputConfigurator(ResourceTexture modesButtonTexture, - String tooltipBaseLangKey, - BooleanSupplier stateSupplier, - BiConsumer onToggle) { - var toggle = new IFancyConfiguratorButton.Toggle( - new GuiTextureGroup( - GuiTextures.TOGGLE_BUTTON_BACK.getSubTexture(0, 0, 1, 0.5), - modesButtonTexture.getSubTexture(0, 1 / 3f, 1, 1 / 3f)), - new GuiTextureGroup( - GuiTextures.TOGGLE_BUTTON_BACK.getSubTexture(0, 0.5, 1, 0.5), - modesButtonTexture.getSubTexture(0, 2 / 3f, 1, 1 / 3f)), - stateSupplier, - onToggle); - - toggle.setTooltipsSupplier(enabled -> { - var key = tooltipBaseLangKey + '.' + (enabled ? "enabled" : "disabled"); - return List.of(Component.translatable(key)); - }); - - return toggle; - } - - @SuppressWarnings("UnstableApiUsage") - public static BiFunction EDITABLE_UI_CREATOR = Util - .memoize((path, recipeType) -> new EditableMachineUI("simple", path, () -> { - WidgetGroup template = recipeType.getRecipeUI().createEditableUITemplate(false, false).createDefault(); - SlotWidget batterySlot = createBatterySlot().createDefault(); - WidgetGroup group = new WidgetGroup(0, 0, template.getSize().width, - Math.max(template.getSize().height, 78)); - template.setSelfPosition(new Position(0, (group.getSize().height - template.getSize().height) / 2)); - batterySlot.setSelfPosition(new Position(group.getSize().width / 2 - 9, group.getSize().height - 18)); - group.addWidget(batterySlot); - group.addWidget(template); - - // TODO fix this. - // if (ConfigHolder.INSTANCE.machines.ghostCircuit) { - // SlotWidget circuitSlot = createCircuitConfigurator().createDefault(); - // circuitSlot.setSelfPosition(new Position(120, 62)); - // group.addWidget(circuitSlot); - // } - - return group; - }, (template, machine) -> { - if (machine instanceof SimpleTieredMachine tieredMachine) { - var storages = Tables.newCustomTable(new EnumMap<>(IO.class), - LinkedHashMap, Object>::new); - storages.put(IO.IN, ItemRecipeCapability.CAP, tieredMachine.importItems.storage); - storages.put(IO.OUT, ItemRecipeCapability.CAP, tieredMachine.exportItems.storage); - storages.put(IO.IN, FluidRecipeCapability.CAP, tieredMachine.importFluids); - storages.put(IO.OUT, FluidRecipeCapability.CAP, tieredMachine.exportFluids); - storages.put(IO.IN, CWURecipeCapability.CAP, tieredMachine.importComputation); - storages.put(IO.OUT, CWURecipeCapability.CAP, tieredMachine.exportComputation); - - tieredMachine.getRecipeType().getRecipeUI().createEditableUITemplate(false, false).setupUI(template, - new GTRecipeTypeUI.RecipeHolder(tieredMachine.recipeLogic::getProgressPercent, - storages, - new CompoundTag(), - Collections.emptyList(), - false, false)); - createBatterySlot().setupUI(template, tieredMachine); - // createCircuitConfigurator().setupUI(template, tieredMachine); - } - })); - - /** - * Create a battery slot widget. - */ - protected static EditableUI createBatterySlot() { - return new EditableUI<>("battery_slot", SlotWidget.class, () -> { - var slotWidget = new SlotWidget(); - slotWidget.setBackground(GuiTextures.SLOT, GuiTextures.CHARGER_OVERLAY); - return slotWidget; - }, (slotWidget, machine) -> { - slotWidget.setHandlerSlot(machine.chargerInventory, 0); - slotWidget.setCanPutItems(true); - slotWidget.setCanTakeItems(true); - slotWidget.setHoverTooltips(LangHandler.getMultiLang("gtceu.gui.charger_slot.tooltip", - GTValues.VNF[machine.getTier()], GTValues.VNF[machine.getTier()]).toArray(Component[]::new)); - }); - } - - /** - * Create a ghost circuit slot widget. - */ - protected static EditableUI createCircuitConfigurator() { - return new EditableUI<>("circuit_configurator", GhostCircuitSlotWidget.class, () -> { - var slotWidget = new GhostCircuitSlotWidget(); - slotWidget.setBackground(GuiTextures.SLOT, GuiTextures.INT_CIRCUIT_OVERLAY); - return slotWidget; - }, (slotWidget, machine) -> { - slotWidget.setCircuitInventory(machine.circuitInventory); - slotWidget.setCanPutItems(false); - slotWidget.setCanTakeItems(false); - slotWidget.setHoverTooltips( - LangHandler.getMultiLang("gtceu.gui.configurator_slot.tooltip").toArray(Component[]::new)); - }); - } - - // Method provided to override - protected IGuiTexture getCircuitSlotOverlay() { - return GuiTextures.INT_CIRCUIT_OVERLAY; - } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/WorkableTieredMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/WorkableTieredMachine.java index ed3e732e423..79597e7496e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/WorkableTieredMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/WorkableTieredMachine.java @@ -90,11 +90,11 @@ public WorkableTieredMachine(BlockEntityCreationInfo info, int tier, Int2IntFunc this.cleanroomReceiver = new CleanroomReceiverTrait(this); this.recipeLogic = new RecipeLogic(this); this.importItems = new NotifiableItemStackHandler(this, getRecipeType().getMaxInputs(ItemRecipeCapability.CAP), - IO.IN); + IO.IN, IO.BOTH); this.exportItems = new NotifiableItemStackHandler(this, getRecipeType().getMaxOutputs(ItemRecipeCapability.CAP), IO.OUT); this.importFluids = new NotifiableFluidTank(this, getRecipeType().getMaxInputs(FluidRecipeCapability.CAP), - tankScalingFunction.applyAsInt(getTier()), IO.IN); + tankScalingFunction.applyAsInt(getTier()), IO.IN, IO.BOTH); this.exportFluids = new NotifiableFluidTank(this, getRecipeType().getMaxOutputs(FluidRecipeCapability.CAP), tankScalingFunction.applyAsInt(getTier()), IO.OUT); this.importComputation = new NotifiableComputationContainer(this, IO.IN, true); diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/fancyconfigurator/CircuitFancyConfigurator.java b/src/main/java/com/gregtechceu/gtceu/api/machine/fancyconfigurator/CircuitFancyConfigurator.java index 522673f4fc7..dec55d9951c 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/fancyconfigurator/CircuitFancyConfigurator.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/fancyconfigurator/CircuitFancyConfigurator.java @@ -5,7 +5,7 @@ import com.gregtechceu.gtceu.api.gui.fancy.IFancyCustomMiddleClickAction; import com.gregtechceu.gtceu.api.gui.fancy.IFancyCustomMouseWheelAction; import com.gregtechceu.gtceu.api.gui.widget.SlotWidget; -import com.gregtechceu.gtceu.common.item.IntCircuitBehaviour; +import com.gregtechceu.gtceu.common.item.behavior.IntCircuitBehaviour; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.data.lang.LangHandler; diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IDataInfoProvider.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IDataInfoProvider.java index da3d9a5f14c..534effbce38 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IDataInfoProvider.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IDataInfoProvider.java @@ -1,6 +1,6 @@ package com.gregtechceu.gtceu.api.machine.feature; -import com.gregtechceu.gtceu.common.item.PortableScannerBehavior; +import com.gregtechceu.gtceu.common.item.behavior.PortableScannerBehavior; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Player; diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardCleaner.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardCleaner.java deleted file mode 100644 index cdb7b0e7709..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardCleaner.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature; - -import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; - -public interface IEnvironmentalHazardCleaner extends IMachineFeature { - - float getRemovedLastSecond(); - - void cleanHazard(MedicalCondition condition, float amount); -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardEmitter.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardEmitter.java deleted file mode 100644 index 4d4edb0c46e..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IEnvironmentalHazardEmitter.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature; - -import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; -import com.gregtechceu.gtceu.api.capability.IHazardParticleContainer; -import com.gregtechceu.gtceu.api.data.chemical.material.properties.HazardProperty; -import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; -import com.gregtechceu.gtceu.common.capability.EnvironmentalHazardSavedData; -import com.gregtechceu.gtceu.common.data.GTMedicalConditions; -import com.gregtechceu.gtceu.config.ConfigHolder; - -import net.minecraft.server.level.ServerLevel; - -/** - * common interface for environmental hazard (e.g. pollution) emitters like mufflers. - */ -public interface IEnvironmentalHazardEmitter extends IMachineFeature { - - /** - * @return the medical condition this hazard emitter creates. - */ - default MedicalCondition getConditionToEmit() { - return GTMedicalConditions.CARBON_MONOXIDE_POISONING; - } - - /** - * @return the starting strength of the hazard zone. - */ - float getHazardStrengthPerOperation(); - - default void spreadEnvironmentalHazard() { - if (!ConfigHolder.INSTANCE.gameplay.environmentalHazards) { - return; - } - - if (self().getLevel() instanceof ServerLevel serverLevel) { - IHazardParticleContainer container = GTCapabilityHelper.getHazardContainer(serverLevel, - self().getBlockPos().relative(self().getFrontFacing()), self().getFrontFacing().getOpposite()); - if (container != null && - container.getHazardCanBeInserted(getConditionToEmit()) > getHazardStrengthPerOperation()) { - container.addHazard(getConditionToEmit(), getHazardStrengthPerOperation()); - return; - } - - var savedData = EnvironmentalHazardSavedData.getOrCreate(serverLevel); - savedData.addZone(self().getBlockPos(), getHazardStrengthPerOperation(), true, - HazardProperty.HazardTrigger.INHALATION, getConditionToEmit()); - } - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IExhaustVentMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IExhaustVentMachine.java deleted file mode 100644 index 7c62d616ba2..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IExhaustVentMachine.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature; - -import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; -import com.gregtechceu.gtceu.api.pattern.util.RelativeDirection; -import com.gregtechceu.gtceu.common.data.GTDamageTypes; -import com.gregtechceu.gtceu.config.ConfigHolder; -import com.gregtechceu.gtceu.utils.GTUtil; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.shapes.Shapes; - -import org.jetbrains.annotations.NotNull; - -/** - * Interface defining the functionality of a machine which vents exhaust from a side. - * - * @implNote {@link com.gregtechceu.gtceu.api.machine.steam.SimpleSteamMachine} - */ -public interface IExhaustVentMachine extends IMachineFeature { - - EnumProperty VENT_DIRECTION_PROPERTY = GTMachineModelProperties.VENT_DIRECTION; - - /** - * @return the direction the vent faces - */ - @NotNull - Direction getVentingDirection(); - - /** - * @return if venting is needed - */ - boolean isNeedsVenting(); - - void setNeedsVenting(boolean needsVenting); - - /** - * Mark the machine as no longer needing venting - */ - void markVentingComplete(); - - /** - * @return the damage to deal to entities in the vent area - */ - float getVentingDamage(); - - /** - * Checks the venting state. Performs venting only if required. - * - * @return if the machine does not need venting - */ - default boolean checkVenting() { - if (isNeedsVenting()) { - tryDoVenting(self().getLevel(), self().getBlockPos()); - } - return !isNeedsVenting(); - } - - /** - * @return if venting is being blocked by something - */ - default boolean isVentingBlocked() { - Level level = self().getLevel(); - Direction ventingSide = getVentingDirection(); - BlockPos ventingBlockPos = self().getBlockPos().relative(ventingSide); - BlockState state = level.getBlockState(ventingBlockPos); - - return state.canOcclude() || Shapes.blockOccudes(state.getCollisionShape(level, ventingBlockPos), - Shapes.block(), ventingSide.getOpposite()); - } - - /** - * Attempts to vent, if needed - * - * @param level the level containing the machine venting - * @param pos the position of the machine - */ - default void tryDoVenting(@NotNull Level level, @NotNull BlockPos pos) { - if (!isNeedsVenting()) return; - - if (!isVentingBlocked()) { - performVenting(level, pos); - return; - } - - BlockPos ventingPos = pos.relative(getVentingDirection()); - if (GTUtil.tryBreakSnow(level, ventingPos, level.getBlockState(ventingPos), false)) { - performVenting(level, pos); - } - } - - private void performVenting(@NotNull Level level, @NotNull BlockPos pos) { - doVentingDamage(level, pos); - - Direction ventingDirection = getVentingDirection(); - double posX = pos.getX() + 0.5 + ventingDirection.getStepX() * 0.6; - double posY = pos.getY() + 0.5 + ventingDirection.getStepY() * 0.6; - double posZ = pos.getZ() + 0.5 + ventingDirection.getStepZ() * 0.6; - createVentingParticles(level, posX, posY, posZ); - - if (ConfigHolder.INSTANCE.machines.machineSounds) { - playVentingSound(level, posX, posY, posZ); - } - - markVentingComplete(); - } - - /** - * Damages entities upon venting - * - * @param level the level containing the machine and entities - * @param pos the position of the machine venting - */ - default void doVentingDamage(@NotNull Level level, @NotNull BlockPos pos) { - for (LivingEntity entity : level.getEntitiesOfClass(LivingEntity.class, - new AABB(pos.relative(getVentingDirection())), - entity -> !(entity instanceof Player player) || !player.isSpectator() && !player.isCreative())) { - entity.hurt(GTDamageTypes.HEAT.source(level), getVentingDamage()); - // TODO ADVANCEMENT - // if (entity instanceof ServerPlayer) { - // AdvancementTriggers.STEAM_VENT_DEATH.trigger((ServerPlayer) entity); - // } - } - } - - /** - * Create the particles for venting - * - * @param level the level containing the machine - * @param posX the x position to send particles to - * @param posY the y position to send particles to - * @param posZ the z position to send particles to - */ - default void createVentingParticles(@NotNull Level level, double posX, double posY, double posZ) { - Direction ventingDirection = getVentingDirection(); - var count = 7 + level.random.nextInt(3); - if (level instanceof ServerLevel serverLevel) { - serverLevel.sendParticles(ParticleTypes.CLOUD, posX, posY, posZ, - count, - ventingDirection.getStepX() / 2.0, - ventingDirection.getStepY() / 2.0, - ventingDirection.getStepZ() / 2.0, 0.1); - } else { - for (int i = 0; i < count; ++i) { - double d1 = level.random.nextGaussian() * (double) ventingDirection.getStepX() / 2.0; - double d3 = level.random.nextGaussian() * (double) ventingDirection.getStepY() / 2.0; - double d5 = level.random.nextGaussian() * (double) ventingDirection.getStepZ() / 2.0; - double d6 = level.random.nextGaussian() * 0.1; - double d7 = level.random.nextGaussian() * 0.1; - double d8 = level.random.nextGaussian() * 0.1; - try { - level.addParticle(ParticleTypes.CLOUD, posX + d1, posY + d3, posZ + d5, d6, d7, d8); - continue; - } catch (Throwable throwable) { - GTCEu.LOGGER.warn("Could not spawn particle effect {}", ParticleTypes.CLOUD); - return; - } - } - } - } - - /** - * Play the venting sound - * - * @param level the level to play the sound in - * @param posX the x position to play the sound at - * @param posY the y position to play the sound at - * @param posZ the z position to play the sound at - */ - default void playVentingSound(@NotNull Level level, double posX, double posY, double posZ) { - level.playSound(null, posX, posY, posZ, SoundEvents.LAVA_EXTINGUISH, SoundSource.BLOCKS, 1F, 1F); - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/ILocalizedHazardEmitter.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/ILocalizedHazardEmitter.java deleted file mode 100644 index f13c10b0af1..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/ILocalizedHazardEmitter.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature; - -import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; -import com.gregtechceu.gtceu.api.capability.IHazardParticleContainer; -import com.gregtechceu.gtceu.api.data.chemical.material.properties.HazardProperty; -import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; -import com.gregtechceu.gtceu.common.capability.LocalizedHazardSavedData; -import com.gregtechceu.gtceu.common.data.GTMedicalConditions; -import com.gregtechceu.gtceu.config.ConfigHolder; - -import net.minecraft.server.level.ServerLevel; - -/** - * common interface for localized hazard (e.g. radiation) emitters like nuclear reactors. - */ -public interface ILocalizedHazardEmitter extends IMachineFeature { - - /** - * @return the medical condition this hazard emitter creates. - */ - default MedicalCondition getConditionToEmit() { - return GTMedicalConditions.CARCINOGEN; - } - - /** - * @return the starting strength of the hazard zone. recommended values are in the range [1,5) - */ - int getHazardSizePerOperation(); - - default void spreadLocalizedHazard() { - if (!ConfigHolder.INSTANCE.gameplay.environmentalHazards) { - return; - } - - if (self().getLevel() instanceof ServerLevel serverLevel) { - IHazardParticleContainer container = GTCapabilityHelper.getHazardContainer(serverLevel, - self().getBlockPos().relative(self().getFrontFacing()), self().getFrontFacing().getOpposite()); - if (container != null && - container.getHazardCanBeInserted(getConditionToEmit()) > getHazardSizePerOperation()) { - container.addHazard(getConditionToEmit(), getHazardSizePerOperation()); - return; - } - - var savedData = LocalizedHazardSavedData.getOrCreate(serverLevel); - savedData.addSphericalZone(self().getBlockPos(), getHazardSizePerOperation(), false, - HazardProperty.HazardTrigger.INHALATION, getConditionToEmit()); - } - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IMuiMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IMuiMachine.java new file mode 100644 index 00000000000..e7ec6992d17 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/IMuiMachine.java @@ -0,0 +1,35 @@ +package com.gregtechceu.gtceu.api.machine.feature; + +import com.gregtechceu.gtceu.api.mui.base.IUIHolder; +import com.gregtechceu.gtceu.api.mui.factory.PosGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.mui.factory.MachineUIFactory; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.BlockHitResult; + +public interface IMuiMachine extends IUIHolder, IMachineFeature { + + @Override + ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings); + + default boolean shouldOpenUI(Player player, InteractionHand hand, BlockHitResult hit) { + return true; + } + + default InteractionResult tryToOpenUI(Player player, InteractionHand hand, BlockHitResult hit) { + if (this.shouldOpenUI(player, hand, hit)) { + if (player instanceof ServerPlayer serverPlayer) { + MachineUIFactory.INSTANCE.open(serverPlayer, this); + } + return InteractionResult.sidedSuccess(player.level().isClientSide); + } else { + return InteractionResult.PASS; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDisplayUIMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDisplayUIMachine.java deleted file mode 100644 index d6bfdb6b5fa..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDisplayUIMachine.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature.multiblock; - -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.UITemplate; -import com.gregtechceu.gtceu.api.machine.feature.IUIMachine; -import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; - -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; -import com.lowdragmc.lowdraglib.gui.texture.IGuiTexture; -import com.lowdragmc.lowdraglib.gui.util.ClickData; -import com.lowdragmc.lowdraglib.gui.widget.ComponentPanelWidget; -import com.lowdragmc.lowdraglib.gui.widget.DraggableScrollableWidgetGroup; -import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; - -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Player; - -import java.util.List; - -public interface IDisplayUIMachine extends IUIMachine { - - default void addDisplayText(List textList) { - for (var part : self().getParts()) { - part.addMultiText(textList); - } - } - - default void handleDisplayClick(String componentData, ClickData clickData) {} - - default IGuiTexture getScreenTexture() { - return GuiTextures.DISPLAY; - } - - @Override - default ModularUI createUI(Player entityPlayer) { - var screen = new DraggableScrollableWidgetGroup(7, 4, 162, 121).setBackground(getScreenTexture()); - screen.addWidget(new LabelWidget(4, 5, self().getBlockState().getBlock().getDescriptionId())); - screen.addWidget(new ComponentPanelWidget(4, 17, this::addDisplayText) - .textSupplier(this.self().getLevel().isClientSide ? null : this::addDisplayText) - .setMaxWidthLimit(150) - .clickHandler(this::handleDisplayClick)); - return new ModularUI(176, 216, this, entityPlayer) - .background(GuiTextures.BACKGROUND) - .widget(screen) - .widget(UITemplate.bindPlayerInventory(entityPlayer.getInventory(), GuiTextures.SLOT, 7, 134, true)); - } - - @Override - default MultiblockControllerMachine self() { - return (MultiblockControllerMachine) this; - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDistinctPart.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDistinctPart.java index 1c1947d7ed3..7922c636d3d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDistinctPart.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IDistinctPart.java @@ -1,36 +1,28 @@ package com.gregtechceu.gtceu.api.machine.feature.multiblock; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; -import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; - -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; - -import java.util.List; - public interface IDistinctPart extends IMultiPart { boolean isDistinct(); void setDistinct(boolean isDistinct); - @Override - default void attachConfigurators(ConfiguratorPanel configuratorPanel) { - superAttachConfigurators(configuratorPanel); - configuratorPanel.attachConfigurators(new IFancyConfiguratorButton.Toggle( - GuiTextures.BUTTON_DISTINCT_BUSES.getSubTexture(0, 0.5, 1, 0.5), - GuiTextures.BUTTON_DISTINCT_BUSES.getSubTexture(0, 0, 1, 0.5), - this::isDistinct, (clickData, pressed) -> setDistinct(pressed)) - .setTooltipsSupplier(pressed -> List.of( - Component.translatable("gtceu.multiblock.universal.distinct") - .setStyle(Style.EMPTY.withColor(ChatFormatting.YELLOW)) - .append(Component.translatable(pressed ? "gtceu.multiblock.universal.distinct.yes" : - "gtceu.multiblock.universal.distinct.no"))))); - } - - default void superAttachConfigurators(ConfiguratorPanel configuratorPanel) { - IMultiPart.super.attachConfigurators(configuratorPanel); - } + /* + * @Override + * default void attachConfigurators(ConfiguratorPanel configuratorPanel) { + * superAttachConfigurators(configuratorPanel); + * configuratorPanel.attachConfigurators(new IFancyConfiguratorButton.Toggle( + * GuiTextures.BUTTON_DISTINCT_BUSES.getSubTexture(0, 0.5, 1, 0.5), + * GuiTextures.BUTTON_DISTINCT_BUSES.getSubTexture(0, 0, 1, 0.5), + * this::isDistinct, (clickData, pressed) -> setDistinct(pressed)) + * .setTooltipsSupplier(pressed -> List.of( + * Component.translatable("gtceu.multiblock.universal.distinct") + * .setStyle(Style.EMPTY.withColor(ChatFormatting.YELLOW)) + * .append(Component.translatable(pressed ? "gtceu.multiblock.universal.distinct.yes" : + * "gtceu.multiblock.universal.distinct.no"))))); + * } + * + * default void superAttachConfigurators(ConfiguratorPanel configuratorPanel) { + * IMultiPart.super.attachConfigurators(configuratorPanel); + * } + */ } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMaintenanceMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMaintenanceMachine.java index 3216cc9850e..81fb6fbd40b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMaintenanceMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMaintenanceMachine.java @@ -1,21 +1,12 @@ package com.gregtechceu.gtceu.api.machine.feature.multiblock; import com.gregtechceu.gtceu.api.GTValues; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.fancy.IFancyTooltip; -import com.gregtechceu.gtceu.api.gui.fancy.TooltipsPanel; -import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.config.ConfigHolder; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; import net.minecraft.world.level.block.state.properties.BooleanProperty; -import java.util.ArrayList; - public interface IMaintenanceMachine extends IMultiPart { BooleanProperty MAINTENANCE_TAPED_PROPERTY = GTMachineModelProperties.IS_TAPED; @@ -153,39 +144,43 @@ default GTRecipe modifyRecipe(GTRecipe recipe) { // ******* FANCY GUI ********// ////////////////////////////////////// - @Override - default void attachFancyTooltipsToController(MultiblockControllerMachine controller, TooltipsPanel tooltipsPanel) { - attachTooltips(tooltipsPanel); - } - - @Override - default void attachTooltips(TooltipsPanel tooltipsPanel) { - if (ConfigHolder.INSTANCE.machines.enableMaintenance) { - tooltipsPanel.attachTooltips(new IFancyTooltip.Basic(() -> GuiTextures.MAINTENANCE_ICON, () -> { - var tooltips = new ArrayList(); - tooltips.add(Component.translatable("gtceu.multiblock.universal.has_problems_header") - .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); - - if ((getMaintenanceProblems() & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.wrench")); - - if (((getMaintenanceProblems() >> 1) & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.screwdriver")); - - if (((getMaintenanceProblems() >> 2) & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.soft_mallet")); - - if (((getMaintenanceProblems() >> 3) & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.hard_hammer")); - - if (((getMaintenanceProblems() >> 4) & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.wire_cutter")); - - if (((getMaintenanceProblems() >> 5) & 1) == 0) - tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.crowbar")); + /* + * @Override + * default void attachFancyTooltipsToController(IMultiController controller, TooltipsPanel tooltipsPanel) { + * attachTooltips(tooltipsPanel); + * } + */ - return tooltips; - }, this::hasMaintenanceProblems, () -> null)); - } - } + /* + * @Override + * default void attachTooltips(TooltipsPanel tooltipsPanel) { + * if (ConfigHolder.INSTANCE.machines.enableMaintenance) { + * tooltipsPanel.attachTooltips(new IFancyTooltip.Basic(() -> GuiTextures.MAINTENANCE_ICON, () -> { + * var tooltips = new ArrayList(); + * tooltips.add(Component.translatable("gtceu.multiblock.universal.has_problems_header") + * .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + * + * if ((getMaintenanceProblems() & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.wrench")); + * + * if (((getMaintenanceProblems() >> 1) & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.screwdriver")); + * + * if (((getMaintenanceProblems() >> 2) & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.soft_mallet")); + * + * if (((getMaintenanceProblems() >> 3) & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.hard_hammer")); + * + * if (((getMaintenanceProblems() >> 4) & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.wire_cutter")); + * + * if (((getMaintenanceProblems() >> 5) & 1) == 0) + * tooltips.add(Component.translatable("gtceu.multiblock.universal.problem.crowbar")); + * + * return tooltips; + * }, this::hasMaintenanceProblems, () -> null)); + * } + * } + */ } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMufflerMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMufflerMachine.java deleted file mode 100644 index db1ea9e0ea4..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMufflerMachine.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.gregtechceu.gtceu.api.machine.feature.multiblock; - -import com.gregtechceu.gtceu.api.GTValues; -import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; -import com.gregtechceu.gtceu.api.capability.IHazardParticleContainer; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.fancy.IFancyTooltip; -import com.gregtechceu.gtceu.api.gui.fancy.TooltipsPanel; -import com.gregtechceu.gtceu.api.machine.feature.IEnvironmentalHazardEmitter; -import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; -import com.gregtechceu.gtceu.api.machine.multiblock.part.TieredPartMachine; -import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import com.gregtechceu.gtceu.common.data.GTParticleTypes; - -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.item.ItemStack; - -import java.util.List; - -public interface IMufflerMachine extends IMultiPart, IEnvironmentalHazardEmitter { - - void recoverItemsTable(ItemStack... recoveryItems); - - /** - * @return true if front face is free and contains only air blocks in 1x1 area OR has a duct block on it. - */ - default boolean isFrontFaceFree() { - var frontPos = self().getBlockPos().relative(self().getFrontFacing()); - return self().getLevel().getBlockState(frontPos).isAir() || - GTCapabilityHelper.getHazardContainer(self().getLevel(), - frontPos, self().getFrontFacing().getOpposite()) != null; - } - - default void emitPollutionParticles() { - var pos = self().getBlockPos(); - var facing = self().getFrontFacing(); - - IHazardParticleContainer container = GTCapabilityHelper.getHazardContainer(self().getLevel(), - pos.relative(facing), facing.getOpposite()); - if (container != null) { - // do not emit particles if front face has a duct on it. - return; - } - - var center = pos.getCenter(); - var offset = .75f; - var xPos = (float) (center.x + facing.getStepX() * offset + (GTValues.RNG.nextFloat() - .5f) * .35f); - var yPos = (float) (center.y + facing.getStepY() * offset + (GTValues.RNG.nextFloat() - .5f) * .35f); - var zPos = (float) (center.z + facing.getStepZ() * offset + (GTValues.RNG.nextFloat() - .5f) * .35f); - - var ySpd = facing.getStepY() + (GTValues.RNG.nextFloat() - .15f) * .5f; - var xSpd = facing.getStepX() + (GTValues.RNG.nextFloat() - .5f) * .5f; - var zSpd = facing.getStepZ() + (GTValues.RNG.nextFloat() - .5f) * .5f; - - self().getLevel().addParticle(GTParticleTypes.MUFFLER_PARTICLE.get(), - xPos, yPos, zPos, xSpd, ySpd, zSpd); - } - - @Override - default GTRecipe modifyRecipe(GTRecipe recipe) { - if (!isFrontFaceFree()) { - return null; - } - return IMultiPart.super.modifyRecipe(recipe); - } - - @Override - default float getHazardStrengthPerOperation() { - float outputAmount = 2.5f; - return this instanceof TieredPartMachine tiered ? outputAmount / Math.max(tiered.getTier(), 1) : outputAmount; - } - - @Override - default boolean afterWorking(IWorkableMultiController controller) { - spreadEnvironmentalHazard(); - var supplier = controller.self().getDefinition().getRecoveryItems(); - if (supplier != null) { - recoverItemsTable(supplier.get()); - } - return IMultiPart.super.afterWorking(controller); - } - - ////////////////////////////////////// - // ******* FANCY GUI ********// - ////////////////////////////////////// - - @Override - default void attachFancyTooltipsToController(MultiblockControllerMachine controller, TooltipsPanel tooltipsPanel) { - attachTooltips(tooltipsPanel); - } - - @Override - default void attachTooltips(TooltipsPanel tooltipsPanel) { - tooltipsPanel.attachTooltips(new IFancyTooltip.Basic( - () -> GuiTextures.INDICATOR_NO_STEAM.get(false), - () -> List.of(Component.translatable("gtceu.multiblock.universal.muffler_obstructed") - .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))), - () -> !isFrontFaceFree(), - () -> null)); - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMultiPart.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMultiPart.java index b3f03725c90..bcb712afa10 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMultiPart.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IMultiPart.java @@ -1,8 +1,8 @@ package com.gregtechceu.gtceu.api.machine.feature.multiblock; import com.gregtechceu.gtceu.api.gui.fancy.TooltipsPanel; -import com.gregtechceu.gtceu.api.machine.feature.IFancyUIMachine; import com.gregtechceu.gtceu.api.machine.feature.IMachineFeature; +import com.gregtechceu.gtceu.api.machine.feature.IMuiMachine; import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; import com.gregtechceu.gtceu.api.machine.multiblock.WorkableMultiblockMachine; import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList; @@ -20,7 +20,7 @@ import java.util.List; import java.util.SortedSet; -public interface IMultiPart extends IMachineFeature, IFancyUIMachine { +public interface IMultiPart extends IMachineFeature, IMuiMachine { /** * Can it be shared among multi multiblock. diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IRotorHolderMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IRotorHolderMachine.java new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/feature/multiblock/IRotorHolderMachine.java @@ -0,0 +1 @@ + diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/CoilWorkableElectricMultiblockMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/CoilWorkableElectricMultiblockMachine.java index a4ea83d5a11..2206a2d182d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/CoilWorkableElectricMultiblockMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/CoilWorkableElectricMultiblockMachine.java @@ -2,12 +2,29 @@ import com.gregtechceu.gtceu.api.block.ICoilType; import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.drawable.*; +import com.gregtechceu.gtceu.api.mui.factory.PosGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.IntSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widget.ParentWidget; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Column; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Row; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import com.gregtechceu.gtceu.common.block.CoilBlock; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.data.mui.GTMultiblockPanelUtil; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.common.mui.GTGuis; import net.minecraft.MethodsReturnNonnullByDefault; import lombok.Getter; +import java.util.function.Supplier; + import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault @@ -36,4 +53,47 @@ public void onStructureFormed() { public int getCoilTier() { return coilType.getTier(); } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 176 + 32, 164 + 36); + + var panelUtil = new GTMultiblockPanelUtil(this); + + IntSyncValue coilTier = syncManager.getOrCreateSyncHandler("coilTier", IntSyncValue.class, + () -> new IntSyncValue(this::getCoilTier)); + + Supplier coilTexture = () -> new UITexture.Builder() + .location(CoilBlock.CoilType.values()[coilTier.getIntValue()].getTexture()) + .imageSize(16, 16).colorType(ColorType.DEFAULT).tiled().build(); + + var widget1 = new DynamicDrawable(coilTexture).asWidget().size(4, 16).heightRel(1.0f); + var widget2 = new DynamicDrawable(coilTexture).asWidget().size(4, 16).heightRel(1.0f); + + panel.child(GTMuiWidgets.createTitleBar(this.getDefinition(), 176 + 36)) + .child(new ParentWidget<>() + .widthRel(0.95f) + .heightRel(.45f) + .margin(4, 0) + .left(3).top(3) + .child(new Row() + .child(widget1) + .child(panelUtil.getMainTextPanel(syncManager, 208, 90)) + .child(widget2)) + + ) + .child(new Column() + .coverChildren() + .leftRel(1.0f) + .reverseLayout(true) + .bottom(16) + .padding(0, 8, 4, 4) + .childPadding(2) + .background(GTGuiTextures.BACKGROUND.getSubArea(0.25f, 0f, 1.0f, 1.0f)) + .child(GTMuiWidgets.createPowerButton(this, syncManager)) + .child(GTMuiWidgets.createVoidingButton(this, syncManager))) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7)); + + return panel; + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java index 89b0be5835f..0f3dd511d2b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/MultiblockControllerMachine.java @@ -8,6 +8,9 @@ import com.gregtechceu.gtceu.api.machine.MultiblockMachineDefinition; import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiPart; import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; +import com.gregtechceu.gtceu.api.machine.trait.MultiblockMachineTrait; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.pattern.BlockPattern; import com.gregtechceu.gtceu.api.pattern.MultiblockState; import com.gregtechceu.gtceu.api.pattern.MultiblockWorldSavedData; @@ -123,6 +126,12 @@ public void onStructureFormed() { } part.addedToController(this); } + updatePartPositions(); + + for (var trait : getTraitHolder().getAllTraits()) { + if (trait instanceof MultiblockMachineTrait multiblockMachineTrait) + multiblockMachineTrait.onStructureFormed(); + } } /** @@ -147,6 +156,11 @@ public void onStructureInvalid() { parallelHatch = null; parts.clear(); updatePartPositions(); + + for (var trait : getTraitHolder().getAllTraits()) { + if (trait instanceof MultiblockMachineTrait multiblockMachineTrait) + multiblockMachineTrait.onStructureInvalid(); + } } /** @@ -410,4 +424,15 @@ public boolean checkPatternWithTryLock() { return false; } } + + /** + * Can be overridden to just add widgets to the black box in the middle instead of overriding the whole UI. + * Don't forget to invoke {@code super.getWidgetsForDisplay} to add the default lines (progress, voltage, etc.). + * + * @param syncManager the sync manager + * @return list of widgets to be displayed inside the black box in the middle of a standard multiblock UI + */ + public List getWidgetsForDisplay(PanelSyncManager syncManager) { + return new ArrayList<>(); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableElectricMultiblockMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableElectricMultiblockMachine.java index 9dc664642b9..7cf357186b9 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableElectricMultiblockMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableElectricMultiblockMachine.java @@ -6,43 +6,41 @@ import com.gregtechceu.gtceu.api.capability.recipe.EURecipeCapability; import com.gregtechceu.gtceu.api.capability.recipe.IO; import com.gregtechceu.gtceu.api.capability.recipe.IRecipeHandler; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.fancy.*; -import com.gregtechceu.gtceu.api.machine.feature.IFancyUIMachine; +import com.gregtechceu.gtceu.api.machine.feature.IMuiMachine; import com.gregtechceu.gtceu.api.machine.feature.IOverclockMachine; import com.gregtechceu.gtceu.api.machine.feature.ITieredMachine; -import com.gregtechceu.gtceu.api.machine.feature.IVoidable; -import com.gregtechceu.gtceu.api.machine.feature.multiblock.IDisplayUIMachine; -import com.gregtechceu.gtceu.api.machine.feature.multiblock.IMultiPart; import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; import com.gregtechceu.gtceu.api.misc.EnergyContainerList; -import com.gregtechceu.gtceu.api.recipe.modifier.RecipeModifierList; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.factory.PosGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widget.ParentWidget; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Flow; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; -import com.gregtechceu.gtceu.common.data.GTRecipeModifiers; -import com.gregtechceu.gtceu.common.machine.multiblock.part.ParallelHatchPartMachine; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.data.mui.GTMultiblockPanelUtil; +import com.gregtechceu.gtceu.common.data.mui.GTMultiblockTextUtil; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.common.mui.GTGuis; import com.gregtechceu.gtceu.utils.GTUtil; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; -import com.lowdragmc.lowdraglib.gui.widget.*; - import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Player; import lombok.Getter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.function.Function; import javax.annotation.ParametersAreNonnullByDefault; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class WorkableElectricMultiblockMachine extends WorkableMultiblockMachine implements IFancyUIMachine, - IDisplayUIMachine, ITieredMachine, IOverclockMachine { +public class WorkableElectricMultiblockMachine extends WorkableMultiblockMachine + implements IMuiMachine, ITieredMachine, IOverclockMachine { // runtime protected EnergyContainerList energyContainer; @@ -98,93 +96,130 @@ public void setBatchEnabled(boolean batch) { ////////////////////////////////////// // ********** GUI ***********// ////////////////////////////////////// - - @Override - public void addDisplayText(List textList) { - int numParallels; - int subtickParallels; - int batchParallels; - int totalRuns; - boolean exact = false; - if (recipeLogic.isActive() && recipeLogic.getLastRecipe() != null) { - numParallels = recipeLogic.getLastRecipe().parallels; - subtickParallels = recipeLogic.getLastRecipe().subtickParallels; - batchParallels = recipeLogic.getLastRecipe().batchParallels; - totalRuns = recipeLogic.getLastRecipe().getTotalRuns(); - exact = true; - } else { - numParallels = getParallelHatch() - .map(ParallelHatchPartMachine::getCurrentParallel) - .orElse(0); - subtickParallels = 0; - batchParallels = 0; - totalRuns = 0; - } - - MultiblockDisplayText.builder(textList, isFormed()) - .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive()) - .addEnergyUsageLine(energyContainer) - .addEnergyTierLine(tier) - .addMachineModeLine(getRecipeType(), getRecipeTypes().length > 1) - .addTotalRunsLine(totalRuns) - .addParallelsLine(numParallels, exact) - .addSubtickParallelsLine(subtickParallels) - .addBatchModeLine(isBatchEnabled(), batchParallels) - .addWorkingStatusLine() - .addProgressLine(recipeLogic) - .addRecipeFailReasonLine(recipeLogic) - .addOutputLines(recipeLogic.getLastRecipe()); - getDefinition().getAdditionalDisplay().accept(this, textList); - IDisplayUIMachine.super.addDisplayText(textList); - } - @Override - public Widget createUIWidget() { - var group = new WidgetGroup(0, 0, 182 + 8, 117 + 8); - group.addWidget(new DraggableScrollableWidgetGroup(4, 4, 182, 117).setBackground(getScreenTexture()) - .addWidget(new LabelWidget(4, 5, self().getBlockState().getBlock().getDescriptionId())) - .addWidget(new ComponentPanelWidget(4, 17, this::addDisplayText) - .textSupplier(this.getLevel().isClientSide ? null : this::addDisplayText) - .setMaxWidthLimit(200) - .clickHandler(this::handleDisplayClick))); - group.setBackground(GuiTextures.BACKGROUND_INVERSE); - return group; + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + var panel = GTGuis.createPanel(this, 196, 237); + + var panelUtil = new GTMultiblockPanelUtil(this); + + panel.child(GTMuiWidgets.createTitleBar(this.getDefinition(), 196)) + .child(new ParentWidget<>() + .widthRel(0.95f) + .heightRel(.65f) + .margin(4, 0) + .left(3).top(2) + .horizontalCenter() + .child(Flow.row() + .child(panelUtil.getMainTextPanel(syncManager, 186, 146)))) + .child(Flow.col() + .coverChildren() + .leftRel(1.0f) + .reverseLayout(true) + .bottom(16) + .padding(0, 8, 4, 4) + .childPadding(2) + .background(GTGuiTextures.BACKGROUND.getSubArea(0.25f, 0f, 1.0f, 1.0f)) + .child(GTMuiWidgets.createPowerButton(this, syncManager)) + .child(GTMuiWidgets.createVoidingButton(this, syncManager))) + .child(SlotGroupWidget.playerInventory(false).left(7).bottom(7).horizontalCenter()); + + return panel; } @Override - public ModularUI createUI(Player entityPlayer) { - return new ModularUI(198, 208, this, entityPlayer).widget(new FancyMachineUIWidget(this, 198, 208)); + public List getWidgetsForDisplay(PanelSyncManager syncManager) { + List widgets = new ArrayList<>(); + widgets.add(GTMultiblockTextUtil.addEnergyTierLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addEnergyUsageLine(this, syncManager)); + widgets.addAll(super.getWidgetsForDisplay(syncManager)); + return widgets; } - @Override - public List getSubTabs() { - return getParts().stream().filter(Objects::nonNull).map(IFancyUIProvider.class::cast).toList(); - } - - @Override - public void attachConfigurators(ConfiguratorPanel configuratorPanel) { - IVoidable.attachConfigurators(configuratorPanel, this); - if (getDefinition().getRecipeModifier() instanceof RecipeModifierList list && Arrays.stream(list.getModifiers()) - .anyMatch(modifier -> modifier == GTRecipeModifiers.BATCH_MODE)) { - configuratorPanel.attachConfigurators(new IFancyConfiguratorButton.Toggle( - GuiTextures.BUTTON_BATCH.getSubTexture(0, 0, 1, 0.5), - GuiTextures.BUTTON_BATCH.getSubTexture(0, 0.5, 1, 0.5), - this::isBatchEnabled, - (cd, p) -> setBatchEnabled(p)) - .setTooltipsSupplier( - p -> List.of( - Component.translatable("gtceu.machine.batch_" + (p ? "enabled" : "disabled"))))); - } - - IFancyUIMachine.super.attachConfigurators(configuratorPanel); - } - - @Override - public void attachTooltips(TooltipsPanel tooltipsPanel) { - for (IMultiPart part : getParts()) { - part.attachFancyTooltipsToController(this, tooltipsPanel); - } - } + // @Override + // public void addDisplayText(List textList) { + // int numParallels; + // int subtickParallels; + // int batchParallels; + // int totalRuns; + // boolean exact = false; + // if (recipeLogic.isActive() && recipeLogic.getLastRecipe() != null) { + // numParallels = recipeLogic.getLastRecipe().parallels; + // subtickParallels = recipeLogic.getLastRecipe().subtickParallels; + // batchParallels = recipeLogic.getLastRecipe().batchParallels; + // totalRuns = recipeLogic.getLastRecipe().getTotalRuns(); + // exact = true; + // } else { + // numParallels = getParallelHatch() + // .map(IParallelHatch::getCurrentParallel) + // .orElse(0); + // subtickParallels = 0; + // batchParallels = 0; + // totalRuns = 0; + // } + // + // MultiblockDisplayText.builder(textList, isFormed()) + // .setWorkingStatus(recipeLogic.isWorkingEnabled(), recipeLogic.isActive()) + // .addEnergyUsageLine(energyContainer) + // .addEnergyTierLine(tier) + // .addMachineModeLine(getRecipeType(), getRecipeTypes().length > 1) + // .addTotalRunsLine(totalRuns) + // .addParallelsLine(numParallels, exact) + // .addSubtickParallelsLine(subtickParallels) + // .addBatchModeLine(isBatchEnabled(), batchParallels) + // .addWorkingStatusLine() + // .addProgressLine(recipeLogic.getProgress(), recipeLogic.getMaxProgress(), + // recipeLogic.getProgressPercent()) + // .addOutputLines(recipeLogic.getLastRecipe()); + // getDefinition().getAdditionalDisplay().accept(this, textList); + // IDisplayUIMachine.super.addDisplayText(textList); + // } + + // @Override + // public Widget createUIWidget() { + // var group = new WidgetGroup(0, 0, 182 + 8, 117 + 8); + // group.addWidget(new DraggableScrollableWidgetGroup(4, 4, 182, 117).setBackground(getScreenTexture()) + // .addWidget(new LabelWidget(4, 5, self().getBlockState().getBlock().getDescriptionId())) + // .addWidget(new ComponentPanelWidget(4, 17, this::addDisplayText) + // .textSupplier(this.getLevel().isClientSide ? null : this::addDisplayText) + // .setMaxWidthLimit(200) + // .clickHandler(this::handleDisplayClick))); + // group.setBackground(GuiTextures.BACKGROUND_INVERSE); + // return group; + // } + // + // @Override + // public ModularUI createUI(Player entityPlayer) { + // return new ModularUI(198, 208, this, entityPlayer).widget(new FancyMachineUIWidget(this, 198, 208)); + // } + // + // @Override + // public List getSubTabs() { + // return getParts().stream().filter(Objects::nonNull).map(IFancyUIProvider.class::cast).toList(); + // } + // + // @Override + // public void attachConfigurators(ConfiguratorPanel configuratorPanel) { + // if (getDefinition().getRecipeModifier() instanceof RecipeModifierList list && Arrays.stream(list.getModifiers()) + // .anyMatch(modifier -> modifier == GTRecipeModifiers.BATCH_MODE)) { + // configuratorPanel.attachConfigurators(new IFancyConfiguratorButton.Toggle( + // GuiTextures.BUTTON_BATCH.getSubTexture(0, 0, 1, 0.5), + // GuiTextures.BUTTON_BATCH.getSubTexture(0, 0.5, 1, 0.5), + // this::isBatchEnabled, + // (cd, p) -> setBatchEnabled(p)) + // .setTooltipsSupplier( + // p -> List.of( + // Component.translatable("gtceu.machine.batch_" + (p ? "enabled" : "disabled"))))); + // } + // + // IFancyUIMachine.super.attachConfigurators(configuratorPanel); + // } + // + // @Override + // public void attachTooltips(TooltipsPanel tooltipsPanel) { + // for (IMultiPart part : getParts()) { + // part.attachFancyTooltipsToController(this, tooltipsPanel); + // } + // } ////////////////////////////////////// // ******** OVERCLOCK *********// diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableMultiblockMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableMultiblockMachine.java index 220b01daf32..be76b7048d3 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableMultiblockMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/WorkableMultiblockMachine.java @@ -10,11 +10,14 @@ import com.gregtechceu.gtceu.api.machine.feature.multiblock.IWorkableMultiController; import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; import com.gregtechceu.gtceu.api.machine.trait.*; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.client.model.machine.MachineRenderState; +import com.gregtechceu.gtceu.common.data.mui.GTMultiblockTextUtil; import com.gregtechceu.gtceu.utils.ISubscription; import net.minecraft.MethodsReturnNonnullByDefault; @@ -320,4 +323,17 @@ public void setVoidingMode(VoidingMode mode) { voidingMode = mode; getRecipeLogic().updateTickSubscription(); } + + @Override + public List getWidgetsForDisplay(PanelSyncManager syncManager) { + List widgets = super.getWidgetsForDisplay(syncManager); + widgets.add(GTMultiblockTextUtil.addProgressLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addWorkingStatusLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addParallelLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addBatchModeLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addSubtickParallelsLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addTotalRunsLine(this, syncManager)); + widgets.add(GTMultiblockTextUtil.addOutputLines(this, syncManager)); + return widgets; + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/MultiblockPartMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/MultiblockPartMachine.java index b552c93432d..96076c61349 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/MultiblockPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/MultiblockPartMachine.java @@ -9,9 +9,14 @@ import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; import com.gregtechceu.gtceu.api.machine.trait.IRecipeHandlerTrait; import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList; +import com.gregtechceu.gtceu.api.mui.factory.PosGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; import com.gregtechceu.gtceu.api.sync_system.annotations.ClientFieldChangeListener; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.client.model.machine.MachineRenderState; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; +import com.gregtechceu.gtceu.common.mui.GTGuis; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; @@ -165,4 +170,9 @@ public BlockState getFormedAppearance(BlockState sourceState, BlockPos sourcePos if (!replacePartModelWhenFormed()) return null; return IMultiPart.super.getFormedAppearance(sourceState, sourcePos, side); } + + @Override + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + return GTGuis.createPanel(this, 176, 166); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/TieredIOPartMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/TieredIOPartMachine.java index 4656665301e..921d4323823 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/TieredIOPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/multiblock/part/TieredIOPartMachine.java @@ -10,7 +10,6 @@ import net.minecraft.MethodsReturnNonnullByDefault; import lombok.Getter; -import org.jetbrains.annotations.Nullable; import javax.annotation.ParametersAreNonnullByDefault; @@ -45,14 +44,17 @@ public void setWorkingEnabled(boolean workingEnabled) { // ***** Initialization ******// ////////////////////////////////////// - @Nullable - @Override - public PageGroupingData getPageGroupingData() { - return switch (this.io) { - case IN -> new PageGroupingData("gtceu.multiblock.page_switcher.io.import", 1); - case OUT -> new PageGroupingData("gtceu.multiblock.page_switcher.io.export", 2); - case BOTH -> new PageGroupingData("gtceu.multiblock.page_switcher.io.both", 3); - case NONE -> null; - }; - } + /* + * @Nullable + * + * @Override + * public PageGroupingData getPageGroupingData() { + * return switch (this.io) { + * case IN -> new PageGroupingData("gtceu.multiblock.page_switcher.io.import", 1); + * case OUT -> new PageGroupingData("gtceu.multiblock.page_switcher.io.export", 2); + * case BOTH -> new PageGroupingData("gtceu.multiblock.page_switcher.io.both", 3); + * case NONE -> null; + * }; + * } + */ } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SimpleSteamMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SimpleSteamMachine.java index 42a5a224244..aa374159f99 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SimpleSteamMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SimpleSteamMachine.java @@ -4,14 +4,9 @@ import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; import com.gregtechceu.gtceu.api.capability.recipe.IO; import com.gregtechceu.gtceu.api.capability.recipe.ItemRecipeCapability; -import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.UITemplate; -import com.gregtechceu.gtceu.api.gui.widget.PredicatedImageWidget; import com.gregtechceu.gtceu.api.machine.MetaMachine; -import com.gregtechceu.gtceu.api.machine.feature.IExhaustVentMachine; -import com.gregtechceu.gtceu.api.machine.feature.IUIMachine; import com.gregtechceu.gtceu.api.machine.property.GTMachineModelProperties; +import com.gregtechceu.gtceu.api.machine.trait.ExhaustVentMachineTrait; import com.gregtechceu.gtceu.api.machine.trait.NotifiableItemStackHandler; import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList; import com.gregtechceu.gtceu.api.pattern.util.RelativeDirection; @@ -23,19 +18,10 @@ import com.gregtechceu.gtceu.client.model.machine.MachineRenderState; import com.gregtechceu.gtceu.common.recipe.condition.VentCondition; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; -import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; -import com.lowdragmc.lowdraglib.utils.Position; - import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.entity.player.Player; -import com.google.common.collect.Tables; import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; import java.util.*; @@ -43,22 +29,23 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public class SimpleSteamMachine extends SteamWorkableMachine implements IExhaustVentMachine, IUIMachine { +public class SimpleSteamMachine extends SteamWorkableMachine { @SaveField public final NotifiableItemStackHandler importItems; @SaveField public final NotifiableItemStackHandler exportItems; + @Getter - @Setter - @SaveField - private boolean needsVenting; + private final ExhaustVentMachineTrait exhaustVentTrait; public SimpleSteamMachine(BlockEntityCreationInfo info, boolean isHighPressure) { super(info, isHighPressure); this.importItems = createImportItemHandler(); this.exportItems = createExportItemHandler(); + this.exhaustVentTrait = new ExhaustVentMachineTrait(this); + exhaustVentTrait.setVentingDamageAmount(isHighPressure() ? 12F : 6F); MachineRenderState renderState = getRenderState(); if (renderState.hasProperty(GTMachineModelProperties.VENT_DIRECTION)) { // outputFacing will always be opposite the front facing on init @@ -81,6 +68,7 @@ protected NotifiableItemStackHandler createExportItemHandler() { @Override public void onLoad() { super.onLoad(); + exhaustVentTrait.setVentingDirection(Objects.requireNonNull(getOutputFacing())); // Simulate an EU machine via a SteamEnergyHandler this.addHandlerList(RecipeHandlerList.of(IO.IN, new SteamEnergyRecipeHandler(steamTank, getConversionRate()))); } @@ -96,17 +84,6 @@ public void onMachineDestroyed() { // ****** Venting Logic ******// ////////////////////////////////////// - @Override - public float getVentingDamage() { - return isHighPressure() ? 12F : 6F; - } - - @SuppressWarnings("DataFlowIssue") - @Override - public @NotNull Direction getVentingDirection() { - return getOutputFacing(); - } - public void updateModelVentDirection() { MachineRenderState renderState = getRenderState(); if (renderState.hasProperty(GTMachineModelProperties.VENT_DIRECTION)) { @@ -115,17 +92,19 @@ public void updateModelVentDirection() { if (getFrontFacing() == Direction.UP && !allowExtendedFacing()) { upwardsDir = upwardsDir.getOpposite(); } - var relative = RelativeDirection.findRelativeOf(getFrontFacing(), getVentingDirection(), upwardsDir); + var relative = RelativeDirection.findRelativeOf(getFrontFacing(), exhaustVentTrait.getVentingDirection(), + upwardsDir); setRenderState(renderState.setValue(GTMachineModelProperties.VENT_DIRECTION, relative)); } } @Override - public void setOutputFacing(@NotNull Direction outputFacing) { + public void setOutputFacing(Direction outputFacing) { var oldFacing = getOutputFacing(); super.setOutputFacing(outputFacing); if (getOutputFacing() != oldFacing) { updateModelVentDirection(); + exhaustVentTrait.setVentingDirection(outputFacing); } } @@ -139,7 +118,7 @@ public void setFrontFacing(Direction facing) { } @Override - public void setUpwardsFacing(@NotNull Direction upwardsFacing) { + public void setUpwardsFacing(Direction upwardsFacing) { var oldFacing = getUpwardsFacing(); super.setUpwardsFacing(upwardsFacing); if (getUpwardsFacing() != oldFacing) { @@ -147,11 +126,6 @@ public void setUpwardsFacing(@NotNull Direction upwardsFacing) { } } - @Override - public void markVentingComplete() { - this.needsVenting = false; - } - public double getConversionRate() { return isHighPressure() ? 2.0 : 1.0; } @@ -171,11 +145,11 @@ public double getConversionRate() { * @param recipe recipe * @return A {@link ModifierFunction} for the given Steam Machine */ - public static ModifierFunction recipeModifier(@NotNull MetaMachine machine, @NotNull GTRecipe recipe) { + public static ModifierFunction recipeModifier(MetaMachine machine, GTRecipe recipe) { if (!(machine instanceof SimpleSteamMachine steamMachine)) { return RecipeModifier.nullWrongType(SimpleSteamMachine.class, machine); } - if (RecipeHelper.getRecipeEUtTier(recipe) > GTValues.LV || !steamMachine.checkVenting()) { + if (RecipeHelper.getRecipeEUtTier(recipe) > GTValues.LV || !steamMachine.exhaustVentTrait.checkVenting()) { return ModifierFunction.NULL; } @@ -187,38 +161,7 @@ public static ModifierFunction recipeModifier(@NotNull MetaMachine machine, @Not @Override public void afterWorking() { super.afterWorking(); - needsVenting = true; - checkVenting(); - } - - ////////////////////////////////////// - // *********** GUI ***********// - ////////////////////////////////////// - - @Override - public ModularUI createUI(Player entityPlayer) { - var storages = Tables.newCustomTable(new EnumMap<>(IO.class), LinkedHashMap, Object>::new); - storages.put(IO.IN, ItemRecipeCapability.CAP, importItems.storage); - storages.put(IO.OUT, ItemRecipeCapability.CAP, exportItems.storage); - - var group = getRecipeType().getRecipeUI().createUITemplate(recipeLogic::getProgressPercent, - storages, - new CompoundTag(), - Collections.emptyList(), - true, - isHighPressure); - Position pos = new Position((Math.max(group.getSize().width + 4 + 8, 176) - 4 - group.getSize().width) / 2 + 4, - 32); - group.setSelfPosition(pos); - return new ModularUI(176, 166, this, entityPlayer) - .background(GuiTextures.BACKGROUND_STEAM.get(isHighPressure)) - .widget(group) - .widget(new LabelWidget(5, 5, getBlockState().getBlock().getDescriptionId())) - .widget(new PredicatedImageWidget(pos.x + group.getSize().width / 2 - 9, - pos.y + group.getSize().height / 2 - 9, 18, 18, - GuiTextures.INDICATOR_NO_STEAM.get(isHighPressure)) - .setPredicate(recipeLogic::isWaiting)) - .widget(UITemplate.bindPlayerInventory(entityPlayer.getInventory(), - GuiTextures.SLOT_STEAM.get(isHighPressure), 7, 84, true)); + exhaustVentTrait.setNeedsVenting(true); + exhaustVentTrait.checkVenting(); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java index 7182caea78a..1cbab0e678b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamBoilerMachine.java @@ -3,36 +3,40 @@ import com.gregtechceu.gtceu.api.GTValues; import com.gregtechceu.gtceu.api.blockentity.BlockEntityCreationInfo; import com.gregtechceu.gtceu.api.capability.recipe.IO; -import com.gregtechceu.gtceu.api.gui.GuiTextures; -import com.gregtechceu.gtceu.api.gui.UITemplate; -import com.gregtechceu.gtceu.api.gui.widget.TankWidget; import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.TickableSubscription; -import com.gregtechceu.gtceu.api.machine.feature.IDataInfoProvider; -import com.gregtechceu.gtceu.api.machine.feature.IUIMachine; +import com.gregtechceu.gtceu.api.machine.feature.*; import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; import com.gregtechceu.gtceu.api.machine.trait.RecipeLogic; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; +import com.gregtechceu.gtceu.api.mui.factory.PosGuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.DoubleSyncValue; +import com.gregtechceu.gtceu.api.mui.value.sync.FluidSlotSyncHandler; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.api.mui.widgets.ProgressWidget; +import com.gregtechceu.gtceu.api.mui.widgets.SlotGroupWidget; +import com.gregtechceu.gtceu.api.mui.widgets.layout.Row; +import com.gregtechceu.gtceu.api.mui.widgets.slot.FluidSlot; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.modifier.ModifierFunction; import com.gregtechceu.gtceu.api.recipe.modifier.RecipeModifier; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; import com.gregtechceu.gtceu.common.data.GTMaterials; -import com.gregtechceu.gtceu.common.item.PortableScannerBehavior; +import com.gregtechceu.gtceu.common.data.mui.GTMuiWidgets; +import com.gregtechceu.gtceu.common.item.behavior.PortableScannerBehavior; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.common.mui.GTGuis; import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.utils.FormattingUtil; import com.gregtechceu.gtceu.utils.GTTransferUtils; import com.gregtechceu.gtceu.utils.GTUtil; import com.gregtechceu.gtceu.utils.ISubscription; -import com.lowdragmc.lowdraglib.gui.modular.ModularUI; -import com.lowdragmc.lowdraglib.gui.texture.ProgressTexture; -import com.lowdragmc.lowdraglib.gui.widget.ImageWidget; -import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; -import com.lowdragmc.lowdraglib.gui.widget.ProgressWidget; - import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.client.resources.language.I18n; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; @@ -66,7 +70,7 @@ @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public abstract class SteamBoilerMachine extends SteamWorkableMachine - implements IUIMachine, IDataInfoProvider { + implements IMuiMachine, IDataInfoProvider { @SaveField public final NotifiableFluidTank waterTank; @@ -181,7 +185,9 @@ protected void updateCurrentTemperature() { currentTemperature -= getCoolDownRate(); timeBeforeCoolingDown = getCooldownInterval(); } - } else--timeBeforeCoolingDown; + } else { + --timeBeforeCoolingDown; + } if (getOffsetTimer() % 10 == 0) { if (currentTemperature >= 100) { @@ -195,7 +201,9 @@ protected void updateCurrentTemperature() { } if (this.hasNoWater && hasDrainedWater) { GTUtil.doExplosion(getLevel(), getBlockPos(), 2.0f); - } else this.hasNoWater = !hasDrainedWater; + } else { + this.hasNoWater = !hasDrainedWater; + } if (filledSteam == 0 && hasDrainedWater && getLevel() instanceof ServerLevel serverLevel) { final float x = getBlockPos().getX() + 0.5F; final float y = getBlockPos().getY() + 0.5F; @@ -218,9 +226,12 @@ protected void updateCurrentTemperature() { // bypass capability check for special case behavior steamTank.drainInternal(FluidType.BUCKET_VOLUME * 4, FluidAction.EXECUTE); } - } else this.hasNoWater = false; + } else { + this.hasNoWater = false; + } } updateSteamSubscription(); + syncDataHolder.markClientSyncFieldDirty("currentTemperature"); } protected int getCooldownInterval() { @@ -310,27 +321,44 @@ public InteractionResult onUse(BlockState state, Level world, BlockPos pos, Play ////////////////////////////////////// @Override - public ModularUI createUI(Player entityPlayer) { - return new ModularUI(176, 166, this, entityPlayer) - .background(GuiTextures.BACKGROUND_STEAM.get(isHighPressure)) - .widget(new LabelWidget(6, 6, getBlockState().getBlock().getDescriptionId())) - .widget(new ProgressWidget(this::getTemperaturePercent, 96, 26, 10, 54) - .setProgressTexture(GuiTextures.PROGRESS_BAR_BOILER_EMPTY.get(isHighPressure), - GuiTextures.PROGRESS_BAR_BOILER_HEAT) - .setFillDirection(ProgressTexture.FillDirection.DOWN_TO_UP) - .setDynamicHoverTips(pct -> I18n.get("gtceu.multiblock.large_boiler.temperature", - currentTemperature + 274, getMaxTemperature() + 274))) - .widget(new TankWidget(waterTank.getStorages()[0], 83, 26, 10, 54, false, true) - .setShowAmount(false) - .setFillDirection(ProgressTexture.FillDirection.DOWN_TO_UP) - .setBackground(GuiTextures.PROGRESS_BAR_BOILER_EMPTY.get(isHighPressure))) - .widget(new TankWidget(steamTank.getStorages()[0], 70, 26, 10, 54, true, false) - .setShowAmount(false) - .setFillDirection(ProgressTexture.FillDirection.DOWN_TO_UP) - .setBackground(GuiTextures.PROGRESS_BAR_BOILER_EMPTY.get(isHighPressure))) - .widget(new ImageWidget(43, 44, 18, 18, GuiTextures.CANISTER_OVERLAY_STEAM.get(isHighPressure))) - .widget(UITemplate.bindPlayerInventory(entityPlayer.getInventory(), - GuiTextures.SLOT_STEAM.get(isHighPressure), 7, 84, true)); + public ModularPanel buildUI(PosGuiData data, PanelSyncManager syncManager, UISettings settings) { + ModularPanel panel = GTGuis.createPanel(this, 176, 166); + panel.child(GTMuiWidgets.createTitleBar(this.getDefinition(), 176)); + + UITexture progressTexture = isHighPressure() ? GTGuiTextures.PROGRESS_BAR_BOILER_EMPTY_STEEL : + GTGuiTextures.PROGRESS_BAR_BOILER_EMPTY_BRONZE; + + DoubleSyncValue tempPercentage = syncManager.getOrCreateSyncHandler("tempPercentage", DoubleSyncValue.class, + () -> new DoubleSyncValue(this::getTemperaturePercent)); + + panel.child(new Row() + .top(12) + .left(50) + .coverChildren() + .childPadding(10) + .child(new FluidSlot() + .syncHandler(new FluidSlotSyncHandler(waterTank.getStorages()[0])) + .size(14, 54) + .alwaysShowFull(true) + .displayAmount(false)) + .child(new FluidSlot() + .syncHandler(new FluidSlotSyncHandler(steamTank.getStorages()[0]) + .canFillSlot(false).canDrainSlot(true)) + .alwaysShowFull(true) + .size(14, 54) + .displayAmount(false)) + .child(new ProgressWidget() + .texture(progressTexture, + GTGuiTextures.PROGRESS_BAR_BOILER_HEAT, 54) + .size(14, 54) + .value(tempPercentage) + .direction(ProgressWidget.Direction.UP) + .tooltipAutoUpdate(true) + .tooltipBuilder((r) -> r.addLine(IKey + .lang(Component.translatable("gtceu.fluid.temperature", getCurrentTemperature())))))) + .child(SlotGroupWidget.playerInventory(false).bottom(7).left(7)); + + return panel; } ////////////////////////////////////// diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java index ab2e69cb39a..5b99437bc4b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamEnergyRecipeHandler.java @@ -13,6 +13,7 @@ import net.minecraftforge.fluids.FluidStack; +import lombok.Getter; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -20,6 +21,7 @@ public class SteamEnergyRecipeHandler implements IRecipeHandler { + @Getter private final NotifiableFluidTank steamTank; private final double conversionRate; // mB steam per EU diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamWorkableMachine.java b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamWorkableMachine.java index 8434c9c9fc3..a1683caf644 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamWorkableMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/steam/SteamWorkableMachine.java @@ -4,19 +4,18 @@ import com.gregtechceu.gtceu.api.capability.recipe.IO; import com.gregtechceu.gtceu.api.capability.recipe.IRecipeHandler; import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability; -import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.item.tool.GTToolType; import com.gregtechceu.gtceu.api.machine.feature.IMufflableMachine; import com.gregtechceu.gtceu.api.machine.feature.IRecipeLogicMachine; import com.gregtechceu.gtceu.api.machine.trait.*; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.sync_system.annotations.RerenderOnChanged; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.ISubscription; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; - import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -192,12 +191,12 @@ public void clientTick() { // ******* Rendering ********// ////////////////////////////////////// @Override - public @Nullable ResourceTexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, - Direction side) { + public @Nullable UITexture sideTips(Player player, BlockPos pos, BlockState state, Set toolTypes, + Direction side) { if (toolTypes.contains(GTToolType.WRENCH)) { if (!player.isShiftKeyDown()) { if (!hasFrontFacing() || side != getFrontFacing()) { - return GuiTextures.TOOL_IO_FACING_ROTATION; + return GTGuiTextures.TOOL_IO_FACING_ROTATION; } } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/AutoOutputTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/AutoOutputTrait.java index b11770867d6..b4b609c5848 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/AutoOutputTrait.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/AutoOutputTrait.java @@ -1,22 +1,21 @@ package com.gregtechceu.gtceu.api.machine.trait; import com.gregtechceu.gtceu.api.capability.recipe.IO; -import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.item.tool.GTToolType; import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.TickableSubscription; import com.gregtechceu.gtceu.api.machine.trait.feature.IFrontFacingTrait; import com.gregtechceu.gtceu.api.machine.trait.feature.IInteractionTrait; import com.gregtechceu.gtceu.api.machine.trait.feature.IRenderingTrait; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import com.gregtechceu.gtceu.api.sync_system.annotations.RerenderOnChanged; import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; import com.gregtechceu.gtceu.api.sync_system.annotations.SyncToClient; import com.gregtechceu.gtceu.common.item.tool.behavior.ToolModeSwitchBehavior; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; import com.gregtechceu.gtceu.utils.GTTransferUtils; import com.gregtechceu.gtceu.utils.ISubscription; -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; - import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; @@ -300,8 +299,8 @@ public boolean shouldRenderGridOverlay(Player player, BlockPos pos, BlockState s } @Override - public @Nullable ResourceTexture getGridOverlayIcon(Player player, BlockPos pos, BlockState state, - Set toolTypes, Direction side) { + public @Nullable UITexture getGridOverlayIcon(Player player, BlockPos pos, BlockState state, + Set toolTypes, Direction side) { if (toolTypes.contains(GTToolType.WRENCH)) { if (!player.isShiftKeyDown()) { if (!machine.hasFrontFacing() || side != machine.getFrontFacing()) { @@ -310,14 +309,14 @@ public boolean shouldRenderGridOverlay(Player player, BlockPos pos, BlockState s var canSwitchFluidOutputToSide = supportsAutoOutputFluids() && fluidOutputDirectionValidator.test(side) && side != getFluidOutputDirection(); if (canSwitchItemOutputToSide || canSwitchFluidOutputToSide) - return GuiTextures.TOOL_IO_FACING_ROTATION; + return GTGuiTextures.TOOL_IO_FACING_ROTATION; } } } if (toolTypes.contains(GTToolType.SCREWDRIVER)) { if (side == getItemOutputDirection() || side == getFluidOutputDirection()) { - if (player.isShiftKeyDown()) return GuiTextures.TOOL_ALLOW_INPUT; - return GuiTextures.TOOL_AUTO_OUTPUT; + if (player.isShiftKeyDown()) return GTGuiTextures.TOOL_ALLOW_INPUT; + return GTGuiTextures.TOOL_AUTO_OUTPUT; } } return null; diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/ExhaustVentMachineTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/ExhaustVentMachineTrait.java new file mode 100644 index 00000000000..a8862e5a58e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/ExhaustVentMachineTrait.java @@ -0,0 +1,136 @@ +package com.gregtechceu.gtceu.api.machine.trait; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.sync_system.annotations.SaveField; +import com.gregtechceu.gtceu.common.data.GTDamageTypes; +import com.gregtechceu.gtceu.config.ConfigHolder; +import com.gregtechceu.gtceu.utils.GTUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.Shapes; + +import lombok.Getter; +import lombok.Setter; + +public class ExhaustVentMachineTrait extends MachineTrait { + + public static final MachineTraitType TYPE = new MachineTraitType<>( + ExhaustVentMachineTrait.class, false); + + @Getter + @Setter + private Direction ventingDirection; + @Getter + @Setter + @SaveField + private boolean needsVenting; + @Getter + @Setter + private float ventingDamageAmount; + + public ExhaustVentMachineTrait(MetaMachine machine) { + super(machine); + + this.ventingDirection = machine.getFrontFacing().getOpposite(); + this.needsVenting = false; + this.ventingDamageAmount = 0; + } + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + public boolean isVentingBlocked() { + BlockPos ventingBlockPos = getBlockPos().relative(getVentingDirection()); + BlockState state = getLevel().getBlockState(ventingBlockPos); + + return state.canOcclude() || Shapes.blockOccudes(state.getCollisionShape(getLevel(), ventingBlockPos), + Shapes.block(), getVentingDirection().getOpposite()); + } + + public boolean checkVenting() { + if (!needsVenting) return true; + + if (!isVentingBlocked()) { + performVenting(); + return true; + } + + BlockPos ventingPos = getBlockPos().relative(getVentingDirection()); + if (GTUtil.tryBreakSnow(getLevel(), ventingPos, getLevel().getBlockState(ventingPos), false)) { + performVenting(); + return true; + } + return false; + } + + private void performVenting() { + doVentingDamage(); + createVentingParticles(); + if (ConfigHolder.INSTANCE.machines.machineSounds) { + playVentingSound(); + } + needsVenting = false; + } + + private void doVentingDamage() { + for (LivingEntity entity : getLevel().getEntitiesOfClass(LivingEntity.class, + new AABB(getBlockPos().relative(getVentingDirection())), + entity -> !(entity instanceof Player player) || !player.isSpectator() && !player.isCreative())) { + entity.hurt(GTDamageTypes.HEAT.source(getLevel()), ventingDamageAmount); + } + } + + private void createVentingParticles() { + var level = getLevel(); + var pos = getBlockPos(); + Direction ventingDirection = getVentingDirection(); + double posX = pos.getX() + 0.5 + ventingDirection.getStepX() * 0.6; + double posY = pos.getY() + 0.5 + ventingDirection.getStepY() * 0.6; + double posZ = pos.getZ() + 0.5 + ventingDirection.getStepZ() * 0.6; + var count = 7 + level.random.nextInt(3); + if (level instanceof ServerLevel serverLevel) { + serverLevel.sendParticles(ParticleTypes.CLOUD, posX, posY, posZ, + count, + ventingDirection.getStepX() / 2.0, + ventingDirection.getStepY() / 2.0, + ventingDirection.getStepZ() / 2.0, 0.1); + } else { + for (int i = 0; i < count; ++i) { + double d1 = level.random.nextGaussian() * (double) ventingDirection.getStepX() / 2.0; + double d3 = level.random.nextGaussian() * (double) ventingDirection.getStepY() / 2.0; + double d5 = level.random.nextGaussian() * (double) ventingDirection.getStepZ() / 2.0; + double d6 = level.random.nextGaussian() * 0.1; + double d7 = level.random.nextGaussian() * 0.1; + double d8 = level.random.nextGaussian() * 0.1; + try { + level.addParticle(ParticleTypes.CLOUD, posX + d1, posY + d3, posZ + d5, d6, d7, d8); + continue; + } catch (Throwable throwable) { + GTCEu.LOGGER.warn("Could not spawn particle effect {}", ParticleTypes.CLOUD); + return; + } + } + } + } + + private void playVentingSound() { + var pos = getBlockPos(); + var level = getLevel(); + double posX = pos.getX() + 0.5 + ventingDirection.getStepX() * 0.6; + double posY = pos.getY() + 0.5 + ventingDirection.getStepY() * 0.6; + double posZ = pos.getZ() + 0.5 + ventingDirection.getStepZ() * 0.6; + level.playSound(null, posX, posY, posZ, SoundEvents.LAVA_EXTINGUISH, SoundSource.BLOCKS, 1F, 1F); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTraitHolder.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTraitHolder.java index 1f12ceaeec1..1e7eb18063a 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTraitHolder.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MachineTraitHolder.java @@ -1,6 +1,12 @@ package com.gregtechceu.gtceu.api.machine.trait; +import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformer; +import com.gregtechceu.gtceu.api.sync_system.data_transformers.ValueTransformers; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -10,6 +16,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; public final class MachineTraitHolder { @@ -18,10 +25,13 @@ public final class MachineTraitHolder { private final List traits; private final Map, List> traitsByType; + private final Map traitsToSave; + public MachineTraitHolder(MetaMachine machine) { this.machine = machine; this.traits = new ObjectArrayList<>(); this.traitsByType = new Object2ObjectOpenHashMap<>(); + this.traitsToSave = new Object2ObjectOpenHashMap<>(); } public @UnmodifiableView List getAllTraits() { @@ -40,6 +50,28 @@ public void attachTrait(MachineTrait trait) { traits.add(trait); } + /** + * Registers a trait to be synced/saved. + * Do not register a trait to be synced and also store that trait as a syncable machine field, otherwise the trait + * data will be duplicated. Use only one sync method. + * + * @param traitName Unique identifier for this trait. + * @param trait The trait to register + */ + public MachineTraitHolder syncTrait(String traitName, MachineTrait trait) { + if (trait.machine != machine) throw new IllegalArgumentException("Trait does not belong to this machine."); + if (traitsToSave.containsKey(traitName)) + throw new IllegalArgumentException("Attempted to register duplicate trait save key \"" + traitName + "\""); + traitsToSave.put(traitName, trait); + return this; + } + + @SuppressWarnings("unchecked") + public @Nullable T getSyncTrait(String traitName) { + MachineTrait trait = traitsToSave.get(traitName); + return trait == null ? null : (T) trait; + } + /** * Gets the first trait with the specified type. */ @@ -62,4 +94,39 @@ public Optional getTraitOptional(MachineTraitType if (traitList == null) return List.of(); return Collections.unmodifiableList(traitList); } + + private static class MachineTraitHolderTransformer implements ValueTransformer { + + @Override + public Tag serializeNBT(MachineTraitHolder value, TransformerContext context) { + CompoundTag tag = new CompoundTag(); + + value.traitsToSave.forEach((k, v) -> tag.put(k, + v.getSyncDataHolder().serializeNBT(context.isClientSync(), context.isClientFullSyncUpdate()))); + + return tag; + } + + @Override + public @Nullable MachineTraitHolder deserializeNBT(Tag tag, TransformerContext context) { + var traitHolder = Objects.requireNonNull(context.currentValue()); + var compoundTag = (CompoundTag) tag; + + for (var key : compoundTag.getAllKeys()) { + var trait = traitHolder.getSyncTrait(key); + if (trait == null) { + GTCEu.LOGGER.warn("Attempted to deserialise syncable trait '{}', but no syncable trait has that ID", + key); + continue; + } + trait.getSyncDataHolder().deserializeNBT(compoundTag.getCompound("key"), context.isClientSync()); + } + + return null; + } + } + + static { + ValueTransformers.registerTransformer(MachineTraitHolder.class, new MachineTraitHolderTransformer()); + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MultiblockMachineTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MultiblockMachineTrait.java new file mode 100644 index 00000000000..4070537b988 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/MultiblockMachineTrait.java @@ -0,0 +1,14 @@ +package com.gregtechceu.gtceu.api.machine.trait; + +import com.gregtechceu.gtceu.api.machine.multiblock.MultiblockControllerMachine; + +public abstract class MultiblockMachineTrait extends MachineTrait { + + public MultiblockMachineTrait(MultiblockControllerMachine multiMachine) { + super(multiMachine); + } + + public void onStructureFormed() {} + + public void onStructureInvalid() {} +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableComputationContainer.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableComputationContainer.java index 4cfb6b28756..f04b8757722 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableComputationContainer.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableComputationContainer.java @@ -1,10 +1,10 @@ package com.gregtechceu.gtceu.api.machine.trait; import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.capability.GTCapability; import com.gregtechceu.gtceu.api.capability.IOpticalComputationHatch; import com.gregtechceu.gtceu.api.capability.IOpticalComputationProvider; import com.gregtechceu.gtceu.api.capability.IOpticalComputationReceiver; -import com.gregtechceu.gtceu.api.capability.forge.GTCapability; import com.gregtechceu.gtceu.api.capability.recipe.*; import com.gregtechceu.gtceu.api.machine.MetaMachine; import com.gregtechceu.gtceu.api.machine.feature.IRecipeLogicMachine; diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java index 203c0ad68e8..82bb38af3ad 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogic.java @@ -298,6 +298,7 @@ public void handleRecipeWorking() { } progress++; totalContinuousRunningTime++; + syncDataHolder.markClientSyncFieldDirty("progress"); } else { setWaiting(handleTick.reason()); @@ -338,6 +339,7 @@ public void handleRecipeWorking() { protected void regressRecipe() { if (progress > 0 && machine.regressWhenWaiting()) { this.progress = 1; + syncDataHolder.markClientSyncFieldDirty("progress"); } } @@ -361,6 +363,7 @@ public void findAndHandleRecipe() { lastOriginRecipe = null; handleSearchingRecipes(searchRecipe()); } + syncDataHolder.markClientSyncFieldDirty("lastRecipe"); recipeDirty = false; } @@ -405,6 +408,7 @@ public void setupRecipe(GTRecipe recipe) { progress = 0; duration = 0; isActive = false; + syncDataHolder.resyncAllFields(); return; } var handledIO = handleRecipeIO(recipe, IO.IN); @@ -419,6 +423,7 @@ public void setupRecipe(GTRecipe recipe) { progress = 0; duration = recipe.duration; isActive = true; + syncDataHolder.resyncAllFields(); } } @@ -438,6 +443,7 @@ public void setStatus(Status status) { updateTickSubscription(); if (this.status != Status.WAITING) { waitingReason = null; + syncDataHolder.markClientSyncFieldDirty("waitingReason"); } } } @@ -445,6 +451,7 @@ public void setStatus(Status status) { public void setWaiting(@Nullable Component reason) { setStatus(Status.WAITING); waitingReason = reason; + syncDataHolder.markClientSyncFieldDirty("waitingReason"); machine.onWaiting(); } @@ -533,6 +540,7 @@ public void onRecipeFinish() { isActive = false; // Force a recipe recheck. lastRecipe = null; + syncDataHolder.resyncAllFields(); return; } if (machine.alwaysTryModifyRecipe()) { @@ -542,6 +550,7 @@ public void onRecipeFinish() { markLastRecipeDirty(); } else { lastRecipe = modified; + syncDataHolder.markClientSyncFieldDirty("lastRecipe"); } } else { markLastRecipeDirty(); @@ -557,6 +566,7 @@ public void onRecipeFinish() { progress = 0; duration = 0; isActive = false; + syncDataHolder.resyncAllFields(); } } } @@ -578,6 +588,8 @@ public void interruptRecipe() { setStatus(Status.IDLE); progress = 0; duration = 0; + syncDataHolder.markClientSyncFieldDirty("progress"); + syncDataHolder.markClientSyncFieldDirty("duration"); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/feature/IRenderingTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/feature/IRenderingTrait.java index bdf175dd2b2..29c0f345333 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/feature/IRenderingTrait.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/feature/IRenderingTrait.java @@ -1,8 +1,7 @@ package com.gregtechceu.gtceu.api.machine.trait.feature; import com.gregtechceu.gtceu.api.item.tool.GTToolType; - -import com.lowdragmc.lowdraglib.gui.texture.ResourceTexture; +import com.gregtechceu.gtceu.api.mui.drawable.UITexture; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -35,9 +34,9 @@ default boolean shouldRenderGridOverlay(Player player, BlockPos pos, BlockState * Called when the machine grid overlay is being rendered to determine the icon to be rendered within the grid * segment on a specifc side. */ - default @Nullable ResourceTexture getGridOverlayIcon(Player player, BlockPos pos, BlockState state, - Set toolTypes, - Direction side) { + default @Nullable UITexture getGridOverlayIcon(Player player, BlockPos pos, BlockState state, + Set toolTypes, + Direction side) { return null; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardCleanerTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardCleanerTrait.java new file mode 100644 index 00000000000..73afc1c5c11 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardCleanerTrait.java @@ -0,0 +1,130 @@ +package com.gregtechceu.gtceu.api.machine.trait.hazard; + +import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; +import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.trait.MachineTrait; +import com.gregtechceu.gtceu.api.machine.trait.MachineTraitType; +import com.gregtechceu.gtceu.common.blockentity.DuctPipeBlockEntity; +import com.gregtechceu.gtceu.common.capability.EnvironmentalHazardSavedData; +import com.gregtechceu.gtceu.common.network.GTNetwork; +import com.gregtechceu.gtceu.common.network.packets.hazard.SPacketRemoveHazardZone; +import com.gregtechceu.gtceu.utils.GTUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; + +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +import java.util.function.BiPredicate; + +public class EnvironmentalHazardCleanerTrait extends MachineTrait { + + public static final MachineTraitType TYPE = new MachineTraitType<>( + EnvironmentalHazardCleanerTrait.class); + + @Getter + protected float removedLastSecond; + + @Getter + protected float amountPerOperation; + @Getter + protected MedicalCondition conditionToRemove; + @Getter + protected int cleaningRadius; + @Getter + private boolean cleaningOperationInProgress; + + private final @Nullable BiPredicate cleaningHandler; + + public EnvironmentalHazardCleanerTrait(MetaMachine machine, int cleaningRadius, + @Nullable BiPredicate validateCleaningOperation) { + super(machine); + this.cleaningRadius = cleaningRadius; + this.cleaningHandler = validateCleaningOperation; + } + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + public boolean cleanHazard(MedicalCondition condition, float totalAmountToRemove) { + if (cleaningHandler == null) return beginCleaningOperation(condition, totalAmountToRemove); + return cleaningHandler.test(condition, totalAmountToRemove); + } + + public boolean beginCleaningOperation(MedicalCondition condition, float amountPerOperation) { + if (cleaningOperationInProgress) return false; + + this.conditionToRemove = condition; + this.amountPerOperation = amountPerOperation; + this.cleaningOperationInProgress = true; + return true; + } + + public void endCleaningOperation() { + this.cleaningOperationInProgress = false; + } + + public void cleanHazard() { + if (!cleaningOperationInProgress) return; + if (machine.getOffsetTimer() % 20 == 0) { + removedLastSecond = 0; + + for (Direction dir : GTUtil.DIRECTIONS) { + BlockPos offset = getBlockPos().relative(dir); + if (GTCapabilityHelper.getHazardContainer(getLevel(), offset, dir.getOpposite()) != null) { + if (getLevel().getBlockEntity(offset) instanceof DuctPipeBlockEntity duct && + !duct.isConnected(dir.getOpposite())) { + continue; + } + return; + } + } + + final ServerLevel serverLevel = (ServerLevel) getLevel(); + EnvironmentalHazardSavedData savedData = EnvironmentalHazardSavedData.getOrCreate(serverLevel); + + final ChunkPos pos = new ChunkPos(getBlockPos()); + Object2FloatMap relativePositions = new Object2FloatOpenHashMap<>(); + if (cleaningRadius <= 0) { + // LV scrubber can only process the chunk it's in + relativePositions.put(pos, 1); + } else { + for (int x = -cleaningRadius; x <= cleaningRadius; ++x) { + for (int z = -cleaningRadius; z <= cleaningRadius; ++z) { + relativePositions.put(new ChunkPos(pos.x + x, pos.z + z), Mth.sqrt(Mth.abs(x * z)) + 1); + } + } + } + for (ChunkPos rel : relativePositions.keySet()) { + final float distance = relativePositions.getFloat(rel); + savedData.getHazardZones().compute(rel, (chunkPos, zone) -> { + if (zone == null || zone.strength() <= 0) { + return null; + } + + float toClean = amountPerOperation / distance; + removedLastSecond += toClean; + zone.removeStrength(toClean); + if (zone.strength() <= 0) { + if (serverLevel.hasChunk(chunkPos.x, chunkPos.z)) { + LevelChunk chunk = serverLevel.getChunk(chunkPos.x, chunkPos.z); + GTNetwork.sendToAllPlayersTrackingChunk(chunk, new SPacketRemoveHazardZone(chunkPos)); + } + return null; + } else return zone; + }); + } + } + return; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardEmitterTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardEmitterTrait.java new file mode 100644 index 00000000000..f3dce5ffe4a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/EnvironmentalHazardEmitterTrait.java @@ -0,0 +1,64 @@ +package com.gregtechceu.gtceu.api.machine.trait.hazard; + +import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; +import com.gregtechceu.gtceu.api.capability.IHazardParticleContainer; +import com.gregtechceu.gtceu.api.data.chemical.material.properties.HazardProperty; +import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.trait.MachineTrait; +import com.gregtechceu.gtceu.api.machine.trait.MachineTraitType; +import com.gregtechceu.gtceu.common.capability.EnvironmentalHazardSavedData; +import com.gregtechceu.gtceu.config.ConfigHolder; + +import net.minecraft.server.level.ServerLevel; + +import lombok.Getter; +import lombok.Setter; + +/** + * trait for environmental hazard (e.g. pollution) emitters like mufflers. + */ +public class EnvironmentalHazardEmitterTrait extends MachineTrait { + + public static final MachineTraitType TYPE = new MachineTraitType<>( + EnvironmentalHazardEmitterTrait.class); + + @Getter + @Setter + protected float emissionStrength; + @Getter + @Setter + protected MedicalCondition conditionToEmit; + + public EnvironmentalHazardEmitterTrait(MetaMachine machine, MedicalCondition conditionToEmit, + float emissionStrength) { + super(machine); + this.conditionToEmit = conditionToEmit; + this.emissionStrength = emissionStrength; + } + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + public void emitHazard() { + if (!ConfigHolder.INSTANCE.gameplay.environmentalHazards) { + return; + } + + if (getLevel() instanceof ServerLevel serverLevel) { + IHazardParticleContainer container = GTCapabilityHelper.getHazardContainer(serverLevel, + getBlockPos().relative(machine.getFrontFacing()), machine.getFrontFacing().getOpposite()); + if (container != null && + container.getHazardCanBeInserted(getConditionToEmit()) > getEmissionStrength()) { + container.addHazard(getConditionToEmit(), getEmissionStrength()); + return; + } + + var savedData = EnvironmentalHazardSavedData.getOrCreate(serverLevel); + savedData.addZone(getBlockPos(), getEmissionStrength(), true, + HazardProperty.HazardTrigger.INHALATION, getConditionToEmit()); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/LocalizedHazardEmitterTrait.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/LocalizedHazardEmitterTrait.java new file mode 100644 index 00000000000..8192765380b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/LocalizedHazardEmitterTrait.java @@ -0,0 +1,64 @@ +package com.gregtechceu.gtceu.api.machine.trait.hazard; + +import com.gregtechceu.gtceu.api.capability.GTCapabilityHelper; +import com.gregtechceu.gtceu.api.capability.IHazardParticleContainer; +import com.gregtechceu.gtceu.api.data.chemical.material.properties.HazardProperty; +import com.gregtechceu.gtceu.api.data.medicalcondition.MedicalCondition; +import com.gregtechceu.gtceu.api.machine.MetaMachine; +import com.gregtechceu.gtceu.api.machine.trait.MachineTrait; +import com.gregtechceu.gtceu.api.machine.trait.MachineTraitType; +import com.gregtechceu.gtceu.common.capability.LocalizedHazardSavedData; +import com.gregtechceu.gtceu.config.ConfigHolder; + +import net.minecraft.server.level.ServerLevel; + +import lombok.Getter; +import lombok.Setter; + +/** + * trait for localized hazard (e.g. radiation) emitters like nuclear reactors. + */ +public class LocalizedHazardEmitterTrait extends MachineTrait { + + public static final MachineTraitType TYPE = new MachineTraitType<>( + LocalizedHazardEmitterTrait.class); + + @Getter + @Setter + private MedicalCondition conditionToEmit; + @Getter + @Setter + private int conditionStrength; + + public LocalizedHazardEmitterTrait(MetaMachine machine, MedicalCondition conditionToEmit, + int defaultConditionStrength) { + super(machine); + this.conditionToEmit = conditionToEmit; + this.conditionStrength = defaultConditionStrength; + } + + @Override + public MachineTraitType getTraitType() { + return TYPE; + } + + public void spreadHazard() { + if (!ConfigHolder.INSTANCE.gameplay.environmentalHazards) { + return; + } + + if (getLevel() instanceof ServerLevel serverLevel) { + IHazardParticleContainer container = GTCapabilityHelper.getHazardContainer(serverLevel, + getBlockPos().relative(machine.getFrontFacing()), machine.getFrontFacing().getOpposite()); + if (container != null && + container.getHazardCanBeInserted(getConditionToEmit()) > getConditionStrength()) { + container.addHazard(getConditionToEmit(), getConditionStrength()); + return; + } + + var savedData = LocalizedHazardSavedData.getOrCreate(serverLevel); + savedData.addSphericalZone(getBlockPos(), getConditionStrength(), false, + HazardProperty.HazardTrigger.INHALATION, getConditionToEmit()); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/package-info.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/package-info.java new file mode 100644 index 00000000000..a4d86990353 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/hazard/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package com.gregtechceu.gtceu.api.machine.trait.hazard; + +import org.jetbrains.annotations.NotNullByDefault; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/forge/FluidTankHandler.java b/src/main/java/com/gregtechceu/gtceu/api/misc/forge/FluidTankHandler.java new file mode 100644 index 00000000000..7c2e533a3ce --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/forge/FluidTankHandler.java @@ -0,0 +1,62 @@ +package com.gregtechceu.gtceu.api.misc.forge; + +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.IFluidHandler; + +import org.jetbrains.annotations.NotNull; + +public class FluidTankHandler implements IFluidHandler { + + public static IFluidHandler getTankFluidHandler(IFluidTank tank) { + if (tank instanceof IFluidHandler fluidHandler) { + return fluidHandler; + } + return new FluidTankHandler(tank); + } + + private final IFluidTank fluidTank; + + public FluidTankHandler(IFluidTank tank) { + this.fluidTank = tank; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return this.fluidTank.fill(resource, action); + } + + @Override + public @NotNull FluidStack drain(FluidStack resource, FluidAction action) { + FluidStack currentFluid = this.fluidTank.getFluid(); + if (currentFluid.isEmpty() || !currentFluid.isFluidEqual(resource)) { + return FluidStack.EMPTY; + } + return this.fluidTank.drain(resource, action); + } + + @Override + public @NotNull FluidStack drain(int maxDrain, FluidAction action) { + return this.fluidTank.drain(maxDrain, action); + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public @NotNull FluidStack getFluidInTank(int tank) { + return this.fluidTank.getFluid(); + } + + @Override + public int getTankCapacity(int tank) { + return this.fluidTank.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack stack) { + return this.fluidTank.isFluidValid(stack); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java index a2b274cfc11..524effb6f65 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/EntryTypes.java @@ -31,14 +31,9 @@ private EntryTypes(ResourceLocation location, Supplier supplier) { this.factory = supplier; } - @Nullable - public static EntryTypes fromString(String name) { - return TYPES_MAP.getOrDefault(GTCEu.id(name), null); - } - @Nullable public static EntryTypes fromLocation(ResourceLocation location) { - return TYPES_MAP.getOrDefault(location, null); + return TYPES_MAP.get(location); } public static EntryTypes addEntryType(ResourceLocation location, Supplier supplier) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java index d76b54600b8..00a9d6203ba 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEnderRegistry.java @@ -3,24 +3,20 @@ import com.gregtechceu.gtceu.GTCEu; import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; -import net.minecraftforge.server.ServerLifecycleHooks; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Predicate; +import java.util.*; public class VirtualEnderRegistry extends SavedData { private static final String DATA_ID = GTCEu.MOD_ID + ".virtual_entry_data"; private static final String PUBLIC_KEY = "Public"; private static final String PRIVATE_KEY = "Private"; - private static volatile VirtualEnderRegistry data; + + private VirtualRegistryMap PUBLIC_REGISTRY = new VirtualRegistryMap(); private final Map VIRTUAL_REGISTRIES = new HashMap<>(); public VirtualEnderRegistry() {} @@ -29,43 +25,34 @@ public VirtualEnderRegistry(CompoundTag name) { readFromNBT(name); } - public static VirtualEnderRegistry getInstance() { - if (data == null) { - var server = ServerLifecycleHooks.getCurrentServer(); - if (server != null) { - data = server.overworld().getDataStorage() - .computeIfAbsent(VirtualEnderRegistry::new, VirtualEnderRegistry::new, DATA_ID); - } - } - - return data; + public static VirtualEnderRegistry get(ServerLevel sLvl) { + return sLvl.getServer().overworld().getDataStorage() + .computeIfAbsent(VirtualEnderRegistry::new, VirtualEnderRegistry::new, DATA_ID); } - /** - * To be called on server stopped event - */ - public static void release() { - if (data != null) { - data = null; - GTCEu.LOGGER.debug("VirtualEnderRegistry has been unloaded"); - } + public @Nullable T getEntry(@Nullable UUID owner, EntryTypes type, String name) { + if (owner == null) return PUBLIC_REGISTRY.getEntry(type, name); + return getRegistry(owner).getEntry(type, name); } - public T getEntry(@Nullable UUID owner, EntryTypes type, String name) { - return getRegistry(owner).getEntry(type, name); + public Map getEntries(@Nullable UUID owner, EntryTypes type) { + if (owner == null) return PUBLIC_REGISTRY.getEntries(type); + return VIRTUAL_REGISTRIES.get(owner).getEntries(type); } public void addEntry(@Nullable UUID owner, String name, VirtualEntry entry) { - getRegistry(owner).addEntry(name, entry); + if (owner == null) PUBLIC_REGISTRY.addEntry(name, entry); + else getRegistry(owner).addEntry(name, entry); } public boolean hasEntry(@Nullable UUID owner, EntryTypes type, String name) { + if (owner == null) return PUBLIC_REGISTRY.contains(type, name); return getRegistry(owner).contains(type, name); } - public @NotNull T getOrCreateEntry(@Nullable UUID owner, EntryTypes type, String name) { + public T getOrCreateEntry(@Nullable UUID owner, EntryTypes type, String name) { if (!hasEntry(owner, type, name)) addEntry(owner, name, type.createInstance()); - return getEntry(owner, type, name); + return Objects.requireNonNull(getEntry(owner, type, name)); } /** @@ -75,35 +62,33 @@ public boolean hasEntry(@Nullable UUID owner, EntryTypes type, String name) { * @param type Type of the registry to remove from * @param name The name of the entry */ - public void deleteEntry(@Nullable UUID owner, EntryTypes type, String name) { + public void forceDeleteEntry(@Nullable UUID owner, EntryTypes type, String name) { + if (owner == null) { + PUBLIC_REGISTRY.deleteEntry(type, name); + return; + } + var registry = getRegistry(owner); if (registry.contains(type, name)) { registry.deleteEntry(type, name); - return; } - GTCEu.LOGGER.warn("Attempted to delete {} entry {} of type {}, which does not exist", - owner == null ? "public" : String.format("private [%s]", owner), name, type); } - public void deleteEntryIf(@Nullable UUID owner, EntryTypes type, String name, - Predicate shouldDelete) { + public void tryDeleteEntry(@Nullable UUID owner, EntryTypes type, String name) { T entry = getEntry(owner, type, name); - if (entry != null && shouldDelete.test(entry)) deleteEntry(owner, type, name); - } - - public Set getEntryNames(UUID owner, EntryTypes type) { - return getRegistry(owner).getEntryNames(type); + if (entry == null) return; + if (entry.canRemove()) forceDeleteEntry(owner, type, name); } private VirtualRegistryMap getRegistry(UUID owner) { - if (data == null) getInstance(); - return data.VIRTUAL_REGISTRIES.computeIfAbsent(owner, key -> new VirtualRegistryMap()); + return VIRTUAL_REGISTRIES.computeIfAbsent(owner, key -> new VirtualRegistryMap()); } public final void readFromNBT(CompoundTag nbt) { if (nbt.contains(PUBLIC_KEY)) { - VIRTUAL_REGISTRIES.put(null, new VirtualRegistryMap(nbt.getCompound(PUBLIC_KEY))); + PUBLIC_REGISTRY = new VirtualRegistryMap(nbt.getCompound(PUBLIC_KEY)); } + if (nbt.contains(PRIVATE_KEY)) { CompoundTag privateEntries = nbt.getCompound(PRIVATE_KEY); for (String owner : privateEntries.getAllKeys()) { @@ -113,17 +98,14 @@ public final void readFromNBT(CompoundTag nbt) { } } - @NotNull @Override - public final CompoundTag save(@NotNull CompoundTag tag) { + public final CompoundTag save(CompoundTag tag) { var privateTag = new CompoundTag(); + tag.put(PUBLIC_KEY, PUBLIC_REGISTRY.serializeNBT()); for (var owner : VIRTUAL_REGISTRIES.keySet()) { + if (VIRTUAL_REGISTRIES.get(owner).isEmpty()) continue; var mapTag = VIRTUAL_REGISTRIES.get(owner).serializeNBT(); - if (owner != null) { - privateTag.put(owner.toString(), mapTag); - } else { - tag.put(PUBLIC_KEY, mapTag); - } + privateTag.put(owner.toString(), mapTag); } tag.put(PRIVATE_KEY, privateTag); return tag; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java index b4f8c322012..38851cc9fa1 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualEntry.java @@ -6,7 +6,6 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import org.jetbrains.annotations.NotNull; import java.util.Locale; @@ -19,8 +18,8 @@ public abstract class VirtualEntry implements INBTSerializable { protected static final String DESC_KEY = "description"; @Setter - @NotNull private String description = ""; + @Getter private int color = 0xFFFFFFFF; private String colorStr = DEFAULT_COLOR; @@ -76,6 +75,6 @@ public void deserializeNBT(CompoundTag nbt) { } public boolean canRemove() { - return this.description.isEmpty(); + return this.description.isBlank(); } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java index 3d48f8b0200..3601c71e247 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/VirtualRegistryMap.java @@ -1,21 +1,19 @@ package com.gregtechceu.gtceu.api.misc.virtualregistry; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.common.util.INBTSerializable; -import org.jetbrains.annotations.NotNull; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.jetbrains.annotations.Nullable; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; public class VirtualRegistryMap implements INBTSerializable { - private final Map, Map> registryMap = new ConcurrentHashMap<>(); + private final Map, Map> registryMap = new Object2ObjectOpenHashMap<>(); public VirtualRegistryMap() {} @@ -29,7 +27,11 @@ public VirtualRegistryMap(CompoundTag tag) { } public void addEntry(String name, VirtualEntry entry) { - registryMap.computeIfAbsent(entry.getType(), k -> new ConcurrentHashMap<>()).put(name, entry); + registryMap.computeIfAbsent(entry.getType(), k -> new Object2ObjectOpenHashMap<>()).put(name, entry); + } + + public Map getEntries(EntryTypes type) { + return registryMap.getOrDefault(type, new Object2ObjectOpenHashMap<>()); } public boolean contains(EntryTypes type, String name) { @@ -50,17 +52,18 @@ public void clear() { registryMap.clear(); } - public Set getEntryNames(EntryTypes type) { - return new HashSet<>(registryMap.getOrDefault(type, Collections.emptyMap()).keySet()); + public boolean isEmpty() { + return registryMap.isEmpty(); } @Override - public @NotNull CompoundTag serializeNBT() { + public CompoundTag serializeNBT() { CompoundTag tag = new CompoundTag(); for (Map.Entry, Map> entry : registryMap.entrySet()) { - CompoundTag entriesTag = new CompoundTag(); - for (Map.Entry subEntry : entry.getValue().entrySet()) { - entriesTag.put(subEntry.getKey(), subEntry.getValue().serializeNBT()); + ListTag entriesTag = new ListTag(); + for (VirtualEntry innerEntry : entry.getValue().values()) { + if (innerEntry.canRemove()) continue; + entriesTag.add(innerEntry.serializeNBT()); } tag.put(entry.getKey().toString(), entriesTag); } @@ -70,17 +73,30 @@ public Set getEntryNames(EntryTypes type) { @Override public void deserializeNBT(CompoundTag nbt) { for (String entryTypeString : nbt.getAllKeys()) { - EntryTypes type = entryTypeString.contains(":") ? - EntryTypes.fromLocation(ResourceLocation.tryParse(entryTypeString)) : - EntryTypes.fromString(entryTypeString); - + ResourceLocation entryTypeLoc = ResourceLocation.tryParse(entryTypeString); + if (entryTypeLoc == null) continue; + EntryTypes type = EntryTypes.fromLocation(entryTypeLoc); if (type == null) continue; - CompoundTag virtualEntries = nbt.getCompound(entryTypeString); - for (String name : virtualEntries.getAllKeys()) { - CompoundTag entryTag = virtualEntries.getCompound(name); - addEntry(name, type.createInstance(entryTag)); + Tag virtualEntries = nbt.get(entryTypeString); + + // backwards compat + if (virtualEntries instanceof CompoundTag compoundTag) { + for (String name : compoundTag.getAllKeys()) { + CompoundTag entryTag = compoundTag.getCompound(name); + VirtualEntry entry = type.createInstance(entryTag); + if (entry.canRemove()) continue; + addEntry(entry.getColorStr(), type.createInstance(entryTag)); + } + } else { + ListTag listTag = (ListTag) virtualEntries; + for (int i = 0; i < Objects.requireNonNull(listTag).size(); i++) { + var entry = type.createInstance(listTag.getCompound(i)); + if (entry.canRemove()) continue; + addEntry(entry.getColorStr(), entry); + } } + } } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualItemStorage.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualItemStorage.java index 755964107b0..80296879c10 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualItemStorage.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualItemStorage.java @@ -8,7 +8,6 @@ import net.minecraft.nbt.CompoundTag; import lombok.Getter; -import org.jetbrains.annotations.NotNull; import javax.annotation.ParametersAreNonnullByDefault; @@ -18,7 +17,6 @@ public class VirtualItemStorage extends VirtualEntry { protected static final int DEFAULT_SLOT_AMOUNT = 1; - @NotNull @Getter private final CustomItemStackHandler handler; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualRedstone.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualRedstone.java index 74a1cfc7987..ac6c1437540 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualRedstone.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualRedstone.java @@ -2,21 +2,16 @@ import com.gregtechceu.gtceu.api.misc.virtualregistry.EntryTypes; import com.gregtechceu.gtceu.api.misc.virtualregistry.VirtualEntry; - -import net.minecraft.nbt.CompoundTag; +import com.gregtechceu.gtceu.common.cover.ender.EnderRedstoneLinkCover; import it.unimi.dsi.fastutil.objects.Object2ShortMap; import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; import lombok.Getter; -import java.util.UUID; - public class VirtualRedstone extends VirtualEntry { - private static final String MEMBERS_KEY = "members"; - @Getter - private final Object2ShortMap members = new Object2ShortOpenHashMap<>(); + private final Object2ShortMap members = new Object2ShortOpenHashMap<>(); public VirtualRedstone() {} @@ -24,17 +19,17 @@ public int getSignal() { return members.values().intStream().max().orElse(0); } - public void addMember(UUID uuid) { - members.put(uuid, (short) 0); + public void addMember(EnderRedstoneLinkCover cover) { + members.put(cover, (short) 0); } - public void setSignal(UUID uuid, int signal) { - if (!members.containsKey(uuid)) return; - members.put(uuid, (short) signal); + public void setSignal(EnderRedstoneLinkCover cover, int signal) { + if (!members.containsKey(cover)) return; + members.put(cover, (short) signal); } - public void removeMember(UUID uuid) { - members.removeShort(uuid); + public void removeMember(EnderRedstoneLinkCover cover) { + members.removeShort(cover); } @Override @@ -42,25 +37,6 @@ public EntryTypes getType() { return EntryTypes.ENDER_REDSTONE; } - @Override - public CompoundTag serializeNBT() { - CompoundTag tag = super.serializeNBT(); - CompoundTag tag2 = new CompoundTag(); - for (var entry : members.object2ShortEntrySet()) - tag2.putShort(entry.getKey().toString(), entry.getShortValue()); - tag.put(MEMBERS_KEY, tag2); - return tag; - } - - @Override - public void deserializeNBT(CompoundTag nbt) { - super.deserializeNBT(nbt); - CompoundTag tag = nbt.getCompound(MEMBERS_KEY); - for (String uuid : tag.getAllKeys()) { - members.put(UUID.fromString(uuid), tag.getShort(uuid)); - } - } - @Override public boolean equals(Object o) { if (!(o instanceof VirtualRedstone other)) return false; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java index b142de09b9a..cc0c8d95ea4 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/VirtualTank.java @@ -8,14 +8,12 @@ import net.minecraftforge.fluids.capability.templates.FluidTank; import lombok.Getter; -import org.jetbrains.annotations.NotNull; public class VirtualTank extends VirtualEntry { public static final int DEFAULT_CAPACITY = 160_000; // 160B for per second transfer protected static final String CAPACITY_KEY = "capacity"; protected static final String FLUID_KEY = "fluid"; - @NotNull @Getter private final FluidTank fluidTank; private int capacity; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/package-info.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/package-info.java new file mode 100644 index 00000000000..d5df35c7c58 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/entries/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package com.gregtechceu.gtceu.api.misc.virtualregistry.entries; + +import org.jetbrains.annotations.NotNullByDefault; diff --git a/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/package-info.java b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/package-info.java new file mode 100644 index 00000000000..d99aeae2306 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/misc/virtualregistry/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package com.gregtechceu.gtceu.api.misc.virtualregistry; + +import org.jetbrains.annotations.NotNullByDefault; diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java new file mode 100644 index 00000000000..a20c4ebedf2 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiError.java @@ -0,0 +1,51 @@ +package com.gregtechceu.gtceu.api.mui; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +import lombok.Getter; +import org.apache.logging.log4j.Level; + +import java.util.Objects; + +public class GuiError { + + public static void throwNew(IWidget guiElement, Type type, String msg) { + if (GTCEu.isClientSide()) { + GuiErrorHandler.INSTANCE.pushError(guiElement, type, msg); + } + } + + @Getter + private final Level level = Level.ERROR; + @Getter + private final String msg; + @Getter + private final IWidget reference; + @Getter + private final Type type; + + protected GuiError(String msg, IWidget reference, Type type) { + this.msg = msg; + this.reference = reference; + this.type = type; + } + + @Override + public String toString() { + return "MUI [" + this.type.toString() + "][" + this.reference.toString() + "]: " + this.msg; + } + + @Override + public int hashCode() { + return Objects.hash(this.level, this.reference, this.type); + } + + public enum Type { + DRAW, + SIZING, + WIDGET_TREE, + INTERACTION, + SYNC + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java new file mode 100644 index 00000000000..4fb450f38ed --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/GuiErrorHandler.java @@ -0,0 +1,40 @@ +package com.gregtechceu.gtceu.api.mui; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@OnlyIn(Dist.CLIENT) +public class GuiErrorHandler { + + public static final GuiErrorHandler INSTANCE = new GuiErrorHandler(); + + private final Set errorSet = new ObjectOpenHashSet<>(); + @Getter + private final List errors = new ArrayList<>(); + + private GuiErrorHandler() {} + + public void clear() { + this.errors.clear(); + } + + void pushError(IWidget reference, GuiError.Type type, String msg) { + GuiError error = new GuiError(msg, reference, type); + if (this.errorSet.add(error)) { + GTCEu.LOGGER.log(error.getLevel(), error); + this.errors.add(error); + } + } + + public void drawErrors(int x, int y) {} +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIOpenEvent.java b/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIOpenEvent.java new file mode 100644 index 00000000000..4817595ef30 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIOpenEvent.java @@ -0,0 +1,27 @@ +package com.gregtechceu.gtceu.api.mui; + +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.client.mui.screen.ModularContainerMenu; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraftforge.eventbus.api.Event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class InWorldMUIOpenEvent extends Event { + + @Getter + private final GuiData guiData; + + @Getter + private final Screen vanillaScreen; + + @Getter + private final ModularScreen screen; + + @Getter + private final ModularContainerMenu menu; +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIRenderEvent.java b/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIRenderEvent.java new file mode 100644 index 00000000000..8a965ac5e63 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/InWorldMUIRenderEvent.java @@ -0,0 +1,15 @@ +package com.gregtechceu.gtceu.api.mui; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraftforge.eventbus.api.Event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class InWorldMUIRenderEvent extends Event { + + private final GuiGraphics graphics; + private final float partialTick; +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Animator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Animator.java new file mode 100644 index 00000000000..5aab34b489e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Animator.java @@ -0,0 +1,199 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IInterpolation; +import com.gregtechceu.gtceu.api.mui.utils.Interpolation; + +import lombok.Getter; + +import java.util.function.DoubleConsumer; +import java.util.function.DoublePredicate; + +public class Animator extends BaseAnimator implements IAnimator { + + @Getter + private float min = 0.0f; + @Getter + private float max = 1.0f; + @Getter + private int duration = 250; + @Getter + private IInterpolation curve = Interpolation.LINEAR; + private DoublePredicate onUpdate; + private Runnable onFinish; + + private int progress = 0; + + @Override + public void reset(boolean atEnd) { + super.reset(atEnd); + this.progress = atEnd ? this.duration : 0; + } + + public Animator copy(boolean reversed) { + Animator animator = new Animator() + .curve(this.curve) + .reverseOnFinish(this.reverseOnFinish) + .repeatsOnFinish(this.repeats) + .onUpdate(this.onUpdate) + .duration(this.duration) + .onFinish(this.onFinish); + if (reversed) { + animator.bounds(this.max, this.min); + } else { + animator.bounds(this.min, this.max); + } + return animator; + } + + @Override + public int advance(int elapsedTime) { + if (!isAnimating()) return elapsedTime; + while (elapsedTime > 0) { + int max = isAnimatingForward() ? this.duration - this.progress : this.progress; + int prog = Math.min(max, elapsedTime); + this.progress += prog * getDirection(); + elapsedTime -= prog; + if (onUpdate()) { + stop(true); + break; + } + if ((isAnimatingForward() && this.progress >= this.duration) || + (isAnimatingReverse() && this.progress <= 0)) { + stop(false); + if (!isAnimating()) { + onAnimationFinished(true, true); + break; + } + } + } + return elapsedTime; + } + + protected boolean onUpdate() { + return this.onUpdate != null && this.onUpdate.test(getRawValue()); + } + + protected void onAnimationFinished(boolean finishedOneCycle, boolean finishedAllRepeats) { + if (this.onFinish != null) { + this.onFinish.run(); + } + } + + public boolean isAtEnd() { + return this.progress >= this.duration; + } + + public boolean isAtStart() { + return this.progress <= 0; + } + + protected float getRawValue() { + return this.curve.interpolate(this.min, this.max, (float) this.progress / this.duration); + } + + public float getValue() { + // advance(); + return getRawValue(); + } + + @Override + public boolean hasProgressed() { + if (!isAnimating()) return false; + return isAnimatingForward() ? this.progress > 0 : this.progress < this.duration; + } + + /** + * Sets the min bound of the value that will be interpolated. + * + * @param min min value + * @return this + */ + public Animator min(float min) { + this.min = min; + return this; + } + + /** + * Sets the max bound of the value that will be interpolated. + * + * @param max max value + * @return this + */ + public Animator max(float max) { + this.max = max; + return this; + } + + /** + * Sets the bounds of the value that will be interpolated. + * + * @param min min value + * @param max max value + * @return this + */ + public Animator bounds(float min, float max) { + this.min = min; + this.max = max; + return this; + } + + /** + * The duration of this animation in milliseconds. Note this is not 100% accurate. + * Usually it's plus minus 2ms, but can rarely be more. + * + * @param duration duration in milliseconds + * @return this + */ + public Animator duration(int duration) { + this.duration = duration; + return this; + } + + /** + * Sets the interpolation curve, which is used to interpolate between the bounds. + * + * @param curve curve to interpolate on + * @return this + */ + public Animator curve(IInterpolation curve) { + this.curve = curve; + return this; + } + + /** + * Sets a function which is executed everytime the progress updates, that is on every frame. + * The argument of the function is the interpolated value. + * + * @param onUpdate update function + * @return this + */ + public Animator onUpdate(DoublePredicate onUpdate) { + this.onUpdate = onUpdate; + return this; + } + + /** + * Sets a function which is executed everytime the progress updates, that is on every frame. + * The argument of the function is the interpolated value. + * + * @param onUpdate update function + * @return this + */ + public Animator onUpdate(DoubleConsumer onUpdate) { + return onUpdate(val -> { + onUpdate.accept(val); + return false; + }); + } + + /** + * Sets a function which is executed everytime, on animation, cycle or all repeats is finished. + * + * @param onFinish finish function + * @return this + */ + public Animator onFinish(Runnable onFinish) { + this.onFinish = onFinish; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/AnimatorManager.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/AnimatorManager.java new file mode 100644 index 00000000000..6398d9c85c7 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/AnimatorManager.java @@ -0,0 +1,66 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import net.minecraft.Util; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import java.util.ArrayList; +import java.util.List; + +public class AnimatorManager { + + public static final AnimatorManager INSTANCE = new AnimatorManager(); + + private static final List animators = new ArrayList<>(16); + private static final List queuedAnimators = new ArrayList<>(8); + private static long lastTime = 0; + private static boolean waitClearAnimators = false; + + static void startAnimation(IAnimator animator) { + if (!animators.contains(animator) && !queuedAnimators.contains(animator)) { + queuedAnimators.add(animator); + } + } + + private AnimatorManager() {} + + public static void init() { + MinecraftForge.EVENT_BUS.register(INSTANCE); + } + + @SubscribeEvent(priority = EventPriority.HIGHEST) + public void onDraw(ScreenEvent.Render.Pre event) { + long time = Util.getMillis(); + int elapsedTime = IAnimator.getTimeDiff(lastTime, time); + checkClearAnimators(); + if (lastTime > 0 && !animators.isEmpty()) { + animators.removeIf(animator -> { + if (animator == null) return true; + if (animator.isPaused()) return false; + animator.advance(elapsedTime); + return !animator.isAnimating(); + }); + } + lastTime = time; + animators.addAll(queuedAnimators); + queuedAnimators.clear(); + checkClearAnimators(); + } + + private static void checkClearAnimators() { + if (waitClearAnimators) { + waitClearAnimators = false; + animators.forEach(iAnimator -> iAnimator.stop(false)); + animators.clear(); + } + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public void onClose(ScreenEvent.Closing event) { + // stop and yeet all animators on gui close + // we can't clear now otherwise we might get a CME because of multithreading + waitClearAnimators = true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/BaseAnimator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/BaseAnimator.java new file mode 100644 index 00000000000..ff452cf1d0a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/BaseAnimator.java @@ -0,0 +1,128 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import org.jetbrains.annotations.Nullable; + +public abstract class BaseAnimator> implements IAnimator { + + private IAnimator parent; + protected boolean reverseOnFinish = false; + protected int repeats = 0; + + private byte direction = 0; + private boolean paused = false; + private boolean startedReverse = false; + private int repeated = 0; + + void setParent(IAnimator parent) { + this.parent = parent; + } + + @SuppressWarnings("unchecked") + public A getThis() { + return (A) this; + } + + @Nullable + public final IAnimator getParent() { + return parent; + } + + @Override + public void reset(boolean atEnd) { + this.startedReverse = atEnd; + this.repeated = 0; + } + + @Override + public boolean stop(boolean force) { + if (isAnimating() && !force) { + if (this.reverseOnFinish && this.startedReverse == isAnimatingReverse()) { + onAnimationFinished(false, false); + // started reverse -> bounce back and animate forward + animate(isAnimatingForward()); + return false; + } + if (repeats != 0 && (repeated < repeats || repeats < 0)) { + onAnimationFinished(true, false); + // started forward -> full cycle finished -> try repeating + boolean reverse = !this.reverseOnFinish == isAnimatingReverse(); + animate(reverse); + repeated++; + return false; + } + } + this.direction = 0; + return true; + } + + protected void onAnimationFinished(boolean finishedOneCycle, boolean finishedAllRepeats) {} + + @Override + public void pause() { + this.paused = true; + } + + @Override + public void resume(boolean reverse) { + this.paused = false; + this.direction = (byte) (reverse ? -1 : 1); + if (this.parent == null) AnimatorManager.startAnimation(this); + } + + @Override + public boolean isPaused() { + return paused; + } + + @Override + public boolean isAnimating() { + return this.direction != 0; + } + + @Override + public boolean isAnimatingReverse() { + return this.direction < 0; + } + + @Override + public boolean isAnimatingForward() { + return this.direction > 0; + } + + public final byte getDirection() { + return direction; + } + + /** + * Sets if the animation should reverse animate once after it finished. + * If the animation started in reverse it will animate forward on finish. + * + * @param reverseOnFinish if animation should bounce back on finish + * @return this + */ + public A reverseOnFinish(boolean reverseOnFinish) { + this.reverseOnFinish = reverseOnFinish; + return getThis(); + } + + /** + * Sets how often the animation should repeat. If {@link #reverseOnFinish(boolean)} is set to true, it will repeat + * the whole cycle. + * If the number of repeats is negative, it will repeat infinitely. + * + * @param repeats how often the animation should repeat. + * @return this + */ + public A repeatsOnFinish(int repeats) { + this.repeats = repeats; + return getThis(); + } + + public SequentialAnimator followedBy(IAnimator animator) { + return new SequentialAnimator(this, animator); + } + + public ParallelAnimator inParallelWith(IAnimator animator) { + return new ParallelAnimator(this, animator); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimatable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimatable.java new file mode 100644 index 00000000000..38bf2801288 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimatable.java @@ -0,0 +1,63 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IInterpolation; + +public interface IAnimatable> { + + T interpolate(T start, T end, float t); + + T copyOrImmutable(); + + default boolean shouldAnimate(T target) { + return !equals(target); + } + + default MutableObjectAnimator animator(T target) { + T self = (T) this; + return new MutableObjectAnimator<>(self, self.copyOrImmutable(), target); + } + + default void animate(T target) { + animate(target, false); + } + + default void animate(T target, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).animate(reverse); + } + } + + default void animate(T target, boolean reverse, boolean reverseOnFinish, int repeatsOnFinish) { + if (shouldAnimate(target)) { + animator(target).reverseOnFinish(reverseOnFinish).repeatsOnFinish(repeatsOnFinish).animate(reverse); + } + } + + default void animate(T target, int durationMs, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).duration(durationMs).animate(reverse); + } + } + + default void animate(T target, IInterpolation curve, boolean reverse) { + if (shouldAnimate(target)) { + animator(target).curve(curve).animate(reverse); + } + } + + default void animate(T target, IInterpolation curve, int durationMs, boolean reverse) { + animate(target, curve, durationMs, reverse, false, 0); + } + + default void animate(T target, IInterpolation curve, int durationMs, boolean reverse, boolean reverseOnFinish, + int repeatsOnFinish) { + if (shouldAnimate(target)) { + animator(target) + .curve(curve) + .duration(durationMs) + .reverseOnFinish(reverseOnFinish) + .repeatsOnFinish(repeatsOnFinish) + .animate(reverse); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimator.java new file mode 100644 index 00000000000..adeac87c6ad --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/IAnimator.java @@ -0,0 +1,63 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import net.minecraft.Util; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public interface IAnimator { + + @Nullable + IAnimator getParent(); + + default void animate(boolean reverse) { + reset(reverse); + resume(reverse); + } + + default void animate() { + animate(false); + } + + boolean stop(boolean force); + + void pause(); + + void resume(boolean reverse); + + void reset(boolean atEnd); + + default void reset() { + reset(false); + } + + /** + * Advances the animation by a given duration. + * + * @param elapsedTime elapsed time in ms + * @return remaining time (elapsed time - consumed time) + */ + @ApiStatus.OverrideOnly + int advance(int elapsedTime); + + boolean isPaused(); + + boolean isAnimating(); + + boolean isAnimatingReverse(); + + boolean hasProgressed(); + + default boolean isAnimatingForward() { + return isAnimating() && !isAnimatingReverse(); + } + + static int getTimeDiff(long startTime) { + return getTimeDiff(startTime, Util.getMillis()); + } + + static int getTimeDiff(long startTime, long currentTime) { + long elapsedTime = Math.abs(currentTime - startTime); + return (int) Math.min(Integer.MAX_VALUE, elapsedTime); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/MutableObjectAnimator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/MutableObjectAnimator.java new file mode 100644 index 00000000000..2e3521ebbb8 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/MutableObjectAnimator.java @@ -0,0 +1,38 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import java.util.function.Consumer; + +public class MutableObjectAnimator> extends Animator { + + private final T from; + private final T to; + private final T animatable; + private Consumer intermediateConsumer; + + public MutableObjectAnimator(T animatable, T from, T to) { + this.from = from; + this.to = to; + this.animatable = animatable; + bounds(0f, 1f); + } + + @Override + public void resume(boolean reverse) { + super.resume(reverse); + this.animatable.interpolate(this.from, this.to, getRawValue()); + } + + @Override + protected boolean onUpdate() { + T intermediate = this.animatable.interpolate(this.from, this.to, getRawValue()); + if (this.intermediateConsumer != null) { + this.intermediateConsumer.accept(intermediate); + } + return super.onUpdate(); + } + + public MutableObjectAnimator intermediateConsumer(Consumer consumer) { + this.intermediateConsumer = consumer; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/ParallelAnimator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/ParallelAnimator.java new file mode 100644 index 00000000000..afe6bcd6e2e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/ParallelAnimator.java @@ -0,0 +1,121 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ParallelAnimator extends BaseAnimator implements IAnimator { + + private final List animators; + private int waitTimeBetweenAnimators; + + private int startedAnimating = 0; + private int finishedAnimating = 0; + private int waitTime = 0; + + public ParallelAnimator(List animators) { + this.animators = new ArrayList<>(animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + public ParallelAnimator(IAnimator... animators) { + this.animators = new ArrayList<>(); + Collections.addAll(this.animators, animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + @Override + public void animate(boolean reverse) { + super.animate(reverse); + if (this.waitTimeBetweenAnimators <= 0) { + for (IAnimator animator : animators) { + animator.animate(reverse); + } + this.startedAnimating = this.animators.size(); + } else { + this.animators.get(this.startedAnimating).animate(reverse); + } + } + + @Override + + public boolean stop(boolean force) { + if (super.stop(force)) { + for (IAnimator animator : animators) { + animator.stop(force); + } + return true; + } + return false; + } + + @Override + public void reset(boolean atEnd) { + super.reset(atEnd); + this.startedAnimating = 0; + this.finishedAnimating = 0; + for (IAnimator animator : animators) { + animator.reset(atEnd); + } + } + + @Override + public int advance(int elapsedTime) { + int remainingTime = 0; + for (int i = 0; i < this.startedAnimating; i++) { + IAnimator animator = this.animators.get(i); + if (!animator.isAnimating()) continue; + remainingTime = Math.max(remainingTime, animator.advance(elapsedTime)); + if (!animator.isAnimating()) { + this.finishedAnimating++; + if (isFinished()) { + stop(false); + return remainingTime; + } + } + } + while (elapsedTime > 0 && this.startedAnimating < this.animators.size()) { + int prog = Math.min(elapsedTime, this.waitTimeBetweenAnimators - this.waitTime); + this.waitTime += prog; + elapsedTime -= prog; + if (this.waitTime >= this.waitTimeBetweenAnimators) { + this.animators.get(this.startedAnimating).animate(isAnimatingReverse()); + this.waitTime -= this.waitTimeBetweenAnimators; + this.startedAnimating++; + } + } + return Math.min(elapsedTime, remainingTime); + } + + public boolean isFinished() { + return this.finishedAnimating == this.animators.size(); + } + + @Override + public boolean hasProgressed() { + return isAnimating() && this.startedAnimating > 0; + } + + public ParallelAnimator waitTimeBetweenAnimators(int waitTime) { + this.waitTimeBetweenAnimators = waitTime; + return this; + } + + @Override + public ParallelAnimator inParallelWith(IAnimator animator) { + if (isAnimating()) { + throw new IllegalStateException("Can't add animators while animating"); + } + reset(); + this.animators.add(animator); + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/SequentialAnimator.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/SequentialAnimator.java new file mode 100644 index 00000000000..00c7ba144ed --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/SequentialAnimator.java @@ -0,0 +1,88 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SequentialAnimator extends BaseAnimator implements IAnimator { + + private final List animators; + private int currentIndex = 0; + + public SequentialAnimator(List animators) { + this.animators = new ArrayList<>(animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + public SequentialAnimator(IAnimator... animators) { + this.animators = new ArrayList<>(); + Collections.addAll(this.animators, animators); + this.animators.forEach(animator -> { + if (animator instanceof BaseAnimator baseAnimator) { + baseAnimator.setParent(this); + } + }); + } + + @Override + public void animate(boolean reverse) { + if (this.animators.isEmpty()) return; + super.animate(reverse); + // start first animation + this.animators.get(this.currentIndex).animate(reverse); + } + + @Override + public void reset(boolean atEnd) { + super.reset(atEnd); + this.currentIndex = atEnd ? this.animators.size() - 1 : 0; + this.animators.forEach(animator -> animator.reset(atEnd)); + } + + @Override + public void resume(boolean reverse) { + super.resume(reverse); + this.animators.get(this.currentIndex).resume(reverse); + } + + @Override + public int advance(int elapsedTime) { + while (isAnimating() && elapsedTime > 0) { + IAnimator animator = this.animators.get(currentIndex); + elapsedTime = animator.advance(elapsedTime); + if (!animator.isAnimating()) { + // animator has finished + this.currentIndex += getDirection(); + // GTCEu.LOGGER.info("Finished {}th animator", this.currentIndex); + if (this.currentIndex >= this.animators.size() || this.currentIndex < 0) { + // whole sequence has finished + stop(false); + } else { + // start next animation + animator = this.animators.get(this.currentIndex); + animator.animate(isAnimatingReverse()); + } + } + } + return elapsedTime; + } + + @Override + public boolean hasProgressed() { + return !this.animators.isEmpty() && this.animators.get(0).hasProgressed(); + } + + @Override + public SequentialAnimator followedBy(IAnimator animator) { + if (isAnimating()) { + throw new IllegalStateException("Can't add animators while animating"); + } + reset(); + this.animators.add(animator); + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Wait.java b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Wait.java new file mode 100644 index 00000000000..787e863c29a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/animation/Wait.java @@ -0,0 +1,41 @@ +package com.gregtechceu.gtceu.api.mui.animation; + +import lombok.Setter; +import lombok.experimental.Accessors; + +@Accessors(fluent = true, chain = true) +public class Wait extends BaseAnimator { + + @Setter + private int duration; + private int progress = 0; + + public Wait() { + this(250); + } + + public Wait(int duration) { + this.duration = duration; + } + + @Override + public void reset(boolean atEnd) { + this.progress = 0; + } + + @Override + public int advance(int elapsedTime) { + int max = this.duration - this.progress; + int prog = Math.min(max, elapsedTime); + this.progress += prog; + if (this.progress >= this.duration) { + stop(false); + } + return elapsedTime - prog; + } + + @Override + public boolean hasProgressed() { + return progress > 0 && isAnimating(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/GuiAxis.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/GuiAxis.java new file mode 100644 index 00000000000..df57956d26e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/GuiAxis.java @@ -0,0 +1,19 @@ +package com.gregtechceu.gtceu.api.mui.base; + +public enum GuiAxis { + + X, + Y; + + public boolean isHorizontal() { + return this == X; + } + + public boolean isVertical() { + return this == Y; + } + + public GuiAxis getOther() { + return this == X ? Y : X; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IItemUIHolder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IItemUIHolder.java new file mode 100644 index 00000000000..882d174f09c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IItemUIHolder.java @@ -0,0 +1,39 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.item.component.IInteractionItem; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryGuiData; +import com.gregtechceu.gtceu.api.mui.factory.PlayerInventoryUIFactory; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; + +public interface IItemUIHolder extends IUIHolder>, IInteractionItem { + + default boolean shouldOpenUI() { + return true; + } + + @Override + default InteractionResultHolder use(Item item, Level level, Player player, InteractionHand usedHand) { + if (!shouldOpenUI()) + return IInteractionItem.super.use(item, level, player, usedHand); + if (level.isClientSide) + PlayerInventoryUIFactory.INSTANCE.openFromHandClient(usedHand); + return InteractionResultHolder.sidedSuccess(player.getItemInHand(usedHand), level.isClientSide); + } + + @Override + default InteractionResult useOn(UseOnContext context) { + if (!shouldOpenUI()) + return IInteractionItem.super.useOn(context); + if (context.getLevel().isClientSide) + PlayerInventoryUIFactory.INSTANCE.openFromHandClient(context.getHand()); + return InteractionResult.SUCCESS; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IJsonSerializable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IJsonSerializable.java new file mode 100644 index 00000000000..2782f238e76 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IJsonSerializable.java @@ -0,0 +1,48 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.google.gson.JsonObject; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.JsonOps; +import org.jetbrains.annotations.ApiStatus; + +public interface IJsonSerializable> { + + /** + * Override this + * + * @return the codec to serialize this object with + */ + // TODO actually implement on subclasses + @ApiStatus.OverrideOnly + default Codec getCodec() { + return Codec.PASSTHROUGH.flatComapMap(dynamic -> { + loadFromJson(dynamic.cast(JsonOps.INSTANCE).getAsJsonObject()); + return (T) this; + }, object -> { + JsonObject jsonObject = new JsonObject(); + if (saveToJson(jsonObject)) { + return DataResult.success(new Dynamic<>(JsonOps.INSTANCE, jsonObject)); + } + return DataResult.error(() -> "Failed to serialize drawable %s".formatted(object)); + }); + } + + /** + * Reads extra json data after this drawable is created. + * + * @param json json to read from + */ + default void loadFromJson(JsonObject json) {} + + /** + * Writes all json data necessary so that deserializing it results in the same drawable. + * + * @param json json to write to + * @return if the drawable was serialized + */ + default boolean saveToJson(JsonObject json) { + return false; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMathValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMathValue.java new file mode 100644 index 00000000000..600f53a5dc1 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMathValue.java @@ -0,0 +1,36 @@ +package com.gregtechceu.gtceu.api.mui.base; + +/** + * Math value interface + *

+ * This interface provides only one method which is used by all + * mathematical related classes. The point of this interface is to + * provide generalized abstract method for computing/fetching some value + * from different mathematical classes. + */ +public interface IMathValue { + + /** + * Get computed or stored value + */ + IMathValue get(); + + boolean isNumber(); + + void set(double value); + + void set(String value); + + double doubleValue(); + + boolean booleanValue(); + + String stringValue(); + + class EvaluateException extends RuntimeException { + + public EvaluateException(String message) { + super(message); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMuiScreen.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMuiScreen.java new file mode 100644 index 00000000000..b9b7f8eb015 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IMuiScreen.java @@ -0,0 +1,105 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.utils.Rectangle; +import com.gregtechceu.gtceu.client.mui.screen.ClientScreenHandler; +import com.gregtechceu.gtceu.client.mui.screen.ContainerScreenWrapper; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.ScreenWrapper; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; +import com.gregtechceu.gtceu.core.mixins.client.AbstractContainerScreenAccessor; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.world.inventory.Slot; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Implement this interface on a {@link Screen} to be able to use it as a custom wrapper. + * The Screen should have final {@link ModularScreen} field, which is set from the constructor. + * Additionally, the Screen MUST call {@link ModularScreen#construct(IMuiScreen)} in its constructor. + * See {@link ScreenWrapper ScreenWrapper} and {@link ContainerScreenWrapper GuiContainerWrapper} + * for default implementations. + */ +@OnlyIn(Dist.CLIENT) +public interface IMuiScreen { + + /** + * Returns the {@link ModularScreen} that is being wrapped. This should return a final instance field. + * + * @return the wrapped modular screen + */ + @NotNull + ModularScreen getScreen(); + + /** + * This method decides how the gui background is drawn. + * The intended usage is to override {@link Screen#renderBackground(GuiGraphics)} and call this method + * with the super method reference as the second parameter. + * + * @param guiGraphics this screen's {@link GuiGraphics} instance + * @param drawFunction a method reference to draw the world background normally with the + * {@code guiGraphics} as the parameter + */ + @ApiStatus.NonExtendable + default void handleDrawBackground(GuiGraphics guiGraphics, Consumer drawFunction) { + if (ClientScreenHandler.shouldDrawWorldBackground()) { + drawFunction.accept(guiGraphics); + } + ClientScreenHandler.drawDarkBackground(getWrappedScreen(), guiGraphics); + } + + /** + * This method is called every time the {@link ModularScreen} resizes. + * This usually only affects {@link AbstractContainerScreen AbstractContainerScreens}. + * + * @param area area of the main panel + */ + default void updateGuiArea(Rectangle area) { + if (getWrappedScreen() instanceof AbstractContainerScreenAccessor acc) { + acc.setLeftPos(area.x); + acc.setTopPos(area.y); + acc.setImageWidth(area.width); + acc.setImageHeight(area.height); + } + } + + /** + * @return if this wrapper is a {@link AbstractContainerScreen} + */ + @ApiStatus.NonExtendable + default boolean isGuiContainer() { + return getWrappedScreen() instanceof AbstractContainerScreen; + } + + /** + * Hovering widget is handled by {@link ModularGuiContext}. + * If it detects a slot, this method is called. Only affects {@link AbstractContainerScreen + * AbstractContainerScreens}. + * + * @param slot hovered slot + */ + @ApiStatus.NonExtendable + default void setHoveredSlot(Slot slot) { + if (getWrappedScreen() instanceof AbstractContainerScreenAccessor acc) { + acc.setHoveredSlot(slot); + } + } + + /** + * Returns the {@link Screen} that wraps the {@link ModularScreen}. + * In most cases this does not need to be overridden as this interfaces should be implemented on {@link Screen + * Screens}. + * + * @return the wrapping gui screen + */ + default Screen getWrappedScreen() { + return (Screen) this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPacketWriter.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPacketWriter.java new file mode 100644 index 00000000000..d1e92c68f13 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPacketWriter.java @@ -0,0 +1,24 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import net.minecraft.network.FriendlyByteBuf; + +import io.netty.buffer.Unpooled; + +/** + * A function that can write any data to an {@link FriendlyByteBuf}. + */ +public interface IPacketWriter { + + /** + * Writes any data to a packet buffer + * + * @param buffer buffer to write to + */ + void write(FriendlyByteBuf buffer); + + default FriendlyByteBuf toPacket() { + FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer()); + write(buffer); + return buffer; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPanelHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPanelHandler.java new file mode 100644 index 00000000000..776900d4941 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IPanelHandler.java @@ -0,0 +1,104 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.value.sync.ItemSlotSyncHandler; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncHandler; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.SecondaryPanel; + +import org.jetbrains.annotations.ApiStatus; + +/** + * This class can handle opening and closing of a {@link ModularPanel}. It makes sure, that the same panel is not + * created multiple + * times and instead reused. + *

+ * Using {@link #openPanel()} is the only way to open multiple panels. + *

+ *

+ * Panels can be closed with {@link #closePanel()}, but also with {@link ModularPanel#closeIfOpen()}. + * With the difference, that the method from this interface also works on server + * side. + *

+ * Synced panels must be created with + * {@link PanelSyncManager#syncedPanel(String, boolean, PanelSyncHandler.IPanelBuilder)}. + * If the panel does not contain any synced widgets, a simple panel handler using + * {@link #simple(ModularPanel, SecondaryPanel.IPanelBuilder, boolean)} + * is likely what you need. + */ +@ApiStatus.NonExtendable +public interface IPanelHandler { + + /** + * Creates a non synced panel handler. Trying to use synced values anyway will result in a crash. + * It only works on client side. Doing anything with it on server side might result in a crash. + * + * @param parent an existing parent panel of the gui + * @param provider the panel builder, that will create the new panel. It must not return null or the main panel. + * @param subPanel true if this panel should close when its parent closes (the parent is defined by the first + * parameter) + * @return a simple panel handler. + * @throws NullPointerException if the build panel of the builder is null + * @throws IllegalArgumentException if the build panel of the builder is the main panel or there are synced values + * in the panel + */ + static IPanelHandler simple(ModularPanel parent, SecondaryPanel.IPanelBuilder provider, boolean subPanel) { + return new SecondaryPanel(parent, provider, subPanel); + } + + boolean isPanelOpen(); + + /** + * Opens the panel. If there is no cached panel, one will be created. + * Can be called on both sides if this handler is synced. + */ + void openPanel(); + + /** + * Initiates the closing animation if the panel is open. + * Can be called on both sides if this handler is synced. + */ + void closePanel(); + + /** + * Initiates the closing animation of all sub panels. + * Usually for internal use. + */ + void closeSubPanels(); + + /** + * Called internally after the panel is closed. + */ + @ApiStatus.OverrideOnly + void closePanelInternal(); + + /** + * Toggles this panel open or closed. Delegates to {@link #openPanel()} and {@link #closePanel()}. + * + * @return {@code true} if the panel was opened, {@code false} if it was closed + */ + default boolean togglePanel() { + if (isPanelOpen()) { + closePanel(); + return false; + } else { + openPanel(); + return true; + } + } + + /** + * Deletes the current cached panel. Should not be used frequently. + * This only works on panels which don't have {@link ItemSlotSyncHandler} sync handlers. + * + * @throws UnsupportedOperationException if this handler has ItemSlot sync handlers + */ + void deleteCachedPanel(); + + /** + * If this is a sub panel of another panel. A sub panel will be closed when its parent is closed. + * + * @return true if this is a sub panel + */ + boolean isSubPanel(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/ISyncedAction.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ISyncedAction.java new file mode 100644 index 00000000000..2ded1a87fb7 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ISyncedAction.java @@ -0,0 +1,13 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import net.minecraft.network.FriendlyByteBuf; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@FunctionalInterface +public interface ISyncedAction { + + @ApiStatus.OverrideOnly + void invoke(@NotNull FriendlyByteBuf packet); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITheme.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITheme.java new file mode 100644 index 00000000000..cf2b4c135a7 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITheme.java @@ -0,0 +1,59 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.theme.*; + +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.Collection; + +/** + * A theme is parsed from json and contains style information like color or background texture. + */ +public interface ITheme { + + /** + * @return the master default theme. + */ + static ITheme getDefault() { + return IThemeApi.get().getDefaultTheme(); + } + + /** + * @param id theme id + * @return theme with given id + */ + static ITheme get(String id) { + return IThemeApi.get().getTheme(id); + } + + /** + * @return theme id + */ + String getId(); + + /** + * @return parent theme + */ + ITheme getParentTheme(); + + @UnmodifiableView + Collection> getWidgetThemes(); + + WidgetThemeEntry getFallback(); + + WidgetThemeEntry getPanelTheme(); + + WidgetThemeEntry getButtonTheme(); + + WidgetThemeEntry getScrollbarTheme(); + + WidgetThemeEntry getItemSlotTheme(); + + WidgetThemeEntry getFluidSlotTheme(); + + WidgetThemeEntry getTextFieldTheme(); + + WidgetThemeEntry getToggleButtonTheme(); + + WidgetThemeEntry getWidgetTheme(WidgetThemeKey key); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java new file mode 100644 index 00000000000..1f5a6fe2c50 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IThemeApi.java @@ -0,0 +1,232 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.drawable.Scrollbar; +import com.gregtechceu.gtceu.api.mui.theme.*; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.common.mui.GTGuiTextures; +import com.gregtechceu.gtceu.utils.serialization.json.JsonBuilder; + +import org.jetbrains.annotations.*; + +import java.util.List; + +/** + * An API interface for Themes. + */ +@ApiStatus.NonExtendable +public interface IThemeApi { + + // widget themes + WidgetThemeKey FALLBACK = get().widgetThemeKeyBuilder("default", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(18, 18, null)) + .register(); + + WidgetThemeKey PANEL = get().widgetThemeKeyBuilder("panel", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(176, 166, GTGuiTextures.BACKGROUND)) + .register(); + + WidgetThemeKey BUTTON = get().widgetThemeKeyBuilder("button", WidgetTheme.class) + .defaultTheme(WidgetTheme.whiteTextShadow(18, 18, GTGuiTextures.MC_BUTTON)) + .defaultHoverTheme(WidgetTheme.whiteTextShadow(18, 18, GTGuiTextures.MC_BUTTON_HOVERED)) + .register(); + + WidgetThemeKey CLOSE_BUTTON = get().widgetThemeKeyBuilder("closeButton", WidgetTheme.class) + .defaultTheme(WidgetTheme.whiteTextShadow(10, 10, GTGuiTextures.MC_BUTTON)) + .defaultHoverTheme(WidgetTheme.whiteTextShadow(10, 10, GTGuiTextures.MC_BUTTON_HOVERED)) + .register(); + + WidgetThemeKey SCROLLBAR = get().widgetThemeKeyBuilder("scrollbar", WidgetTheme.class) + .defaultTheme(WidgetTheme.darkTextNoShadow(4, 4, Scrollbar.VANILLA)) + .register(); + + WidgetThemeKey ITEM_SLOT = get().widgetThemeKeyBuilder("itemSlot", SlotTheme.class) + .defaultTheme(new SlotTheme(GTGuiTextures.SLOT)) + .register(); + + WidgetThemeKey FLUID_SLOT = get().widgetThemeKeyBuilder("fluidSlot", SlotTheme.class) + .defaultTheme(new SlotTheme(GTGuiTextures.FLUID_SLOT)) + .register(); + + WidgetThemeKey TEXT_FIELD = get().widgetThemeKeyBuilder("textField", TextFieldTheme.class) + .defaultTheme(new TextFieldTheme(0xFF2F72A8, 0xFF5F5F5F)) + .register(); + + WidgetThemeKey TOGGLE_BUTTON = get().widgetThemeKeyBuilder("toggleButton", SelectableTheme.class) + .defaultTheme( + SelectableTheme.whiteTextShadow(18, 18, GTGuiTextures.MC_BUTTON, GTGuiTextures.MC_BUTTON_DISABLED)) + .defaultHoverTheme(SelectableTheme.whiteTextShadow(18, 18, GTGuiTextures.MC_BUTTON_HOVERED, + IDrawable.NONE)) + .register(); + + // subwidget themes + WidgetThemeKey ITEM_SLOT_PLAYER = ITEM_SLOT.createSubKey("player"); + WidgetThemeKey ITEM_SLOT_PLAYER_HOTBAR = ITEM_SLOT_PLAYER.createSubKey("playerHotbar"); + WidgetThemeKey ITEM_SLOT_PLAYER_MAIN_INV = ITEM_SLOT_PLAYER.createSubKey("playerMainInventory"); + WidgetThemeKey ITEM_SLOT_PLAYER_OFFHAND = ITEM_SLOT_PLAYER.createSubKey("playerOffhand"); + WidgetThemeKey ITEM_SLOT_PLAYER_ARMOR = ITEM_SLOT_PLAYER.createSubKey("playerArmor"); + + String HOVER_SUFFIX = ":hover"; + + // properties + String PARENT = "parent"; + String DEFAULT_WIDTH = "defaultWidth"; + String DEFAULT_HEIGHT = "defaultHeight"; + String BACKGROUND = "background"; + String HOVER_BACKGROUND = "hoverBackground"; + String COLOR = "color"; + String TEXT_COLOR = "textColor"; + String TEXT_SHADOW = "textShadow"; + String ICON_COLOR = "iconColor"; + String SLOT_HOVER_COLOR = "slotHoverColor"; + String MARKED_COLOR = "markedColor"; + String HINT_COLOR = "hintColor"; + String SELECTED_BACKGROUND = "selectedBackground"; + String SELECTED_COLOR = "selectedColor"; + String SELECTED_TEXT_COLOR = "selectedTextColor"; + String SELECTED_TEXT_SHADOW = "selectedTextShadow"; + String SELECTED_ICON_COLOR = "selectedIconColor"; + + /** + * @return the default api implementation + */ + @Contract(pure = true) + static IThemeApi get() { + return ThemeAPI.INSTANCE; + } + + /** + * @return the absolute fallback theme + */ + ITheme getDefaultTheme(); + + /** + * Finds a theme for an id + * + * @param id id of the theme + * @return the found theme or {@link #getDefaultTheme()} if no theme was found + */ + @NotNull + ITheme getTheme(String id); + + /** + * @param id id of the theme + * @return if a theme with the id is registered + */ + boolean hasTheme(String id); + + /** + * Registers a theme json object. Themes from resource packs always have greater priority. + * Json builders are used here as they are much easier to merge as opposed to normal java objects. + * + * @param id id of the theme + * @param json theme data + */ + void registerTheme(String id, JsonBuilder json); + + /** + * Registers a theme json object. Themes from resource packs always have greater priority. + * + * @param themeBuilder theme data + */ + default void registerTheme(ThemeBuilder themeBuilder) { + registerTheme(themeBuilder.getId(), themeBuilder); + } + + /** + * Gets all currently from java side registered theme json's for a theme. + * + * @param id id of the theme + * @return all theme json's for a theme. + */ + List getJavaDefaultThemes(String id); + + /** + * Gets the appropriate theme for a screen. + * + * @param owner owner of the screen + * @param name name of the screen + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + default ITheme getThemeForScreen(String owner, String name, @Nullable String defaultTheme, + @Nullable String fallbackTheme) { + return getThemeForScreen(owner, name, null, defaultTheme, fallbackTheme); + } + + /** + * Gets the appropriate theme for a screen. + * + * @param owner owner of the screen + * @param name name of the screen + * @param panel the name + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + ITheme getThemeForScreen(String owner, String name, @Nullable String panel, @Nullable String defaultTheme, + @Nullable String fallbackTheme); + + /** + * Gets the appropriate theme for a specific panel. + * + * @param panel the panel to find a theme for + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + default ITheme getThemeForScreen(ModularPanel panel, @Nullable String defaultTheme) { + ModularScreen screen = panel.getScreen(); + return getThemeForScreen(screen.getOwner(), screen.getName(), panel.getName(), defaultTheme, + screen.getThemeOverride()); + } + + /** + * Gets the appropriate theme for a screen. + * + * @param screen screen + * @param defaultTheme default theme if no theme was found + * @return the registered theme for the given screen or the given default theme or {@link #getDefaultTheme()} + */ + default ITheme getThemeForScreen(ModularScreen screen, @Nullable String defaultTheme) { + return getThemeForScreen(screen.getOwner(), screen.getName(), defaultTheme, null); + } + + /** + * Registers a theme for a screen. Themes from resource packs always have greater priority. + * + * @param owner owner of the screen + * @param name name of the screen + * @param theme theme to register + */ + default void registerThemeForScreen(String owner, String name, String theme) { + registerThemeForScreen(owner + ":" + name, theme); + } + + /** + * Registers a theme for a screen. Themes from resource packs always have greater priority. + * + * @param screen full screen id + * @param theme theme to register + */ + void registerThemeForScreen(String screen, String theme); + + /** + * Registers a widget theme. It is recommended to store the resulting key in a static variable and make it + * accessible by public methods. + * + * @param id id of the widget theme + * @param defaultTheme the fallback widget theme + * @param defaultHoverTheme the fallback hover widget theme + * @param parser the widget theme json parser function. This is usually another constructor. + * @return key to access the widget theme + */ + WidgetThemeKey registerWidgetTheme(String id, T defaultTheme, T defaultHoverTheme, + WidgetThemeParser parser); + + default WidgetThemeKeyBuilder widgetThemeKeyBuilder(String id, Class type) { + return new WidgetThemeKeyBuilder<>(id); + } + + @UnmodifiableView + List> getWidgetThemeKeys(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java new file mode 100644 index 00000000000..734788304e9 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/ITreeNode.java @@ -0,0 +1,18 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import java.util.List; + +public interface ITreeNode> { + + T getParent(); + + default boolean hasParent() { + return getParent() != null; + } + + List getChildren(); + + default boolean hasChildren() { + return !getChildren().isEmpty(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java new file mode 100644 index 00000000000..ce498c5f101 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/IUIHolder.java @@ -0,0 +1,47 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.UISettings; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +/** + * An interface to implement on {@link net.minecraft.world.level.block.entity.BlockEntity} or + * {@link net.minecraft.world.item.Item}. + */ +@FunctionalInterface +public interface IUIHolder { + + /** + * Only called on client side. + * + * @param data information about the creation context + * @param mainPanel the panel created in {@link #buildUI(GuiData, PanelSyncManager, UISettings)} + * @return a modular screen instance with the given panel + */ + @OnlyIn(Dist.CLIENT) + default ModularScreen createScreen(T data, ModularPanel mainPanel) { + GTCEu.LOGGER + .warn("IGuiHolder.createScreen() should be overridden to pass your own mod id to the ModularScreen." + + " In future versions this method must be overridden or else it will crash!"); + return new ModularScreen(GTCEu.MOD_ID, mainPanel); + } + + /** + * Called on server and client. Create only the main panel here. Only here you can add sync handlers to widgets + * directly. + * If the widget to be synced is not in this panel yet (f.e. in another panel) the sync handler must be registered + * here + * with {@link PanelSyncManager}. + * + * @param data information about the creation context + * @param syncManager sync handler where widget sync handlers should be registered + * @param settings settings which apply to the whole ui and not just this panel + */ + ModularPanel buildUI(T data, PanelSyncManager syncManager, UISettings settings); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/MCHelper.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/MCHelper.java new file mode 100644 index 00000000000..e6ef11fb86e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/MCHelper.java @@ -0,0 +1,74 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.common.network.ModularNetwork; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +import java.util.List; + +public class MCHelper { + + @SideOnly(Side.CLIENT) + public static Minecraft getMc() { + return Minecraft.getInstance(); + } + + @SideOnly(Side.CLIENT) + public static Player getPlayer() { + return getMc().player; + } + + @SideOnly(Side.CLIENT) + public static boolean closeScreen() { + getMc().popGuiLayer(); + return false; + } + + @SideOnly(Side.CLIENT) + public static void popScreen(boolean openParentOnClose, Screen parent) { + Player player = MCHelper.getPlayer(); + if (player != null) { + // container should not just be closed here + // instead they are kept in a stack until all screens are closed + // prepareCloseContainer(player); + if (openParentOnClose) { + Minecraft.getInstance().setScreen(parent); + ModularNetwork.CLIENT.reopenSyncerOf(parent); + } else { + Minecraft.getInstance().setScreen(null); + } + } else { + // we are currently not in a world and want to display the previous screen + Minecraft.getInstance().setScreen(parent); + } + } + + public static void setScreen(Screen screen) { + if (screen == null) { + closeScreen(); + } else { + getMc().setScreen(screen); + } + } + + @SideOnly(Side.CLIENT) + public static Screen getCurrentScreen() { + return getMc().screen; + } + + @SideOnly(Side.CLIENT) + public static Font getFont() { + return getMc().font; + } + + public static List getItemToolTip(ItemStack item) { + return Screen.getTooltipFromItem(getMc(), item); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/RecipeViewerSettings.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/RecipeViewerSettings.java new file mode 100644 index 00000000000..bc644585695 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/RecipeViewerSettings.java @@ -0,0 +1,122 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.Rectangle; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.integration.recipeviewer.handlers.GhostIngredientSlot; + +import org.jetbrains.annotations.ApiStatus; + +/** + * Keeps track of everything related to JEI in a Modular GUI. + * By default, JEI is disabled in client only GUIs. + * This class can be safely interacted with even when JEI/HEI is not installed. + */ +@ApiStatus.NonExtendable +public interface RecipeViewerSettings { + + /** + * Force recipe viewer to be enabled + */ + void enable(); + + /** + * Force recipe viewer to be disabled + */ + void disable(); + + /** + * Only enable the recipe viewer in synced GUIs + */ + void defaultState(); + + /** + * Checks if the recipe viewer is enabled for a given screen + * + * @param screen modular screen + * @return true if the recipe viewer is enabled + */ + boolean isEnabled(ModularScreen screen); + + /** + * Adds an exclusion zone. Recipe viewers will always try to avoid exclusion zones.
+ * If a widgets wishes to have an exclusion zone it should use {@link #addExclusionArea(IWidget)}! + * + * @param area exclusion area + */ + void addExclusionArea(Rectangle area); + + /** + * Removes an exclusion zone. + * + * @param area exclusion area to remove (must be the same instance) + */ + void removeExclusionArea(Rectangle area); + + /** + * Adds an exclusion zone of a widget. Recipe viewers will always try to avoid exclusion zones.
+ * Useful when a widget is outside its panel. + * + * @param area widget + */ + void addExclusionArea(IWidget area); + + /** + * Removes a widget exclusion area. + * + * @param area widget + */ + void removeExclusionArea(IWidget area); + + /** + * Adds a recipe viewer ghost slot. Ghost slots can display an ingredient, but the ingredient does not really exist. + * By calling this method users will be able to drag ingredients from recipe viewers into the slot. + * + * @param slot slot widget + * @param slot widget type + */ + > void addGhostIngredientSlot(W slot); + + /** + * Removes a recipe viewer ghost slot. + * + * @param slot slot widget + * @param slot widget type + */ + > void removeGhostIngredientSlot(W slot); + + RecipeViewerSettings DUMMY = new RecipeViewerSettings() { + + @Override + public void enable() {} + + @Override + public void disable() {} + + @Override + public void defaultState() {} + + @Override + public boolean isEnabled(ModularScreen screen) { + return false; + } + + @Override + public void addExclusionArea(Rectangle area) {} + + @Override + public void removeExclusionArea(Rectangle area) {} + + @Override + public void addExclusionArea(IWidget area) {} + + @Override + public void removeExclusionArea(IWidget area) {} + + @Override + public > void addGhostIngredientSlot(W slot) {} + + @Override + public > void removeGhostIngredientSlot(W slot) {} + }; +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/UIFactory.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/UIFactory.java new file mode 100644 index 00000000000..0d58fc06be3 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/UIFactory.java @@ -0,0 +1,114 @@ +package com.gregtechceu.gtceu.api.mui.base; + +import com.gregtechceu.gtceu.api.mui.factory.GuiData; +import com.gregtechceu.gtceu.api.mui.value.sync.PanelSyncManager; +import com.gregtechceu.gtceu.client.mui.screen.*; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * An interface for UI factories. They are responsible for opening synced GUIs and syncing necessary data. + * + * @param gui data type + */ +public interface UIFactory { + + /** + * The name of this factory. Must be constant. + * + * @return the factory name + */ + @NotNull + ResourceLocation getFactoryName(); + + /** + * Creates the main panel for the GUI. Is called on client and server side. + * + * @param guiData gui data + * @param syncManager sync manager + * @param settings ui settings + * @return new main panel + */ + @ApiStatus.OverrideOnly + ModularPanel createPanel(D guiData, PanelSyncManager syncManager, UISettings settings); + + /** + * Creates the screen for the GUI. Is only called on client side. + * + * @param guiData gui data + * @param mainPanel main panel created in {@link #createPanel(GuiData, PanelSyncManager, UISettings)} + * @return new main panel + */ + @OnlyIn(Dist.CLIENT) + @ApiStatus.OverrideOnly + ModularScreen createScreen(D guiData, ModularPanel mainPanel); + + /** + * Creates the screen wrapper for the GUI. Is only called on client side. + * + * @param container container for the gui + * @param screen the screen which was created in {@link #createScreen(GuiData, ModularPanel)} + * @return new screen wrapper + * @throws IllegalStateException if the wrapping screen is not a + * {@link AbstractContainerMenu AbstractContainerMenu} + * or if the container inside is not the same as the one passed to this method. + * This method is not the thrower, but the caller of this method. + */ + @OnlyIn(Dist.CLIENT) + @ApiStatus.OverrideOnly + default IMuiScreen createScreenWrapper(ModularContainerMenu container, ModularScreen screen) { + return new ContainerScreenWrapper(container, screen); + } + + /** + * The default container supplier. This is called when no custom container in {@link UISettings} is set. + * + * @return new container instance + */ + default ModularContainerMenu createContainer(int containerId) { + return new ModularContainerMenu(containerId); + } + + /** + * A default function to check if the current interacting player can interact with the ui. If not overridden on + * {@link UISettings}, + * then this is called every tick while a UI opened by this factory is open. Once this function returns false, the + * UI is immediately + * closed. + * + * @param player current interacting player + * @param guiData gui data of the current ui + * @return if the player can interact with the player. + */ + default boolean canInteractWith(Player player, D guiData) { + return player == guiData.getPlayer(); + } + + /** + * Writes the gui data to a buffer. + * + * @param guiData gui data + * @param buffer buffer + */ + @ApiStatus.OverrideOnly + void writeGuiData(D guiData, FriendlyByteBuf buffer); + + /** + * Reads and creates the gui data from the buffer. + * + * @param player player + * @param buffer buffer + * @return new gui data + */ + @NotNull + @ApiStatus.OverrideOnly + D readGuiData(Player player, FriendlyByteBuf buffer); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IDrawable.java new file mode 100644 index 00000000000..052c7ebce4d --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IDrawable.java @@ -0,0 +1,219 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.api.mui.drawable.DrawableStack; +import com.gregtechceu.gtceu.api.mui.drawable.Icon; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import org.jetbrains.annotations.Nullable; + +/** + * An object which can be drawn at any size. This is mainly used for backgrounds and overlays in + * {@link com.gregtechceu.gtceu.api.mui.base.widget.IWidget}. + * To draw at a fixed size, use {@link IIcon} (see {@link #asIcon()}). + */ +public interface IDrawable { + + static IDrawable of(IDrawable... drawables) { + if (drawables == null || drawables.length == 0) { + return null; + } else if (drawables.length == 1) { + return drawables[0]; + } else { + return new DrawableStack(drawables); + } + } + + /** + * Draws this drawable at the given position with the given size. It's the implementors responsibility to properly + * apply the widget theme by calling {@link #applyColor(int)} before drawing. + * + * @param context current context to draw with + * @param x x position + * @param y y position + * @param width draw width + * @param height draw height + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme); + + /** + * Draws this drawable at the current (0|0) with the given size. This is useful inside widgets since GL is + * transformed to their position when they are drawing. + * + * @param context gui context + * @param width draw width + * @param height draw height + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + default void drawAtZero(GuiContext context, int width, int height, WidgetTheme widgetTheme) { + draw(context, 0, 0, width, height, widgetTheme); + } + + /** + * Draws this drawable in a given area. The padding of the area is not applied here. + * + * @param context current context to draw with + * @param area draw area + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + default void draw(GuiContext context, Area area, WidgetTheme widgetTheme) { + draw(context, area.x, area.y, area.width, + area.height, widgetTheme); + } + + /** + * Draws this drawable in a given area with its padding applied. + * + * @param context current context to draw with + * @param area draw area + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + default void drawPadded(GuiContext context, Area area, WidgetTheme widgetTheme) { + draw(context, area.x + area.getPadding().left(), area.y + area.getPadding().top(), + area.paddedWidth(), area.paddedHeight(), widgetTheme); + } + + /** + * Draws this drawable at the current (0|0) with the given area's size. This is useful inside widgets since GL is + * transformed to their position when they are drawing. The padding of the area is not applied here. + * + * @param context gui context + * @param area draw area + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + default void drawAtZero(GuiContext context, Area area, WidgetTheme widgetTheme) { + draw(context, 0, 0, area.width, area.height, widgetTheme); + } + + /** + * Draws this drawable at the current (0|0) with the given area's size and its padding applied + * (this means its technically not at 0|0). This is useful inside widgets since GL is transformed to their position + * when they are drawing. + * + * @param context gui context + * @param area draw area + * @param widgetTheme current theme + */ + @OnlyIn(Dist.CLIENT) + default void drawAtZeroPadded(GuiContext context, Area area, WidgetTheme widgetTheme) { + draw(context, area.getPadding().left(), area.getPadding().top(), area.paddedWidth(), area.paddedHeight(), + widgetTheme); + } + + /** + * @return if theme color can be applied on this drawable + */ + default boolean canApplyTheme() { + return false; + } + + /** + * Applies the theme color to OpenGL if this drawable can have theme colors applied. This is determined by + * {@link #canApplyTheme()}. + * If this drawable does not allow theme colors, it will reset the current color (to white). + * This method should be called before drawing. + * + * @param themeColor theme color to apply (usually {@link WidgetTheme#getColor()}) + */ + default void applyColor(int themeColor) { + if (canApplyTheme()) { + Color.setGlColor(themeColor); + } else { + Color.setGlColorOpaque(Color.WHITE.main); + } + } + + default int getDefaultWidth() { + return 0; + } + + default int getDefaultHeight() { + return 0; + } + + /** + * @return a widget with this drawable as a background + */ + default Widget asWidget() { + return new DrawableWidget(this); + } + + /** + * @return this drawable as an icon + */ + default Icon asIcon() { + return new Icon(this).size(getDefaultWidth(), getDefaultHeight()); + } + + /** + * An empty drawable. Does nothing. + */ + IDrawable EMPTY = (context, x, y, width, height, widgetTheme) -> {}; + + /** + * An empty drawable used to mark hover textures as "should not be used"! + */ + IDrawable NONE = (context, x, y, width, height, widgetTheme) -> {}; + + static boolean isVisible(@Nullable IDrawable drawable) { + if (drawable == null || drawable == EMPTY || drawable == NONE) return false; + if (drawable instanceof DrawableStack array) { + return array.getDrawables().length > 0; + } + return true; + } + + /** + * A widget wrapping a drawable. The drawable is drawn between the background and the overlay. + */ + class DrawableWidget extends Widget { + + private final IDrawable drawable; + + public DrawableWidget(IDrawable drawable) { + this.drawable = drawable; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + this.drawable.drawAtZero(context, getArea(), getActiveWidgetTheme(widgetTheme, isHovering())); + } + } + + /** + * A widget wrapping multiple drawables + */ + class MultiDrawableWidget extends Widget { + + private final IDrawable[] drawables; + + public MultiDrawableWidget(IDrawable... drawables) { + this.drawables = drawables; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) { + for (IDrawable drawable : this.drawables) { + drawable.draw(context, getArea().getPadding().left(), getArea().getPadding().top(), + getArea().paddedWidth(), getArea().paddedHeight(), + getActiveWidgetTheme(widgetTheme, isHovering())); + } + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IHoverable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IHoverable.java new file mode 100644 index 00000000000..fdca45a2433 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IHoverable.java @@ -0,0 +1,38 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.api.mui.drawable.text.RichText; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * This marks an {@link IDrawable} as hoverable in a {@link RichText RichText}. This should not be + * extended in most cases instead obtain an instance by calling {@link IIcon#asHoverable()}. + */ +@ApiStatus.NonExtendable +public interface IHoverable extends IIcon { + + /** + * Called every frame this hoverable is hovered inside a + * {@link com.gregtechceu.gtceu.api.mui.drawable.text.RichText RichText}. + */ + default void onHover() {} + + @Nullable + default RichTooltip getTooltip() { + return null; + } + + /** + * An internal function to set the current rendered position. This is used to detect if this element is under the + * mouse. + */ + void setRenderedAt(int x, int y); + + /** + * @return the last area this drawable was drawn at. + */ + Area getRenderedArea(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IIcon.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IIcon.java new file mode 100644 index 00000000000..23035e1750c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IIcon.java @@ -0,0 +1,78 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.drawable.HoverableIcon; +import com.gregtechceu.gtceu.api.mui.drawable.InteractableIcon; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; + +import org.jetbrains.annotations.Nullable; + +/** + * A {@link IDrawable} with a fixed size. + */ +public interface IIcon extends IDrawable { + + /** + * @return the drawable this icon wraps or null if it doesn't wrap anything + */ + @Nullable + IDrawable getWrappedDrawable(); + + /** + * @return width of this icon or 0 if the width should be dynamic + */ + int getWidth(); + + /** + * @return height of this icon or 0 of the height should be dynamic + */ + int getHeight(); + + default int getSize(GuiAxis axis) { + return axis.isHorizontal() ? getWidth() : getHeight(); + } + + @Override + default int getDefaultWidth() { + return getWrappedDrawable() != null ? getWrappedDrawable().getDefaultWidth() : 0; + } + + @Override + default int getDefaultHeight() { + return getWrappedDrawable() != null ? getWrappedDrawable().getDefaultHeight() : 0; + } + + /** + * @return the margin of this icon. Only used if width or height is 0 + */ + Box getMargin(); + + default IDrawable getRootDrawable() { + IDrawable drawable = this; + while (drawable instanceof IIcon icon) { + drawable = icon.getWrappedDrawable(); + if (drawable == null) return icon; + } + return drawable; + } + + /** + * This returns a hoverable wrapper of this icon. This is only used in + * {@link com.gregtechceu.gtceu.api.mui.drawable.text.RichText RichText}. + * This allows this icon to have its own tooltip. + */ + default HoverableIcon asHoverable() { + return new HoverableIcon(this); + } + + /** + * This returns an interactable wrapper of this icon. This is only used in + * {@link com.gregtechceu.gtceu.api.mui.drawable.text.RichText RichText}. + * This allows this icon to be able to listen to clicks and other inputs. + */ + default InteractableIcon asInteractable() { + return new InteractableIcon(this); + } + + IIcon EMPTY_2PX = EMPTY.asIcon().height(2); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IInterpolation.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IInterpolation.java new file mode 100644 index 00000000000..d008d1ccc81 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IInterpolation.java @@ -0,0 +1,17 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +/** + * A function which interpolates between two values. + */ +public interface IInterpolation { + + /** + * Calculates a new value between a and b based on a curve. + * + * @param a start value + * @param b end value + * @param x progress (between 0.0 and 1.0) + * @return new value + */ + float interpolate(float a, float b, float x); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IKey.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IKey.java new file mode 100644 index 00000000000..7183f99f616 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IKey.java @@ -0,0 +1,329 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.drawable.text.*; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.widgets.TextWidget; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.IntSupplier; +import java.util.function.Supplier; + +/** + * This represents a piece of text in a GUI. + */ +public interface IKey extends IDrawable, IJsonSerializable { + + int TEXT_COLOR = 0xFF404040; + + TextRenderer renderer = new TextRenderer(); + + IKey EMPTY = str(""); + IKey LINE_FEED = str("\n"); + IKey SPACE = str(" "); + + // Formatting for convenience + ChatFormatting BLACK = ChatFormatting.BLACK; + ChatFormatting DARK_BLUE = ChatFormatting.DARK_BLUE; + ChatFormatting DARK_GREEN = ChatFormatting.DARK_GREEN; + ChatFormatting DARK_AQUA = ChatFormatting.DARK_AQUA; + ChatFormatting DARK_RED = ChatFormatting.DARK_RED; + ChatFormatting DARK_PURPLE = ChatFormatting.DARK_PURPLE; + ChatFormatting GOLD = ChatFormatting.GOLD; + ChatFormatting GRAY = ChatFormatting.GRAY; + ChatFormatting DARK_GRAY = ChatFormatting.DARK_GRAY; + ChatFormatting BLUE = ChatFormatting.BLUE; + ChatFormatting GREEN = ChatFormatting.GREEN; + ChatFormatting AQUA = ChatFormatting.AQUA; + ChatFormatting RED = ChatFormatting.RED; + ChatFormatting LIGHT_PURPLE = ChatFormatting.LIGHT_PURPLE; + ChatFormatting YELLOW = ChatFormatting.YELLOW; + ChatFormatting WHITE = ChatFormatting.WHITE; + ChatFormatting OBFUSCATED = ChatFormatting.OBFUSCATED; + ChatFormatting BOLD = ChatFormatting.BOLD; + ChatFormatting STRIKETHROUGH = ChatFormatting.STRIKETHROUGH; + ChatFormatting UNDERLINE = ChatFormatting.UNDERLINE; + ChatFormatting ITALIC = ChatFormatting.ITALIC; + ChatFormatting RESET = ChatFormatting.RESET; + + /** + * Creates a translated text. + * + * @param key translation key + * @return text key + */ + static IKey lang(@NotNull String key) { + return new LangKey(key); + } + + /** + * Creates a translated text. + * + * @param component translation component + * @return text key + */ + static IKey lang(@NotNull Component component) { + return new LangKey(component); + } + + /** + * Creates a translated text with arguments. The arguments can change. + * + * @param key translation key + * @param args translation arguments + * @return text key + */ + static IKey lang(@NotNull String key, @Nullable Object... args) { + return new LangKey(key, args); + } + + /** + * Creates a translated text with arguments supplier. + * + * @param key translation key + * @param argsSupplier translation arguments supplier + * @return text key + */ + static IKey lang(@NotNull String key, @NotNull Supplier argsSupplier) { + return new LangKey(key, argsSupplier); + } + + /** + * Creates a translated text. + * + * @param keySupplier translation key supplier + * @return text key + */ + static IKey lang(@NotNull Supplier keySupplier) { + return new LangKey(keySupplier); + } + + /** + * Creates a translated text with arguments supplier. + * + * @param keySupplier translation key supplier + * @param argsSupplier translation arguments supplier + * @return text key + */ + static IKey lang(@NotNull Supplier keySupplier, @NotNull Supplier argsSupplier) { + return new LangKey(keySupplier, argsSupplier); + } + + /** + * Creates a string literal text. + * + * @param key string + * @return text key + */ + static IKey str(@NotNull String key) { + return new StringKey(key); + } + + /** + * Creates a formatted string literal text with arguments. The arguments can be dynamic. + * The string is formatted using {@link String#format(String, Object...)}. + * + * @param key string + * @param args arguments + * @return text key + */ + static IKey str(@NotNull String key, @Nullable Object... args) { + return new StringKey(key, args); + } + + /** + * Creates a composed text key. + * + * @param keys text keys + * @return composed text key. + */ + static IKey comp(@NotNull IKey... keys) { + return new CompoundKey(keys); + } + + /** + * Creates a dynamic text key. + * + * @param supp string supplier + * @return dynamic text key + */ + static IKey dynamic(@NotNull Supplier<@NotNull Component> supp) { + // DO NOT PULL OUT INTO A LOCAL VAR IT WILL BREAK THE SUPPLIER + if (supp.get() instanceof MutableComponent) { + return dynamicKey(() -> IKey.lang(supp.get())); + } else { + return dynamicKey(() -> IKey.lang(supp.get().copy())); + } + } + + /** + * Creates a dynamic text key. + * + * @param supp key supplier + * @return dynamic text key + */ + static IKey dynamicKey(@NotNull Supplier<@NotNull IKey> supp) { + return new DynamicKey(supp); + } + + /** + * @return the current unformatted string + */ + MutableComponent get(); + + /** + * @param parentFormatting formatting of the parent in case of composite keys + * @return the current formatted string + */ + default MutableComponent getFormatted(@Nullable FormattingState parentFormatting) { + return get(); + } + + /** + * @return the current formatted string + */ + default MutableComponent getFormatted() { + return getFormatted(null); + } + + @OnlyIn(Dist.CLIENT) + @Override + default void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + drawAligned(context, x, y, width, height, widgetTheme, Alignment.CENTER); + } + + @OnlyIn(Dist.CLIENT) + default void drawAligned(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme, + Alignment alignment) { + renderer.setColor(widgetTheme.getTextColor()); + renderer.setShadow(widgetTheme.isTextShadow()); + renderer.setAlignment(alignment, width, height); + renderer.setScale(getScale()); + renderer.setPos(x, y); + renderer.draw(context.getGraphics(), getFormatted()); + } + + @Override + default boolean canApplyTheme() { + return true; + } + + @Override + default int getDefaultWidth() { + renderer.setAlignment(Alignment.TopLeft, -1, -1); + renderer.setScale(getScale()); + renderer.setPos(0, 0); + renderer.setSimulate(true); + renderer.draw(null, getFormatted()); + renderer.setSimulate(false); + return (int) renderer.getLastWidth(); + } + + @Override + default int getDefaultHeight() { + renderer.setAlignment(Alignment.TopLeft, -1, -1); + renderer.setScale(getScale()); + renderer.setPos(0, 0); + renderer.setSimulate(true); + renderer.draw(null, getFormatted()); + renderer.setSimulate(false); + return (int) renderer.getLastWidth(); + } + + default float getScale() { + return 1f; + } + + @Override + default TextWidget asWidget() { + return new TextWidget<>(this); + } + + default StyledText withStyle() { + return new StyledText(this); + } + + default AnimatedText withAnimation() { + return new AnimatedText(this); + } + + /** + * @return a formatting state of this key + */ + default @Nullable FormattingState getFormatting() { + return null; + } + + /** + * Set text formatting to this key. If {@link IKey#RESET} is used, then that's applied first and then all other + * formatting of this key. + * With {@code null}, you can remove a color formatting. No matter the parents color, the default color will be + * used. + * + * @param formatting a formatting rule + * @return this + */ + IKey style(@Nullable ChatFormatting formatting); + + default IKey style(ChatFormatting... formatting) { + for (ChatFormatting cf : formatting) style(cf); + return this; + } + + default IKey removeFormatColor() { + return style((ChatFormatting) null); + } + + IKey removeStyle(); + + default StyledText alignment(Alignment alignment) { + return withStyle().alignment(alignment); + } + + default @NotNull StyledText color(int color) { + return color(() -> color); + } + + default StyledText color(@Nullable IntSupplier color) { + return withStyle().color(color); + } + + default StyledText scale(float scale) { + return withStyle().scale(scale); + } + + default StyledText shadow(@Nullable Boolean shadow) { + return withStyle().shadow(shadow); + } + + default KeyIcon asTextIcon() { + return new KeyIcon(this); + } + + @Override + default void loadFromJson(JsonObject json) { + if (json.has("color") || json.has("shadow") || json.has("align") || json.has("alignment") || + json.has("scale")) { + StyledText styledText = this instanceof StyledText styledText1 ? styledText1 : withStyle(); + if (json.has("color")) { + styledText.color(JsonHelper.getInt(json, 0, "color")); + } + styledText.shadow(JsonHelper.getBoolean(json, false, "shadow")); + styledText.alignment( + JsonHelper.deserialize(json, Alignment.class, styledText.alignment(), "align", "alignment")); + styledText.scale(JsonHelper.getFloat(json, 1, "scale")); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IRichTextBuilder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IRichTextBuilder.java new file mode 100644 index 00000000000..dd60bcfcdf9 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/IRichTextBuilder.java @@ -0,0 +1,400 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.api.mui.drawable.text.Spacer; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.data.lang.LangHandler; + +import net.minecraft.network.chat.FormattedText; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.inventory.tooltip.TooltipComponent; + +import java.util.function.UnaryOperator; +import java.util.regex.Pattern; + +public interface IRichTextBuilder> { + + T getThis(); + + IRichTextBuilder getRichText(); + + /** + * Removes all text and style. + * + * @return this + */ + default T reset() { + getRichText().reset(); + return getThis(); + } + + /** + * Adds a component to the current line + * + * @param c component to add + * @return this + */ + default T add(FormattedText c) { + getRichText().add(c); + return getThis(); + } + + /** + * Adds a string to the current line + * + * @param s string to add + * @return this + */ + default T add(String s) { + getRichText().add(s); + return getThis(); + } + + /** + * Adds a drawable to the current line. If the drawable is not a {@link IIcon} it will convert to one with + * {@link IDrawable#asIcon()}. + * If that icon then has no default height (<=0) then it is set to the default text height (9 pixel). If the width + * of the icon is not set, then the width of the widest tooltip line is used. + * + * @param drawable drawable to add. + * @return this + */ + default T add(IDrawable drawable) { + getRichText().add(drawable); + return getThis(); + } + + /** + * Adds a vanilla {@link TooltipComponent} to the current line. + * The tooltip component will always be converted into a {@link IIcon} regardless of what it is and drawn inline + * with the other components. + * It's recommended to use {@link #addLine(TooltipComponent)} instead if you want to preserve how vanilla handles + * this. + * + * @param tooltipComponent tooltip component to add. + * @return this + * @see #addLine(TooltipComponent) + */ + default T add(TooltipComponent tooltipComponent) { + getRichText().add(tooltipComponent); + return getThis(); + } + + default T addLine(FormattedText formattedText) { + getRichText().add(formattedText).newLine(); + return getThis(); + } + + default T addLine(String s) { + getRichText().add(s).newLine(); + return getThis(); + } + + default T addLine(ITextLine line) { + getRichText().addLine(line); + return getThis(); + } + + default T addLine(TooltipComponent tooltipComponent) { + getRichText().add(tooltipComponent).newLine(); + return getThis(); + } + + /** + * Adds a drawable to the current line and creates a new line. + * Refer to {@link #add(IDrawable)} for additional information. + * + * @param line drawable to add. + * @return this + * @see #add(IDrawable) + */ + default T addLine(IDrawable line) { + getRichText().add(line).newLine(); + return getThis(); + } + + /** + * Adds all translated components of a multilang's subkeys and creates a new line for each. + * + * @param lang lang key of the multilang to add. + * @return this + */ + default T addMultiLine(String lang) { + for (MutableComponent text : LangHandler.getMultiLang(lang)) { + getRichText().addLine(text); + } + return getThis(); + } + + /** + * Starts a new line. This is always preferred over {@code "\n"} or {@code IKey.str("\n")}, it reduces computation a + * lot and maybe saves a tiny bit of memory. + * + * @return this + */ + default T newLine() { + return add(IKey.LINE_FEED); + } + + /** + * Adds a space character to the current line. This is rarely useful. + * + * @return this + */ + default T space() { + return add(IKey.SPACE); + } + + /** + * Adds a line with a given thickness in pixels. This will result in larger text line gap. + * + * @param pixelSpace thickness in pixel + * @return this + */ + default T spaceLine(int pixelSpace) { + return addLine(Spacer.of(pixelSpace)); + } + + /** + * Adds a two pixel thick empty line. This will result in larger text line gap. + * This is useful for titles. + * + * @return this + */ + default T spaceLine() { + return addLine(Spacer.SPACER_2PX); + } + + /** + * Adds an empty line which is as tall as a normal text line. + * + * @return this + */ + default T emptyLine() { + return addLine(Spacer.LINE_SPACER); + } + + /** + * Adds multiple drawables to the current line. + * Refer to {@link #add(IDrawable)} for additional information. + * + * @param drawables drawables to add. + * @return this + * @see #add(IDrawable) + */ + default T addElements(Iterable drawables) { + for (IDrawable drawable : drawables) { + getRichText().add(drawable); + } + return getThis(); + } + + /** + * Adds each drawable and creates a new line after each. + * Refer to {@link #add(IDrawable)} for additional information. + * + * @param drawables drawables to add. + * @return this + * @see #add(IDrawable) + */ + default T addDrawableLines(Iterable drawables) { + for (IDrawable drawable : drawables) { + getRichText().add(drawable).newLine(); + } + return getThis(); + } + + /** + * Adds each string and creates a new line after each. + * Refer to {@link #add(String)} for additional information. + * + * @param strings strings to add. + * @return this + * @see #add(IDrawable) + */ + default T addStringLines(Iterable strings) { + for (String string : strings) { + getRichText().add(string).newLine(); + } + return getThis(); + } + + /** + * Finds the next element which contains the matching regex and put the cursor after it. + * If none was found the cursor is at the end. + * + * @param regex regex to match strings for + * @return this + */ + default T moveCursorAfterElement(String regex) { + return moveCursorAfterElement(Pattern.compile(regex)); + } + + /** + * Finds the next element which contains the matching regex and put the cursor after it. + * If none was found the cursor is at the end. + * + * @param regex regex to match strings for + * @return this + */ + default T moveCursorAfterElement(Pattern regex) { + getRichText().moveCursorAfterElement(regex); + return getThis(); + } + + /** + * Finds the next element which contains the matching regex and replaces the whole element with the result of the + * function. + * The cursor is then placed after the new element. If the function returns {@code null}, then the element is + * removed. + * If no element is found nothing happens and the cursor stays in place. + * + * @param regex regex to match strings for + * @param function function to modify the found element + * @return this + */ + default T replace(String regex, UnaryOperator function) { + return replace(Pattern.compile(regex), function); + } + + /** + * Finds the next element which contains the matching regex and replaces the whole element with the result of the + * function. + * The cursor is then placed after the new element. If the function returns {@code null}, then the element is + * removed. + * If no element is found nothing happens and the cursor stays in place. + * + * @param regex regex to match strings for + * @param function function to modify the found element + * @return this + */ + default T replace(Pattern regex, UnaryOperator function) { + getRichText().replace(regex, function); + return getThis(); + } + + /** + * Moves the cursor to the very start. + * + * @return this + */ + default T moveCursorToStart() { + getRichText().moveCursorToStart(); + return getThis(); + } + + /** + * Moves the cursor to the very end (default). + * + * @return this + */ + default T moveCursorToEnd() { + getRichText().moveCursorToEnd(); + return getThis(); + } + + /** + * Moves the cursor a given number of elements forward. The cursor will be clamped at the end. + * + * @param by amount to move cursor by + * @return this + */ + default T moveCursorForward(int by) { + getRichText().moveCursorForward(by); + return getThis(); + } + + /** + * Moves the cursor one element forward. The cursor will be clamped at the end. + * + * @return this + */ + default T moveCursorForward() { + return moveCursorForward(1); + } + + /** + * Moves the cursor a given number of elements backward. The cursor will be clamped at the start. + * + * @param by amount to move cursor by + * @return this + */ + default T moveCursorBackward(int by) { + getRichText().moveCursorBackward(by); + return getThis(); + } + + /** + * Moves the cursor one element backward. The cursor will be clamped at the start. + * + * @return this + */ + default T moveCursorBackward() { + return moveCursorBackward(1); + } + + /** + * This finds the next element ending with a line break and moves the cursor after it. Note that if the line break + * is somewhere in the middle of the element, that element will be ignored. + * + * @return this + */ + default T moveCursorToNextLine() { + getRichText().moveCursorToNextLine(); + return getThis(); + } + + /** + * When the cursor is locked it will no longer move automatically when elements are added, but it can still be moved + * manually with the move methods from above. + * + * @return this + * @see #unlockCursor() + */ + default T lockCursor() { + getRichText().lockCursor(); + return getThis(); + } + + /** + * When the cursor is locked it will no longer move automatically when elements are added, but it can still be moved + * manually with the move methods from above. + * + * @return this + * @see #lockCursor() () + */ + default T unlockCursor() { + getRichText().unlockCursor(); + return getThis(); + } + + /** + * Removes all text. + * + * @return this + */ + + default T clearText() { + getRichText().clearText(); + return getThis(); + } + + default T alignment(Alignment alignment) { + getRichText().alignment(alignment); + return getThis(); + } + + default T textColor(int color) { + getRichText().textColor(color); + return getThis(); + } + + default T scale(float scale) { + getRichText().scale(scale); + return getThis(); + } + + default T textShadow(boolean shadow) { + getRichText().textShadow(shadow); + return getThis(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/ITextLine.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/ITextLine.java new file mode 100644 index 00000000000..40d3e68b683 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/drawable/ITextLine.java @@ -0,0 +1,17 @@ +package com.gregtechceu.gtceu.api.mui.base.drawable; + +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.Font; + +public interface ITextLine { + + int getWidth(); + + int getHeight(Font font); + + void draw(GuiContext context, Font font, float x, float y, int color, boolean shadow, + int availableWidth, int availableHeight); + + Object getHoveringElement(Font font, int x, int y); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/gui.json b/src/main/java/com/gregtechceu/gtceu/api/mui/base/gui.json new file mode 100644 index 00000000000..1e2c6b1ae07 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/gui.json @@ -0,0 +1,9 @@ +{ + "name": "test", + "gui": [ + { + "widget": "image", + "min": 50 + } + ] +} \ No newline at end of file diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/ILayoutWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/ILayoutWidget.java new file mode 100644 index 00000000000..21e57899ecd --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/ILayoutWidget.java @@ -0,0 +1,55 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.widget.INotifyEnabled; +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; + +/** + * This is responsible for laying out widgets. This method responsible for laying out its children + * in itself. This includes calling {@link IResizeable#setSizeResized(boolean, boolean)} or one of its variants after a + * size with + * {@link com.gregtechceu.gtceu.api.mui.widget.sizer.Area#setSize(GuiAxis, int)} or one of its variants on each child. + * The same goes for + * position. If this widget also applies margin and padding (this is usually the case), then + * {@link IResizeable#setMarginPaddingApplied(boolean)} + * or one of its variants needs to be called to. + */ +public interface ILayoutWidget extends INotifyEnabled { + + /** + * Called after the children tried to calculate their size. + * Might be called multiple times. + * + * @return true if the layout was successful and no further iteration is needed. + */ + boolean layoutWidgets(); + + /** + * Called after post calculation of this widget. The last call guarantees, that this widget is fully calculated. + * + * @return true if the layout was successful and no further iteration is needed + */ + default boolean postLayoutWidgets() { + return true; + } + + default boolean canCoverByDefaultSize(GuiAxis axis) { + return false; + } + + /** + * Called when determining wrapping size of this widget. + * If this method returns true, size and margin of the queried child will be ignored for calculation. + * Typically return true when the child is disabled and you want to collapse it for layout. + * This method should also be used for layouting children with {@link #layoutWidgets} if it might return true. + */ + default boolean shouldIgnoreChildSize(IWidget child) { + return false; + } + + @Override + default void onChildChangeEnabled(IWidget child, boolean enabled) { + layoutWidgets(); + postLayoutWidgets(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java new file mode 100644 index 00000000000..df16e1125cb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeParent.java @@ -0,0 +1,75 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; + +public interface IResizeParent { + + /** + * @return area of the element + */ + Area getArea(); + + /** + * @return true if the relative x position is calculated + */ + boolean isXCalculated(); + + /** + * @return true if the relative y position is calculated + */ + boolean isYCalculated(); + + /** + * @return true if the width is calculated + */ + boolean isWidthCalculated(); + + /** + * @return true if the height is calculated + */ + boolean isHeightCalculated(); + + boolean areChildrenCalculated(); + + boolean isLayoutDone(); + + boolean canRelayout(boolean isParentLayout); + + default boolean isSizeCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isWidthCalculated() : isHeightCalculated(); + } + + default boolean isPosCalculated(GuiAxis axis) { + return axis.isHorizontal() ? isXCalculated() : isYCalculated(); + } + + /** + * @return true if the relative position and size are fully calculated + */ + default boolean isSelfFullyCalculated(boolean isParentLayout) { + return isSelfFullyCalculated() && !canRelayout(isParentLayout); + } + + default boolean isSelfFullyCalculated() { + return isXCalculated() && isYCalculated() && isWidthCalculated() && isHeightCalculated(); + } + + default boolean isFullyCalculated() { + return isSelfFullyCalculated() && areChildrenCalculated() && isLayoutDone(); + } + + default boolean isFullyCalculated(boolean isParentLayout) { + return isFullyCalculated() && !canRelayout(isParentLayout); + } + + /** + * @return true if margin and padding are applied on the x-axis + */ + boolean isXMarginPaddingApplied(); + + /** + * @return true if margin and padding are applied on the y-axis + */ + boolean isYMarginPaddingApplied(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java new file mode 100644 index 00000000000..3c6a76c76a3 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IResizeable.java @@ -0,0 +1,140 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; + +/** + * An interface that handles resizing of widgets. + * Usually this interface is not implemented by the users of this library or will even interact with it. + */ +public interface IResizeable extends IResizeParent { + + /** + * Called once before resizing + */ + void initResizing(boolean onOpen); + + /** + * Resizes the given element + * + * @param isParentLayout if the parent is a layout widget + * @return true if element is fully resized + */ + boolean resize(boolean isParentLayout); + + /** + * Called if {@link #resize(boolean)} returned false after children have been resized. + * + * @return if element is fully resized + */ + boolean postResize(); + + /** + * Called after all elements in the tree are resized and the absolute positions needs to be calculated from the + * relative position. + */ + default void preApplyPos() {} + + /** + * This converts the relative pos to resizer parent to relative pos to widget parent. + */ + default void applyPos() {} + + void setChildrenResized(boolean resized); + + void setLayoutDone(boolean done); + + default void setAxisResized(GuiAxis axis, boolean pos, boolean size) { + if (axis.isHorizontal()) { + setXAxisResized(pos, size); + } else { + setYAxisResized(pos, size); + } + } + + void setXAxisResized(boolean pos, boolean size); + + void setYAxisResized(boolean pos, boolean size); + + /** + * Marks position and size as calculated. + */ + default void setResized(boolean x, boolean y, boolean w, boolean h) { + setXAxisResized(x, w); + setYAxisResized(y, h); + } + + default void setPosResized(boolean x, boolean y) { + setResized(x, y, isWidthCalculated(), isHeightCalculated()); + } + + default void setSizeResized(boolean w, boolean h) { + setResized(isXCalculated(), isYCalculated(), w, h); + } + + default void setXResized(boolean v) { + setXAxisResized(v, isWidthCalculated()); + } + + default void setYResized(boolean v) { + setYAxisResized(v, isHeightCalculated()); + } + + default void setPosResized(GuiAxis axis, boolean v) { + if (axis.isHorizontal()) { + setXResized(v); + } else { + setYResized(v); + } + } + + default void setWidthResized(boolean v) { + setXAxisResized(isXCalculated(), v); + } + + default void setHeightResized(boolean v) { + setYAxisResized(isYCalculated(), v); + } + + default void setSizeResized(GuiAxis axis, boolean v) { + if (axis.isHorizontal()) { + setWidthResized(v); + } else { + setHeightResized(v); + } + } + + default void setResized(boolean b) { + setResized(b, b, b, b); + } + + default void updateResized() { + setResized(isXCalculated(), isYCalculated(), isWidthCalculated(), isHeightCalculated()); + } + + /** + * Sets if margin and padding on the x-axis is applied + * + * @param b true if margin and padding are applied + */ + void setXMarginPaddingApplied(boolean b); + + /** + * Sets if margin and padding on the y-axis is applied + * + * @param b true if margin and padding are applied + */ + void setYMarginPaddingApplied(boolean b); + + default void setMarginPaddingApplied(boolean b) { + setXMarginPaddingApplied(b); + setYMarginPaddingApplied(b); + } + + default void setMarginPaddingApplied(GuiAxis axis, boolean b) { + if (axis.isHorizontal()) { + setXMarginPaddingApplied(b); + } else { + setYMarginPaddingApplied(b); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewport.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewport.java new file mode 100644 index 00000000000..335c229cf01 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewport.java @@ -0,0 +1,130 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.base.widget.IWidget; +import com.gregtechceu.gtceu.api.mui.utils.HoveredWidgetList; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +import java.util.function.Predicate; + +/** + * A gui element which can transform its children f.e. a scrollable list. + */ +public interface IViewport extends IWidget { + + /** + * Apply shifts of this viewport. + * + * @param stack viewport stack + */ + default void transformChildren(IViewportStack stack) {} + + /** + * Gathers all children at a position. Transformations from this viewport are already applied. + * + * @param stack current viewport stack. Should not be modified. + * @param widgets widget list of already gathered widgets. Add children here. + * @param x x position + * @param y y position + */ + default void getWidgetsAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (hasChildren()) { + getChildrenAt(this, stack, widgets, x, y); + } + } + + /** + * Gathers all children at a position. Transformations from this viewport are not applied. + * Called before {@link #getWidgetsAt(IViewportStack, HoveredWidgetList, int, int)} + * + * @param stack current viewport stack. Should not be modified. + * @param widgets widget list of already gathered widgets. Add children here. + * @param x x position + * @param y y position + */ + default void getSelfAt(IViewportStack stack, HoveredWidgetList widgets, int x, int y) { + if (isInside(stack, x, y)) { + widgets.add(this, stack, getAdditionalHoverInfo(stack, x, y)); + } + } + + /** + * Called during drawing twice (before children are drawn). Once with transformation of this viewport and once + * without + * + * @param context gui context + * @param transformed if transformation from this viewport is active + */ + default void preDraw(ModularGuiContext context, boolean transformed) {} + + /** + * Called during drawing twice (after children are drawn). Once with transformation of this viewport and once + * without + * + * @param context gui context + * @param transformed if transformation from this viewport is active + */ + default void postDraw(ModularGuiContext context, boolean transformed) {} + + static void getChildrenAt(IWidget parent, IViewportStack stack, HoveredWidgetList widgetList, int x, int y) { + for (IWidget child : parent.getChildren()) { + if (!child.isEnabled()) { + continue; + } + if (child instanceof IViewport viewport) { + stack.pushViewport(viewport, parent.getArea()); + child.transform(stack); + viewport.getSelfAt(stack, widgetList, x, y); + viewport.transformChildren(stack); + viewport.getWidgetsAt(stack, widgetList, x, y); + stack.popViewport(viewport); + } else { + stack.pushMatrix(); + child.transform(stack); + if (child.isInside(stack, x, y)) { + widgetList.add(child, stack, child.getAdditionalHoverInfo(stack, x, y)); + } + if (child.hasChildren()) { + getChildrenAt(child, stack, widgetList, x, y); + } + stack.popMatrix(); + } + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + static boolean forEachChild(IViewportStack stack, IWidget parent, Predicate predicate, int context) { + for (IWidget child : parent.getChildren()) { + if (!child.isEnabled()) { + continue; + } + stack.popMatrix(); + if (child instanceof IViewport viewport) { + stack.pushViewport(viewport, parent.getArea()); + parent.transform(stack); + if (!predicate.test(child)) { + stack.popViewport(viewport); + return false; + } + viewport.transformChildren(parent.getContext()); + if (child.hasChildren() && !forEachChild(stack, child, predicate, context)) { + stack.popViewport(viewport); + return false; + } + stack.popViewport(viewport); + } else { + stack.pushMatrix(); + parent.transform(stack); + if (!predicate.test(child)) { + stack.popMatrix(); + return false; + } + if (child.hasChildren() && !forEachChild(stack, child, predicate, context)) { + stack.popMatrix(); + return false; + } + stack.popMatrix(); + } + } + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewportStack.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewportStack.java new file mode 100644 index 00000000000..0f95085bba2 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/layout/IViewportStack.java @@ -0,0 +1,217 @@ +package com.gregtechceu.gtceu.api.mui.base.layout; + +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.viewport.TransformationMatrix; + +import com.mojang.blaze3d.vertex.PoseStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Vector3f; + +/** + * This handles all viewports in a GUI. Also keeps track of a matrix stack used for rendering and + * user interaction. + */ +@ApiStatus.NonExtendable +public interface IViewportStack { + + /** + * Reset all viewports and the matrix stack. + */ + void reset(); + + /** + * @return current viewport + */ + Area getViewport(); + + /** + * Pushes a viewport to the top. Also pushes a new matrix. + * + * @param viewport viewport to push + * @param area area of the viewport + */ + void pushViewport(IViewport viewport, Area area); + + /** + * Only pushes a matrix without a viewport. + */ + void pushMatrix(); + + /** + * Removes the top viewport and its matrix from the stack. + * + * @param viewport viewport to remove from the top. + * @throws IllegalStateException if the given viewport doesn't match the viewport at the top. + */ + void popViewport(IViewport viewport); + + /** + * Removes the top matrix from the stack. + * + * @throws IllegalStateException if the top matrix is a viewport. + */ + void popMatrix(); + + /** + * @return the matrix stack size + */ + int getStackSize(); + + /** + * Removes all matrices ABOVE the given index. + * + * @param index matrices are removed above this index. + */ + void popUntilIndex(int index); + + /** + * Removes all matrices ABOVE the given viewport. + * + * @param viewport matrices are removed above this viewport. + */ + void popUntilViewport(IViewport viewport); + + /** + * Applies translation transformation to the current top matrix. + * + * @param x translation in x + * @param y translation in y + */ + void translate(float x, float y); + + /** + * Applies translation transformation to the current top matrix. + * + * @param x translation in x + * @param y translation in y + * @param z translation in z + */ + void translate(float x, float y, float z); + + /** + * Applies rotation transformation to the current top matrix. + * + * @param angle clockwise rotation angle in radians + * @param x x-axis rotation. 1 for yes, 0 for no + * @param y y-axis rotation. 1 for yes, 0 for no + * @param z z-axis rotation. 1 for yes, 0 for no + */ + void rotate(float angle, float x, float y, float z); + + /** + * Applies rotation transformation to the current top matrix around z. + * + * @param angle clockwise rotation angle in radians + */ + void rotateZ(float angle); + + /** + * Applies scaling transformation to the current top matrix around. + * + * @param x x scale factor + * @param y y scale factor + */ + void scale(float x, float y); + + /** + * Multiplies current matrix transformation by another matrix + * + * @param matrix the matrix to multiply with + */ + void multiply(Matrix4f matrix); + + /** + * Resets the top matrix to the matrix below. + */ + void resetCurrent(); + + /** + * Transforms the x component of a position with the current matrix transformations. + * + * @param x x component of position + * @param y y component of position + * @return transformed x component + */ + int transformX(float x, float y); + + /** + * Transforms the y component of a position with the current matrix transformations. + * + * @param x x component of position + * @param y y component of position + * @return transformed y component + */ + int transformY(float x, float y); + + /** + * Transforms the x component of a position with the current inverted matrix transformations. + * + * @param x x component of position + * @param y y component of position + * @return un-transformed x component + */ + int unTransformX(float x, float y); + + /** + * Transforms the y component of a position with the current inverted matrix transformations. + * + * @param x x component of position + * @param y y component of position + * @return un-transformed y component + */ + int unTransformY(float x, float y); + + /** + * Transforms a vector with the current matrix transformations. + * This modifies the given vector. + * + * @param vec vector to transform + * @return transformed vector + */ + default Vector3f transform(Vector3f vec) { + return transform(vec, vec); + } + + /** + * Transforms a vector with the current matrix transformations. + * + * @param vec vector to transform + * @param dest vector to write the result to + * @return transformed vector + */ + Vector3f transform(Vector3f vec, Vector3f dest); + + /** + * Transforms a vector with the current inverted matrix transformations. + * This modifies the given vector. + * + * @param vec vector to un-transform + * @return un-transformed vector + */ + default Vector3f unTransform(Vector3f vec) { + return unTransform(vec, vec); + } + + /** + * Transforms a vector with the current inverted matrix transformations. + * This modifies the given vector. + * + * @param vec vector to un-transform + * @param dest vector to write the result to + * @return un-transformed vector + */ + Vector3f unTransform(Vector3f vec, Vector3f dest); + + /** + * Applies the current matrix transformations the current OpenGL matrix. + */ + void applyTo(PoseStack poseStack); + + /** + * @return the top matrix or null if stack is empty + */ + @Nullable + TransformationMatrix peek(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IBoolValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IBoolValue.java new file mode 100644 index 00000000000..df57a91e30d --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IBoolValue.java @@ -0,0 +1,18 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IBoolValue extends IValue, IIntValue { + + boolean getBoolValue(); + + void setBoolValue(boolean val); + + @Override + default int getIntValue() { + return getBoolValue() ? 1 : 0; + } + + @Override + default void setIntValue(int val) { + setBoolValue(val == 1); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IByteValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IByteValue.java new file mode 100644 index 00000000000..89be4e97256 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IByteValue.java @@ -0,0 +1,28 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IByteValue extends IIntValue, IStringValue { + + @Override + default void setIntValue(int val) { + setByteValue((byte) val); + } + + @Override + default void setStringValue(String val) { + setByteValue(Byte.parseByte(val)); + } + + @Override + default int getIntValue() { + return getByteValue(); + } + + @Override + default String getStringValue() { + return String.valueOf(getByteValue()); + } + + void setByteValue(byte b); + + byte getByteValue(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IDoubleValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IDoubleValue.java new file mode 100644 index 00000000000..0e50cbb29e8 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IDoubleValue.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IDoubleValue extends IValue { + + double getDoubleValue(); + + void setDoubleValue(double val); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IEnumValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IEnumValue.java new file mode 100644 index 00000000000..3d4d2092b28 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IEnumValue.java @@ -0,0 +1,6 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IEnumValue> extends IValue { + + Class getEnumClass(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IFloatValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IFloatValue.java new file mode 100644 index 00000000000..91df2287614 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IFloatValue.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IFloatValue extends IValue { + + float getFloatValue(); + + void setFloatValue(float val); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IIntValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IIntValue.java new file mode 100644 index 00000000000..c61b4fe51a4 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IIntValue.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IIntValue extends IValue { + + int getIntValue(); + + void setIntValue(int val); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ILongValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ILongValue.java new file mode 100644 index 00000000000..fe271ae8e4c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ILongValue.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface ILongValue extends IValue { + + long getLongValue(); + + void setLongValue(long val); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IStringValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IStringValue.java new file mode 100644 index 00000000000..c6983326c70 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IStringValue.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +public interface IStringValue extends IValue { + + String getStringValue(); + + void setStringValue(String val); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ISyncOrValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ISyncOrValue.java new file mode 100644 index 00000000000..9ab9466ac16 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/ISyncOrValue.java @@ -0,0 +1,126 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An interface that is implemented on {@link IValue} and {@link com.gregtechceu.gtceu.api.mui.value.sync.SyncHandler + * SyncHandler} for easier + * validation and setters. + */ +@ApiStatus.NonExtendable +public interface ISyncOrValue { + + /** + * A sync handler or value representing null. + */ + ISyncOrValue EMPTY = new ISyncOrValue() { + + @Override + public @Nullable T castNullable(Class type) { + return null; + } + + @Override + public boolean isTypeOrEmpty(Class type) { + return true; + } + }; + + /** + * Returns the given sync handler or value or {@link #EMPTY} if null. + * + * @param syncOrValue sync handler or value + * @return a non-null representation of the given sync handler or value + */ + @NotNull + static ISyncOrValue orEmpty(@Nullable ISyncOrValue syncOrValue) { + return syncOrValue != null ? syncOrValue : EMPTY; + } + + /** + * Returns if this sync handler or value is an instance of the given type or if this represents null. This is + * useful, when the value or + * sync handler can be null in the widget. + * + * @param type type to check for + * @return if this sync handler or value is an instance of the type or empty + */ + default boolean isTypeOrEmpty(Class type) { + return type.isAssignableFrom(getClass()); + } + + /** + * Casts this sync handler or value to the given type or null if this isn't a subtype of the given type. + * + * @param type type to cast this sync handle or value to + * @param type to cast to + * @return this cast sync handler or value + */ + @Nullable + @SuppressWarnings("unchecked") + default T castNullable(Class type) { + return type.isAssignableFrom(getClass()) ? (T) this : null; + } + + /** + * Casts this sync handler or value to a {@link IValue IValue<V>} if it is a value handler and the containing + * value is of type + * {@link V} else null. + * + * @param valueType expected type of the containing value + * @param expected type of the containing value + * @return a {@link IValue IValue<V>} if types match or null + */ + @Nullable + default IValue castValueNullable(Class valueType) { + return null; + } + + /** + * Casts this sync handler or value to the given type or throws an exception if this isn't a subtype of the given + * type. + * + * @param type type to cast this sync handle or value to + * @param type to cast to + * @return this cast sync handler or value + * @throws IllegalStateException if this is not a subtype of the given type + */ + default T castOrThrow(Class type) { + T t = castNullable(type); + if (t == null) { + if (!isSyncHandler() && !isValueHandler()) { + throw new IllegalStateException("Empty sync handler or value can't be used for anything."); + } + String self = isSyncHandler() ? "sync handler" : "value"; + throw new IllegalStateException("Can't cast " + self + " of type '" + getClass().getSimpleName() + + "' to type '" + type.getSimpleName() + "'."); + } + return t; + } + + /** + * Returns if the containing value of this is of the given type. If this is not a value it will always return false. + * + * @param type expected value type + * @return if the containing value of this is of the given type + */ + default boolean isValueOfType(Class type) { + return false; + } + + /** + * @return if this is a sync handler (false if this represents null) + */ + default boolean isSyncHandler() { + return false; + } + + /** + * @return if this is a value handler (false if this represents null) + */ + default boolean isValueHandler() { + return false; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IValue.java new file mode 100644 index 00000000000..2e3e0f9fe77 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/IValue.java @@ -0,0 +1,40 @@ +package com.gregtechceu.gtceu.api.mui.base.value; + +/** + * A value wrapper for widgets. + * + * @param value type + */ +public interface IValue extends ISyncOrValue { + + /** + * Gets the current value. + * + * @return the current value + */ + T getValue(); + + /** + * Updates the current value. + * + * @param value new value + */ + void setValue(T value); + + Class getValueType(); + + default boolean isValueOfType(Class type) { + return type.isAssignableFrom(getValueType()); + } + + @SuppressWarnings("unchecked") + @Override + default IValue castValueNullable(Class valueType) { + return isValueOfType(valueType) ? (IValue) this : null; + } + + @Override + default boolean isValueHandler() { + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IBoolSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IBoolSyncValue.java new file mode 100644 index 00000000000..a043de7df29 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IBoolSyncValue.java @@ -0,0 +1,37 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IBoolValue; + +/** + * A helper interface for sync values which can be turned into an integer. + * + * @param value type + */ +public interface IBoolSyncValue extends IValueSyncHandler, IBoolValue, IIntSyncValue { + + @Override + default void setBoolValue(boolean val) { + setBoolValue(val, true, true); + } + + default void setBoolValue(boolean val, boolean setSource) { + setBoolValue(val, setSource, true); + } + + void setBoolValue(boolean value, boolean setSource, boolean sync); + + @Override + default void setIntValue(int value, boolean setSource, boolean sync) { + setBoolValue(value == 1, setSource, sync); + } + + @Override + default int getIntValue() { + return IBoolValue.super.getIntValue(); + } + + @Override + default void setIntValue(int val) { + IBoolValue.super.setIntValue(val); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IByteSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IByteSyncValue.java new file mode 100644 index 00000000000..c47b3e2da37 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IByteSyncValue.java @@ -0,0 +1,17 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IByteValue; + +public interface IByteSyncValue extends IByteValue, IValueSyncHandler { + + @Override + default void setByteValue(byte val) { + setByteValue(val, true); + } + + default void setByteValue(byte val, boolean setSource) { + setByteValue(val, setSource, true); + } + + void setByteValue(byte value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IDoubleSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IDoubleSyncValue.java new file mode 100644 index 00000000000..b1c9aa10f0e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IDoubleSyncValue.java @@ -0,0 +1,22 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IDoubleValue; + +/** + * A helper interface for sync values which can be turned into an integer. + * + * @param value type + */ +public interface IDoubleSyncValue extends IValueSyncHandler, IDoubleValue { + + @Override + default void setDoubleValue(double val) { + setDoubleValue(val, true, true); + } + + default void setDoubleValue(double val, boolean setSource) { + setDoubleValue(val, setSource, true); + } + + void setDoubleValue(double value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IFloatSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IFloatSyncValue.java new file mode 100644 index 00000000000..cfa3353e30c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IFloatSyncValue.java @@ -0,0 +1,17 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IFloatValue; + +public interface IFloatSyncValue extends IValueSyncHandler, IFloatValue { + + @Override + default void setFloatValue(float val) { + setFloatValue(val, true, true); + } + + default void setFloatValue(float val, boolean setSource) { + setFloatValue(val, setSource, true); + } + + void setFloatValue(float value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IIntSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IIntSyncValue.java new file mode 100644 index 00000000000..630bd244375 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IIntSyncValue.java @@ -0,0 +1,22 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IIntValue; + +/** + * A helper interface for sync values which can be turned into an integer. + * + * @param value type + */ +public interface IIntSyncValue extends IValueSyncHandler, IIntValue { + + @Override + default void setIntValue(int val) { + setIntValue(val, true, true); + } + + default void setIntValue(int val, boolean setSource) { + setIntValue(val, setSource, true); + } + + void setIntValue(int value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/ILongSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/ILongSyncValue.java new file mode 100644 index 00000000000..5259e61ba0a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/ILongSyncValue.java @@ -0,0 +1,22 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.ILongValue; + +/** + * A helper interface for sync values which can be turned into an integer. + * + * @param value type + */ +public interface ILongSyncValue extends IValueSyncHandler, ILongValue { + + @Override + default void setLongValue(long val) { + setLongValue(val, true, true); + } + + default void setLongValue(long val, boolean setSource) { + setLongValue(val, setSource, true); + } + + void setLongValue(long value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerKeyboardAction.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerKeyboardAction.java new file mode 100644 index 00000000000..412770b744a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerKeyboardAction.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.utils.KeyboardData; + +public interface IServerKeyboardAction { + + void onServerKeyboardAction(KeyboardData data); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerMouseAction.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerMouseAction.java new file mode 100644 index 00000000000..bc9e0bcca2e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IServerMouseAction.java @@ -0,0 +1,8 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.utils.MouseData; + +public interface IServerMouseAction { + + void onServerMouseAction(MouseData mouseData); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IStringSyncValue.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IStringSyncValue.java new file mode 100644 index 00000000000..1a73864b374 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IStringSyncValue.java @@ -0,0 +1,22 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IStringValue; + +/** + * A helper interface for sync values which can be turned into a string. + * + * @param value type + */ +public interface IStringSyncValue extends IValueSyncHandler, IStringValue { + + @Override + default void setStringValue(String val) { + setStringValue(val, true, true); + } + + default void setStringValue(String val, boolean setSource) { + setStringValue(val, setSource, true); + } + + void setStringValue(String value, boolean setSource, boolean sync); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IValueSyncHandler.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IValueSyncHandler.java new file mode 100644 index 00000000000..c7398c27aeb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/value/sync/IValueSyncHandler.java @@ -0,0 +1,76 @@ +package com.gregtechceu.gtceu.api.mui.base.value.sync; + +import com.gregtechceu.gtceu.api.mui.base.value.IValue; + +import net.minecraft.network.FriendlyByteBuf; + +/** + * A helper interface for syncing an object value. + * + * @param object value type + */ +public interface IValueSyncHandler extends IValue { + + /** + * Updates the current value and the source and syncs it to client/server. + * + * @param value new value + */ + @Override + default void setValue(T value) { + setValue(value, true, true); + } + + /** + * Updates the current value and syncs it to client/server. + * + * @param value new value + * @param setSource whether the source should be updated with the new value + */ + default void setValue(T value, boolean setSource) { + setValue(value, setSource, true); + } + + /** + * Updates the current value. + * + * @param value new value + * @param setSource whether the source should be updated with the new value + * @param sync whether the new value should be synced to client/server + */ + void setValue(T value, boolean setSource, boolean sync); + + /** + * Determines if the current value is different from source and updates the current value if it is. + * + * @param isFirstSync true if it's the first tick in the ui + * @return true if the current value was different from source + */ + boolean updateCacheFromSource(boolean isFirstSync); + + /** + * Updates the cache from source and syncs it to the other sides. + *

+ * Usually this is + * + * setValue(getter.get(), false, true); + * + *

+ * Where {@code getter} is the source. + */ + void notifyUpdate(); + + /** + * Writes the current value to the buffer + * + * @param buffer buffer to write to + */ + void write(FriendlyByteBuf buffer); + + /** + * Reads a value from the buffer and sets the current value + * + * @param buffer buffer to read from + */ + void read(FriendlyByteBuf buffer); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java new file mode 100644 index 00000000000..2e7713861c2 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDelegatingWidget.java @@ -0,0 +1,18 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import net.minecraft.world.inventory.Slot; + +public interface IDelegatingWidget extends IWidget, IVanillaSlot { + + IWidget getDelegate(); + + @Override + default Slot getVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot ? vanillaSlot.getVanillaSlot() : null; + } + + @Override + default boolean handleAsVanillaSlot() { + return getDelegate() instanceof IVanillaSlot vanillaSlot && vanillaSlot.handleAsVanillaSlot(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java new file mode 100644 index 00000000000..aea715db3fe --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDragResizeable.java @@ -0,0 +1,119 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.utils.GTUtil; + +/** + * Implement this interface on a {@link IWidget} to allow it being resized by dragging the edges similar to windows. + */ +public interface IDragResizeable { + + /** + * @return if this widget can currently be resized by dragging an edge + */ + default boolean isCurrentlyResizable() { + return true; + } + + /** + * @return if the center position of this widget should be retained, by also resizing the opposite edge + */ + default boolean keepPosOnDragResize() { + return true; + } + + /** + * Called every time the mouse moves one or more pixels while this widget is resized by dragging an edge. + */ + default void onDragResize() { + ((IWidget) this).scheduleResize(); + } + + /** + * @return The border size in which to allow drag resizing in pixels. + */ + default int getDragAreaSize() { + return 3; + } + + /** + * @return The minimum width this widget can be dragged to. + */ + default int getMinDragWidth() { + return 18; + } + + /** + * @return The minimum height this widget can be dragged to. + */ + default int getMinDragHeight() { + return 18; + } + + /** + * An internal method to detect if the mouse is currently hovering an area where a drag resize can be started. + */ + static ResizeDragArea getDragResizeCorner(IDragResizeable widget, Area area, IViewportStack stack, int x, int y) { + if (!widget.isCurrentlyResizable()) return null; + + int mx = stack.unTransformX(x, y); + int my = stack.unTransformY(x, y); + + if (mx < 0 || my < 0 || mx > area.w() || my > area.h()) return null; + + int ras = widget.getDragAreaSize(); + if (mx < ras) { + if (my < ras) return ResizeDragArea.TOP_LEFT; + if (my > area.h() - ras) return ResizeDragArea.BOTTOM_LEFT; + return ResizeDragArea.LEFT; + } + if (mx > area.w() - ras) { + if (my < ras) return ResizeDragArea.TOP_RIGHT; + if (my > area.h() - ras) return ResizeDragArea.BOTTOM_RIGHT; + return ResizeDragArea.RIGHT; + } + if (my < ras) return ResizeDragArea.TOP; + if (my > area.h() - ras) return ResizeDragArea.BOTTOM; + return null; + } + + /** + * An internal method to actually resize the widget while an edge is being dragged. + */ + static void applyDrag(IDragResizeable resizeable, IWidget widget, ResizeDragArea dragArea, Area startArea, int dx, + int dy) { + int keepPosFactor = resizeable.keepPosOnDragResize() || GTUtil.isShiftDown() ? 2 : 1; + if (dx != 0) { + if (dragArea.left) { + int s = startArea.width - dx * keepPosFactor; + if (s >= resizeable.getMinDragWidth()) { + widget.resizer().left(startArea.rx + dx); + widget.resizer().width(s); + } + } else if (dragArea.right) { + int s = startArea.width + dx * keepPosFactor; + if (s >= resizeable.getMinDragWidth()) { + widget.resizer().left(startArea.rx - dx * (keepPosFactor - 1)); + widget.resizer().width(s); + } + } + } + if (dy != 0) { + if (dragArea.top) { + int s = startArea.height - dy * keepPosFactor; + if (s >= resizeable.getMinDragHeight()) { + widget.resizer().top(startArea.ry + dy); + widget.resizer().height(s); + } + } else if (dragArea.bottom) { + int s = startArea.height + dy * keepPosFactor; + if (s >= resizeable.getMinDragHeight()) { + widget.resizer().top(startArea.ry - dy * (keepPosFactor - 1)); + widget.resizer().height(s); + } + } + } + resizeable.onDragResize(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java new file mode 100644 index 00000000000..339392b0a29 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IDraggable.java @@ -0,0 +1,68 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; +import com.gregtechceu.gtceu.api.mui.widget.DraggableWidget; +import com.gregtechceu.gtceu.api.mui.widget.WidgetTree; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +import net.minecraft.client.gui.GuiGraphics; + +import org.jetbrains.annotations.Nullable; + +/** + * Marks a widget as draggable. + * The dragging is handled by ModularUI. + * + * @see DraggableWidget + */ +public interface IDraggable { + + /** + * Gets called every frame after everything else is rendered. + * Is only called when {@link #isMoving()} is true. + * Translate to the mouse pos and draw with + * {@link WidgetTree#drawTree(IWidget, ModularGuiContext, boolean, boolean)}. + * + * @param graphics + * @param partialTicks difference from last from + */ + void drawMovingState(GuiGraphics graphics, ModularGuiContext context, float partialTicks); + + /** + * @param button the mouse button that's holding down + * @return false if the action should be canceled + */ + boolean onDragStart(int button); + + /** + * The dragging has ended and getState == IDLE + * + * @param successful is false if this returned to its old position + */ + void onDragEnd(boolean successful); + + void onDrag(int mouseButton, double timeSinceLastClick); + + /** + * Gets called when the mouse is released + * + * @param widget current top most widget below the mouse + * @return if the location is valid + */ + default boolean canDropHere(int x, int y, @Nullable IWidget widget) { + return true; + } + + /** + * @return the size and pos during move + */ + @Nullable + Area getMovingArea(); + + boolean isMoving(); + + void setMoving(boolean moving); + + void transform(IViewportStack viewportStack); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IFocusedWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IFocusedWidget.java new file mode 100644 index 00000000000..518acdcabb4 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IFocusedWidget.java @@ -0,0 +1,29 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; + +/** + * An interface for {@link IWidget}'s, that makes them focusable. + * Usually used for text fields to receive keyboard and mouse input first, no matter if its hovered or not. + */ +public interface IFocusedWidget { + + /** + * @return this widget is currently focused + */ + boolean isFocused(); + + /** + * Called when this widget gets focused + * + * @param context gui context + */ + void onFocus(ModularGuiContext context); + + /** + * Called when the focus is removed from this widget + * + * @param context gui context + */ + void onRemoveFocus(ModularGuiContext context); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiAction.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiAction.java new file mode 100644 index 00000000000..4eeb2471f3e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IGuiAction.java @@ -0,0 +1,51 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; + +/** + * Gui action listeners that can be registered in {@link ModularScreen#registerGuiActionListener(IGuiAction)} + */ +public interface IGuiAction { + + @FunctionalInterface + interface MousePressed extends IGuiAction { + + boolean press(double mouseX, double mouseY, int button); + } + + @FunctionalInterface + interface MouseReleased extends IGuiAction { + + boolean release(double mouseX, double mouseY, int button); + } + + @FunctionalInterface + interface KeyPressed extends IGuiAction { + + boolean press(int keyCode, int scanCode, int modifiers); + } + + @FunctionalInterface + interface KeyReleased extends IGuiAction { + + boolean release(int keyCode, int scanCode, int modifiers); + } + + @FunctionalInterface + interface CharTyped extends IGuiAction { + + boolean type(char codePoint, int modifiers); + } + + @FunctionalInterface + interface MouseScroll extends IGuiAction { + + boolean scroll(double mouseX, double mouseY, double delta); + } + + @FunctionalInterface + interface MouseDrag extends IGuiAction { + + boolean drag(double mouseX, double mouseY, int button, double dragX, double dragY); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/INotifyEnabled.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/INotifyEnabled.java new file mode 100644 index 00000000000..5f65fb976d1 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/INotifyEnabled.java @@ -0,0 +1,6 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +public interface INotifyEnabled { + + void onChildChangeEnabled(IWidget child, boolean enabled); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IParentWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IParentWidget.java new file mode 100644 index 00000000000..6231cba2552 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IParentWidget.java @@ -0,0 +1,29 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import java.util.function.Supplier; + +public interface IParentWidget> { + + W getThis(); + + boolean addChild(I child, int index); + + default W child(int index, I child) { + if (!addChild(child, index)) { + throw new IllegalStateException("Failed to add child"); + } + return getThis(); + } + + default W child(I child) { + if (!addChild(child, -1)) { + throw new IllegalStateException("Failed to add child"); + } + return getThis(); + } + + default W childIf(boolean condition, Supplier child) { + if (condition) return child(child.get()); + return getThis(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java new file mode 100644 index 00000000000..5048f1c9121 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IPositioned.java @@ -0,0 +1,515 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.widget.sizer.*; + +import java.util.function.Consumer; +import java.util.function.DoubleSupplier; + +/** + * Helper interface for position and size builder methods for widgets. + * + * @param widget type + */ +@SuppressWarnings({ "unused", "UnusedReturnValue" }) +public interface IPositioned> { + + StandardResizer resizer(); + + Area getArea(); + + void scheduleResize(); + + boolean requiresResize(); + + @SuppressWarnings("unchecked") + default W getThis() { + return (W) this; + } + + default W coverChildrenWidth() { + resizer().coverChildrenWidth(); + return getThis(); + } + + default W coverChildrenHeight() { + resizer().coverChildrenHeight(); + return getThis(); + } + + default W coverChildren() { + return coverChildrenWidth().coverChildrenHeight(); + } + + default W expanded() { + resizer().expanded(); + return getThis(); + } + + @Deprecated + default W relative(Area area) { + return relative(new AreaResizer(area)); + } + + default W relative(ResizeNode resizeNode) { + resizer().relative(resizeNode); + return getThis(); + } + + default W relative(IWidget widget) { + return relative(widget.resizer()); + } + + default W relativeToScreen() { + resizer().relativeToScreen(); + return getThis(); + } + + default W relativeToParent() { + resizer().relativeToParent(); + return getThis(); + } + + default W left(int val) { + resizer().left(val, 0, 0, Unit.Measure.PIXEL, true); + return getThis(); + } + + default W leftRel(float val) { + resizer().left(val, 0, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W leftRelOffset(float val, int offset) { + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W leftRelAnchor(float val, float anchor) { + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W leftRel(float val, int offset, float anchor) { + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W left(float val, int offset, float anchor, Unit.Measure measure) { + resizer().left(val, offset, anchor, measure, false); + return getThis(); + } + + default W left(DoubleSupplier val, Unit.Measure measure) { + resizer().left(val, 0, 0, measure, true); + return getThis(); + } + + default W leftRelOffset(DoubleSupplier val, int offset) { + resizer().left(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W leftRelAnchor(DoubleSupplier val, float anchor) { + resizer().left(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W leftRel(DoubleSupplier val, int offset, float anchor) { + resizer().left(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W right(int val) { + resizer().right(val, 0, 0, Unit.Measure.PIXEL, true); + return getThis(); + } + + default W rightRel(float val) { + resizer().right(val, 0, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W rightRelOffset(float val, int offset) { + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W rightRelAnchor(float val, float anchor) { + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W rightRel(float val, int offset, float anchor) { + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W right(float val, int offset, float anchor, Unit.Measure measure) { + resizer().right(val, offset, anchor, measure, false); + return getThis(); + } + + default W right(DoubleSupplier val, Unit.Measure measure) { + resizer().right(val, 0, 0, measure, true); + return getThis(); + } + + default W rightRelOffset(DoubleSupplier val, int offset) { + resizer().right(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W rightRelAnchor(DoubleSupplier val, float anchor) { + resizer().right(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W rightRel(DoubleSupplier val, int offset, float anchor) { + resizer().right(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W top(int val) { + resizer().top(val, 0, 0, Unit.Measure.PIXEL, true); + return getThis(); + } + + default W topRel(float val) { + resizer().top(val, 0, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W topRelOffset(float val, int offset) { + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W topRelAnchor(float val, float anchor) { + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W topRel(float val, int offset, float anchor) { + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W top(float val, int offset, float anchor, Unit.Measure measure) { + resizer().top(val, offset, anchor, measure, false); + return getThis(); + } + + default W top(DoubleSupplier val, Unit.Measure measure) { + resizer().top(val, 0, 0, measure, true); + return getThis(); + } + + default W topRelOffset(DoubleSupplier val, int offset) { + resizer().top(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W topRelAnchor(DoubleSupplier val, float anchor) { + resizer().top(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W topRel(DoubleSupplier val, int offset, float anchor) { + resizer().top(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W bottom(int val) { + resizer().bottom(val, 0, 0, Unit.Measure.PIXEL, true); + return getThis(); + } + + default W bottomRel(float val) { + resizer().bottom(val, 0, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W bottomRelOffset(float val, int offset) { + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W bottomRelAnchor(float val, float anchor) { + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W bottomRel(float val, int offset, float anchor) { + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W bottom(float val, int offset, float anchor, Unit.Measure measure) { + resizer().bottom(val, offset, anchor, measure, false); + return getThis(); + } + + default W bottom(DoubleSupplier val, Unit.Measure measure) { + resizer().bottom(val, 0, 0, measure, true); + return getThis(); + } + + default W bottomRelOffset(DoubleSupplier val, int offset) { + resizer().bottom(val, offset, 0, Unit.Measure.RELATIVE, true); + return getThis(); + } + + default W bottomRelAnchor(DoubleSupplier val, float anchor) { + resizer().bottom(val, 0, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W bottomRel(DoubleSupplier val, int offset, float anchor) { + resizer().bottom(val, offset, anchor, Unit.Measure.RELATIVE, false); + return getThis(); + } + + default W width(int val) { + resizer().width(val, 0, Unit.Measure.PIXEL); + return getThis(); + } + + default W widthRel(float val) { + resizer().width(val, 0, Unit.Measure.RELATIVE); + return getThis(); + } + + default W widthRelOffset(float val, int offset) { + resizer().width(val, offset, Unit.Measure.RELATIVE); + return getThis(); + } + + default W width(float val, Unit.Measure measure) { + resizer().width(val, 0, measure); + return getThis(); + } + + default W width(DoubleSupplier val, Unit.Measure measure) { + resizer().width(val, 0, measure); + return getThis(); + } + + default W height(int val) { + resizer().height(val, 0, Unit.Measure.PIXEL); + return getThis(); + } + + default W heightRel(float val) { + resizer().height(val, 0, Unit.Measure.RELATIVE); + return getThis(); + } + + default W height(float val, Unit.Measure measure) { + resizer().height(val, 0, measure); + return getThis(); + } + + default W heightRelOffset(float val, int offset) { + resizer().height(val, offset, Unit.Measure.RELATIVE); + return getThis(); + } + + default W heightRelOffset(DoubleSupplier val, int offset) { + resizer().height(val, offset, Unit.Measure.RELATIVE); + return getThis(); + } + + default W height(DoubleSupplier val, Unit.Measure measure) { + resizer().height(val, 0, measure); + return getThis(); + } + + default W pos(int x, int y) { + left(x).top(y); + return getThis(); + } + + default W posRel(float x, float y) { + leftRel(x).topRel(y); + return getThis(); + } + + default W size(int w, int h) { + width(w).height(h); + return getThis(); + } + + default W sizeRel(float w, float h) { + widthRel(w).heightRel(h); + return getThis(); + } + + default W size(int val) { + return width(val).height(val); + } + + default W sizeRel(float val) { + return widthRel(val).heightRel(val); + } + + default W fullWidth() { + return widthRel(1f); + } + + default W fullHeight() { + return heightRel(1f); + } + + default W full() { + return widthRel(1f).heightRel(1f); + } + + default W anchorLeft(float val) { + resizer().anchorLeft(val); + return getThis(); + } + + default W anchorRight(float val) { + resizer().anchorRight(val); + return getThis(); + } + + default W anchorTop(float val) { + resizer().anchorTop(val); + return getThis(); + } + + default W anchorBottom(float val) { + resizer().anchorBottom(val); + return getThis(); + } + + default W anchor(Alignment alignment) { + resizer().anchor(alignment); + return getThis(); + } + + default W alignX(float val) { + leftRel(val).anchorLeft(val); + return getThis(); + } + + default W alignX(Alignment alignment) { + return alignX(alignment.x); + } + + default W alignY(float val) { + topRel(val).anchorTop(val); + return getThis(); + } + + default W alignY(Alignment alignment) { + return alignY(alignment.y); + } + + default W align(Alignment alignment) { + return alignX(alignment).alignY(alignment); + } + + default W horizontalCenter() { + return alignX(Alignment.CENTER); + } + + default W verticalCenter() { + return alignY(Alignment.CENTER); + } + + default W center() { + return align(Alignment.Center); + } + + default W resizer(Consumer resizerConsumer) { + resizerConsumer.accept(resizer()); + return getThis(); + } + + default W padding(int left, int right, int top, int bottom) { + getArea().getPadding().all(left, right, top, bottom); + scheduleResize(); + return getThis(); + } + + default W padding(int horizontal, int vertical) { + getArea().getPadding().all(horizontal, vertical); + scheduleResize(); + return getThis(); + } + + default W padding(int all) { + getArea().getPadding().all(all); + scheduleResize(); + return getThis(); + } + + default W paddingLeft(int val) { + getArea().getPadding().left(val); + scheduleResize(); + return getThis(); + } + + default W paddingRight(int val) { + getArea().getPadding().right(val); + scheduleResize(); + return getThis(); + } + + default W paddingTop(int val) { + getArea().getPadding().top(val); + scheduleResize(); + return getThis(); + } + + default W paddingBottom(int val) { + getArea().getPadding().bottom(val); + scheduleResize(); + return getThis(); + } + + default W margin(int left, int right, int top, int bottom) { + getArea().getMargin().all(left, right, top, bottom); + scheduleResize(); + return getThis(); + } + + default W margin(int horizontal, int vertical) { + getArea().getMargin().all(horizontal, vertical); + scheduleResize(); + return getThis(); + } + + default W margin(int all) { + getArea().getMargin().all(all); + scheduleResize(); + return getThis(); + } + + default W marginLeft(int val) { + getArea().getMargin().left(val); + scheduleResize(); + return getThis(); + } + + default W marginRight(int val) { + getArea().getMargin().right(val); + scheduleResize(); + return getThis(); + } + + default W marginTop(int val) { + getArea().getMargin().top(val); + scheduleResize(); + return getThis(); + } + + default W marginBottom(int val) { + getArea().getMargin().bottom(val); + scheduleResize(); + return getThis(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ISynced.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ISynced.java new file mode 100644 index 00000000000..5be499d2a32 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ISynced.java @@ -0,0 +1,107 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.base.value.ISyncOrValue; +import com.gregtechceu.gtceu.api.mui.value.sync.ModularSyncManager; +import com.gregtechceu.gtceu.api.mui.value.sync.SyncHandler; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Marks a widget as synced + * + * @param widget type + */ +public interface ISynced { + + /** + * @return this cast to the true widget type + */ + @SuppressWarnings("unchecked") + default W getThis() { + return (W) this; + } + + /** + * Called when this widget gets initialised or when this widget is added to the gui + * + * @param syncManager sync manager + * @param late if this is called at any point after the panel this widget belongs to opened + */ + void initialiseSyncHandler(ModularSyncManager syncManager, boolean late); + + /** + * Returns if the given value or sync handler is valid for this widget. This is usually a call to + * {@link ISyncOrValue#isTypeOrEmpty(Class)}. If the widget must specify a value (disallow null) instanceof check + * can be used. You can + * check for primitive types which don't have a dedicated {@link com.gregtechceu.gtceu.api.mui.base.value.IValue + * IValue} interface with + * {@link ISyncOrValue#isValueOfType(Class)}. + * + * @param syncOrValue a sync handler or a value, but never null + * @return if the value or sync handler is valid for this class + */ + default boolean isValidSyncOrValue(@NotNull ISyncOrValue syncOrValue) { + return false; + } + + /** + * Checks if the given sync handler is valid for this widget and throws an exception if not. + * Override {@link #isValidSyncOrValue(ISyncOrValue)} + * + * @param syncHandler given sync handler + * @throws IllegalStateException if the given sync handler is invalid for this widget. + */ + @ApiStatus.NonExtendable + default void checkValidSyncOrValue(ISyncOrValue syncHandler) { + if (!isValidSyncOrValue(syncHandler)) { + throw new IllegalStateException( + "SyncHandler of type '" + syncHandler.getClass().getSimpleName() + "' is not valid " + + "for widget '" + this + "'."); + } + } + + /** + * @return true if this widget has a valid sync handler + */ + boolean isSynced(); + + /** + * @return the sync handler of this widget + * @throws IllegalStateException if this widget has no valid sync handler + */ + @NotNull + SyncHandler getSyncHandler(); + + /** + * Sets the sync handler key. The sync handler will be obtained in + * {@link #initialiseSyncHandler(ModularSyncManager, boolean)} + * + * @param name sync handler key name + * @param id sync handler key id + * @return this + */ + W syncHandler(String name, int id); + + /** + * Sets the sync handler key. The sync handler will be obtained in + * {@link #initialiseSyncHandler(ModularSyncManager, boolean)} + * + * @param key sync handler name + * @return this + */ + default W syncHandler(String key) { + return syncHandler(key, 0); + } + + /** + * Sets the sync handler key. The sync handler will be obtained in + * {@link #initialiseSyncHandler(ModularSyncManager, boolean)} + * + * @param id sync handler id + * @return this + */ + default W syncHandler(int id) { + return syncHandler("_", id); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ITooltip.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ITooltip.java new file mode 100644 index 00000000000..5e0b2438407 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ITooltip.java @@ -0,0 +1,283 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.base.drawable.ITextLine; +import com.gregtechceu.gtceu.api.mui.drawable.text.StyledText; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Helper interface with tooltip builder methods for widgets. + * + * @param widget type + */ +public interface ITooltip> { + + /** + * @return the current tooltip of this widget. Null if there is none + */ + @Nullable + RichTooltip getTooltip(); + + /** + * @return the current tooltip of this widget. Creates a new one if there is none + */ + @NotNull + RichTooltip tooltip(); + + /** + * Overwrites the current tooltip with the given one + * + * @param tooltip new tooltip + * @return this + */ + W tooltip(RichTooltip tooltip); + + /** + * @return true if this widget has a tooltip + */ + default boolean hasTooltip() { + return getTooltip() != null && !getTooltip().isEmpty(); + } + + /** + * @return this cast to the true widget type + */ + @SuppressWarnings("unchecked") + default W getThis() { + return (W) this; + } + + /** + * Helper method to call tooltip setters within a widget tree initialisation. + * Only called once. + * + * @param tooltipConsumer tooltip function + * @return this + */ + default W tooltip(Consumer tooltipConsumer) { + return tooltipStatic(tooltipConsumer); + } + + /** + * Helper method to call tooltip setters within a widget tree initialisation. + * Only called once. + * + * @param tooltipConsumer tooltip function + * @return this + */ + default W tooltipStatic(Consumer tooltipConsumer) { + tooltipConsumer.accept(tooltip()); + return getThis(); + } + + /** + * Sets a tooltip builder. The builder will be called every time the tooltip is marked dirty. + * Should be used for dynamic tooltips. + * + * @param tooltipBuilder tooltip function + * @return this + */ + default W tooltipBuilder(Consumer tooltipBuilder) { + return tooltipDynamic(tooltipBuilder); + } + + /** + * Sets a tooltip builder. The builder will be called every time the tooltip is marked dirty. + * Should be used for dynamic tooltips. + * + * @param tooltipBuilder tooltip function + * @return this + */ + default W tooltipDynamic(Consumer tooltipBuilder) { + tooltip().tooltipBuilder(tooltipBuilder); + return getThis(); + } + + /** + * Sets a general tooltip position. The true position is calculated every frame. + * + * @param pos tooltip pos + * @return this + */ + default W tooltipPos(RichTooltip.Pos pos) { + tooltip().pos(pos); + return getThis(); + } + + /** + * Sets a fixed tooltip position. + * + * @param x x pos + * @param y y pos + * @return this + */ + default W tooltipPos(int x, int y) { + tooltip().pos(x, y); + return getThis(); + } + + /** + * Sets an alignment. The alignment determines how the content is aligned in the tooltip. + * + * @param alignment alignment + * @return this + */ + default W tooltipAlignment(Alignment alignment) { + tooltip().alignment(alignment); + return getThis(); + } + + /** + * Sets if the tooltip text should have shadow enabled by default. + * Can be overridden with {@link StyledText} lines. + * + * @param textShadow true if text should have a shadow + * @return this + */ + default W tooltipTextShadow(boolean textShadow) { + tooltip().textShadow(textShadow); + return getThis(); + } + + /** + * Sets a default tooltip text color. Can be overridden with text formatting. + * + * @param textColor text color + * @return this + */ + default W tooltipTextColor(int textColor) { + tooltip().textColor(textColor); + return getThis(); + } + + /** + * Sets a tooltip scale. The whole tooltip with content will be drawn with this scale. + * + * @param scale scale + * @return this + */ + default W tooltipScale(float scale) { + tooltip().scale(scale); + return getThis(); + } + + /** + * Sets a show up timer. This is the time in ticks needed for the cursor to hover this widget for the tooltip to + * appear. + * + * @param showUpTimer show up timer in ticks + * @return this + */ + default W tooltipShowUpTimer(int showUpTimer) { + tooltip().showUpTimer(showUpTimer); + return getThis(); + } + + /** + * Sets whether the tooltip should automatically update on every render tick. In most of the cases you don't need + * this, + * as ValueSyncHandler handles tooltip update for you when value is updated. However, if you don't handle + * differently, + * you either need to manually set change listener for the sync value, or set auto update to true. + * + * @param update true if the tooltip should automatically update + * @return this + */ + default W tooltipAutoUpdate(boolean update) { + tooltip().autoUpdate(update); + return getThis(); + } + + /** + * Sets whether the tooltip has a title margin, which is 2px space between first and second line inserted by + * default. + * + * @param hasTitleMargin true if the tooltip should have a title margin + * @return this + */ + default W tooltipHasTitleMargin(boolean hasTitleMargin) { + // tooltip().setHasTitleMargin(hasTitleMargin); + return getThis(); + } + + /** + * Sets the line padding for the tooltip. 1px by default, and you can disable it by passing 0. + * + * @param linePadding line padding in px + * @return this + */ + default W tooltipLinePadding(int linePadding) { + // tooltip().setLinePadding(linePadding); + return getThis(); + } + + default W addTooltipElement(String s) { + tooltip().add(s); + return getThis(); + } + + default W addTooltipElement(IDrawable drawable) { + tooltip().add(drawable); + return getThis(); + } + + default W addTooltipLine(ITextLine line) { + tooltip().addLine(line); + return getThis(); + } + + /** + * Adds any drawable as a new line. Inlining elements is currently not possible. + * + * @param drawable drawable element. + * @return this + */ + default W addTooltipLine(IDrawable drawable) { + tooltip().add(drawable).newLine(); + return getThis(); + } + + /** + * Helper method to add a simple string as a line. Adds multiple lines if string contains \n. + * + * @param line text line + * @return this + */ + default W addTooltipLine(String line) { + return addTooltipLine(IKey.str(line)); + } + + /** + * Helper method to add multiple drawable lines. + * + * @param lines collection of drawable elements + * @return this + */ + default W addTooltipDrawableLines(Iterable lines) { + tooltip().addDrawableLines(lines); + return getThis(); + } + + /** + * Helper method to add multiple text lines. + * + * @param lines lines of text + * @return this + */ + default W addTooltipStringLines(Iterable lines) { + tooltip().addStringLines(lines); + return getThis(); + } + + default W removeAllTooltips() { + tooltip().reset(); + return getThis(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IValueWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IValueWidget.java new file mode 100644 index 00000000000..e3f7cf72915 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IValueWidget.java @@ -0,0 +1,14 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +/** + * Marks a widget as containing a value + * + * @param + */ +public interface IValueWidget extends IWidget { + + /** + * @return stored value + */ + T getWidgetValue(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IVanillaSlot.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IVanillaSlot.java new file mode 100644 index 00000000000..950b6aa86d9 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IVanillaSlot.java @@ -0,0 +1,16 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import net.minecraft.world.inventory.Slot; + +/** + * Marks a {@link IWidget} as containing a vanilla item slot. + */ +public interface IVanillaSlot { + + /** + * @return the item slot of this widget + */ + Slot getVanillaSlot(); + + boolean handleAsVanillaSlot(); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java new file mode 100644 index 00000000000..4137719be58 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/IWidget.java @@ -0,0 +1,377 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import com.gregtechceu.gtceu.api.mui.base.ITheme; +import com.gregtechceu.gtceu.api.mui.base.ITreeNode; +import com.gregtechceu.gtceu.api.mui.base.layout.IViewportStack; +import com.gregtechceu.gtceu.api.mui.theme.WidgetThemeEntry; +import com.gregtechceu.gtceu.api.mui.utils.Stencil; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.api.mui.widget.sizer.StandardResizer; +import com.gregtechceu.gtceu.client.mui.screen.ModularPanel; +import com.gregtechceu.gtceu.client.mui.screen.ModularScreen; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; +import com.gregtechceu.gtceu.utils.FormattingUtil; + +import com.google.common.base.CharMatcher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * A widget in a GUI. + */ +public interface IWidget extends ITreeNode { + + String WIDGET_TRANSLATION_KEY_FORMAT = "widget.%s.name"; + /** + * This char matcher is used to remove any non-{@code [a-z0-9_.-]} characters in translation keys. + * In essence, it + */ + CharMatcher DISALLOWED_TRANSLATION_KEY_CHARS = CharMatcher.inRange('a', 'z') + .or(CharMatcher.inRange('0', '9')) + .or(CharMatcher.anyOf("-_.")) + .negate(); + + default String getTranslationId() { + String className = FormattingUtil.toLowerCaseUnderscore(this.getClass().getSimpleName()); + className = DISALLOWED_TRANSLATION_KEY_CHARS.removeFrom(className); + return WIDGET_TRANSLATION_KEY_FORMAT.formatted(className); + } + + /** + * @return the screen this element is in + */ + ModularScreen getScreen(); + + /** + * @return the parent of this widget + */ + @NotNull + @Override + IWidget getParent(); + + @Override + default boolean hasParent() { + return isValid(); + } + + /** + * @return the context the current screen + */ + ModularGuiContext getContext(); + + /** + * @return the panel this widget is in + */ + @NotNull + ModularPanel getPanel(); + + /** + * @return the area this widget occupies + */ + Area getArea(); + + /** + * Shortcut to get the area of the parent + * + * @return parent area + */ + default Area getParentArea() { + return getParent().getArea(); + } + + /** + * Calculates if a given pos is inside this widgets area. + * This should be used over {@link Area#isInside(int, int)}, since this accounts for transformations. + * + * @param stack viewport stack + * @param mx x pos + * @param my y pos + * @return if pos is inside this widgets area + */ + default boolean isInside(IViewportStack stack, int mx, int my) { + return isInside(stack, mx, my, true); + } + + /** + * Calculates if a given pos is inside this widgets area. + * This should be used over {@link Area#isInside(int, int)}, since this accounts for transformations. + * + * @param stack viewport stack + * @param mx x pos + * @param my y pos + * @param absolute true if the position is absolute or relative to the current stack transform otherwise + * @return if pos is inside this widgets area + */ + default boolean isInside(IViewportStack stack, int mx, int my, boolean absolute) { + int x = mx; + int y = my; + if (absolute) { + x = stack.unTransformX(mx, my); + y = stack.unTransformY(mx, my); + } + return x >= 0 && x < getArea().w() && y >= 0 && y < getArea().h(); + } + + /** + * Called when the mouse hovers this element. This means this element is directly below the mouse or there are + * widgets in between which + * all allow to pass hover through. This is not called when the element is at any point below the mouse. + */ + default void onMouseStartHover() {} + + /** + * Called when the mouse no longer hovers this element. This widget can still be below the mouse on some level. + */ + default void onMouseEndHover() {} + + /** + * Called when the mouse enters this elements area with any amount of widgets above it from the current panel. + */ + default void onMouseEnterArea() {} + + /** + * Called when the mouse leaves the area, or it started hovering a different panel. + */ + default void onMouseLeaveArea() {} + + /** + * @return if this widget is currently right below the mouse + */ + default boolean isHovering() { + return isHoveringFor(0); + } + + /** + * Returns if this element is right blow the mouse for a certain amount of time + * + * @param ticks time in ticks + * @return if this element is right blow the mouse for a certain amount of time + */ + default boolean isHoveringFor(int ticks) { + return false; + } + + default boolean isBelowMouse() { + return isBelowMouseFor(0); + } + + default boolean isBelowMouseFor(int ticks) { + return false; + } + + /** + * If this widget can be seen on the screen even partly. If this returns false it will be culled. This is visually + * only! + * + * @param stack viewport stack + * @return false if this widget can not be seen currently and should not be drawn + */ + default boolean canBeSeen(IViewportStack stack) { + return Stencil.isInsideScissorArea(getArea(), stack); + } + + /** + * Determines if this widget can have any hover interaction. Interactions with mouse or keyboard like clicks ignore + * this. + * This is useful, when you have a widget which changes its background when hovered or has a tooltip and some + * decoration child. Normally + * you can click through the child, but while you hover it the widget will not show its tooltip etc. To change that + * return false here. + * + * @return if this widget can have any hover interaction + */ + default boolean canHover() { + return true; + } + + /** + * Determines if widgets below this can receive a click callback. This is only called when this widget didn't + * consume the click. + * + * @return if widgets below this should be able to receive a click + */ + default boolean canClickThrough() { + return true; + } + + default boolean canHoverThrough() { + return false; + } + + /** + * @return default width if it can't be calculated + */ + default int getDefaultWidth() { + return 18; + } + + /** + * @return default height if it can't be calculated + */ + default int getDefaultHeight() { + return 18; + } + + /** + * Validates and initialises this element. + * This element now becomes valid + * + * @param parent the parent this element belongs to + * @param late true if this is called some time after the widget tree of the parent has been initialised + */ + void initialise(@NotNull IWidget parent, boolean late); + + /** + * Invalidates this element. + */ + void dispose(); + + /** + * Determines if this element exist in an active gui. + * + * @return if this is in a valid gui + */ + boolean isValid(); + + /** + * Called 20 times per second. + */ + default void onUpdate() {} + + /** + * Draws the background of this widget. + * + * @param context gui context + * @param widgetTheme widget theme of this widget + */ + default void drawBackground(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws extra elements of this widget. Called after {@link #drawBackground(ModularGuiContext, WidgetThemeEntry)} + * and before + * {@link #drawOverlay(ModularGuiContext, WidgetThemeEntry)} + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void draw(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws the overlay of this theme. + * + * @param context gui context + * @param widgetTheme widget theme + */ + default void drawOverlay(ModularGuiContext context, WidgetThemeEntry widgetTheme) {} + + /** + * Draws foreground elements of this widget. For example tooltips. + * No transformations are applied here. + * + * @param context gui context + */ + default void drawForeground(ModularGuiContext context) {} + + default void transform(IViewportStack stack) { + stack.translate(getArea().rx, getArea().ry, 0); + } + + default Object getAdditionalHoverInfo(IViewportStack viewportStack, int mouseX, int mouseY) { + return null; + } + + default WidgetThemeEntry getWidgetTheme(ITheme theme) { + return theme.getFallback(); + } + + /** + * @return all children of this widget + */ + @NotNull + @Override + default List getChildren() { + return Collections.emptyList(); + } + + /** + * @return if this widget has any children + */ + @Override + default boolean hasChildren() { + return !getChildren().isEmpty(); + } + + void scheduleResize(); + + boolean requiresResize(); + + /** + * @return resizer of this widget + */ + @NotNull + StandardResizer resizer(); + + default IWidget resizerBuilder(Consumer builder) { + builder.accept(resizer()); + return this; + } + + /** + * Called before a widget is resized. + */ + default void beforeResize(boolean onOpen) {} + + /** + * Called after a widget is fully resized. + */ + default void onResized() {} + + /** + * Called after the full widget tree is resized and the absolute positions are calculated. + */ + default void postResize() {} + + /** + * Returns if this element is enabled. Disabled elements are not drawn and can not be interacted with. If this is + * disabled, the children + * will be considered disabled to without actually being disabled. + * + * @return if this element is enabled + */ + boolean isEnabled(); + + void setEnabled(boolean enabled); + + /** + * Checks if all ancestors are enabled. Only then this widget is visible and interactable. + * + * @return if all ancestors are enabled. + */ + default boolean areAncestorsEnabled() { + IWidget parent = this; + do { + if (!parent.isEnabled()) return false; + parent = parent.getParent(); + } while (parent.hasParent()); + return true; + } + + @Nullable + String getName(); + + default boolean isName(String name) { + return name.equals(getName()); + } + + default boolean isType(Class type) { + return type.isAssignableFrom(getClass()); + } + + default boolean isNameAndType(String name, Class type) { + return isName(name) && isType(type); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/Interactable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/Interactable.java new file mode 100644 index 00000000000..5bd576ae2ce --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/Interactable.java @@ -0,0 +1,232 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.mojang.blaze3d.platform.InputConstants; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.glfw.GLFW; + +/** + * An interface that handles user interactions on {@link IWidget} objects. + * These methods get called on the client + */ +public interface Interactable { + + /** + * Called when this widget is pressed. + * + * @param mouseX the X coordinate of the mouse. + * @param mouseY the Y coordinate of the mouse. + * @param button mouse button that was pressed. + * @return result that determines what happens to other widgets + * {@link #onMouseTapped(double, double, int)} is only called if this returns {@link Result#ACCEPT} or + * {@link Result#SUCCESS} + */ + @NotNull + default Result onMousePressed(double mouseX, double mouseY, int button) { + return Result.ACCEPT; + } + + /** + * Called when a mouse button was released over this widget. + * + * @param mouseX the X coordinate of the mouse. + * @param mouseY the Y coordinate of the mouse. + * @param button mouse button that was released. + * @return whether other widgets should get called to. If this returns false, + * {@link #onMouseTapped(double, double, int)} will NOT be called. + */ + default boolean onMouseReleased(double mouseX, double mouseY, int button) { + return false; + } + + /** + * Called when this widget was pressed and then released within a certain time frame. + * + * @param mouseX the X coordinate of the mouse. + * @param mouseY the Y coordinate of the mouse. + * @param button mouse button that was pressed. + * @return result that determines if other widgets should get tapped to + * {@link Result#IGNORE IGNORE} and {@link Result#ACCEPT ACCEPT} will both "ignore" the result and + * {@link Result#STOP STOP} and {@link Result#SUCCESS SUCCESS} will both stop other widgets + * from getting tapped. + */ + @NotNull + default Result onMouseTapped(double mouseX, double mouseY, int button) { + return Result.IGNORE; + } + + /** + * Called when a key over this widget is pressed. + * + * @param keyCode key that was pressed. + * @param scanCode character that was pressed. + * @param modifiers any modifiers that were used. + * @return result that determines what happens to other widgets + * {@link #onKeyTapped(int, int, int)} is only called if this returns {@link Result#ACCEPT} or + * {@link Result#SUCCESS} + */ + @NotNull + default Result onKeyPressed(int keyCode, int scanCode, int modifiers) { + return Result.IGNORE; + } + + /** + * Called when a key was released over this widget. + * + * @param keyCode key that was pressed. + * @param scanCode character that was pressed. + * @param modifiers any modifiers that were used. + * @return whether other widgets should get called too. If this returns false, {@link #onKeyTapped(int, int, int)} + * will NOT be called. + */ + default boolean onKeyReleased(int keyCode, int scanCode, int modifiers) { + return false; + } + + /** + * Called when this widget was pressed and then released within a certain time frame. + * + * @param keyCode key that was pressed. + * @param scanCode character that was pressed. + * @param modifiers any modifiers that were used. + * @return result that determines if other widgets should get tapped to + * {@link Result#IGNORE} and {@link Result#ACCEPT} will both "ignore" the result and {@link Result#STOP} and + * {@link Result#SUCCESS} will both stop other widgets from getting tapped. + */ + @NotNull + default Result onKeyTapped(int keyCode, int scanCode, int modifiers) { + return Result.IGNORE; + } + + /** + * Called when a key over this widget is pressed. + * + * @param codePoint character that was typed + * @param modifiers any modifiers that were used. + * @return result that determines what happens to other widgets + * {@link #onKeyTapped(int, int, int)} is only called if this returns {@link Result#ACCEPT} or + * {@link Result#SUCCESS} + */ + @NotNull + default Result onCharTyped(char codePoint, int modifiers) { + return Result.IGNORE; + } + + /** + * Called when this widget is focused or when the mouse is above this widget. + * This method should return true if it can scroll at all and not if it scrolled right now. + * If this scroll view scrolled to the end and this returns false, the scroll will get passed through another scroll + * view below this. + * + * @param mouseX the X coordinate of the mouse. + * @param mouseY the Y coordinate of the mouse. + * @param delta amount scrolled by (usually irrelevant) + * @return true if this widget can be scrolled at all + */ + default boolean onMouseScrolled(double mouseX, double mouseY, double delta) { + return false; + } + + /** + * Called when this widget was clicked and mouse is now dragging. + * + * @param mouseX current mouse X coordinate relative to the screen + * @param mouseY current mouse Y coordinate relative to the screen + * @param button mouse button that is held down + * (0 = left button, 1 = right button, 2 = scroll button, 4 and 5 = side buttons) + * @param dragX amount of drag on the X axis (e.g. the distance that has been dragged) + * @param dragY amount of drag on the Y axis (e.g. the distance that has been dragged) + */ + default void onMouseDrag(double mouseX, double mouseY, int button, double dragX, double dragY) {} + + /** + * @return if left or right ctrl/cmd is pressed + */ + @OnlyIn(Dist.CLIENT) + static boolean hasControlDown() { + return Screen.hasControlDown(); + } + + /** + * @return if left or right shift is pressed + */ + @OnlyIn(Dist.CLIENT) + static boolean hasShiftDown() { + return Screen.hasShiftDown(); + } + + /** + * @return if alt or alt gr is pressed + */ + @OnlyIn(Dist.CLIENT) + static boolean hasAltDown() { + return Screen.hasAltDown(); + } + + static boolean isModifierActive(int mod, int key) { + return (mod & key) != 0; + } + + static boolean isControl(int mod) { + return isModifierActive(mod, GLFW.GLFW_MOD_CONTROL); + } + + static boolean isShift(int mod) { + return isModifierActive(mod, GLFW.GLFW_MOD_SHIFT); + } + + static boolean isAlt(int mod) { + return isModifierActive(mod, GLFW.GLFW_MOD_ALT); + } + + /** + * @param key key id, see {@link InputConstants} + * @return if the key is pressed + */ + @OnlyIn(Dist.CLIENT) + static boolean isKeyPressed(int key) { + return InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), key); + } + + /** + * Plays the default button click sound + */ + @OnlyIn(Dist.CLIENT) + static void playButtonClickSound() { + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } + + enum Result { + + /** + * Nothing happens. + */ + IGNORE(false, false), + /** + * Interaction is accepted, but other widgets will get checked. + */ + ACCEPT(true, false), + /** + * Interaction is rejected and no other widgets will get checked. + */ + STOP(false, true), + /** + * Interaction is accepted and no other widgets will get checked. + */ + SUCCESS(true, true); + + public final boolean accepts; + public final boolean stops; + + Result(boolean accepts, boolean stops) { + this.accepts = accepts; + this.stops = stops; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ResizeDragArea.java b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ResizeDragArea.java new file mode 100644 index 00000000000..afe811efdbf --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/base/widget/ResizeDragArea.java @@ -0,0 +1,23 @@ +package com.gregtechceu.gtceu.api.mui.base.widget; + +public enum ResizeDragArea { + + TOP_LEFT(true, true, false, false), + TOP_RIGHT(true, false, false, true), + BOTTOM_LEFT(false, true, true, false), + BOTTOM_RIGHT(false, false, true, true), + TOP(true, false, false, false), + LEFT(false, true, false, false), + BOTTOM(false, false, true, false), + RIGHT(false, false, false, true); + + public final boolean top, left, bottom, right; + + ResizeDragArea(boolean top, boolean left, boolean bottom, boolean right) { + if (top && bottom || left && right) throw new IllegalArgumentException(); + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/AdaptableUITexture.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/AdaptableUITexture.java new file mode 100644 index 00000000000..e568335764a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/AdaptableUITexture.java @@ -0,0 +1,226 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.resources.ResourceLocation; + +import com.google.gson.JsonObject; +import com.mojang.blaze3d.systems.RenderSystem; +import org.joml.Matrix4f; + +/** + * This class is a
9-slice texture. It can be created using + * {@link UITexture.Builder#adaptable(int, int, int, int)}. + */ +public class AdaptableUITexture extends UITexture { + + private final int imageWidth, imageHeight, bl, bt, br, bb; + private final boolean tiled; + + /** + * Use {@link UITexture#builder()} with {@link Builder#adaptable(int, int)} + */ + AdaptableUITexture(ResourceLocation location, float u0, float v0, float u1, float v1, ColorType colorType, + boolean nonOpaque, int imageWidth, int imageHeight, int bl, int bt, int br, int bb, + boolean tiled) { + super(location, u0, v0, u1, v1, colorType, nonOpaque); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.bl = bl; + this.bt = bt; + this.br = br; + this.bb = bb; + this.tiled = tiled; + } + + @Override + public AdaptableUITexture getSubArea(float uStart, float vStart, float uEnd, float vEnd) { + return new AdaptableUITexture(this.location, lerpU(uStart), lerpV(vStart), lerpU(uEnd), lerpV(vEnd), + this.colorType, this.nonOpaque, this.imageWidth, this.imageHeight, this.bl, this.bt, this.br, this.bb, + this.tiled); + } + + @Override + public void draw(GuiContext context, float x, float y, float width, float height) { + if (width == this.imageWidth && height == this.imageHeight) { + super.draw(context, x, y, width, height); + return; + } + if (this.tiled) { + drawTiled(context, x, y, width, height); + } else { + drawStretched(context, x, y, width, height); + } + } + + public void drawStretched(GuiContext context, float x, float y, float width, float height) { + Matrix4f pose = context.getLastGraphicsPose(); + + if (this.bl <= 0 && this.bt <= 0 && this.br <= 0 && this.bb <= 0) { + super.draw(context, x, y, width, height); + return; + } + if (this.nonOpaque) { + RenderSystem.enableBlend(); + } else { + RenderSystem.disableBlend(); + } + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, this.location); + + float uBl = this.bl * 1f / this.imageWidth, uBr = this.br * 1f / this.imageWidth; + float vBt = this.bt * 1f / this.imageHeight, vBb = this.bb * 1f / this.imageHeight; + float x1 = x + width, y1 = y + height; + float uInnerStart = this.u0 + uBl, vInnerStart = this.v0 + vBt, uInnerEnd = this.u1 - uBr, + vInnerEnd = this.v1 - vBb; + + if ((this.bl > 0 || this.br > 0) && this.bt <= 0 && this.bb <= 0) { + // left border + GuiDraw.drawTexture(pose, x, y, x + this.bl, y1, this.u0, this.v0, uInnerStart, this.v1); + // right border + GuiDraw.drawTexture(pose, x1 - this.br, y, x1, y1, uInnerEnd, this.v0, this.u1, this.v1); + // center + GuiDraw.drawTexture(pose, x + this.bl, y, x1 - this.br, y1, uInnerStart, this.v0, uInnerEnd, this.v1); + } else if (this.bl <= 0 && this.br <= 0) { + // top border + GuiDraw.drawTexture(pose, x, y, x1, y + this.bt, this.u0, this.v0, this.u1, vInnerStart); + // bottom border + GuiDraw.drawTexture(pose, x, y1 - this.bb, x1, y1, this.u0, vInnerEnd, this.u1, this.v1); + // center + GuiDraw.drawTexture(pose, x, y + this.bt, x1, y1 - this.bb, this.u0, vInnerStart, this.u1, vInnerEnd); + } else { + // top left corner + GuiDraw.drawTexture(pose, x, y, x + this.bl, y + this.bt, this.u0, this.v0, uInnerStart, vInnerStart); + // top right corner + GuiDraw.drawTexture(pose, x1 - this.br, y, x1, y + this.bt, uInnerEnd, this.v0, this.u1, vInnerStart); + // bottom left corner + GuiDraw.drawTexture(pose, x, y1 - this.bb, x + this.bl, y1, this.u0, vInnerEnd, uInnerStart, this.v1); + // bottom right corner + GuiDraw.drawTexture(pose, x1 - this.br, y1 - this.bb, x1, y1, uInnerEnd, vInnerEnd, this.u1, this.v1); + + // left border + GuiDraw.drawTexture(pose, x, y + this.bt, x + this.bl, y1 - this.bb, this.u0, vInnerStart, uInnerStart, + vInnerEnd); + // top border + GuiDraw.drawTexture(pose, x + this.bl, y, x1 - this.br, y + this.bt, uInnerStart, this.v0, uInnerEnd, + vInnerStart); + // right border + GuiDraw.drawTexture(pose, x1 - this.br, y + this.bt, x1, y1 - this.bb, uInnerEnd, vInnerStart, this.u1, + vInnerEnd); + // bottom border + GuiDraw.drawTexture(pose, x + this.bl, y1 - this.bb, x1 - this.br, y1, uInnerStart, vInnerEnd, uInnerEnd, + this.v1); + + // center + GuiDraw.drawTexture(pose, x + this.bl, y + this.bt, x1 - this.br, y1 - this.bb, uInnerStart, vInnerStart, + uInnerEnd, vInnerEnd); + } + RenderSystem.disableBlend(); + } + + public void drawTiled(GuiContext context, float x, float y, float width, float height) { + Matrix4f pose = context.getLastGraphicsPose(); + + if (this.bl <= 0 && this.bt <= 0 && this.br <= 0 && this.bb <= 0) { + GuiDraw.drawTiledTexture(pose, this.location, x, y, width, height, this.u0, this.v0, this.u1, this.v1, + this.imageWidth, this.imageHeight, 0); + return; + } + if (this.nonOpaque) { + RenderSystem.enableBlend(); + } else { + RenderSystem.disableBlend(); + } + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, this.location); + + float uBl = this.bl * 1f / this.imageWidth, uBr = this.br * 1f / this.imageWidth; + float vBt = this.bt * 1f / this.imageHeight, vBb = this.bb * 1f / this.imageHeight; + float x1 = x + width, y1 = y + height; + float uInnerStart = this.u0 + uBl, vInnerStart = this.v0 + vBt, uInnerEnd = this.u1 - uBr, + vInnerEnd = this.v1 - vBb; + + int tw = (int) (this.imageWidth * (this.u1 - this.u0)); + int th = (int) (this.imageHeight * (this.v1 - this.v0)); + + if ((this.bl > 0 || this.br > 0) && this.bt <= 0 && this.bb <= 0) { + // left border + GuiDraw.drawTiledTexture(pose, x, y, this.bl, height, this.u0, this.v0, uInnerStart, this.v1, this.bl, th, + 0); + // right border + GuiDraw.drawTiledTexture(pose, x1 - this.br, y, this.br, height, uInnerEnd, this.v0, this.u1, this.v1, + this.br, th, 0); + // center + GuiDraw.drawTiledTexture(pose, x + this.bl, y, width - this.bl - this.br, height, uInnerStart, this.v0, + uInnerEnd, this.v1, tw - this.bl - this.br, th, 0); + } else if (this.bl <= 0 && this.br <= 0) { + // top border + GuiDraw.drawTiledTexture(pose, x, y, width, this.bt, this.u0, this.v0, this.u1, vInnerStart, tw, this.bt, + 0); + // bottom border + GuiDraw.drawTiledTexture(pose, x, y1 - this.bb, width, this.bb, this.u0, vInnerEnd, this.u1, this.v1, tw, + this.bb, 0); + // center + GuiDraw.drawTiledTexture(pose, x, y + this.bt, width, height - this.bt - this.bb, this.u0, vInnerStart, + this.u1, vInnerEnd, tw, th - this.bt - this.bb, 0); + } else { + // top left corner + GuiDraw.drawTiledTexture(pose, x, y, this.bl, this.bt, this.u0, this.v0, uInnerStart, vInnerStart, this.bl, + this.bt, 0); + // top right corner + GuiDraw.drawTiledTexture(pose, x1 - this.br, y, this.br, this.bt, uInnerEnd, this.v0, this.u1, vInnerStart, + this.br, this.bt, 0); + // bottom left corner + GuiDraw.drawTiledTexture(pose, x, y1 - this.bb, this.bl, this.bb, this.u0, vInnerEnd, uInnerStart, this.v1, + this.bl, this.bb, 0); + // bottom right corner + GuiDraw.drawTiledTexture(pose, x1 - this.br, y1 - this.bb, this.br, this.bb, uInnerEnd, vInnerEnd, this.u1, + this.v1, this.br, this.bb, 0); + + // left border + GuiDraw.drawTiledTexture(pose, x, y + this.bt, this.bl, height - this.bt - this.bb, this.u0, vInnerStart, + uInnerStart, vInnerEnd, this.bl, th - this.bt - this.bb, 0); + // top border + GuiDraw.drawTiledTexture(pose, x + this.bl, y, width - this.bl - this.br, this.bt, uInnerStart, this.v0, + uInnerEnd, vInnerStart, tw - this.bl - this.bb, this.bt, 0); + // right border + GuiDraw.drawTiledTexture(pose, x1 - this.br, y + this.bt, this.br, height - this.bt - this.bb, uInnerEnd, + vInnerStart, this.u1, vInnerEnd, this.br, th - this.bt - this.bb, 0); + // bottom border + GuiDraw.drawTiledTexture(pose, x + this.bl, y1 - this.bb, width - this.bl - this.br, this.bb, uInnerStart, + vInnerEnd, uInnerEnd, this.v1, tw - this.bl - this.br, this.bb, 0); + + // center + GuiDraw.drawTiledTexture(pose, x + this.bl, y + this.bt, width - this.bl - this.br, + height - this.bt - this.bb, uInnerStart, vInnerStart, uInnerEnd, vInnerEnd, + tw - this.bl - this.br, th - this.bt - this.bb, 0); + } + RenderSystem.disableBlend(); + } + + @Override + public boolean saveToJson(JsonObject json) { + super.saveToJson(json); + if (json.entrySet().size() == 1) return true; + json.addProperty("imageWidth", this.imageWidth); + json.addProperty("imageHeight", this.imageHeight); + json.addProperty("bl", this.bl); + json.addProperty("br", this.br); + json.addProperty("bt", this.bt); + json.addProperty("bb", this.bb); + json.addProperty("tiled", this.tiled); + return true; + } + + @Override + protected AdaptableUITexture copy() { + return new AdaptableUITexture(location, u0, v0, u1, v1, colorType, nonOpaque, imageWidth, imageHeight, bl, bt, + br, bb, tiled); + } + + @Override + public AdaptableUITexture withColorOverride(int color) { + return (AdaptableUITexture) super.withColorOverride(color); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/BorderDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/BorderDrawable.java new file mode 100644 index 00000000000..0dbaf0cd222 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/BorderDrawable.java @@ -0,0 +1,44 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import java.util.function.IntSupplier; + +@Accessors(fluent = true) +public class BorderDrawable implements IDrawable { + + @Getter + @Setter + private IntSupplier color = () -> 0xFFFFFFFF; + @Getter + @Setter + private int borderWidth = 2; + + public BorderDrawable() {} + + public BorderDrawable(int color, int borderWidth) { + this.color = () -> color; + this.borderWidth = borderWidth; + } + + public BorderDrawable(IntSupplier color, int borderWidth) { + this.color = color; + this.borderWidth = borderWidth; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + context.getGraphics().fill(x - borderWidth, y - borderWidth, x + borderWidth + width, y, color.getAsInt()); + context.getGraphics().fill(x - borderWidth, y - borderWidth, x, y + borderWidth + height, color.getAsInt()); + context.getGraphics().fill(x + width, y - borderWidth, x + width + borderWidth, y + height + borderWidth, + color.getAsInt()); + context.getGraphics().fill(x - borderWidth, y + height, x + borderWidth + width, y + height + borderWidth, + color.getAsInt()); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Circle.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Circle.java new file mode 100644 index 00000000000..73f6a24acfe --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Circle.java @@ -0,0 +1,94 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.animation.IAnimatable; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.Interpolations; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonObject; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Accessors(fluent = true, chain = true) +public class Circle implements IDrawable, IJsonSerializable, IAnimatable { + + @Setter + private int colorInner, colorOuter, segments; + + public Circle() { + this.colorInner = 0; + this.colorOuter = 0; + this.segments = 40; + } + + public Circle setColorInner(int colorInner) { + return colorInner(colorInner); + } + + public Circle setColorOuter(int colorOuter) { + return colorOuter(colorOuter); + } + + public Circle setColor(int inner, int outer) { + return color(inner, outer); + } + + public Circle setSegments(int segments) { + return segments(segments); + } + + public Circle color(int inner, int outer) { + this.colorInner = inner; + this.colorOuter = outer; + return this; + } + + public Circle color(int color) { + return color(color, color); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x0, int y0, int width, int height, WidgetTheme widgetTheme) { + applyColor(widgetTheme.getColor()); + GuiDraw.drawEllipse(context.getGraphics(), x0, y0, width, height, + this.colorInner, this.colorOuter, this.segments); + } + + @Override + public void loadFromJson(JsonObject json) { + this.colorInner = JsonHelper.getColor(json, Color.WHITE.main, "colorInner", "color"); + this.colorOuter = JsonHelper.getColor(json, Color.WHITE.main, "colorOuter", "color"); + this.segments = JsonHelper.getInt(json, 40, "segments"); + } + + @Override + public boolean saveToJson(JsonObject json) { + json.addProperty("colorInner", this.colorInner); + json.addProperty("colorOuter", this.colorOuter); + json.addProperty("segments", this.segments); + return true; + } + + @Override + public Circle interpolate(Circle start, Circle end, float t) { + this.colorInner = Color.lerp(start.colorInner, end.colorInner, t); + this.colorOuter = Color.lerp(start.colorOuter, end.colorOuter, t); + this.segments = Interpolations.lerp(start.segments, end.segments, t); + return this; + } + + @Override + public Circle copyOrImmutable() { + return new Circle() + .setColor(this.colorInner, this.colorOuter) + .setSegments(this.segments); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ColorType.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ColorType.java new file mode 100644 index 00000000000..865d312d993 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ColorType.java @@ -0,0 +1,36 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; + +import java.util.Map; +import java.util.function.ToIntFunction; + +public class ColorType { + + private static final Map COLOR_TYPES = new Object2ObjectOpenHashMap<>(); + + public static ColorType get(String name) { + return COLOR_TYPES.getOrDefault(name, DEFAULT); + } + + public static final ColorType DEFAULT = new ColorType("default", WidgetTheme::getColor); + public static final ColorType TEXT = new ColorType("text", WidgetTheme::getTextColor); + public static final ColorType ICON = new ColorType("default", WidgetTheme::getIconColor); + + @Getter + private final String name; + private final ToIntFunction colorGetter; + + public ColorType(String name, ToIntFunction colorGetter) { + this.name = name; + this.colorGetter = colorGetter; + COLOR_TYPES.put(name, this); + } + + public int getColor(WidgetTheme theme) { + return colorGetter.applyAsInt(theme); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateDrawable.java new file mode 100644 index 00000000000..d451834f7ed --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateDrawable.java @@ -0,0 +1,64 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class DelegateDrawable implements IDrawable { + + @NotNull + private IDrawable drawable; + + public DelegateDrawable(@Nullable IDrawable drawable) { + setDrawable(drawable); + } + + // protected, so subclasses can define mutability + protected void setDrawable(@Nullable IDrawable drawable) { + this.drawable = drawable != null ? drawable : IDrawable.EMPTY; + } + + @NotNull + public IDrawable getWrappedDrawable() { + return drawable; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + this.drawable.draw(context, x, y, width, height, widgetTheme); + } + + @Override + public boolean canApplyTheme() { + return this.drawable.canApplyTheme(); + } + + @Override + public void applyColor(int themeColor) { + this.drawable.applyColor(themeColor); + } + + @Override + public int getDefaultWidth() { + return this.drawable.getDefaultWidth(); + } + + @Override + public int getDefaultHeight() { + return this.drawable.getDefaultHeight(); + } + + @Override + public Widget asWidget() { + return this.drawable.asWidget(); + } + + @Override + public Icon asIcon() { + return this.drawable.asIcon(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateIcon.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateIcon.java new file mode 100644 index 00000000000..fd8ac8e3827 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DelegateIcon.java @@ -0,0 +1,61 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +public class DelegateIcon implements IIcon { + + private IIcon icon; + + public DelegateIcon(IIcon icon) { + this.icon = icon; + } + + @Override + public int getWidth() { + return this.icon.getWidth(); + } + + @Override + public int getHeight() { + return this.icon.getHeight(); + } + + @Override + public Box getMargin() { + return this.icon.getMargin(); + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + this.icon.draw(context, x, y, width, height, widgetTheme); + } + + @Override + public IIcon getWrappedDrawable() { + return icon; + } + + public IIcon getDelegate() { + return icon; + } + + public IIcon findRootDelegate() { + IIcon icon = this; + while (icon instanceof DelegateIcon di) { + icon = di.getDelegate(); + } + return icon; + } + + protected void setDelegate(IIcon icon) { + this.icon = icon; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.icon + ")"; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java new file mode 100644 index 00000000000..63dad61c32f --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableSerialization.java @@ -0,0 +1,224 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.utils.ObjectList; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import com.google.gson.*; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.function.Function; + +public class DrawableSerialization implements JsonSerializer, JsonDeserializer { + + private static final Map> DRAWABLE_TYPES = new Object2ObjectOpenHashMap<>(); + private static final Map, String> REVERSE_DRAWABLE_TYPES = new Object2ObjectOpenHashMap<>(); + private static final Map TEXTURES = new Object2ObjectOpenHashMap<>(); + private static final Map REVERSE_TEXTURES = new Object2ObjectOpenHashMap<>(); + + public static void registerTexture(String s, UITexture texture) { + TEXTURES.put(s, texture); + REVERSE_TEXTURES.put(texture, s); + } + + public static UITexture getTexture(String s) { + return TEXTURES.get(s); + } + + public static String getTextureId(UITexture texture) { + return REVERSE_TEXTURES.get(texture); + } + + public static < + T extends IDrawable & IJsonSerializable> void registerDrawableType(String id, Class type, + Function<@NotNull JsonObject, ? extends @NotNull IDrawable> creator) { + if (DRAWABLE_TYPES.containsKey(id)) { + throw new IllegalArgumentException("Drawable type '" + id + "' already exists!"); + } + DRAWABLE_TYPES.put(id, creator); + if (type != null) { + REVERSE_DRAWABLE_TYPES.put(type, id); + } + } + + @ApiStatus.Internal + public static void init() { + // empty, none and text are special cases + registerDrawableType("texture", UITexture.class, UITexture::parseFromJson); + registerDrawableType("color", Rectangle.class, json -> new Rectangle()); + registerDrawableType("rectangle", Rectangle.class, json -> new Rectangle()); + registerDrawableType("ellipse", Circle.class, json -> new Circle()); + registerDrawableType("item", ItemDrawable.class, ItemDrawable::ofJson); + registerDrawableType("icon", Icon.class, Icon::ofJson); + registerDrawableType("stack", DrawableStack.class, DrawableStack::parseJson); + registerDrawableType("scrollbar", Scrollbar.class, Scrollbar::ofJson); + } + + public static IDrawable deserialize(JsonElement json) { + return JsonHelper.deserialize(json, IDrawable.class); + } + + public static JsonElement serialize(IDrawable drawable) { + return JsonHelper.serialize(drawable); + } + + @Override + public IDrawable deserialize(JsonElement element, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + if (element.isJsonNull()) { + return IDrawable.EMPTY; + } + if (element.isJsonPrimitive()) { + if ("empty".equals(element.getAsString()) || "null".equals(element.getAsString())) { + return IDrawable.EMPTY; + } + if ("none".equals(element.getAsString())) { + return IDrawable.NONE; + } + } + if (element.isJsonArray()) { + return DrawableStack.parseJson(element.getAsJsonArray()); + } + if (!element.isJsonObject()) { + GTCEu.LOGGER.throwing(new JsonParseException("Drawable json should be an object or an array.")); + return IDrawable.EMPTY; + } + JsonObject json = element.getAsJsonObject(); + if (json.entrySet().isEmpty()) { + return IDrawable.EMPTY; + } + String type = JsonHelper.getString(json, "empty", "type"); + if ("text".equals(type)) { + IKey key = parseText(json); + key.loadFromJson(json); + return key; + } + if (!DRAWABLE_TYPES.containsKey(type)) { + GTCEu.LOGGER + .throwing(new JsonParseException("Drawable type '" + type + "' is not json serializable!")); + return IDrawable.EMPTY; + } + IDrawable drawable = DRAWABLE_TYPES.get(type).apply(json); + ((IJsonSerializable) drawable).loadFromJson(json); + return drawable; + } + + @Override + public JsonElement serialize(IDrawable src, Type typeOfSrc, JsonSerializationContext context) { + if (src == IDrawable.EMPTY) return JsonNull.INSTANCE; + if (src == IDrawable.NONE) return new JsonPrimitive("none"); + if (src instanceof DrawableStack drawableStack) { + JsonArray jsonArray = new JsonArray(); + for (IDrawable drawable : drawableStack.getDrawables()) { + jsonArray.add(JsonHelper.serialize(drawable)); + } + return jsonArray; + } + JsonObject json = new JsonObject(); + if (src instanceof IKey key) { + json.addProperty("type", "text"); + // TODO serialize text properly + json.addProperty("text", Component.Serializer.toJson(key.getFormatted())); + } else if (!(src instanceof IJsonSerializable serializable)) { + throw new IllegalArgumentException("Can't serialize IDrawable which doesn't implement IJsonSerializable!"); + } else { + Class type = src.getClass(); + String key = REVERSE_DRAWABLE_TYPES.get(type); + while (key == null && type != null && type != Object.class) { + type = type.getSuperclass(); + key = REVERSE_DRAWABLE_TYPES.get(type); + } + if (key == null) { + GTCEu.LOGGER.error( + "Serialization of drawable {} failed, because a key for the type could not be found!", + src.getClass().getSimpleName()); + return JsonNull.INSTANCE; + } + json.addProperty("type", key); + if (!serializable.saveToJson(json)) { + GTCEu.LOGGER.error("Serialization of drawable {} failed!", src.getClass().getSimpleName()); + } + } + return json; + } + + private static IKey parseText(JsonObject json) throws JsonParseException { + JsonParseException exception = new JsonParseException("Could not parse IKey from %s".formatted(json)); + try { + MutableComponent component = Component.Serializer.fromJson(json); + if (component != null) { + return unpackSiblings(component); + } + } catch (JsonSyntaxException e) { + exception = e; + } + JsonElement element = JsonHelper.getJsonElement(json, "text", "string", "key"); + if (element == null || element.isJsonNull()) { + return IKey.str("No text found!"); + } else if (element.isJsonPrimitive()) { + String s = element.getAsString(); + if (s.startsWith("translate:")) { + return IKey.lang(s.substring(10)); + } + return JsonHelper.getBoolean(json, false, "lang", "translate") ? IKey.lang(s) : IKey.str(s); + } else if (element.isJsonArray()) { + ObjectList strings = ObjectList.create(); + for (JsonElement element1 : element.getAsJsonArray()) { + strings.add(parseText(element1)); + } + strings.trim(); + return IKey.comp(strings.elements()); + } + throw exception; + } + + private static IKey parseText(JsonElement element) throws JsonParseException { + JsonParseException exception = new JsonParseException("Could not parse IKey from %s".formatted(element)); + try { + MutableComponent component = Component.Serializer.fromJson(element); + if (component != null) { + return IKey.lang(component); + } + } catch (JsonSyntaxException e) { + exception = e; + } + if (element.isJsonPrimitive()) { + String s = element.getAsString(); + if (s.startsWith("translate:")) { + return IKey.lang(s.substring(10)); + } + return IKey.str(s); + } + if (element.isJsonObject()) { + return parseText(element.getAsJsonObject()); + } + throw exception; + } + + private static IKey parseKeyFromJson(JsonObject json, Function keyFunction) { + return keyFunction.apply(JsonHelper.getString(json, "No text found!", "text", "string", "key")); + } + + private static IKey unpackSiblings(Component component) { + if (component.getSiblings().isEmpty()) { + return IKey.lang(component); + } + ObjectArrayList siblings = new ObjectArrayList<>(); + for (Component sibling : component.getSiblings()) { + siblings.add(unpackSiblings(sibling)); + } + siblings.trim(); + return IKey.comp(siblings.elements()); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableStack.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableStack.java new file mode 100644 index 00000000000..49aea3410a1 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DrawableStack.java @@ -0,0 +1,92 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * A stack of {@link IDrawable} backed by an array which are drawn on top of each other. + */ +public class DrawableStack implements IDrawable, IJsonSerializable { + + public static final IDrawable[] EMPTY_BACKGROUND = {}; + public static final DrawableStack EMPTY = new DrawableStack(EMPTY_BACKGROUND); + + @Getter + private final IDrawable[] drawables; + + public DrawableStack(IDrawable... drawables) { + this.drawables = drawables == null || drawables.length == 0 ? EMPTY_BACKGROUND : drawables; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + for (IDrawable drawable : this.drawables) { + if (drawable != null) drawable.draw(context, x, y, width, height, widgetTheme); + } + } + + @Override + public boolean canApplyTheme() { + for (IDrawable drawable : this.drawables) { + if (drawable != null && drawable.canApplyTheme()) { + return true; + } + } + return false; + } + + public static IDrawable parseJson(JsonObject json) { + JsonElement drawables = JsonHelper.getJsonElement(json, "drawables", "children"); + if (drawables != null && drawables.isJsonArray()) { + return parseJson(drawables.getAsJsonArray()); + } + GTCEu.LOGGER.throwing( + new JsonParseException("DrawableStack json should have an array named 'drawables' or 'children'.")); + return IDrawable.EMPTY; + } + + public static IDrawable parseJson(JsonArray drawables) { + List list = new ArrayList<>(); + for (JsonElement child : drawables) { + IDrawable drawable = JsonHelper.deserialize(child, IDrawable.class); + if (drawable != null) { + list.add(drawable); + } + } + if (list.isEmpty()) { + return IDrawable.EMPTY; + } + if (list.size() == 1) { + return list.get(0); + } + return new DrawableStack(list.toArray(IDrawable[]::new)); + } + + // this method should never be called, but the special casing code is copied here in case it does. + @Override + public boolean saveToJson(JsonObject json) { + JsonArray jsonArray = new JsonArray(); + for (IDrawable drawable : this.getDrawables()) { + jsonArray.add(JsonHelper.serialize(drawable)); + } + json.add("drawables", jsonArray); + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DynamicDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DynamicDrawable.java new file mode 100644 index 00000000000..147c2a72bcb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/DynamicDrawable.java @@ -0,0 +1,47 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.ITheme; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Getter; + +import java.util.function.Supplier; + +/** + * Takes supplier of {@link IDrawable} and draws conditional drawable. + * Return value of the supplier should be deterministic per render frame, + * in order to apply {@link ITheme} to correct object. + */ +public class DynamicDrawable implements IDrawable { + + @Getter + private final Supplier supplier; + + public DynamicDrawable(Supplier supplier) { + this.supplier = supplier; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + IDrawable drawable = this.supplier.get(); + if (drawable != null) { + drawable.draw(context, x, y, width, height, widgetTheme); + } + } + + @Override + public boolean canApplyTheme() { + IDrawable drawable = this.supplier.get(); + if (drawable != null) { + return drawable.canApplyTheme(); + } else { + return false; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/EntityDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/EntityDrawable.java new file mode 100644 index 00000000000..7429c576112 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/EntityDrawable.java @@ -0,0 +1,67 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.entity.Entity; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Nullable; + +import java.util.function.BiConsumer; + +@Accessors(fluent = true, chain = true) +public class EntityDrawable implements IDrawable { + + @Getter + protected final T entity; + @Setter + protected @Nullable BiConsumer preDraw; + @Setter + protected @Nullable BiConsumer postDraw; + + @Setter + private boolean followMouse; + private float lookTargetX = 0.0f; + private float lookTargetY = 0.0f; + + public EntityDrawable(T entity) { + this(entity, null, null); + } + + public EntityDrawable(T entity, @Nullable BiConsumer preDraw, + @Nullable BiConsumer postDraw) { + this.entity = entity; + this.preDraw = preDraw; + this.postDraw = postDraw; + } + + public EntityDrawable followMouse() { + return followMouse(true); + } + + public EntityDrawable lookTowardAngle(float xAngle, float yAngle) { + this.followMouse = false; + this.lookTargetX = xAngle; + this.lookTargetY = yAngle; + + return this; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.followMouse) { + GuiDraw.drawEntityLookingAtMouse(context.getGraphics(), this.entity, x, y, width, height, + context.getCurrentDrawingZ(), context.getMouseX(), context.getMouseY(), + this.preDraw, this.postDraw); + } else { + GuiDraw.drawEntityLookingAtAngle(context.getGraphics(), this.entity, x, y, width, height, + context.getCurrentDrawingZ(), this.lookTargetX, this.lookTargetY, + this.preDraw, this.postDraw); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FlowDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FlowDrawable.java new file mode 100644 index 00000000000..6940afde4d6 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FlowDrawable.java @@ -0,0 +1,199 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.function.IntFunction; + +public class FlowDrawable implements IDrawable { + + public static FlowDrawable row() { + return new FlowDrawable(GuiAxis.X); + } + + public static FlowDrawable column() { + return new FlowDrawable(GuiAxis.Y); + } + + @Getter + private final GuiAxis axis; + @Getter + private final List icons = new ArrayList<>(); + private Alignment.MainAxis mainAxisAlignment = Alignment.MainAxis.START; + private Alignment.CrossAxis crossAxisAlignment = Alignment.CrossAxis.CENTER; + + public FlowDrawable(GuiAxis axis) { + this.axis = axis; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.icons.isEmpty()) return; + if (this.icons.size() == 1) { + this.icons.get(0).draw(context, x, y, width, height, widgetTheme); + return; + } + + GuiAxis otherAxis = this.axis.getOther(); + int size = this.axis.isHorizontal() ? width : height; + int crossSize = this.axis.isHorizontal() ? height : width; + int pos = this.axis.isHorizontal() ? x : y; + int amount = this.icons.size(); + int childrenSize = 0; + int expandedAmount = 0; + int space = 0; // child margin + + for (IIcon icon : this.icons) { + int s = icon.getSize(this.axis); + if (s <= 0) { + expandedAmount++; + } else { + childrenSize += s; + } + } + Alignment.MainAxis maa = this.mainAxisAlignment; + if (maa == Alignment.MainAxis.SPACE_BETWEEN || maa == Alignment.MainAxis.SPACE_AROUND) { + if (expandedAmount > 0) { + maa = Alignment.MainAxis.START; + } else { + space = 0; + } + } + final int spaceCount = Math.max(amount - 1, 0); + childrenSize += spaceCount * space; + + int lastP = pos; + if (maa == Alignment.MainAxis.CENTER) { + lastP += (int) (size / 2f - childrenSize / 2f); + } else if (maa == Alignment.MainAxis.END) { + lastP += size - childrenSize; + } + + int expanderSize = (size - childrenSize) / expandedAmount; + int is, ics, ip, icp; // s = size, p = pos, c = cross + for (IIcon icon : this.icons) { + Box margin = icon.getMargin(); + int s = icon.getSize(this.axis); + int cs = icon.getSize(otherAxis); + is = s; + ics = cs; + ip = lastP; + icp = 0; + if (s <= 0) { + is = expanderSize; + if (margin != null) ip += margin.getStart(this.axis); + } + if (cs <= 0) { + ics = crossSize; + if (margin != null) { + ics -= margin.getTotal(otherAxis); + icp = margin.getStart(otherAxis); + } + } else { + if (this.crossAxisAlignment == Alignment.CrossAxis.CENTER) { + icp = (crossSize - cs) / 2; + } else if (this.crossAxisAlignment == Alignment.CrossAxis.END) { + icp = crossSize - cs; + } + } + if (this.axis.isHorizontal()) { + icon.draw(context, ip, icp, is, ics, widgetTheme); + } else { + icon.draw(context, icp, ip, ics, is, widgetTheme); + } + lastP += is; + + if (maa == Alignment.MainAxis.SPACE_BETWEEN) { + lastP += (size - childrenSize) / spaceCount; + } + } + } + + @Override + public int getDefaultWidth() { + return this.axis.isHorizontal() ? getMainAxisDefaultSize() : getCrossAxisDefaultSize(); + } + + @Override + public int getDefaultHeight() { + return this.axis.isHorizontal() ? getCrossAxisDefaultSize() : getMainAxisDefaultSize(); + } + + public int getMainAxisDefaultSize() { + int s = 0; + for (IIcon icon : this.icons) { + int is = icon.getSize(this.axis); + if (is <= 0) is = this.axis.isHorizontal() ? icon.getDefaultWidth() : icon.getDefaultHeight(); + if (is <= 0) is = 10; + s += is + icon.getMargin().getTotal(this.axis); + } + return s; + } + + public int getCrossAxisDefaultSize() { + int s = 0; + GuiAxis axis = this.axis.getOther(); + for (IIcon icon : this.icons) { + int is = icon.getSize(axis); + if (is <= 0) is = axis.isHorizontal() ? icon.getDefaultWidth() : icon.getDefaultHeight(); + if (is <= 0) is = 10; + s += is + icon.getMargin().getTotal(axis); + } + return s; + } + + public FlowDrawable mainAxisAlignment(Alignment.MainAxis maa) { + this.mainAxisAlignment = maa; + return this; + } + + public FlowDrawable crossAxisAlignment(Alignment.CrossAxis caa) { + this.crossAxisAlignment = caa; + return this; + } + + public FlowDrawable icon(IIcon icon) { + this.icons.add(icon); + return this; + } + + public FlowDrawable icons(int amount, IntFunction func) { + for (int i = 0; i < amount; i++) { + icon(func.apply(i)); + } + return this; + } + + public FlowDrawable icons(Iterable it, Function func) { + for (T t : it) { + icon(func.apply(t)); + } + return this; + } + + public FlowDrawable icons(Collection icons) { + this.icons.addAll(icons); + return this; + } + + public FlowDrawable removeIcon(IIcon icon) { + this.icons.remove(icon); + return this; + } + + public FlowDrawable removeAll() { + this.icons.clear(); + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FluidDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FluidDrawable.java new file mode 100644 index 00000000000..0052a92052b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/FluidDrawable.java @@ -0,0 +1,51 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraftforge.fluids.FluidStack; + +import org.jetbrains.annotations.NotNull; + +public class FluidDrawable implements IDrawable { + + private FluidStack fluid = null; + + public FluidDrawable() {} + + /** + * Takes a fluid stack, it can be null but will not draw anything + * + * @param fluid - fluid stack to draw + */ + public FluidDrawable(@NotNull FluidStack fluid) { + setFluid(fluid); + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + GuiDraw.drawFluidTexture(context.getGraphics(), fluid, x, y, width, height, context.getCurrentDrawingZ()); + } + + @Override + public int getDefaultWidth() { + return 16; + } + + @Override + public int getDefaultHeight() { + return 16; + } + + @Override + public Widget asWidget() { + return IDrawable.super.asWidget().size(16); + } + + public FluidDrawable setFluid(FluidStack fluid) { + this.fluid = fluid; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/GuiDraw.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/GuiDraw.java new file mode 100644 index 00000000000..dd516c1a558 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/GuiDraw.java @@ -0,0 +1,1090 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.drawable.text.TextRenderer; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.RectangleF; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.GuiSpriteManager; +import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; +import com.gregtechceu.gtceu.client.mui.screen.event.RichTooltipEvent; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.client.mui.screen.viewport.ModularGuiContext; +import com.gregtechceu.gtceu.client.renderer.GTRenderTypes; +import com.gregtechceu.gtceu.utils.FormattingUtil; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.event.RenderTooltipEvent; +import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fluids.FluidStack; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.platform.Lighting; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4d; +import org.joml.Matrix4f; +import org.joml.Quaternionf; +import org.joml.Vector3d; + +import java.util.List; +import java.util.function.BiConsumer; + +import static com.gregtechceu.gtceu.api.mui.drawable.UITexture.GUI_TEXTURE_ID_CONVERTER; +import static net.minecraft.util.Mth.HALF_PI; +import static net.minecraft.util.Mth.TWO_PI; + +public class GuiDraw { + + private static final TextRenderer textRenderer = new TextRenderer(); + + public static void drawRect(GuiGraphics graphics, float x0, float y0, float w, float h, int color) { + Matrix4f pose = graphics.pose().last().pose(); + VertexConsumer builder = graphics.bufferSource().getBuffer(RenderType.guiOverlay()); + drawRectRaw(builder, pose, x0, y0, x0 + w, y0 + h, color); + } + + public static void drawHorizontalGradientRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorLeft, int colorRight) { + drawRect(graphics, x0, y0, w, h, colorLeft, colorRight, colorLeft, colorRight); + } + + public static void drawVerticalGradientRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorTop, int colorBottom) { + drawRect(graphics, x0, y0, w, h, colorTop, colorTop, colorBottom, colorBottom); + } + + public static void drawRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorTL, int colorTR, int colorBL, int colorBR) { + Matrix4f pose = graphics.pose().last().pose(); + VertexConsumer bufferbuilder = graphics.bufferSource().getBuffer(RenderType.guiOverlay()); + + float x1 = x0 + w, y1 = y0 + h; + bufferbuilder.vertex(pose, x0, y0, 0.0f) + .color(Color.getRed(colorTL), Color.getGreen(colorTL), Color.getBlue(colorTL), Color.getAlpha(colorTL)) + .endVertex(); + bufferbuilder.vertex(pose, x0, y1, 0.0f) + .color(Color.getRed(colorBL), Color.getGreen(colorBL), Color.getBlue(colorBL), Color.getAlpha(colorBL)) + .endVertex(); + bufferbuilder.vertex(pose, x1, y1, 0.0f) + .color(Color.getRed(colorBR), Color.getGreen(colorBR), Color.getBlue(colorBR), Color.getAlpha(colorBR)) + .endVertex(); + bufferbuilder.vertex(pose, x1, y0, 0.0f) + .color(Color.getRed(colorTR), Color.getGreen(colorTR), Color.getBlue(colorTR), Color.getAlpha(colorTR)) + .endVertex(); + } + + public static void drawRectRaw(VertexConsumer buffer, Matrix4f pose, float x0, float y0, float x1, float y1, + int color) { + int r = Color.getRed(color); + int g = Color.getGreen(color); + int b = Color.getBlue(color); + int a = Color.getAlpha(color); + drawRectRaw(buffer, pose, x0, y0, x1, y1, r, g, b, a); + } + + public static void drawRectRaw(VertexConsumer buffer, Matrix4f pose, float x0, float y0, float x1, float y1, int r, + int g, int b, int a) { + buffer.vertex(pose, x0, y0, 0.0f).color(r, g, b, a).endVertex(); + buffer.vertex(pose, x0, y1, 0.0f).color(r, g, b, a).endVertex(); + buffer.vertex(pose, x1, y1, 0.0f).color(r, g, b, a).endVertex(); + buffer.vertex(pose, x1, y0, 0.0f).color(r, g, b, a).endVertex(); + } + + public static void drawCircle(GuiGraphics graphics, float x0, float y0, float diameter, int color, int segments) { + drawEllipse(graphics, x0, y0, diameter, diameter, color, color, segments); + } + + public static void drawCircle(GuiGraphics graphics, float x0, float y0, float diameter, + int centerColor, int outerColor, int segments) { + drawEllipse(graphics, x0, y0, diameter, diameter, centerColor, outerColor, segments); + } + + public static void drawEllipse(GuiGraphics graphics, float x0, float y0, float w, float h, + int color, int segments) { + drawEllipse(graphics, x0, y0, w, h, color, color, segments); + } + + public static void drawEllipse(GuiGraphics graphics, float x0, float y0, float w, float h, + int centerColor, int outerColor, int segments) { + Matrix4f pose = graphics.pose().last().pose(); + VertexConsumer bufferbuilder = graphics.bufferSource().getBuffer(GTRenderTypes.guiOverlayTriangleFan()); + + float x_2 = x0 + w / 2f, y_2 = y0 + h / 2f; + // start at center + bufferbuilder.vertex(pose, x_2, y_2, 0.0f) + .color(Color.getRed(centerColor), Color.getGreen(centerColor), Color.getBlue(centerColor), + Color.getAlpha(centerColor)) + .endVertex(); + int a = Color.getAlpha(outerColor), r = Color.getRed(outerColor), g = Color.getGreen(outerColor), + b = Color.getBlue(outerColor); + float incr = TWO_PI / segments; + for (int i = 0; i <= segments; i++) { + float angle = incr * i; + float x = Mth.sin(angle) * (w / 2) + x_2; + float y = Mth.cos(angle) * (h / 2) + y_2; + bufferbuilder.vertex(x, y, 0.0f).color(r, g, b, a).endVertex(); + } + RenderSystem.disableBlend(); + } + + public static void drawRoundedRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int color, int cornerRadius, int segments) { + drawRoundedRect(graphics, x0, y0, w, h, color, color, color, color, cornerRadius, segments); + } + + public static void drawVerticalGradientRoundedRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorTop, int colorBottom, int cornerRadius, int segments) { + drawRoundedRect(graphics, x0, y0, w, h, colorTop, colorTop, colorBottom, colorBottom, cornerRadius, segments); + } + + public static void drawHorizontalGradientRoundedRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorLeft, int colorRight, + int cornerRadius, int segments) { + drawRoundedRect(graphics, x0, y0, w, h, colorLeft, colorRight, colorLeft, colorRight, cornerRadius, segments); + } + + public static void drawRoundedRect(GuiGraphics graphics, float x0, float y0, float w, float h, + int colorTL, int colorTR, int colorBL, int colorBR, + int cornerRadius, int segments) { + Matrix4f pose = graphics.pose().last().pose(); + VertexConsumer bufferbuilder = graphics.bufferSource().getBuffer(GTRenderTypes.guiOverlayTriangleFan()); + + float x1 = x0 + w, y1 = y0 + h; + int color = Color.average(colorBL, colorBR, colorTR, colorTL); + // start at center + bufferbuilder.vertex(pose, x0 + w / 2f, y0 + h / 2f, 0.0f) + .color(Color.getRed(color), Color.getGreen(color), Color.getBlue(color), Color.getAlpha(color)) + .endVertex(); + // left side + bufferbuilder.vertex(pose, x0, y0 + cornerRadius, 0.0f) + .color(Color.getRed(colorTL), Color.getGreen(colorTL), Color.getBlue(colorTL), Color.getAlpha(colorTL)) + .endVertex(); + bufferbuilder.vertex(pose, x0, y1 - cornerRadius, 0.0f) + .color(Color.getRed(colorBL), Color.getGreen(colorBL), Color.getBlue(colorBL), Color.getAlpha(colorBL)) + .endVertex(); + // bottom left corner + for (int i = 1; i <= segments; i++) { + float x = x0 + cornerRadius - Mth.cos(HALF_PI / segments * i) * cornerRadius; + float y = y1 - cornerRadius + Mth.sin(HALF_PI / segments * i) * cornerRadius; + bufferbuilder.vertex(pose, x, y, 0.0f) + .color(Color.getRed(colorBL), Color.getGreen(colorBL), Color.getBlue(colorBL), + Color.getAlpha(colorBL)) + .endVertex(); + } + // bottom side + bufferbuilder.vertex(pose, x1 - cornerRadius, y1, 0.0f) + .color(Color.getRed(colorBR), Color.getGreen(colorBR), Color.getBlue(colorBR), Color.getAlpha(colorBR)) + .endVertex(); + // bottom right corner + for (int i = 1; i <= segments; i++) { + float x = x1 - cornerRadius + Mth.sin(HALF_PI / segments * i) * cornerRadius; + float y = y1 - cornerRadius + Mth.cos(HALF_PI / segments * i) * cornerRadius; + bufferbuilder.vertex(pose, x, y, 0.0f) + .color(Color.getRed(colorBR), Color.getGreen(colorBR), Color.getBlue(colorBR), + Color.getAlpha(colorBR)) + .endVertex(); + } + // right side + bufferbuilder.vertex(pose, x1, y0 + cornerRadius, 0.0f) + .color(Color.getRed(colorTR), Color.getGreen(colorTR), Color.getBlue(colorTR), Color.getAlpha(colorTR)) + .endVertex(); + // top right corner + for (int i = 1; i <= segments; i++) { + float x = x1 - cornerRadius + Mth.cos(HALF_PI / segments * i) * cornerRadius; + float y = y0 + cornerRadius - Mth.sin(HALF_PI / segments * i) * cornerRadius; + bufferbuilder.vertex(pose, x, y, 0.0f) + .color(Color.getRed(colorTR), Color.getGreen(colorTR), Color.getBlue(colorTR), + Color.getAlpha(colorTR)) + .endVertex(); + } + // top side + bufferbuilder.vertex(pose, x0 + cornerRadius, y0, 0.0f) + .color(Color.getRed(colorTL), Color.getGreen(colorTL), Color.getBlue(colorTL), Color.getAlpha(colorTL)) + .endVertex(); + // top left corner + for (int i = 1; i <= segments; i++) { + float x = x0 + cornerRadius - Mth.sin(HALF_PI / segments * i) * cornerRadius; + float y = y0 + cornerRadius - Mth.cos(HALF_PI / segments * i) * cornerRadius; + bufferbuilder.vertex(pose, x, y, 0.0f) + .color(Color.getRed(colorTL), Color.getGreen(colorTL), Color.getBlue(colorTL), + Color.getAlpha(colorTL)) + .endVertex(); + } + bufferbuilder.vertex(pose, x0, y0 + cornerRadius, 0.0f) + .color(Color.getRed(colorTL), Color.getGreen(colorTL), Color.getBlue(colorTL), Color.getAlpha(colorTL)) + .endVertex(); + } + + /** + * Set up the texture as a sampler differently depending on if it's part of the GUI atlas or not. + *

+ * If the texture is part of the GUI atlas, the atlas will be used for drawing and the UV coordinates will be scaled + * to the atlas texture.
+ * If it's not part of the GUI atlas, nothing special is done. + * + * @param location the texture file's location, e.g. {@code "modularui:textures/gui/slot/item.png"} + * @param u0 u0 UV coordinate + * @param v0 v0 UV coordinate + * @param u1 u1 UV coordinate + * @param v1 v1 UV coordinate + * @return If texture is in the GUI atlas, rescaled UV coordinates {@code u0, v0, u1, v1}.
+ * If not, the same coordinates that were passed in. + */ + public static RectangleF setupTexture(ResourceLocation location, float u0, float v0, float u1, float v1) { + TextureAtlasSprite sprite = GuiSpriteManager.getInstance() + .getSprite(GUI_TEXTURE_ID_CONVERTER.fileToId(location)); + + // check if the atlas doesn't have this sprite, default to using the resloc as is if so + if (!sprite.contents().name().equals(MissingTextureAtlasSprite.getLocation())) { + RenderSystem.setShaderTexture(0, sprite.atlasLocation()); + + // have to multiply by 16 here because of MC weirdness + // REMOVE THE MULTIPLICATION IN 1.21!!! + return new RectangleF(sprite.getU(u0 * 16), sprite.getV(v0 * 16), sprite.getU(u1 * 16), + sprite.getV(v1 * 16)); + } else { + RenderSystem.setShaderTexture(0, location); + return new RectangleF(u0, v0, u1, v1); + } + } + + public static boolean isGuiAtlasSprite(ResourceLocation location) { + location = GUI_TEXTURE_ID_CONVERTER.fileToId(location); + return GuiSpriteManager.getInstance().getSprite(location).atlasLocation() != + MissingTextureAtlasSprite.getLocation(); + } + + public static void drawTexture(Matrix4f pose, ResourceLocation location, float x, float y, float w, float h, + int u, int v, int textureWidth, int textureHeight) { + if (!isGuiAtlasSprite(location)) { + RenderSystem.setShaderTexture(0, location); + drawTexture(pose, x, y, u, v, w, h, textureWidth, textureHeight); + return; + } + + float tw = 1F / textureWidth; + float th = 1F / textureHeight; + RectangleF newUvs = setupTexture(location, u * tw, v * th, (u + w) * tw, (v + h) * th); + drawTexture(pose, x, y, x + w, y + h, newUvs.u0(), newUvs.v0(), newUvs.u1(), newUvs.v1()); + } + + public static void drawTexture(Matrix4f pose, float x, float y, int u, int v, float w, float h, + int textureW, int textureH) { + drawTexture(pose, x, y, u, v, w, h, textureW, textureH, 0); + } + + /** + * Draw a textured quad with given UV, dimensions and custom texture size + */ + public static void drawTexture(Matrix4f pose, float x, float y, int u, int v, float w, float h, + int textureW, int textureH, float z) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + drawTexture(pose, buffer, x, y, u, v, w, h, textureW, textureH, z); + tesselator.end(); + } + + public static void drawTexture(Matrix4f pose, VertexConsumer buffer, float x, float y, int u, int v, + float w, float h, int textureW, int textureH, float z) { + float tw = 1F / textureW; + float th = 1F / textureH; + + buffer.vertex(pose, x, y + h, z).uv(u * tw, (v + h) * th).endVertex(); + buffer.vertex(pose, x + w, y + h, z).uv((u + w) * tw, (v + h) * th).endVertex(); + buffer.vertex(pose, x + w, y, z).uv((u + w) * tw, v * th).endVertex(); + buffer.vertex(pose, x, y, z).uv(u * tw, v * th).endVertex(); + } + + public static void drawTexture(Matrix4f pose, float x, float y, int u, int v, float w, float h, + int textureW, int textureH, int tu, int tv) { + drawTexture(pose, x, y, u, v, w, h, textureW, textureH, tu, tv, 0); + } + + /** + * Draw a textured quad with given UV, dimensions and custom texture size + */ + public static void drawTexture(Matrix4f pose, float x, float y, int u, int v, float w, float h, + int textureW, int textureH, int tu, int tv, float z) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + drawTexture(pose, buffer, x, y, u, v, w, h, textureW, textureH, tu, tv, z); + tesselator.end(); + } + + public static void drawTexture(Matrix4f pose, VertexConsumer buffer, float x, float y, int u, int v, + float w, float h, int textureW, int textureH, int tu, int tv, float z) { + float tw = 1F / textureW; + float th = 1F / textureH; + + buffer.vertex(pose, x, y + h, z).uv(u * tw, tv * th).endVertex(); + buffer.vertex(pose, x + w, y + h, z).uv(tu * tw, tv * th).endVertex(); + buffer.vertex(pose, x + w, y, z).uv(tu * tw, v * th).endVertex(); + buffer.vertex(pose, x, y, z).uv(u * tw, v * th).endVertex(); + } + + public static void drawTexture(Matrix4f pose, ResourceLocation location, float x0, float y0, float x1, float y1, + float u0, float v0, float u1, float v1) { + drawTexture(pose, location, x0, y0, x1, y1, u0, v0, u1, v1, false); + } + + public static void drawTexture(Matrix4f pose, ResourceLocation location, float x0, float y0, float x1, float y1, + float u0, float v0, float u1, float v1, boolean withBlend) { + RectangleF newUvs = setupTexture(location, u0, v0, u1, v1); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + if (withBlend) { + RenderSystem.enableBlend(); + } else { + RenderSystem.disableBlend(); + } + drawTexture(pose, x0, y0, x1, y1, newUvs.u0(), newUvs.v0(), newUvs.u1(), newUvs.v1(), 0); + } + + public static void drawTexture(Matrix4f pose, float x0, float y0, float x1, float y1, + float u0, float v0, float u1, float v1) { + drawTexture(pose, x0, y0, x1, y1, u0, v0, u1, v1, 0); + } + + public static void drawTexture(Matrix4f pose, float x0, float y0, float x1, float y1, + float u0, float v0, float u1, float v1, float z) { + RenderSystem.disableDepthTest(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + drawTexture(pose, buffer, x0, y0, x1, y1, u0, v0, u1, v1, z); + tesselator.end(); + } + + public static void drawTexture(Matrix4f pose, VertexConsumer buffer, float x0, float y0, float x1, float y1, + float u0, float v0, float u1, float v1, float z) { + buffer.vertex(pose, x0, y1, z).uv(u0, v1).endVertex(); + buffer.vertex(pose, x1, y1, z).uv(u1, v1).endVertex(); + buffer.vertex(pose, x1, y0, z).uv(u1, v0).endVertex(); + buffer.vertex(pose, x0, y0, z).uv(u0, v0).endVertex(); + } + + public static void drawTiledTexture(Matrix4f pose, ResourceLocation location, float x, float y, float w, float h, + int u, int v, int tileWidth, int tileHeight, int textureWidth, + int textureHeight, float z) { + if (!isGuiAtlasSprite(location)) { + RenderSystem.setShaderTexture(0, location); + drawTiledTexture(pose, x, y, w, h, u, v, tileWidth, tileHeight, textureWidth, textureHeight, z); + return; + } + + float wRatio = 1f / textureWidth; + float hRatio = 1f / textureHeight; + RectangleF newUvs = setupTexture(location, u * wRatio, v * hRatio, (u + w) * wRatio, (v + h) * hRatio); + drawTiledTexture(pose, x, y, x + w, y + h, newUvs.u0(), newUvs.v0(), + newUvs.u1(), newUvs.v1(), tileWidth, tileHeight, z); + } + + public static void drawTiledTexture(Matrix4f pose, float x, float y, float w, float h, + int u, int v, int tileW, int tileH, int tw, int th, float z) { + int countX = (((int) w - 1) / tileW) + 1; + int countY = (((int) h - 1) / tileH) + 1; + float fillerX = w - (countX - 1) * tileW; + float fillerY = h - (countY - 1) * tileH; + + RenderSystem.disableDepthTest(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + + for (int i = 0, c = countX * countY; i < c; i++) { + int ix = i % countX; + int iy = i / countX; + float xx = x + ix * tileW; + float yy = y + iy * tileH; + float xw = ix == countX - 1 ? fillerX : tileW; + float yh = iy == countY - 1 ? fillerY : tileH; + + drawTexture(pose, buffer, xx, yy, u, v, xw, yh, tw, th, z); + } + } + + public static void drawTiledTexture(Matrix4f pose, ResourceLocation location, float x, float y, float w, float h, + float u0, float v0, float u1, float v1, + int textureWidth, int textureHeight, float z) { + RenderSystem.enableBlend(); + RectangleF newUvs = setupTexture(location, u0, v0, u1, v1); + drawTiledTexture(pose, x, y, w, h, newUvs.u0(), newUvs.v0(), newUvs.u1(), newUvs.v1(), textureWidth, + textureHeight, z); + RenderSystem.disableBlend(); + } + + public static void drawTiledTexture(Matrix4f pose, float x, float y, float w, float h, + float u0, float v0, float u1, float v1, + int tileWidth, int tileHeight, float z) { + int countX = (((int) w - 1) / tileWidth) + 1; + int countY = (((int) h - 1) / tileHeight) + 1; + float fillerX = w - (countX - 1) * tileWidth; + float fillerY = h - (countY - 1) * tileHeight; + float fillerU = u0 + (u1 - u0) * fillerX / tileWidth; + float fillerV = v0 + (v1 - v0) * fillerY / tileHeight; + + RenderSystem.disableDepthTest(); + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX); + + for (int i = 0, c = countX * countY; i < c; i++) { + int ix = i % countX; + int iy = i / countX; + float xx = x + ix * tileWidth; + float yy = y + iy * tileHeight; + float xw = tileWidth, yh = tileHeight, uEnd = u1, vEnd = v1; + if (ix == countX - 1) { + xw = fillerX; + uEnd = fillerU; + } + if (iy == countY - 1) { + yh = fillerY; + vEnd = fillerV; + } + + drawTexture(pose, buffer, xx, yy, xx + xw, yy + yh, u0, v0, uEnd, vEnd, z); + } + + tesselator.end(); + } + + /** + * Draws an entity. Note that this does NOT do any necessary setup for rendering the entity. Please see + * {@link #drawEntity(GuiGraphics, Entity, float, float, float, float, float, BiConsumer, BiConsumer)} for a full + * draw method. + * + * @param graphics the current {@link GuiGraphics} instance. + * @param entity entity to draw. + * @see #drawEntity(GuiGraphics, Entity, float, float, float, float, float, BiConsumer, BiConsumer) + */ + public static void drawEntityRaw(GuiGraphics graphics, Entity entity) { + EntityRenderDispatcher entityRenderer = Minecraft.getInstance().getEntityRenderDispatcher(); + entityRenderer.setRenderShadow(false); + + RenderSystem.runAsFancy(() -> { + entityRenderer.render(entity, 0.0D, 0.0D, 0.0D, 0.0f, Minecraft.getInstance().getPartialTick(), + graphics.pose(), graphics.bufferSource(), LightTexture.FULL_BRIGHT); + }); + graphics.flush(); + entityRenderer.setRenderShadow(true); + } + + /** + * A simple method to a draw an entity in a GUI. Using the consumers is not always ideal to modify and restore + * entity state. In those cases just copy and paste this method and put your code where the consumers would be + * called. The entity will be scaled so that it fits right in the given size when untransformed (default). When + * transforming during pre draw, you may need to manually correct the scale and offset. + * + * @param graphics the current {@link GuiGraphics} instance. + * @param entity entity to draw + * @param x x pos + * @param y y pos + * @param w the width of the area where the entity should be drawn + * @param h the height of the area where the entity should be drawn + * @param z the z layer ({@link GuiContext#getCurrentDrawingZ()} if drawn in a MUI screen) + * @param preDraw a function to call before rendering. Transform or modify the entity here. + * @param postDraw a function to call after rendering. Restore old entity state here if needed. + * @param type of the entity to render + */ + public static void drawEntity(GuiGraphics graphics, T entity, + float x, float y, float w, float h, float z, + @Nullable BiConsumer preDraw, + @Nullable BiConsumer postDraw) { + graphics.pose().pushPose(); + setupDrawEntity(graphics, entity, x, y, w, h, z); + if (preDraw != null) preDraw.accept(graphics, entity); + drawEntityRaw(graphics, entity); + if (postDraw != null) postDraw.accept(graphics, entity); + endDrawEntity(); + graphics.pose().popPose(); + } + + /** + * Draws an entity which looks in the direction of the mouse like the player render in the player inventory does. + * The code was copied from + * {@link net.minecraft.client.gui.screens.inventory.InventoryScreen#renderEntityInInventoryFollowsMouse(GuiGraphics, int, int, int, float, float, LivingEntity) + * InventoryScreen.renderEntityInInventoryFollowsMouse}. + * + * @param graphics the current {@link GuiGraphics} instance. + * @param entity entity to draw + * @param x x pos + * @param y y pos + * @param w the width of the area where the entity should be drawn + * @param h the height of the area where the entity should be drawn + * @param z the z layer ({@link GuiContext#getCurrentDrawingZ()} if drawn in a MUI screen) + * @param mouseX current x pos of the mouse + * @param mouseY current y pos of the mouse + */ + public static void drawEntityLookingAtMouse(GuiGraphics graphics, T entity, + float x, float y, float w, float h, float z, + int mouseX, int mouseY, + @Nullable BiConsumer preDraw, + @Nullable BiConsumer postDraw) { + float xAngle = (float) Math.atan((x + w / 2 - mouseX) / h); + float yAngle = (float) Math.atan((y + h / 2 - mouseY) / h); + drawEntityLookingAtAngle(graphics, entity, x, y, w, h, z, xAngle, yAngle, preDraw, postDraw); + } + + /** + * Draws an entity which looks toward a specific angle. + * The code was copied from + * {@link net.minecraft.client.gui.screens.inventory.InventoryScreen#renderEntityInInventoryFollowsAngle(GuiGraphics, int, int, int, float, float, LivingEntity) + * InventoryScreen.renderEntityInInventoryFollowsAngle}. + * + * @param graphics the current {@link GuiGraphics} instance. + * @param entity entity to draw + * @param x x pos + * @param y y pos + * @param w the width of the area where the entity should be drawn + * @param h the height of the area where the entity should be drawn + * @param z the z layer ({@link GuiContext#getCurrentDrawingZ()} if drawn in a MUI screen) + * @param xAngle the x angle to look toward + * @param yAngle the y angle to look toward + */ + public static void drawEntityLookingAtAngle(GuiGraphics graphics, T entity, + float x, float y, float w, float h, float z, + float xAngle, float yAngle, + @Nullable BiConsumer preDraw, + @Nullable BiConsumer postDraw) { + graphics.pose().pushPose(); + setupDrawEntity(graphics, entity, x, y, w, h, z); + + // pre draw + float oldYRot = entity.getYRot(); + float oldYRotO = entity.yRotO; + float oldXRot = entity.getXRot(); + float oldXRotO = entity.xRotO; + float oldYBodyRot = 0.0f; + float oldYBodyRotO = 0.0f; + float oldYHeadRotO = 0.0f; + float oldYHeadRot = 0.0f; + + entity.setYRot(entity.yRotO = 180.0f + xAngle * 40.0f); + entity.setXRot(entity.xRotO = -yAngle * 20.0f); + // made this method more generic by only updating these if the entity is a LivingEntity + if (entity instanceof LivingEntity livingEntity) { + oldYBodyRot = livingEntity.yBodyRot; + oldYBodyRotO = livingEntity.yBodyRotO; + oldYHeadRotO = livingEntity.yHeadRotO; + oldYHeadRot = livingEntity.yHeadRot; + + livingEntity.yBodyRotO = livingEntity.yBodyRot = 180.0f + xAngle * 20.0f; + livingEntity.yHeadRotO = livingEntity.yHeadRot = entity.getYRot(); + } + + // skip rotating the render by 180° on the Z axis here, because we always do that in setupDrawEntity + Quaternionf cameraRot = new Quaternionf().rotateX(yAngle * 20.0f * Mth.DEG_TO_RAD); + graphics.pose().mulPose(cameraRot); + // set the camera orientation (vanilla also does this) + cameraRot.conjugate(); + Minecraft.getInstance().getEntityRenderDispatcher().overrideCameraOrientation(cameraRot); + + if (preDraw != null) preDraw.accept(graphics, entity); + drawEntityRaw(graphics, entity); + if (postDraw != null) postDraw.accept(graphics, entity); + + // post draw + entity.setYRot(oldYRot); + entity.yRotO = oldYRotO; + entity.setXRot(oldXRot); + entity.xRotO = oldXRotO; + if (entity instanceof LivingEntity livingEntity) { + livingEntity.yBodyRot = oldYBodyRot; + livingEntity.yBodyRotO = oldYBodyRotO; + livingEntity.yHeadRotO = oldYHeadRotO; + livingEntity.yHeadRot = oldYHeadRot; + } + + endDrawEntity(); + graphics.pose().popPose(); + } + + /** + * Sets up the gl state for rendering an entity. The entity will be scaled so that it fits right in the given size + * when untransformed. + * + * @param graphics the current {@link GuiGraphics} instance. + * @param entity entity to set up drawing for + * @param x x pos + * @param y y pos + * @param w the width of the area where the entity should be drawn + * @param h the height of the area where the entity should be drawn + * @param z the z layer ({@link GuiContext#getCurrentDrawingZ()} if drawn in a MUI) + */ + public static void setupDrawEntity(GuiGraphics graphics, Entity entity, float x, float y, + float w, float h, float z) { + float size; + float scale; + if (h / entity.getBbHeight() < w / entity.getBbWidth()) { + size = entity.getBbHeight(); + scale = h / size; + } else { + size = entity.getBbWidth(); + scale = w / size; + } + graphics.pose().translate(x + w / 2, y + h / 2, z + 50.0f); + graphics.pose().scale(scale, scale, -scale); + graphics.pose().translate(0, size / 2f, 0); + graphics.pose().mulPose(new Quaternionf().rotateZ(Mth.PI)); + + Lighting.setupForEntityInInventory(); + } + + public static void endDrawEntity() { + Lighting.setupFor3DItems(); + } + + public static void drawItem(GuiGraphics graphics, ItemStack item, int x, int y, float width, float height, int z) { + if (item.isEmpty()) return; + graphics.pose().pushPose(); + graphics.pose().translate(x, y, z); + graphics.pose().scale(width / 16f, height / 16f, 1); + graphics.renderItem(item, 0, 0); + graphics.renderItemDecorations(Minecraft.getInstance().font, item, 0, 0); + graphics.pose().popPose(); + } + + public static void drawFluidTexture(GuiGraphics graphics, FluidStack content, float x0, float y0, + float width, float height, float z) { + if (content == null || content.isEmpty()) { + return; + } + Fluid fluid = content.getFluid(); + ResourceLocation fluidStill = IClientFluidTypeExtensions.of(fluid).getStillTexture(content); + TextureAtlasSprite sprite = Minecraft.getInstance().getTextureAtlas(InventoryMenu.BLOCK_ATLAS) + .apply(fluidStill); + int fluidColor = IClientFluidTypeExtensions.of(fluid).getTintColor(content); + graphics.setColor(Color.getRedF(fluidColor), Color.getGreenF(fluidColor), Color.getBlueF(fluidColor), + Color.getAlphaF(fluidColor)); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + drawTiledTexture(graphics.pose().last().pose(), InventoryMenu.BLOCK_ATLAS, x0, y0, width, height, + sprite.getU0(), sprite.getV0(), + sprite.getU1(), sprite.getV1(), sprite.contents().width(), sprite.contents().height(), z); + graphics.setColor(1f, 1f, 1f, 1f); + } + + public static void drawStandardSlotAmountText(GuiContext context, int amount, String format, Area area, + float z) { + drawAmountText(context.getMuiContext(), amount, format, 0, 0, area.width, area.height, Alignment.BottomRight, + z); + } + + public static void drawAmountText(ModularGuiContext context, int amount, String format, + int x, int y, int width, int height, Alignment alignment, float z) { + if (amount <= 1) return; + + String amountText = FormattingUtil.formatNumberReadable(amount, false); + if (format != null) { + amountText = format + amountText; + } + drawScaledAlignedTextInBox(context, amountText, x, y, width, height, alignment, 1f, z); + } + + public static void drawScaledAlignedTextInBox(ModularGuiContext context, String amountText, + int x, int y, int width, int height, + Alignment alignment) { + drawScaledAlignedTextInBox(context, amountText, x, y, width, height, alignment, 1f, 0.0f); + } + + public static void drawScaledAlignedTextInBox(ModularGuiContext context, String amountText, + int x, int y, int width, int height, + Alignment alignment, float maxScale, float z) { + if (amountText == null || amountText.isEmpty()) return; + // render the amount overlay + textRenderer.setShadow(true); + textRenderer.setScale(1f); + textRenderer.setColor(Color.WHITE.main); + textRenderer.setAlignment(alignment, width, height); + textRenderer.setPos(x, y); + textRenderer.setHardWrapOnBorder(false); + RenderSystem.disableDepthTest(); + RenderSystem.disableBlend(); + if (amountText.length() > 2 && width > 16) { // we know that numbers below 100 will always fit in standard slots + // simulate and calculate scale with width + textRenderer.setSimulate(true); + textRenderer.draw(context.getGraphics(), amountText); + textRenderer.setSimulate(false); + textRenderer.setScale(Math.min(maxScale, width / textRenderer.getLastWidth())); + } + context.graphicsPose().translate(0, 0, 100 + z); + textRenderer.draw(context.getGraphics(), amountText); + textRenderer.setHardWrapOnBorder(true); + RenderSystem.enableDepthTest(); + RenderSystem.enableBlend(); + } + + public static void drawSprite(Matrix4f pose, TextureAtlasSprite sprite, float x0, float y0, float w, float h) { + RenderSystem.enableBlend(); + RenderSystem.setShaderTexture(0, sprite.atlasLocation()); + drawTexture(pose, x0, y0, x0 + w, y0 + h, sprite.getU0(), sprite.getV0(), sprite.getU1(), sprite.getV1()); + RenderSystem.disableBlend(); + } + + public static void drawTiledSprite(Matrix4f pose, TextureAtlasSprite sprite, float x0, float y0, float w, float h) { + RenderSystem.enableBlend(); + RenderSystem.setShaderTexture(0, sprite.atlasLocation()); + drawTiledTexture(pose, sprite.atlasLocation(), x0, y0, x0 + w, y0 + h, sprite.getU0(), sprite.getV0(), + sprite.getU1(), sprite.getV1(), sprite.contents().width(), sprite.contents().height(), 0); + RenderSystem.disableBlend(); + } + + public static void drawOutlineCenter(GuiGraphics graphics, int x, int y, int offset, int color) { + drawOutlineCenter(graphics, x, y, offset, color, 1); + } + + public static void drawOutlineCenter(GuiGraphics graphics, int x, int y, int offset, int color, int border) { + drawOutline(graphics, x - offset, y - offset, x + offset, y + offset, color, border); + } + + public static void drawOutline(GuiGraphics graphics, int left, int top, int right, int bottom, int color) { + drawOutline(graphics, left, top, right, bottom, color, 1); + } + + /** + * Draw rectangle outline with given border + */ + public static void drawOutline(GuiGraphics graphics, int left, int top, int right, int bottom, + int color, int border) { + graphics.fill(left, top, left + border, bottom, color); + graphics.fill(right - border, top, right, bottom, color); + graphics.fill(left + border, top, right - border, top + border, color); + graphics.fill(left + border, bottom - border, right - border, bottom, color); + } + + private static void drawBorderLTRB(GuiGraphics graphics, float left, float top, float right, float bottom, + float border, int color, boolean outside) { + if (outside) { + left -= border; + top -= border; + right += border; + bottom += border; + } + float x0 = left, y0 = top, x1 = right, y1 = bottom, d = border; + + var buffer = graphics.bufferSource().getBuffer(GTRenderTypes.guiTriangleStrip()); + var pose = graphics.pose().last().pose(); + pc(buffer, pose, x0, y0, color); + pc(buffer, pose, x1 - d, y0 + d, color); + pc(buffer, pose, x1, y0, color); + pc(buffer, pose, x1 - d, y1 - d, color); + pc(buffer, pose, x1, y1, color); + pc(buffer, pose, x0 + d, y1 - d, color); + pc(buffer, pose, x0, y1, color); + pc(buffer, pose, x0 + d, y0 + d, color); + pc(buffer, pose, x0, y0, color); + pc(buffer, pose, x1 - d, y0 + d, color); + } + + public static void drawBorderOutsideLTRB(GuiGraphics graphics, float left, float top, float right, float bottom, + int color) { + drawBorderLTRB(graphics, left, top, right, bottom, 1, color, true); + } + + public static void drawBorderOutsideLTRB(GuiGraphics graphics, float left, float top, float right, float bottom, + float border, int color) { + drawBorderLTRB(graphics, left, top, right, bottom, border, color, true); + } + + public static void drawBorderInsideLTRB(GuiGraphics graphics, float left, float top, float right, float bottom, + int color) { + drawBorderLTRB(graphics, left, top, right, bottom, 1, color, false); + } + + public static void drawBorderInsideLTRB(GuiGraphics graphics, float left, float top, float right, float bottom, + float border, int color) { + drawBorderLTRB(graphics, left, top, right, bottom, border, color, false); + } + + private static void drawBorderXYWH(GuiGraphics graphics, float x, float y, float w, float h, float border, + int color, boolean outside) { + drawBorderLTRB(graphics, x, y, x + w, y + h, border, color, outside); + } + + public static void drawBorderOutsideXYWH(GuiGraphics graphics, float x, float y, float w, float h, float border, + int color) { + drawBorderXYWH(graphics, x, y, w, h, border, color, true); + } + + public static void drawBorderOutsideXYWH(GuiGraphics graphics, float x, float y, float w, float h, int color) { + drawBorderXYWH(graphics, x, y, w, h, 1, color, true); + } + + public static void drawBorderInsideXYWH(GuiGraphics graphics, float x, float y, float w, float h, float border, + int color) { + drawBorderXYWH(graphics, x, y, w, h, border, color, false); + } + + public static void drawBorderInsideXYWH(GuiGraphics graphics, float x, float y, float w, float h, int color) { + drawBorderXYWH(graphics, x, y, w, h, 1, color, false); + } + + private static void pc(VertexConsumer buffer, Matrix4f pose, float x, float y, int c) { + buffer.vertex(pose, x, y, 0).color(Color.getRed(c), Color.getGreen(c), Color.getBlue(c), Color.getAlpha(c)) + .endVertex(); + } + + /** + * Draws a rectangular shadow + * + * @param x left of solid shadow part + * @param y top of solid shadow part + * @param w width of solid shadow part + * @param h height of solid shadow part + * @param oX shadow gradient size in x + * @param oY shadow gradient size in y + * @param opaque solid shadow color + * @param shadow gradient end color + */ + public static void drawDropShadow(Matrix4f pose, int x, int y, int w, int h, int oX, int oY, + int opaque, int shadow) { + float a1 = Color.getAlphaF(opaque); + float r1 = Color.getRedF(opaque); + float g1 = Color.getGreenF(opaque); + float b1 = Color.getBlueF(opaque); + float a2 = Color.getAlphaF(shadow); + float r2 = Color.getRedF(shadow); + float g2 = Color.getGreenF(shadow); + float b2 = Color.getBlueF(shadow); + + RenderSystem.setShader(GameRenderer::getPositionColorShader); + RenderSystem.enableBlend(); + RenderSystem.disableDepthTest(); + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, + GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, + GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO); + + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder buffer = tesselator.getBuilder(); + + buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + + float x1 = x + w, y1 = y + h; + + /* Draw opaque part */ + buffer.vertex(pose, x1, y, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x, y, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x, y1, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x1, y1, 0).color(r1, g1, b1, a1).endVertex(); + + /* Draw top shadow */ + buffer.vertex(pose, x1 + oX, y - oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x - oX, y - oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x, y, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x1, y, 0).color(r1, g1, b1, a1).endVertex(); + + /* Draw bottom shadow */ + buffer.vertex(pose, x1, y1, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x, y1, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x - oX, y1 + oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x1 + oX, y1 + oY, 0).color(r2, g2, b2, a2).endVertex(); + + /* Draw left shadow */ + buffer.vertex(pose, x, y, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x - oX, y - oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x - oX, y1 + oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x, y1, 0).color(r1, g1, b1, a1).endVertex(); + + /* Draw right shadow */ + buffer.vertex(pose, x1 + oX, y - oY, 0).color(r2, g2, b2, a2).endVertex(); + buffer.vertex(pose, x1, y, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x1, y1, 0).color(r1, g1, b1, a1).endVertex(); + buffer.vertex(pose, x1 + oX, y1 + oY, 0).color(r2, g2, b2, a2).endVertex(); + + tesselator.end(); + RenderSystem.disableBlend(); + } + + public static void drawDropCircleShadow(GuiGraphics graphics, int x, int y, int radius, int segments, + int opaque, int shadow) { + Matrix4f pose = graphics.pose().last().pose(); + Matrix4d poseD = new Matrix4d(pose); + + float a1 = Color.getAlphaF(opaque); + float r1 = Color.getRedF(opaque); + float g1 = Color.getGreenF(opaque); + float b1 = Color.getBlueF(opaque); + float a2 = Color.getAlphaF(shadow); + float r2 = Color.getRedF(shadow); + float g2 = Color.getGreenF(shadow); + float b2 = Color.getBlueF(shadow); + + VertexConsumer buffer = graphics.bufferSource().getBuffer(GTRenderTypes.guiOverlayTriangleFan()); + buffer.vertex(pose, x, y, 0).color(r1, g1, b1, a1).endVertex(); + + Vector3d pos = new Vector3d(); + for (int i = 0; i <= segments; i++) { + float a = i / (float) segments * TWO_PI - HALF_PI; + circleVertex(buffer, poseD, pos, x, Mth.cos(a), y, Mth.sin(a), radius).color(r2, g2, b2, a2).endVertex(); + } + } + + public static void drawDropCircleShadow(GuiGraphics graphics, int x, int y, int radius, int offset, int segments, + int opaque, int shadow) { + if (offset >= radius) { + drawDropCircleShadow(graphics, x, y, radius, segments, opaque, shadow); + return; + } + Matrix4f pose = graphics.pose().last().pose(); + Matrix4d poseD = new Matrix4d(pose); + + float a1 = Color.getAlphaF(opaque); + float r1 = Color.getRedF(opaque); + float g1 = Color.getGreenF(opaque); + float b1 = Color.getBlueF(opaque); + float a2 = Color.getAlphaF(shadow); + float r2 = Color.getRedF(shadow); + float g2 = Color.getGreenF(shadow); + float b2 = Color.getBlueF(shadow); + + VertexConsumer buffer = graphics.bufferSource().getBuffer(GTRenderTypes.guiOverlayTriangleFan()); + /* Draw opaque base */ + buffer.vertex(pose, x, y, 0).color(r1, g1, b1, a1).endVertex(); + + Vector3d pos = new Vector3d(); + for (int i = 0; i <= segments; i++) { + float a = i / (float) segments * TWO_PI - HALF_PI; + circleVertex(buffer, poseD, pos, x, Mth.cos(a), y, Mth.sin(a), offset).color(r1, g1, b1, a1).endVertex(); + } + + /* Draw outer shadow */ + buffer = graphics.bufferSource().getBuffer(RenderType.gui()); + + for (int i = 0; i < segments; i++) { + float alpha1 = i / (float) segments * TWO_PI - HALF_PI; + float alpha2 = (i + 1) / (float) segments * TWO_PI - HALF_PI; + + float cosA1 = Mth.cos(alpha1); + float cosA2 = Mth.cos(alpha2); + float sinA1 = Mth.sin(alpha1); + float sinA2 = Mth.sin(alpha2); + + circleVertex(buffer, poseD, pos, x, cosA2, y, sinA2, offset).color(r1, g1, b1, a1).endVertex(); + circleVertex(buffer, poseD, pos, x, cosA1, y, sinA1, offset).color(r1, g1, b1, a1).endVertex(); + circleVertex(buffer, poseD, pos, x, cosA1, y, sinA1, radius).color(r2, g2, b2, a2).endVertex(); + circleVertex(buffer, poseD, pos, x, cosA2, y, sinA2, radius).color(r2, g2, b2, a2).endVertex(); + } + } + + private static VertexConsumer circleVertex(VertexConsumer buffer, Matrix4d pose, Vector3d pos, + float x, float xOffset, float y, float yOffset, float mul) { + pos.x = x - xOffset * mul; + pos.y = y + yOffset * mul; + pose.transformPosition(pos); + return buffer.vertex(pos.x, pos.y, pos.z); + } + + @OnlyIn(Dist.CLIENT) + public static void drawBorder(GuiGraphics graphics, float x, float y, float width, float height, + int color, float border) { + drawBorderLTRB(graphics, x, y, x + width, y + height, border, color, false); + // drawRect(graphics, x - border, y - border, width + 2 * border, border, color); + // drawRect(graphics, x - border, y + height, width + 2 * border, border, color); + // drawRect(graphics, x - border, y, border, height, color); + // drawRect(graphics, x + width, y, border, height, color); + } + + @OnlyIn(Dist.CLIENT) + public static void drawText(GuiGraphics graphics, String text, float x, float y, float scale, + int color, boolean shadow) { + graphics.pose().pushPose(); + graphics.pose().scale(scale, scale, 0f); + float sf = 1 / scale; + graphics.drawString(Minecraft.getInstance().font, text, x * sf, y * sf, color, shadow); + graphics.pose().popPose(); + } + + @OnlyIn(Dist.CLIENT) + public static void drawText(GuiGraphics graphics, Component text, float x, float y, float scale, + int color, boolean shadow) { + drawText(graphics, text.getVisualOrderText(), x, y, scale, color, shadow); + } + + @OnlyIn(Dist.CLIENT) + public static void drawText(GuiGraphics graphics, FormattedCharSequence text, float x, float y, float scale, + int color, boolean shadow) { + graphics.pose().pushPose(); + graphics.pose().scale(scale, scale, 0f); + float sf = 1 / scale; + graphics.drawString(Minecraft.getInstance().font, text, x * sf, y * sf, color, shadow); + graphics.pose().popPose(); + } + + @SuppressWarnings("UnstableApiUsage") + public static void drawTooltipBackground(GuiContext context, ItemStack stack, List lines, + int x, int y, int textWidth, int height, @Nullable RichTooltip tooltip) { + GuiGraphics graphics = context.getGraphics(); + + // TODO theme color + int backgroundTop = 0xF0100010; + int backgroundBottom = backgroundTop; + int borderColorStart = 0x505000FF; + int borderColorEnd = (borderColorStart & 0xFEFEFE) >> 1 | borderColorStart & 0xFF000000; + RenderTooltipEvent.Color colorEvent; + + if (tooltip != null) { + colorEvent = new RichTooltipEvent.Color(stack, graphics, x, y, context.getFont(), + backgroundTop, borderColorStart, borderColorEnd, lines, tooltip); + } else { + colorEvent = new RenderTooltipEvent.Color(stack, graphics, x, y, context.getFont(), + backgroundTop, borderColorStart, borderColorEnd, lines); + } + + MinecraftForge.EVENT_BUS.post(colorEvent); + backgroundTop = colorEvent.getBackgroundStart(); + backgroundBottom = colorEvent.getBackgroundEnd(); + borderColorStart = colorEvent.getBorderStart(); + borderColorEnd = colorEvent.getBorderEnd(); + + // top background border + drawVerticalGradientRect(graphics, x - 3, y - 4, textWidth + 6, 1, backgroundTop, backgroundTop); + // bottom background border + drawVerticalGradientRect(graphics, x - 3, y + height + 3, textWidth + 6, 1, backgroundBottom, backgroundBottom); + // center background + drawVerticalGradientRect(graphics, x - 3, y - 3, textWidth + 6, height + 6, backgroundTop, backgroundBottom); + // left background border + drawVerticalGradientRect(graphics, x - 4, y - 3, 1, height + 6, backgroundTop, backgroundBottom); + // right background border + drawVerticalGradientRect(graphics, x + textWidth + 3, y - 3, 1, height + 6, backgroundTop, backgroundBottom); + + // left accent border + drawVerticalGradientRect(graphics, x - 3, y - 2, 1, height + 4, borderColorStart, borderColorEnd); + // right accent border + drawVerticalGradientRect(graphics, x + textWidth + 2, y - 2, 1, height + 4, borderColorStart, borderColorEnd); + // top accent border + drawVerticalGradientRect(graphics, x - 3, y - 3, textWidth + 6, 1, borderColorStart, borderColorStart); + // bottom accent border + drawVerticalGradientRect(graphics, x - 3, y + height + 2, textWidth + 6, 1, borderColorEnd, borderColorEnd); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HoverableIcon.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HoverableIcon.java new file mode 100644 index 00000000000..ab944967ecf --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HoverableIcon.java @@ -0,0 +1,46 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IHoverable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.widget.ITooltip; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.RichTooltip; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class HoverableIcon extends DelegateIcon implements IHoverable, ITooltip { + + private final Area area = new Area(); + @Getter + private @Nullable RichTooltip tooltip; + + public HoverableIcon(IIcon icon) { + super(icon); + setRenderedAt(0, 0); + } + + @Override + public void setRenderedAt(int x, int y) { + this.area.set(x, y, getWidth(), getHeight()); + } + + @Override + public Area getRenderedArea() { + this.area.setSize(getWidth(), getHeight()); + return this.area; + } + + @Override + public @NotNull RichTooltip tooltip() { + if (this.tooltip == null) this.tooltip = new RichTooltip().parent(area -> area.set(getRenderedArea())); + return tooltip; + } + + @Override + public HoverableIcon tooltip(RichTooltip tooltip) { + this.tooltip = tooltip; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HueBar.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HueBar.java new file mode 100644 index 00000000000..11d91b38108 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/HueBar.java @@ -0,0 +1,44 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +public class HueBar implements IDrawable { + + private static final int[] COLORS = { + Color.ofHSV(60, 1f, 1f, 1f), + Color.ofHSV(120, 1f, 1f, 1f), + Color.ofHSV(180, 1f, 1f, 1f), + Color.ofHSV(240, 1f, 1f, 1f), + Color.ofHSV(300, 1f, 1f, 1f), + Color.ofHSV(0, 1f, 1f, 1f) + }; + + private final GuiAxis axis; + + public HueBar(GuiAxis axis) { + this.axis = axis; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + applyColor(widgetTheme.getColor()); + int size = this.axis.isHorizontal() ? width : height; + float step = size / 6f; + int previous = COLORS[5]; + for (int i = 0; i < 6; i++) { + int current = COLORS[i]; + if (this.axis.isHorizontal()) { + GuiDraw.drawHorizontalGradientRect(context.getGraphics(), x + step * i, y, step, height, previous, + current); + } else { + GuiDraw.drawVerticalGradientRect(context.getGraphics(), x, y + step * i, width, step, previous, + current); + } + previous = current; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Icon.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Icon.java new file mode 100644 index 00000000000..b26e8e7c513 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Icon.java @@ -0,0 +1,195 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Box; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonObject; +import lombok.Getter; + +/** + * A {@link IDrawable} wrapper with a fixed size and an alignment. + */ +public class Icon implements IIcon, IJsonSerializable { + + private final IDrawable drawable; + @Getter + private int width = 0, height = 0; + private float aspectRatio = 0; + @Getter + private Alignment alignment = Alignment.Center; + @Getter + private final Box margin = new Box(); + + public Icon(IDrawable drawable) { + this.drawable = drawable; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + x += this.margin.left(); + y += this.margin.top(); + width -= this.margin.horizontal(); + height -= this.margin.vertical(); + int frameWidth = width; + int frameHeight = height; + if (this.width > 0) width = this.width; + if (this.height > 0) height = this.height; + if (this.aspectRatio > 0) { + if (this.width <= 0) { + if (this.height <= 0) { + // width and height is unset, so adjust width or height so that one of them takes the full space + float w = width, h = height; + float properW = this.aspectRatio * h; + if (w > properW) { + width = (int) properW; + } else if (w < properW) { + height = (int) (w / this.aspectRatio); + } + } else { + // height is set, so adjust width to height + float properW = this.aspectRatio * height; + width = (int) properW; + } + } else if (this.height <= 0) { + // width is set, so adjust height to width + height = (int) (width / this.aspectRatio); + } else if (GTCEu.isDev()) { + GTCEu.LOGGER.error("Aspect ratio in Icon can't be applied when width and height are specified"); + // remove aspect ratio to avoid log spamming, it does nothing in the current state anyway + this.aspectRatio = 0; + } + } + // apply alignment + if (width != frameWidth) { + x += (int) (frameWidth * this.alignment.x - width * this.alignment.x); + } + if (height != frameHeight) { + y += (int) (frameHeight * this.alignment.y - height * this.alignment.y); + } + this.drawable.draw(context, x, y, width, height, widgetTheme); + } + + @Override + public IDrawable getWrappedDrawable() { + return drawable; + } + + public Icon expandWidth() { + return width(0); + } + + public Icon expandHeight() { + return height(0); + } + + public Icon width(int width) { + this.width = Math.max(0, width); + return this; + } + + public Icon height(int height) { + this.height = Math.max(0, height); + return this; + } + + public Icon size(int width, int height) { + return width(width).height(height); + } + + public Icon size(int size) { + return width(size).height(size); + } + + public Icon aspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; + return this; + } + + public Icon alignment(Alignment alignment) { + this.alignment = alignment; + return this; + } + + public Icon center() { + return alignment(Alignment.Center); + } + + public Icon margin(int left, int right, int top, int bottom) { + this.margin.all(left, right, top, bottom); + return this; + } + + public Icon margin(int horizontal, int vertical) { + this.margin.all(horizontal, vertical); + return this; + } + + public Icon margin(int all) { + this.margin.all(all); + return this; + } + + public Icon marginLeft(int val) { + this.margin.left(val); + return this; + } + + public Icon marginRight(int val) { + this.margin.right(val); + return this; + } + + public Icon marginTop(int val) { + this.margin.top(val); + return this; + } + + public Icon marginBottom(int val) { + this.margin.bottom(val); + return this; + } + + @Override + public void loadFromJson(JsonObject json) { + this.width = (json.has("autoWidth") || json.has("autoSize")) && + JsonHelper.getBoolean(json, true, "autoWidth", "autoSize") ? 0 : + JsonHelper.getInt(json, 0, "width", "w", "size"); + this.height = (json.has("autoHeight") || json.has("autoSize")) && + JsonHelper.getBoolean(json, true, "autoHeight", "autoSize") ? 0 : + JsonHelper.getInt(json, 0, "height", "h", "size"); + this.aspectRatio = JsonHelper.getFloat(json, 0, "aspectRatio"); + this.alignment = JsonHelper.deserialize(json, Alignment.class, Alignment.Center, "alignment", "align"); + this.margin.fromJson(json); + } + + public static Icon ofJson(JsonObject json) { + return JsonHelper.deserialize(json, IDrawable.class, IDrawable.EMPTY, "drawable", "icon").asIcon(); + } + + @Override + public boolean saveToJson(JsonObject json) { + json.add("drawable", JsonHelper.serialize(this.drawable)); + json.addProperty("width", this.width); + json.addProperty("height", this.height); + json.addProperty("aspectRatio", this.aspectRatio); + json.add("alignment", JsonHelper.serialize(this.alignment)); + this.margin.toJson(json); + return true; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + this.drawable.getClass().getSimpleName() + ")"; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IconRenderer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IconRenderer.java new file mode 100644 index 00000000000..b1975cb18eb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IconRenderer.java @@ -0,0 +1,154 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.drawable.text.StyledText; +import com.gregtechceu.gtceu.api.mui.drawable.text.TextIcon; +import com.gregtechceu.gtceu.api.mui.drawable.text.TextRenderer; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Deprecated +public class IconRenderer { + + public static final IconRenderer SHARED = new IconRenderer(); + + protected float maxWidth = -1, maxHeight = -1; + protected int x = 0, y = 0; + protected Alignment alignment = Alignment.TopLeft; + @Setter + protected float scale = 1f; + @Setter + protected boolean shadow = false; + @Setter + protected int color = 0; + @Setter + protected int linePadding = 1; + @Setter + protected boolean simulate; + @Getter + protected float lastWidth = 0, lastHeight = 0; + @Setter + protected boolean useWholeWidth = false; + + public void setAlignment(Alignment alignment, float maxWidth) { + setAlignment(alignment, maxWidth, -1); + } + + public void setAlignment(Alignment alignment, float maxWidth, float maxHeight) { + this.alignment = alignment; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + } + + public void setPos(int x, int y) { + this.x = x; + this.y = y; + } + + public void draw(GuiContext context, IDrawable text) { + draw(context, Collections.singletonList(text)); + } + + public void draw(GuiContext context, List lines) { + drawMeasuredLines(context, measureLines(lines)); + } + + public void drawMeasuredLines(GuiContext context, List lines) { + TextRenderer.SHARED.setColor(this.color); + TextRenderer.SHARED.setShadow(this.shadow); + TextRenderer.SHARED.setScale(this.scale); + TextRenderer.SHARED.setAlignment(this.alignment, this.maxWidth); + int totalHeight = -1, maxWidth = 0; + if (this.useWholeWidth) { + maxWidth = (int) this.maxWidth; + } + for (IIcon icon : lines) { + totalHeight += icon.getHeight() + this.linePadding; + if (!this.useWholeWidth && icon.getWidth() > 0) { + maxWidth = Math.max(maxWidth, icon.getWidth()); + } + } + if (!lines.isEmpty()) { + // don't add padding to last line + totalHeight -= this.linePadding; + } + int y = getStartY(totalHeight); + for (IIcon icon : lines) { + int x = icon.getWidth() > 0 ? getStartX(icon.getWidth()) : this.x; + if (!this.simulate) { + icon.draw(context, x, y, maxWidth, icon.getHeight(), WidgetTheme.getDefault().getTheme()); + } + y += (int) ((icon.getHeight() + this.linePadding) * this.scale); + } + this.lastWidth = this.maxWidth > 0 ? Math.min(this.maxWidth, maxWidth) : maxWidth; + this.lastHeight = totalHeight * this.scale; + } + + public List measureLines(List lines) { + List icons = new ArrayList<>(); + for (IDrawable element : lines) { + if (element instanceof IIcon icon) { + icons.add(icon); + } else if (element instanceof IKey key) { + float scale = this.scale; + Alignment alignment1 = this.alignment; + if (element instanceof StyledText styledText) { + scale = styledText.scale(); + alignment1 = styledText.alignment(); + } + Component text = key.get(); + int width = (int) (getFont().width(text) * scale); + icons.add(new TextIcon(text, width, (int) (getFont().lineHeight * scale), scale, alignment1)); + } else { + icons.add(element.asIcon().height(getFont().lineHeight)); + } + } + return icons; + } + + public List wrapLine(Component line, float scale) { + return this.maxWidth > 0 ? getFont().split(line, (int) (this.maxWidth / scale)) : + Collections.singletonList(line.getVisualOrderText()); + } + + protected int getStartY(int totalHeight) { + if (this.alignment.y > 0 && this.maxHeight > 0) { + float height = totalHeight * this.scale; + return (int) (this.y + (this.maxHeight * this.alignment.y) - height * this.alignment.y); + } + return this.y; + } + + protected int getStartX(float lineWidth) { + if (this.alignment.x > 0 && this.maxWidth > 0) { + return (int) (this.x + (this.maxWidth * this.alignment.x) - lineWidth * this.alignment.x); + } + return this.x; + } + + public float getFontHeight() { + return getFont().lineHeight * this.scale; + } + + @OnlyIn(Dist.CLIENT) + public static Font getFont() { + return Minecraft.getInstance().font; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IngredientDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IngredientDrawable.java new file mode 100644 index 00000000000..2d44f13ca7c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/IngredientDrawable.java @@ -0,0 +1,61 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.Util; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Tolerate; + +public class IngredientDrawable implements IDrawable, IJsonSerializable { + + @Getter + @Setter + private ItemStack[] items; + @Getter + @Setter + private int cycleTime = 1000; + + public IngredientDrawable(Ingredient ingredient) { + this(ingredient.getItems()); + } + + public IngredientDrawable(ItemStack... items) { + setItems(items); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.items.length == 0) return; + ItemStack item = this.items[(int) (Util.getMillis() % (this.cycleTime * this.items.length)) / this.cycleTime]; + if (item != null) { + GuiDraw.drawItem(context.getGraphics(), item, x, y, width, height, context.getCurrentDrawingZ()); + } + } + + /** + * Sets how many milliseconds each item shows up + * + * @param cycleTime time per item in milliseconds + * @return this + */ + @Tolerate + public IngredientDrawable cycleTime(int cycleTime) { + this.cycleTime = cycleTime; + return this; + } + + @Tolerate + public void setItems(Ingredient ingredient) { + setItems(ingredient.getItems()); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/InteractableIcon.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/InteractableIcon.java new file mode 100644 index 00000000000..b3d780e9a3a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/InteractableIcon.java @@ -0,0 +1,91 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.widget.IGuiAction; +import com.gregtechceu.gtceu.api.mui.base.widget.Interactable; + +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; + +@Accessors(fluent = true, chain = true) +public class InteractableIcon extends DelegateIcon implements Interactable { + + @Setter + private IGuiAction.MousePressed onMousePressed; + @Setter + private IGuiAction.MouseReleased onMouseReleased; + @Setter + private IGuiAction.MousePressed onMouseTapped; + @Setter + private IGuiAction.MouseScroll onMouseScrolled; + @Setter + private IGuiAction.KeyPressed onKeyPressed; + @Setter + private IGuiAction.KeyReleased onKeyReleased; + @Setter + private IGuiAction.KeyPressed onKeyTapped; + @Setter + public boolean playClickSound = true; + + public InteractableIcon(IIcon icon) { + super(icon); + } + + public void playClickSound() { + if (this.playClickSound) { + Interactable.playButtonClickSound(); + } + } + + @Override + public @NotNull Result onMousePressed(double mouseX, double mouseY, int button) { + if (this.onMousePressed != null && this.onMousePressed.press(mouseX, mouseY, button)) { + playClickSound(); + return Result.SUCCESS; + } + return Result.ACCEPT; + } + + @Override + public boolean onMouseReleased(double mouseX, double mouseY, int button) { + return this.onMouseReleased != null && this.onMouseReleased.release(mouseX, mouseY, button); + } + + @NotNull + @Override + public Result onMouseTapped(double mouseX, double mouseY, int button) { + if (this.onMouseTapped != null && this.onMouseTapped.press(mouseX, mouseY, button)) { + playClickSound(); + return Result.SUCCESS; + } + return Result.IGNORE; + } + + @Override + public @NotNull Result onKeyPressed(int keyCode, int scanCode, int modifiers) { + if (this.onKeyPressed != null && this.onKeyPressed.press(keyCode, scanCode, modifiers)) { + return Result.SUCCESS; + } + return Result.ACCEPT; + } + + @Override + public boolean onKeyReleased(int keyCode, int scanCode, int modifiers) { + return this.onKeyReleased != null && this.onKeyReleased.release(keyCode, scanCode, modifiers); + } + + @NotNull + @Override + public Result onKeyTapped(int keyCode, int scanCode, int modifiers) { + if (this.onKeyTapped != null && this.onKeyTapped.press(keyCode, scanCode, modifiers)) { + return Result.SUCCESS; + } + return Result.IGNORE; + } + + @Override + public boolean onMouseScrolled(double mouseX, double mouseY, double delta) { + return this.onMouseScrolled != null && this.onMouseScrolled.scroll(mouseX, mouseY, delta); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ItemDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ItemDrawable.java new file mode 100644 index 00000000000..49b86465cc0 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/ItemDrawable.java @@ -0,0 +1,140 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.mojang.serialization.JsonOps; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.NoSuchElementException; + +public class ItemDrawable implements IDrawable, IJsonSerializable { + + @Getter + private ItemStack item = ItemStack.EMPTY; + + public ItemDrawable() {} + + public ItemDrawable(@NotNull ItemStack item) { + setItem(item); + } + + public ItemDrawable(@NotNull Item item) { + setItem(item); + } + + public ItemDrawable(@NotNull Item item, int amount) { + setItem(item, amount); + } + + public ItemDrawable(@NotNull Item item, int amount, @Nullable CompoundTag nbt) { + setItem(item, amount, nbt); + } + + public ItemDrawable(@NotNull Block item) { + setItem(item); + } + + public ItemDrawable(@NotNull Block item, int amount) { + setItem(new ItemStack(item, amount)); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + applyColor(widgetTheme.getColor()); + GuiDraw.drawItem(context.getGraphics(), this.item, x, y, width, height, context.getCurrentDrawingZ()); + } + + @Override + public int getDefaultWidth() { + return 16; + } + + @Override + public int getDefaultHeight() { + return 16; + } + + @Override + public Widget asWidget() { + return IDrawable.super.asWidget().size(16); + } + + public ItemDrawable setItem(@NotNull ItemStack item) { + this.item = item; + return this; + } + + public ItemDrawable setItem(@NotNull Item item) { + return setItem(item, 1, null); + } + + public ItemDrawable setItem(@NotNull Item item, int amount) { + return setItem(item, amount, null); + } + + public ItemDrawable setItem(@NotNull Item item, int amount, @Nullable CompoundTag nbt) { + ItemStack itemStack = new ItemStack(item, amount); + itemStack.setTag(nbt); + return setItem(itemStack); + } + + public ItemDrawable setItem(@NotNull Block item) { + return setItem(item, 1); + } + + public ItemDrawable setItem(@NotNull Block item, int amount) { + return setItem(new ItemStack(item, amount)); + } + + public static ItemDrawable ofJson(JsonObject json) { + String itemName = JsonHelper.getString(json, null, "item"); + if (itemName == null) throw new JsonParseException("Item property not found!"); + if (itemName.isEmpty()) return new ItemDrawable(); + ItemStack stack; + try { + ResourceLocation id = new ResourceLocation(itemName); + stack = new ItemStack(BuiltInRegistries.ITEM.get(id)); + } catch (NoSuchElementException e) { + throw new JsonParseException(e); + } + if (json.has("nbt")) { + CompoundTag nbt = (CompoundTag) JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE, + JsonHelper.getObject(json, new JsonObject(), o -> o, "nbt")); + stack.setTag(nbt); + } + return new ItemDrawable(stack); + } + + @Override + public boolean saveToJson(JsonObject json) { + if (this.item == null || this.item.isEmpty()) { + json.addProperty("item", ""); + return true; + } + json.addProperty("item", this.item.getItemHolder().unwrapKey().get().location().toString()); + if (this.item.hasTag()) { + json.addProperty("nbt", this.item.getTag().toString()); + } + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/NamedDrawableRow.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/NamedDrawableRow.java new file mode 100644 index 00000000000..0a5fe5b765f --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/NamedDrawableRow.java @@ -0,0 +1,66 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +public class NamedDrawableRow implements IDrawable { + + @Getter + private IKey name; + @Getter + private IIcon drawable; + + public NamedDrawableRow() { + this(null, null); + } + + public NamedDrawableRow(@Nullable IKey name, @Nullable IIcon drawable) { + this.name = name; + this.drawable = drawable; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.name != null) { + this.name.drawAligned(context, x, y, width, height, widgetTheme, Alignment.CenterLeft); + } + if (this.drawable != null) { + int wd = this.drawable.getWidth() + this.drawable.getMargin().horizontal(); + int xd = x + width - wd; + this.drawable.draw(context, xd, y, wd, height, widgetTheme); + } + } + + @Override + public int getDefaultWidth() { + int w = 0; + if (this.name != null) w += this.name.getDefaultWidth(); + if (this.drawable != null) w += this.drawable.getWidth(); + return w; + } + + @Override + public int getDefaultHeight() { + int h = 0; + if (this.name != null) h += this.name.getDefaultHeight(); + if (this.drawable != null) h += this.drawable.getHeight(); + return h; + } + + public NamedDrawableRow name(@Nullable IKey key) { + this.name = key; + return this; + } + + public NamedDrawableRow drawable(@Nullable IIcon drawable) { + this.drawable = drawable; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Rectangle.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Rectangle.java new file mode 100644 index 00000000000..3222416724f --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Rectangle.java @@ -0,0 +1,209 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.animation.IAnimatable; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.Interpolations; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.client.renderer.GTRenderTypes; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.blaze3d.vertex.VertexConsumer; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; +import org.joml.Matrix4f; + +import java.util.function.IntConsumer; + +@Accessors(fluent = true, chain = true) +public class Rectangle implements IDrawable, IJsonSerializable, IAnimatable { + + private int cornerRadius, colorTL, colorTR, colorBL, colorBR; + @Setter + private int cornerSegments; + private float borderThickness; + @Getter + @Setter + private boolean canApplyTheme = false; + + public Rectangle() { + color(0xFFFFFFFF); + this.cornerRadius = 0; + this.cornerSegments = 6; + } + + public int getColor() { + return this.colorTL; + } + + public Rectangle cornerRadius(int cornerRadius) { + this.cornerRadius = Math.max(0, cornerRadius); + if (this.borderThickness > 0 && cornerRadius > 0) { + GTCEu.LOGGER.error("Hollow rectangles currently can't have a corner radius."); + } + return this; + } + + public Rectangle color(int colorTL, int colorTR, int colorBL, int colorBR) { + this.colorTL = colorTL; + this.colorTR = colorTR; + this.colorBL = colorBL; + this.colorBR = colorBR; + return this; + } + + public Rectangle verticalGradient(int colorTop, int colorBottom) { + return color(colorTop, colorTop, colorBottom, colorBottom); + } + + public Rectangle horizontalGradient(int colorLeft, int colorRight) { + return color(colorLeft, colorRight, colorLeft, colorRight); + } + + public Rectangle color(int color) { + return color(color, color, color, color); + } + + public Rectangle solid() { + this.borderThickness = 0; + return this; + } + + public Rectangle hollow(float borderThickness) { + this.borderThickness = borderThickness; + if (borderThickness > 0 && this.cornerRadius > 0) { + GTCEu.LOGGER.error("Hollow rectangles currently can't have a corner radius."); + } + return this; + } + + public Rectangle hollow() { + return hollow(1); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x0, int y0, int width, int height, WidgetTheme widgetTheme) { + applyColor(widgetTheme.getColor()); + if (this.borderThickness <= 0) { + if (this.cornerRadius <= 0) { + GuiDraw.drawRect(context.getGraphics(), x0, y0, width, height, + this.colorTL, this.colorTR, this.colorBL, this.colorBR); + return; + } + GuiDraw.drawRoundedRect(context.getGraphics(), x0, y0, width, height, + this.colorTL, this.colorTR, this.colorBL, this.colorBR, + this.cornerRadius, this.cornerSegments); + } else { + float d = this.borderThickness; + float x1 = x0 + width, y1 = y0 + height; + + Matrix4f pose = context.getGraphics().pose().last().pose(); + VertexConsumer bufferbuilder = context.getGraphics().bufferSource() + .getBuffer(GTRenderTypes.guiTriangleStrip()); + v(pose, bufferbuilder, x0, y0, this.colorTL); + v(pose, bufferbuilder, x1 - d, y0 + d, this.colorTR); + v(pose, bufferbuilder, x1, y0, this.colorTR); + v(pose, bufferbuilder, x1 - d, y1 - d, this.colorBR); + v(pose, bufferbuilder, x1, y1, this.colorBR); + v(pose, bufferbuilder, x0 + d, y1 - d, this.colorBL); + v(pose, bufferbuilder, x0, y1, this.colorBL); + v(pose, bufferbuilder, x0 + d, y0 + d, this.colorTL); + v(pose, bufferbuilder, x0, y0, this.colorTL); + v(pose, bufferbuilder, x1 - d, y0 + d, this.colorTR); + } + } + + private static void v(Matrix4f pose, VertexConsumer buffer, float x, float y, int c) { + buffer.vertex(pose, x, y, 0).color(Color.getRed(c), Color.getGreen(c), Color.getBlue(c), Color.getAlpha(c)) + .endVertex(); + } + + @Override + public void loadFromJson(JsonObject json) { + if (json.has("color")) { + color(Color.ofJson(json.get("color"))); + } + if (json.has("colorTop")) { + int c = Color.ofJson(json.get("colorTop")); + this.colorTL = c; + this.colorTR = c; + } + if (json.has("colorBottom")) { + int c = Color.ofJson(json.get("colorBottom")); + this.colorBL = c; + this.colorBR = c; + } + if (json.has("colorLeft")) { + int c = Color.ofJson(json.get("colorLeft")); + this.colorTL = c; + this.colorBL = c; + } + if (json.has("colorRight")) { + int c = Color.ofJson(json.get("colorRight")); + this.colorTR = c; + this.colorBR = c; + } + setColor(json, val -> this.colorTL = val, "colorTopLeft", "colorTL"); + setColor(json, val -> this.colorTR = val, "colorTopRight", "colorTR"); + setColor(json, val -> this.colorBL = val, "colorBottomLeft", "colorBL"); + setColor(json, val -> this.colorBR = val, "colorBottomRight", "colorBR"); + this.cornerRadius = JsonHelper.getInt(json, 0, "cornerRadius"); + this.cornerSegments = JsonHelper.getInt(json, 10, "cornerSegments"); + if (JsonHelper.getBoolean(json, false, "solid")) { + this.borderThickness = 0; + } else if (JsonHelper.getBoolean(json, false, "hollow")) { + this.borderThickness = 1; + } else { + this.borderThickness = JsonHelper.getFloat(json, 0, "borderThickness"); + } + } + + @Override + public boolean saveToJson(JsonObject json) { + json.addProperty("colorTL", this.colorTL); + json.addProperty("colorTR", this.colorTR); + json.addProperty("colorBL", this.colorBL); + json.addProperty("colorBR", this.colorBR); + json.addProperty("cornerRadius", this.cornerRadius); + json.addProperty("cornerSegments", this.cornerSegments); + json.addProperty("borderThickness", this.borderThickness); + return true; + } + + private void setColor(JsonObject json, IntConsumer color, String... keys) { + JsonElement element = JsonHelper.getJsonElement(json, keys); + if (element != null) { + color.accept(Color.ofJson(element)); + } + } + + @Override + public Rectangle interpolate(Rectangle start, Rectangle end, float t) { + this.cornerRadius = Interpolations.lerp(start.cornerRadius, end.cornerRadius, t); + this.cornerSegments = Interpolations.lerp(start.cornerSegments, end.cornerSegments, t); + this.colorTL = Color.lerp(start.colorTL, end.colorTL, t); + this.colorTR = Color.lerp(start.colorTR, end.colorTR, t); + this.colorBL = Color.lerp(start.colorBL, end.colorBL, t); + this.colorBR = Color.lerp(start.colorBR, end.colorBR, t); + return this; + } + + @Override + public Rectangle copyOrImmutable() { + return new Rectangle() + .color(this.colorTL, this.colorTR, this.colorBL, this.colorBR) + .cornerRadius(this.cornerRadius) + .cornerSegments(this.cornerSegments) + .canApplyTheme(this.canApplyTheme); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SchemaRenderer.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SchemaRenderer.java new file mode 100644 index 00000000000..c9f4a10a285 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SchemaRenderer.java @@ -0,0 +1,96 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.schema.ISchema; +import com.gregtechceu.gtceu.client.mui.schemarenderer.BaseSchemaRenderer; +import com.gregtechceu.gtceu.client.mui.schemarenderer.BlockHighlight; +import com.gregtechceu.gtceu.client.mui.schemarenderer.Camera; + +import net.minecraft.world.phys.BlockHitResult; + +import com.mojang.blaze3d.vertex.PoseStack; +import lombok.Setter; +import lombok.experimental.Accessors; +import lombok.experimental.Tolerate; +import org.jetbrains.annotations.NotNull; + +import java.util.function.*; + +@Accessors(fluent = true, chain = true) +public class SchemaRenderer extends BaseSchemaRenderer { + + @Setter + protected DoubleSupplier scale; + @Setter + protected BooleanSupplier disableBER; + @Setter + protected Consumer afterRender; + @Setter + protected BiConsumer cameraFunc; + @Setter + protected Supplier highlight; + @Setter + protected boolean isometric = false; + @Setter + private boolean rayTracing = false; + + public SchemaRenderer(ISchema schema) { + super(schema); + } + + @Tolerate + public SchemaRenderer scale(double scale) { + this.scale = () -> scale; + return this; + } + + @Tolerate + public SchemaRenderer disableBER(boolean disableBER) { + this.disableBER = () -> disableBER; + return this; + } + + public SchemaRenderer highlightRenderer(BlockHighlight highlight) { + this.highlight = () -> highlight; + this.rayTracing = true; + return this; + } + + @Override + protected void onSetupCamera() { + if (this.scale != null) { + camera().scaleDistanceKeepLookAt((float) this.scale.getAsDouble()); + } + if (this.cameraFunc != null) { + this.cameraFunc.accept(camera(), schema()); + } + } + + @Override + protected void onRendered() { + if (this.afterRender != null) { + this.afterRender.accept(this); + } + } + + @Override + protected void onSuccessfulRayTrace(PoseStack poseStack, @NotNull BlockHitResult result) { + if (this.highlight != null) { + this.highlight.get().renderHighlight(poseStack, result, camera().pos()); + } + } + + @Override + public boolean doRayTrace() { + return this.rayTracing; + } + + @Override + public boolean isBEREnabled() { + return this.disableBER == null || !this.disableBER.getAsBoolean(); + } + + @Override + public boolean isIsometric() { + return isometric; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Scrollbar.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Scrollbar.java new file mode 100644 index 00000000000..6886f662488 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/Scrollbar.java @@ -0,0 +1,69 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import com.google.gson.JsonObject; +import lombok.Getter; + +public class Scrollbar implements IDrawable, IJsonSerializable { + + public static final Scrollbar DEFAULT = new Scrollbar(false); + public static final Scrollbar VANILLA = new Scrollbar(true); + + public static Scrollbar ofJson(JsonObject json) { + if (JsonHelper.getBoolean(json, false, "striped", "vanilla")) { + return VANILLA; + } + return DEFAULT; + } + + @Getter + private final boolean striped; + + public Scrollbar(boolean striped) { + this.striped = striped; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + GuiDraw.drawRect(context.getGraphics(), x, y, width, height, Color.mix(0xFFEEEEEE, widgetTheme.getColor())); + GuiDraw.drawRect(context.getGraphics(), x + 1, y + 1, width - 1, height - 1, + Color.mix(0xFF666666, widgetTheme.getColor())); + GuiDraw.drawRect(context.getGraphics(), x + 1, y + 1, width - 2, height - 2, + Color.mix(0xFFAAAAAA, widgetTheme.getColor())); + + if (isStriped()) { + if (height <= 5 && width <= 5) return; + int color = widgetTheme.getTextColor(); + if (height >= width) { + int start = y + 2; + int end = height + start - 4; + for (int cy = start; cy < end; cy += 2) { + GuiDraw.drawRect(context.getGraphics(), x + 2, cy, width - 4, 1, color); + } + } else { + int start = x + 2; + int end = width + start - 4; + for (int cx = start; cx < end; cx += 2) { + GuiDraw.drawRect(context.getGraphics(), cx, y + 2, 1, height - 4, color); + } + } + } + } + + @Override + public boolean canApplyTheme() { + return true; + } + + @Override + public boolean saveToJson(JsonObject json) { + json.addProperty("striped", this.striped); + return true; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SpriteDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SpriteDrawable.java new file mode 100644 index 00000000000..8b2c374511b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/SpriteDrawable.java @@ -0,0 +1,43 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.widget.Widget; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public class SpriteDrawable implements IDrawable { + + private final TextureAtlasSprite sprite; + private boolean canApplyTheme = false; + + public SpriteDrawable(TextureAtlasSprite sprite) { + this.sprite = sprite; + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + GuiDraw.drawSprite(context.getLastGraphicsPose(), this.sprite, x, y, width, height); + } + + @Override + public Widget asWidget() { + return IDrawable.super.asWidget().size(this.sprite.contents().width(), this.sprite.contents().height()); + } + + @Override + public Icon asIcon() { + return IDrawable.super.asIcon().size(this.sprite.contents().width(), this.sprite.contents().height()); + } + + public SpriteDrawable canApplyTheme(boolean canApplyTheme) { + this.canApplyTheme = canApplyTheme; + return this; + } + + @Override + public boolean canApplyTheme() { + return canApplyTheme; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TabTexture.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TabTexture.java new file mode 100644 index 00000000000..8d7ee69158b --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TabTexture.java @@ -0,0 +1,102 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; + +import lombok.Getter; + +import java.util.Objects; + +public class TabTexture { + + public static TabTexture of(UITexture texture, GuiAxis axis, boolean positive, int width, int height, + int textureInset) { + Objects.requireNonNull(texture); + UITexture sa, ma, ea, si, mi, ei; + if (axis.isVertical() && !positive) { + si = texture.getSubArea(0, 0, 1 / 3f, 0.5f); + mi = texture.getSubArea(1 / 3f, 0, 2 / 3f, 0.5f); + ei = texture.getSubArea(2 / 3f, 0, 1f, 0.5f); + sa = texture.getSubArea(0, 0.5f, 1 / 3f, 1); + ma = texture.getSubArea(1 / 3f, 0.5f, 2 / 3f, 1); + ea = texture.getSubArea(2 / 3f, 0.5f, 1f, 1); + } else if (axis.isVertical()) { + sa = texture.getSubArea(0, 0, 1 / 3f, 0.5f); + ma = texture.getSubArea(1 / 3f, 0, 2 / 3f, 0.5f); + ea = texture.getSubArea(2 / 3f, 0, 1f, 0.5f); + si = texture.getSubArea(0, 0.5f, 1 / 3f, 1); + mi = texture.getSubArea(1 / 3f, 0.5f, 2 / 3f, 1); + ei = texture.getSubArea(2 / 3f, 0.5f, 1f, 1); + } else if (axis.isHorizontal() && !positive) { + si = texture.getSubArea(0, 0, 0.5f, 1 / 3f); + mi = texture.getSubArea(0, 1 / 3f, 0.5f, 2 / 3f); + ei = texture.getSubArea(0, 2 / 3f, 0.5f, 1f); + sa = texture.getSubArea(0.5f, 0, 1, 1 / 3f); + ma = texture.getSubArea(0.5f, 1 / 3f, 1, 2 / 3f); + ea = texture.getSubArea(0.5f, 2 / 3f, 1, 1f); + } else if (axis.isHorizontal()) { + sa = texture.getSubArea(0, 0, 0.5f, 1 / 3f); + ma = texture.getSubArea(0, 1 / 3f, 0.5f, 2 / 3f); + ea = texture.getSubArea(0, 2 / 3f, 0.5f, 1f); + si = texture.getSubArea(0.5f, 0, 1, 1 / 3f); + mi = texture.getSubArea(0.5f, 1 / 3f, 1, 2 / 3f); + ei = texture.getSubArea(0.5f, 2 / 3f, 1, 1f); + } else { + throw new IllegalArgumentException(); + } + return new TabTexture(sa, ma, ea, si, mi, ei, width, height, textureInset, axis, positive); + } + + private final UITexture startActive; + private final UITexture active; + private final UITexture endActive; + + private final UITexture startInactive; + private final UITexture inactive; + private final UITexture endInactive; + @Getter + private final int width, height; + @Getter + private final int textureInset; + @Getter + private final GuiAxis axis; + @Getter + private final boolean positive; + + public TabTexture(UITexture startActive, UITexture active, UITexture endActive, UITexture startInactive, + UITexture inactive, UITexture endInactive, int width, int height, int textureInset, GuiAxis axis, + boolean positive) { + this.startActive = startActive; + this.active = active; + this.endActive = endActive; + this.startInactive = startInactive; + this.inactive = inactive; + this.endInactive = endInactive; + this.width = width; + this.height = height; + this.textureInset = textureInset; + this.axis = axis; + this.positive = positive; + } + + public UITexture getStart(boolean active) { + return active ? this.startActive : this.startInactive; + } + + public UITexture getMiddle(boolean active) { + return active ? this.active : this.inactive; + } + + public UITexture getEnd(boolean active) { + return active ? this.endActive : this.endInactive; + } + + public UITexture get(int location, boolean active) { + if (location == 0) { + return getMiddle(active); + } + if (location < 0) { + return getStart(active); + } + return getEnd(active); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TiledUITexture.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TiledUITexture.java new file mode 100644 index 00000000000..aa94aee4431 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/TiledUITexture.java @@ -0,0 +1,55 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.resources.ResourceLocation; + +import com.google.gson.JsonObject; +import com.mojang.blaze3d.systems.RenderSystem; + +public class TiledUITexture extends UITexture { + + private final int imageWidth, imageHeight; + + /** + * Use {@link UITexture#builder()} with {@link Builder#tiled()} + */ + TiledUITexture(ResourceLocation location, float u0, float v0, float u1, float v1, int imageWidth, int imageHeight, + ColorType colorType, boolean nonOpaque) { + super(location, u0, v0, u1, v1, colorType, nonOpaque); + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + } + + @Override + public void draw(GuiContext context, float x, float y, float width, float height) { + if (width == this.imageWidth && height == this.imageHeight) { + super.draw(context, x, y, width, height); + return; + } + RenderSystem.setShader(GameRenderer::getPositionTexShader); + GuiDraw.drawTiledTexture(context.getLastGraphicsPose(), this.location, x, y, width, height, + this.u0, this.v0, this.u1, this.v1, + this.imageWidth, this.imageHeight, 0); + } + + @Override + public boolean saveToJson(JsonObject json) { + super.saveToJson(json); + if (json.entrySet().size() > 1) { + json.addProperty("tiled", true); + } + return true; + } + + @Override + protected TiledUITexture copy() { + return new TiledUITexture(location, u0, v0, u1, v1, imageWidth, imageHeight, colorType, nonOpaque); + } + + @Override + public TiledUITexture withColorOverride(int color) { + return (TiledUITexture) super.withColorOverride(color); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/UITexture.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/UITexture.java new file mode 100644 index 00000000000..d03ee52243f --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/UITexture.java @@ -0,0 +1,585 @@ +package com.gregtechceu.gtceu.api.mui.drawable; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.mui.base.IJsonSerializable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.Interpolations; +import com.gregtechceu.gtceu.api.mui.widget.sizer.Area; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; +import com.gregtechceu.gtceu.utils.serialization.json.JsonHelper; + +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import lombok.Getter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Nullable; + +@Accessors(fluent = true, chain = true) +public class UITexture implements IDrawable, IJsonSerializable { + + public static final UITexture DEFAULT = fullImage("gui/options_background", ColorType.DEFAULT); + public static final FileToIdConverter GUI_TEXTURE_ID_CONVERTER = new FileToIdConverter("textures/gui", ".png"); + + private static final ResourceLocation ICONS_LOCATION = GTCEu.id("textures/gui/icons.png"); + + // only for usage in GuiTextures + static UITexture icon(String name, int x, int y, int w, int h) { + return UITexture.builder() + .location(ICONS_LOCATION) + .imageSize(256, 256) + .subAreaXYWH(x, y, w, h) + .iconColorType() + .name(name) + .build(); + } + + static UITexture icon(String name, int x, int y) { + return icon(name, x, y, 16, 16); + } + + private static final String TEXTURES_PREFIX = "textures/"; + private static final String PNG_SUFFIX = ".png"; + + @Getter + public final ResourceLocation location; + public final float u0, v0, u1, v1; + @Getter + @Nullable + public final ColorType colorType; + public final boolean nonOpaque; + + private int colorOverride = 0; + + /** + * Creates a drawable texture + * + * @param location location of the texture + * @param u0 x offset of the image (0-1) + * @param v0 y offset of the image (0-1) + * @param u1 x end offset of the image (0-1) + * @param v1 y end offset of the image (0-1) + * @param colorType a function to get which color from a widget theme should be used to color this texture. + */ + public UITexture(ResourceLocation location, float u0, float v0, float u1, float v1, @Nullable ColorType colorType) { + this(location, u0, v0, u1, v1, colorType, false); + } + + /** + * Creates a drawable texture + * + * @param location location of the texture + * @param u0 x offset of the image (0-1) + * @param v0 y offset of the image (0-1) + * @param u1 x end offset of the image (0-1) + * @param v1 y end offset of the image (0-1) + * @param colorType a function to get which color from a widget theme should be used to color this texture. + * @param nonOpaque whether the texture should draw with blend (if true) or not (if false) + */ + public UITexture(ResourceLocation location, float u0, float v0, float u1, float v1, @Nullable ColorType colorType, + boolean nonOpaque) { + this.colorType = colorType; + boolean png = !location.getPath().endsWith(".png"); + boolean textures = !location.getPath().startsWith("textures/"); + if (png || textures) { + String path = location.getPath(); + path = png ? (textures ? TEXTURES_PREFIX + path + PNG_SUFFIX : path + PNG_SUFFIX) : TEXTURES_PREFIX + path; + location = new ResourceLocation(location.getNamespace(), path); + } + this.location = location; + this.u0 = u0; + this.v0 = v0; + this.u1 = u1; + this.v1 = v1; + this.nonOpaque = nonOpaque; + } + + public static Builder builder() { + return new Builder(); + } + + public static UITexture fullImage(ResourceLocation location) { + return new UITexture(location, 0, 0, 1, 1, null); + } + + public static UITexture fullImage(String location) { + return fullImage(new ResourceLocation(location), null); + } + + public static UITexture fullImage(String mod, String location) { + return fullImage(new ResourceLocation(mod, location), null); + } + + public static UITexture fullImage(ResourceLocation location, ColorType colorType) { + return new UITexture(location, 0, 0, 1, 1, colorType); + } + + public static UITexture fullImage(String location, ColorType colorType) { + return fullImage(new ResourceLocation(location), colorType); + } + + public static UITexture fullImage(String mod, String location, ColorType colorType) { + return fullImage(new ResourceLocation(mod, location), colorType); + } + + public UITexture getSubArea(Area bounds) { + return getSubArea(bounds.x, bounds.y, bounds.ex(), bounds.ey()); + } + + /** + * Returns a texture with a sub area relative to this area texture + * + * @param uStart x offset of the image (0-1) + * @param vStart y offset of the image (0-1) + * @param uEnd x end offset of the image (0-1) + * @param vEnd y end offset of the image (0-1) + * @return relative sub area + */ + public UITexture getSubArea(float uStart, float vStart, float uEnd, float vEnd) { + return new UITexture(this.location, lerpU(uStart), lerpV(vStart), lerpU(uEnd), lerpV(vEnd), this.colorType); + } + + protected final float lerpU(float u) { + return Interpolations.lerp(this.u0, this.u1, u); + } + + protected final float lerpV(float v) { + return Interpolations.lerp(this.v0, this.v1, v); + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + applyColor(this.colorType != null ? this.colorType.getColor(widgetTheme) : + ColorType.DEFAULT.getColor(widgetTheme)); + draw(context, (float) x, y, width, height); + } + + public void draw(GuiContext context, float x, float y, float width, float height) { + GuiDraw.drawTexture(context.getLastGraphicsPose(), this.location, x, y, x + width, y + height, this.u0, this.v0, + this.u1, this.v1); + } + + public void drawSubArea(GuiContext context, float x, float y, float width, float height, float uStart, float vStart, + float uEnd, + float vEnd, WidgetTheme widgetTheme) { + applyColor(this.colorType != null ? this.colorType.getColor(widgetTheme) : + ColorType.DEFAULT.getColor(widgetTheme)); + GuiDraw.drawTexture(context.getLastGraphicsPose(), this.location, x, y, x + width, y + height, lerpU(uStart), + lerpV(vStart), lerpU(uEnd), + lerpV(vEnd), this.nonOpaque); + } + + @Override + public boolean canApplyTheme() { + return this.colorType != null; + } + + @Override + public void applyColor(int themeColor) { + if (this.colorOverride != 0) { + Color.setGlColor(this.colorOverride); + } else { + IDrawable.super.applyColor(themeColor); + } + } + + public static UITexture parseFromJson(JsonObject json) { + String name = JsonHelper.getString(json, null, "name", "id"); + if (name != null) { + UITexture drawable = DrawableSerialization.getTexture(name); + if (drawable != null) return drawable; + } + Builder builder = builder(); + builder.location(JsonHelper.getString(json, GTCEu.MOD_ID + ":gui/widgets/error", "location")) + .imageSize(JsonHelper.getInt(json, defaultImageWidth, "imageWidth", "iw"), + JsonHelper.getInt(json, defaultImageHeight, "imageHeight", "ih")); + boolean mode1 = json.has("x") || json.has("y") || json.has("w") || json.has("h") || json.has("width") || + json.has("height"); + boolean mode2 = json.has("u0") || json.has("v0") || json.has("u1") || json.has("u1"); + if (mode1) { + if (mode2) { + throw new JsonParseException("Tried to specify x, y, w, h and u0, v0, u1, v1!"); + } + builder.subAreaXYWH(JsonHelper.getInt(json, 0, "x"), + JsonHelper.getInt(json, 0, "y"), + JsonHelper.getInt(json, builder.iw, "w", "width"), + JsonHelper.getInt(json, builder.ih, "h", "height")); + } else if (mode2) { + builder.subAreaUV(JsonHelper.getFloat(json, 0, "u0"), + JsonHelper.getFloat(json, 0, "v0"), + JsonHelper.getFloat(json, 1, "u1"), + JsonHelper.getFloat(json, 1, "v1")); + } + int bl = JsonHelper.getInt(json, 0, "bl", "borderLeft", "borderX", "border"); + int br = JsonHelper.getInt(json, 0, "br", "borderRight", "borderY", "border"); + int bt = JsonHelper.getInt(json, 0, "bt", "borderTop", "borderBottom", "border"); + int bb = JsonHelper.getInt(json, 0, "bb", "borderBottom", "borderTop", "border"); + if (bl > 0 || br > 0 || bt > 0 || bb > 0) { + builder.adaptable(bl, bt, br, bb); + } + if (JsonHelper.getBoolean(json, false, "tiled")) { + builder.tiled(); + } + String colorTypeName = JsonHelper.getString(json, null, "colorType", "color"); + if (colorTypeName != null) { + builder.colorType(ColorType.get(colorTypeName)); + } else if (JsonHelper.getBoolean(json, false, "canApplyTheme")) { + builder.canApplyTheme(); + } + UITexture uiTexture = builder.build(); + uiTexture.colorOverride = JsonHelper.getColor(json, 0, "colorOverride"); + return uiTexture; + } + + @Override + public boolean saveToJson(JsonObject json) { + String name = DrawableSerialization.getTextureId(this); + if (name != null) { + json.addProperty("id", name); + return true; + } + json.addProperty("location", this.location.toString()); + json.addProperty("u0", this.u0); + json.addProperty("v0", this.v0); + json.addProperty("u1", this.u1); + json.addProperty("v1", this.v1); + if (this.colorType != null) json.addProperty("colorType", this.colorType.getName()); + json.addProperty("colorOverride", this.colorOverride); + return true; + } + + protected UITexture copy() { + return new UITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType); + } + + public UITexture withColorOverride(int color) { + UITexture t = copy(); + t.colorOverride = color; + return t; + } + + private static int defaultImageWidth = 16, defaultImageHeight = 16; + + public static void setDefaultImageSize(int w, int h) { + defaultImageWidth = w; + defaultImageHeight = h; + } + + /** + * A builder class to help create image textures. + */ + public static class Builder { + + private ResourceLocation location; + private int iw = defaultImageWidth, ih = defaultImageHeight; + private int x, y, w, h; + private float u0 = 0, v0 = 0, u1 = 1, v1 = 1; + private Mode mode = Mode.FULL; + private int bl = 0, bt = 0, br = 0, bb = 0; + private String name; + private boolean tiled = false; + private ColorType colorType = null; + private boolean nonOpaque = false; + + /** + * @param loc location of the image to draw + */ + public Builder location(ResourceLocation loc) { + this.location = loc; + return this; + } + + /** + * @param mod mod location of the image to draw + * @param path path of the image to draw + */ + public Builder location(String mod, String path) { + this.location = new ResourceLocation(mod, path); + return this; + } + + /** + * @param path path of the image to draw in minecraft asset folder + */ + public Builder location(String path) { + this.location = new ResourceLocation(path); + return this; + } + + /** + * Set the image size. Required for {@link #tiled()}, {@link #adaptable(int, int)} and + * {@link #subAreaXYWH(int, int, int, int)} + * + * @param w image width + * @param h image height + */ + public Builder imageSize(int w, int h) { + this.iw = w; + this.ih = h; + return this; + } + + /** + * This will make the image be drawn tiled rather than stretched. + * + * @param imageWidth image width + * @param imageHeight image height + */ + public Builder tiled(int imageWidth, int imageHeight) { + return tiled().imageSize(imageWidth, imageHeight); + } + + /** + * This will make the image be drawn tiled rather than stretched. + */ + public Builder tiled() { + this.tiled = true; + return this; + } + + /** + * Will draw the whole image file. + */ + public Builder fullImage() { + this.mode = Mode.FULL; + return this; + } + + /** + * Specify a sub area of the image in pixels, with a position and a size. + * + * @param x x in pixels + * @param y y in pixels + * @param w width in pixels + * @param h height in pixels + */ + public Builder subAreaXYWH(int x, int y, int w, int h) { + this.mode = Mode.PIXEL; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + return this; + } + + /** + * Specify a sub area of the image in pixels, with a start position and an end position. + * + * @param left start position on the x-axis (equivalent to x in above methods) + * @param top start position on the y-axis (equivalent to y in above methods) + * @param right end position on the x-axis (equivalent to x + w in above methods) + * @param bottom end position on the y-axis (equivalent to y + h in above methods) + */ + public Builder subAreaLTRB(int left, int top, int right, int bottom) { + return subAreaXYWH(left, top, right - left, bottom - top); + } + + /** + * Specify a sub area of the image in relative uv values (0 - 1). u0 and v0 are start positions, while u1 and v1 + * are end positions. + * This means that the relative size is u1 - u0 and v1 - v0. + * + * @param u0 x start + * @param v0 y start + * @param u1 x end + * @param v1 y end + */ + public Builder subAreaUV(float u0, float v0, float u1, float v1) { + this.mode = Mode.RELATIVE; + this.u0 = u0; + this.v0 = v0; + this.u1 = u1; + this.v1 = v1; + return this; + } + + /** + * This will draw the corners, edges and body of the image separately. This will only stretch/tile the + * body so the border looks right on all sizes. This is also known as a + * 9-slice texture. + * + * @param bl left border width. Can be 0. + * @param bt top border width. Can be 0. + * @param br right border width. Can be 0. + * @param bb bottom border width. Can be 0. + */ + public Builder adaptable(int bl, int bt, int br, int bb) { + this.bl = bl; + this.bt = bt; + this.br = br; + this.bb = bb; + return this; + } + + /** + * This will draw the corners, edges and body of the image separately. This will only stretch/tile the + * body so the border looks right on all sizes. This is also known as a + * 9-slice texture. + * + * @param borderX left and right border width. Can be 0. + * @param borderY top and bottom border width. Can be 0 + */ + public Builder adaptable(int borderX, int borderY) { + return adaptable(borderX, borderY, borderX, borderY); + } + + /** + * This will draw the corners, edges and body of the image separately. This will only stretch/tile the + * body so the border looks right on all sizes. This is also known as a + * 9-slice texture. + * + * @param border border width + */ + public Builder adaptable(int border) { + return adaptable(border, border); + } + + /** + * Specify if theme color should apply to this texture. + * + * @see #defaultColorType() + */ + public Builder canApplyTheme() { + return defaultColorType(); + } + + /** + * Sets a function which defines how theme color is applied to this texture. Null means no color will be + * applied. + * + *

  • Background textures should use {@link ColorType#DEFAULT} or {@link #defaultColorType()}
  • + *
  • White icons (only has a shape and some grey shading) should use {@link ColorType#ICON} or + * {@link #iconColorType()}
  • + *
  • Text should use {@link ColorType#TEXT} or {@link #textColorType()}
  • + *
  • Everything else (f.e. colored icons and overlays) should use null
  • + * + * + * @param colorType function which defines how theme color is applied to this texture + * @return this + */ + public Builder colorType(@Nullable ColorType colorType) { + this.colorType = colorType; + return this; + } + + /** + * Sets this texture to use default theme color. + * Usually used for background textures (grey shaded). + * + * @return this + * @see #colorType(ColorType) + */ + public Builder defaultColorType() { + return colorType(ColorType.DEFAULT); + } + + /** + * Sets this texture to use text theme color. + * Usually used for texts. + * + * @return this + * @see #colorType(ColorType) + */ + public Builder textColorType() { + return colorType(ColorType.TEXT); + } + + /** + * Sets this texture to use icon theme color. + * Usually used for grey shaded icons without color. + * + * @return this + * @see #colorType(ColorType) + */ + public Builder iconColorType() { + return colorType(ColorType.ICON); + } + + /** + * Registers the texture with a name, so it can be used in json without creating the texture again. + * By default, theme color is applicable. + * + * @param name texture name + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * Sets this texture as at least partially transparent, will not disable glBlend when drawing. + */ + public Builder nonOpaque() { + this.nonOpaque = true; + return this; + } + + /** + * Creates the texture + * + * @return the created texture + */ + public UITexture build() { + UITexture texture = create(); + if (this.name == null) { + String[] p = texture.location.getPath().split("/"); + p = p[p.length - 1].split("\\."); + this.name = texture.location.getNamespace().equals(GTCEu.MOD_ID) ? p[0] : + texture.location.getNamespace() + ":" + p[0]; + if (DrawableSerialization.getTexture(this.name) != null) { + return texture; + } + } + DrawableSerialization.registerTexture(this.name, texture); + return texture; + } + + private UITexture create() { + if (this.location == null) { + throw new NullPointerException("Location must not be null"); + } + if (this.iw <= 0 || this.ih <= 0) throw new IllegalArgumentException("Image size must be > 0"); + if (this.mode == Mode.FULL) { + this.u0 = 0; + this.v0 = 0; + this.u1 = 1; + this.v1 = 1; + this.mode = Mode.RELATIVE; + } else if (this.mode == Mode.PIXEL) { + float tw = 1f / this.iw, th = 1f / this.ih; + this.u0 = this.x * tw; + this.v0 = this.y * th; + this.u1 = (this.x + this.w) * tw; + this.v1 = (this.y + this.h) * th; + this.mode = Mode.RELATIVE; + } + if (this.mode == Mode.RELATIVE) { + if (this.u0 < 0 || this.v0 < 0 || this.u1 > 1 || this.v1 > 1) + throw new IllegalArgumentException("UV values must be 0 - 1"); + if (this.bl > 0 || this.bt > 0 || this.br > 0 || this.bb > 0) { + return new AdaptableUITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType, + this.nonOpaque, this.iw, this.ih, this.bl, this.bt, this.br, this.bb, this.tiled); + } + if (this.tiled) { + return new TiledUITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.iw, this.ih, + this.colorType, this.nonOpaque); + } + return new UITexture(this.location, this.u0, this.v0, this.u1, this.v1, this.colorType, this.nonOpaque); + } + throw new IllegalStateException(); + } + } + + private enum Mode { + FULL, + PIXEL, + RELATIVE + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMajorTickFinder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMajorTickFinder.java new file mode 100644 index 00000000000..62a6fd327c8 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMajorTickFinder.java @@ -0,0 +1,54 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import lombok.Getter; +import lombok.Setter; + +public class AutoMajorTickFinder implements MajorTickFinder { + + @Getter + private final boolean autoAdjust; + @Setter + private double multiple = 10; + + public AutoMajorTickFinder(boolean autoAdjust) { + this.autoAdjust = autoAdjust; + } + + public AutoMajorTickFinder(double multiple) { + autoAdjust = false; + this.multiple = multiple; + } + + @Override + public double[] find(double min, double max, double[] ticks) { + int s = (int) Math.ceil((max - min) / multiple) + 2; + if (s > ticks.length) ticks = new double[s]; + double next = (Math.floor(min / multiple) * multiple); + for (int i = 0; i < s; i++) { + ticks[i] = next; + if (next > max) { + s = i + 1; + break; + } + next += multiple; + } + if (ticks.length > s) ticks[s] = Float.NaN; + return ticks; + } + + void calculateAutoTickMultiple(double min, double max) { + double step = (max - min) / 5; + if (step < 1) { + int significantPlaces = (int) Math.abs(Math.log10(step)) + 2; + double ten = Math.pow(10, significantPlaces); + step = (int) (step * ten + 0.2f) / ten; + } else if (step == 1) { + step = 0.2f; + } else { + int significantPlaces = (int) Math.log10(step) - 1; + double ten = Math.pow(10, significantPlaces); + step = (int) (step / ten + 0.2f) * ten; + } + setMultiple(step); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMinorTickFinder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMinorTickFinder.java new file mode 100644 index 00000000000..6354eff7af7 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/AutoMinorTickFinder.java @@ -0,0 +1,32 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +public class AutoMinorTickFinder implements MinorTickFinder { + + private int amountBetweenMajors; + + public AutoMinorTickFinder(int amountBetweenMajors) { + this.amountBetweenMajors = amountBetweenMajors; + } + + @Override + public double[] find(double min, double max, double[] majorTicks, double[] ticks) { + int s = majorTicks.length * this.amountBetweenMajors; + if (ticks.length < s) ticks = new double[s]; + int k = 0; + for (int i = 0; i < majorTicks.length - 1; i++) { + if (Double.isNaN(majorTicks[i + 1])) break; + double next = majorTicks[i]; + double d = (majorTicks[i + 1] - next) / (amountBetweenMajors + 1); + for (int j = 0; j < amountBetweenMajors; j++) { + next += d; + if (next >= min) ticks[k++] = next; + if (next > max) { + ticks[k] = Float.NaN; + break; + } + } + } + if (k < ticks.length) ticks[k] = Float.NaN; + return ticks; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphAxis.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphAxis.java new file mode 100644 index 00000000000..e5670579252 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphAxis.java @@ -0,0 +1,201 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.drawable.GuiDraw; +import com.gregtechceu.gtceu.api.mui.drawable.text.TextRenderer; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.api.mui.utils.DAM; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import lombok.Getter; +import org.jetbrains.annotations.ApiStatus; +import org.joml.Matrix4f; + +import java.text.DecimalFormat; +import java.util.List; + +@ApiStatus.Experimental +public class GraphAxis { + + private static final TextRenderer textRenderer = new TextRenderer(); + private static final float TICK_LABEL_SCALE = 0.4f; + private static final float AXIS_LABEL_SCALE = 1f; + private static final float TICK_LABEL_OFFSET = 2f; + private static final float AXIS_LABEL_OFFSET = 3f; + + @Getter + public final GuiAxis axis; + + public double[] majorTicks = new double[8]; + public double[] minorTicks = new double[16]; + public TextRenderer.Line[] tickLabels = new TextRenderer.Line[8]; + private float maxLabelWidth = 0; + @Getter + public MajorTickFinder majorTickFinder = new AutoMajorTickFinder(true); + @Getter + public MinorTickFinder minorTickFinder = new AutoMinorTickFinder(2); + @Getter + public String label; + @Getter + public double min, max; + public boolean autoLimits = true; + public float[] data; + + public GraphAxis(GuiAxis axis) { + this.axis = axis; + } + + void compute(List plots) { + if (this.autoLimits) { + if (plots.isEmpty()) { + this.min = 0; + this.max = 0; + } else if (plots.size() == 1) { + this.min = DAM.min(plots.get(0).getData(this.axis)); + this.max = DAM.max(plots.get(0).getData(this.axis)); + } else { + double min = Double.MAX_VALUE, max = Double.MIN_VALUE; + for (Plot plot : plots) { + double m = DAM.min(plot.getData(this.axis)); + if (m < min) min = m; + m = DAM.max(plot.getData(this.axis)); + if (m > max) max = m; + } + this.min = min; + this.max = max; + } + if (this.axis.isVertical()) { + double padding = (this.max - this.min) * 0.05f; + this.max += padding; + this.min -= padding; + } + } + if (this.majorTickFinder instanceof AutoMajorTickFinder tickFinder && tickFinder.isAutoAdjust()) { + tickFinder.calculateAutoTickMultiple(this.min, this.max); + } + this.majorTicks = this.majorTickFinder.find(this.min, this.max, this.majorTicks); + this.minorTicks = this.minorTickFinder.find(this.min, this.max, this.majorTicks, this.minorTicks); + + if (this.tickLabels.length < this.majorTicks.length) { + this.tickLabels = new TextRenderer.Line[this.majorTicks.length]; + } + textRenderer.setScale(TICK_LABEL_SCALE); + this.maxLabelWidth = 0; + double maxDiff = DAM.max(DAM.diff(this.majorTicks)); + int significantPlaces = (int) Math.abs(Math.log10(maxDiff)) + 2; + DecimalFormat format = new DecimalFormat(); + format.setMaximumFractionDigits(significantPlaces); + for (int i = 0; i < this.tickLabels.length; i++) { + if (Double.isNaN(this.majorTicks[i])) break; + this.tickLabels[i] = textRenderer + .line(Component.literal(format.format(this.majorTicks[i])).getVisualOrderText()); + if (this.tickLabels[i].getWidth() > this.maxLabelWidth) { + this.maxLabelWidth = this.tickLabels[i].getWidth(); + } + } + } + + void applyPadding(GraphView graphView) { + textRenderer.setScale(TICK_LABEL_SCALE); + if (this.axis.isHorizontal()) { + graphView.sy1 -= textRenderer.getFontHeight() + TICK_LABEL_OFFSET; + if (this.label != null) { + textRenderer.setScale(AXIS_LABEL_SCALE); + graphView.sy1 -= textRenderer.getFontHeight() + AXIS_LABEL_OFFSET; + } + } else { + float off = this.maxLabelWidth + TICK_LABEL_OFFSET; + if (this.label != null) { + textRenderer.setScale(AXIS_LABEL_SCALE); + off += textRenderer.getFontHeight() + AXIS_LABEL_OFFSET; + } + graphView.sx0 += off; + } + } + + void drawGridLines(Matrix4f pose, VertexConsumer buffer, GraphView view, GraphAxis other, boolean major, float d, + int r, int g, int b, int a) { + double[] pos = major ? this.majorTicks : this.minorTicks; + float dHalf = d / 2; + if (axis.isHorizontal()) { + float otherMin = view.g2sY(other.max); + float otherMax = view.g2sY(other.min); + drawLinesOnHorizontal(pose, buffer, view, pos, dHalf, otherMin, otherMax, r, g, b, a); + } else { + float otherMin = view.g2sX(other.min); + float otherMax = view.g2sX(other.max); + drawLinesOnVertical(pose, buffer, view, pos, dHalf, otherMin, otherMax, r, g, b, a); + } + } + + void drawTicks(Matrix4f pose, VertexConsumer buffer, GraphView view, GraphAxis other, boolean major, + float thickness, float length, int r, int g, int b, int a) { + double[] pos = major ? this.majorTicks : this.minorTicks; + float dHalf = thickness / 2; + if (axis.isHorizontal()) { + float otherMin = view.g2sY(other.min); + drawLinesOnHorizontal(pose, buffer, view, pos, dHalf, otherMin - length, otherMin, r, g, b, a); + } else { + float otherMin = view.g2sX(other.min); + drawLinesOnVertical(pose, buffer, view, pos, dHalf, otherMin, otherMin + length, r, g, b, a); + } + } + + private void drawLinesOnHorizontal(Matrix4f pose, VertexConsumer buffer, GraphView view, double[] pos, float dHalf, + float crossLow, float crossHigh, int r, int g, int b, int a) { + for (double p : pos) { + if (Double.isNaN(p)) break; + if (p < min || p > max) continue; + + float fp = view.g2sX(p); + + float x0 = fp - dHalf; + float x1 = fp + dHalf; + GuiDraw.drawRectRaw(buffer, pose, x0, crossLow, x1, crossHigh, r, g, b, a); + } + } + + private void drawLinesOnVertical(Matrix4f pose, VertexConsumer buffer, GraphView view, double[] pos, float dHalf, + float crossLow, float crossHigh, int r, int g, int b, int a) { + for (double p : pos) { + if (Double.isNaN(p)) break; + if (p < min || p > max) continue; + + float fp = view.g2sY(p); + + float y0 = fp - dHalf; + float y1 = fp + dHalf; + GuiDraw.drawRectRaw(buffer, pose, crossLow, y0, crossHigh, y1, r, g, b, a); + } + } + + void drawLabels(GraphView view, GraphAxis other, GuiGraphics graphics) { + textRenderer.setHardWrapOnBorder(false); + if (axis.isHorizontal()) { + textRenderer.setScale(TICK_LABEL_SCALE); + textRenderer.setAlignment(Alignment.TopCenter, 100); + float y = view.g2sY(other.min) + TICK_LABEL_OFFSET; + for (int i = 0; i < this.majorTicks.length; i++) { + double pos = this.majorTicks[i]; + if (Double.isNaN(pos)) break; + if (pos < min || pos > max) continue; + textRenderer.setPos((int) (view.g2sX(pos) - 50), (int) y); + textRenderer.drawSimple(graphics, this.tickLabels[i].getText()); + } + } else { + textRenderer.setScale(TICK_LABEL_SCALE); + textRenderer.setAlignment(Alignment.CenterRight, this.maxLabelWidth, 20); + float x = view.g2sX(other.min) - TICK_LABEL_OFFSET - this.maxLabelWidth; + for (int i = 0; i < this.majorTicks.length; i++) { + double pos = this.majorTicks[i]; + if (Double.isNaN(pos)) break; + if (pos < min || pos > max) continue; + textRenderer.setPos((int) x, (int) (view.g2sY(pos) - 10)); + textRenderer.drawSimple(graphics, this.tickLabels[i].getText()); + } + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphDrawable.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphDrawable.java new file mode 100644 index 00000000000..3d0bcdeb1c5 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphDrawable.java @@ -0,0 +1,283 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.base.drawable.IDrawable; +import com.gregtechceu.gtceu.api.mui.drawable.GuiDraw; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.RenderType; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.VertexConsumer; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class GraphDrawable implements IDrawable { + + private final GraphView view = new GraphView(); + // private IDrawable background; + private int backgroundColor = Color.WHITE.main; + + private float majorTickThickness = 1f, majorTickLength = 3f, minorTickThickness = 0.5f, minorTickLength = 1.5f; + private float gridLineWidth = 0.5f; + private int gridLineColor = Color.withAlpha(Color.BLACK.main, 0.4f); + private float minorGridLineWidth = 0f; + private int minorGridLineColor = Color.withAlpha(Color.BLACK.main, 0.15f); + + @Getter + private final GraphAxis x = new GraphAxis(GuiAxis.X), y = new GraphAxis(GuiAxis.Y); + private final List plots = new ArrayList<>(); + + private boolean dirty = true; + + public void redraw() { + this.dirty = true; + for (Plot plot : this.plots) plot.redraw(); + } + + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.view.setScreen(x, y, x + width, y + height) | compute()) { + this.x.applyPadding(this.view); + this.y.applyPadding(this.view); + this.view.setGraph(this.x.min, this.y.min, this.x.max, this.y.max); + this.view.postResize(); + } + + var graphics = context.getGraphics(); + // background + if (this.backgroundColor != 0) { + GuiDraw.drawRect(graphics, this.view.sx0, this.view.sy0, this.view.getScreenWidth(), + this.view.getScreenHeight(), this.backgroundColor); + } + + RenderSystem.setShader(GameRenderer::getPositionColorShader); + var buffer = graphics.bufferSource().getBuffer(RenderType.guiOverlay()); + // grid lines + drawGrid(graphics, buffer); + + var stencil = context.getStencil(); + stencil.push((int) this.view.sx0, (int) this.view.sy0, (int) (this.view.getScreenWidth() + 1), + (int) (this.view.getScreenHeight() + 1)); + // plots + for (Plot plot : this.plots) { + plot.draw(graphics, this.view); + } + stencil.pop(); + // axis ticks + buffer = graphics.bufferSource().getBuffer(RenderType.guiOverlay()); + drawTicks(graphics, buffer); + + // GuiDraw.drawBorderOutsideLTRB(graphics, this.view.sx0, this.view.sy0, this.view.sx1, this.view.sy1, 0.5f, + // Color.BLACK.main); + this.x.drawLabels(this.view, this.y, graphics); + this.y.drawLabels(this.view, this.x, graphics); + } + + public void drawGrid(GuiGraphics graphics, VertexConsumer buffer) { + if (this.minorGridLineWidth > 0) { + int r = Color.getRed(this.minorGridLineColor); + int g = Color.getGreen(this.minorGridLineColor); + int b = Color.getBlue(this.minorGridLineColor); + int a = Color.getAlpha(this.minorGridLineColor); + this.x.drawGridLines(graphics.pose().last().pose(), buffer, this.view, this.y, false, + this.minorGridLineWidth, r, g, b, a); + } + if (this.gridLineWidth > 0) { + int r = Color.getRed(this.gridLineColor); + int g = Color.getGreen(this.gridLineColor); + int b = Color.getBlue(this.gridLineColor); + int a = Color.getAlpha(this.gridLineColor); + var pose = graphics.pose().last().pose(); + this.x.drawGridLines(pose, buffer, this.view, this.y, true, this.gridLineWidth, r, g, b, a); + this.y.drawGridLines(pose, buffer, this.view, this.x, true, this.gridLineWidth, r, g, b, a); + } + } + + public void drawTicks(GuiGraphics graphics, VertexConsumer buffer) { + var pose = graphics.pose().last().pose(); + this.x.drawTicks(pose, buffer, this.view, this.y, false, this.minorTickThickness, this.minorTickLength, 0, 0, 0, + 0xFF); + this.y.drawTicks(pose, buffer, this.view, this.x, false, this.minorTickThickness, this.minorTickLength, 0, 0, 0, + 0xFF); + this.x.drawTicks(pose, buffer, this.view, this.y, true, this.majorTickThickness, this.majorTickLength, 0, 0, 0, + 0xFF); + this.y.drawTicks(pose, buffer, this.view, this.x, true, this.majorTickThickness, this.majorTickLength, 0, 0, 0, + 0xFF); + } + + private boolean compute() { + if (!this.dirty) return false; + this.dirty = false; + this.x.compute(this.plots); + this.y.compute(this.plots); + int colorIndex = 0; + for (Plot plot : this.plots) { + if (plot.defaultColor) { + plot.color = Plot.DEFAULT_PLOT_COLORS[colorIndex]; + if (++colorIndex == Plot.DEFAULT_PLOT_COLORS.length) { + colorIndex = 0; + } + } + } + return true; + } + + public GraphDrawable autoXLim() { + this.x.autoLimits = true; + redraw(); + return this; + } + + public GraphDrawable autoYLim() { + this.y.autoLimits = true; + redraw(); + return this; + } + + public GraphDrawable xLim(float min, float max) { + this.x.min = min; + this.x.max = max; + this.x.autoLimits = false; + redraw(); + return this; + } + + public GraphDrawable yLim(float min, float max) { + this.y.min = min; + this.y.max = max; + this.y.autoLimits = false; + redraw(); + return this; + } + + public GraphDrawable majorTickStyle(float thickness, float length) { + this.majorTickThickness = thickness; + this.majorTickLength = length; + return this; + } + + public GraphDrawable minorTickStyle(float thickness, float length) { + this.minorTickThickness = thickness; + this.minorTickLength = length; + return this; + } + + public GraphDrawable xTickFinder(MajorTickFinder majorTickFinder, MinorTickFinder minorTickFinder) { + this.x.majorTickFinder = majorTickFinder; + this.x.minorTickFinder = minorTickFinder; + redraw(); + return this; + } + + public GraphDrawable yTickFinder(MajorTickFinder majorTickFinder, MinorTickFinder minorTickFinder) { + this.y.majorTickFinder = majorTickFinder; + this.y.minorTickFinder = minorTickFinder; + redraw(); + return this; + } + + public GraphDrawable xTickFinder(float majorMultiples, int minorTicksBetweenMajors) { + return xTickFinder(new AutoMajorTickFinder(majorMultiples), new AutoMinorTickFinder(minorTicksBetweenMajors)); + } + + public GraphDrawable yTickFinder(float majorMultiples, int minorTicksBetweenMajors) { + return yTickFinder(new AutoMajorTickFinder(majorMultiples), new AutoMinorTickFinder(minorTicksBetweenMajors)); + } + + public GraphDrawable backgroundColor(int color) { + if (color != 0 && Color.getAlpha(color) == 0) { + color = Color.withAlpha(color, 0xFF); + } + this.backgroundColor = color; + return this; + } + + public GraphDrawable plot(double[] x, double[] y) { + return plot(new Plot().data(x, y)); + } + + public GraphDrawable plot(double[] x, double[] y, int color) { + return plot(new Plot() + .data(x, y) + .color(color)); + } + + public GraphDrawable plot(double[] x, double[] y, float thickness) { + return plot(new Plot() + .data(x, y) + .thickness(thickness)); + } + + public GraphDrawable plot(double[] x, double[] y, float thickness, int color) { + return plot(new Plot() + .data(x, y) + .thickness(thickness) + .color(color)); + } + + public GraphDrawable plot(Plot plot) { + this.plots.add(plot); + plot.redraw(); + return this; + } + + public GraphDrawable majorGridStyle(float thickness, int color) { + this.gridLineWidth = thickness; + this.gridLineColor = color; + return this; + } + + public GraphDrawable minorGridStyle(float thickness, int color) { + this.minorGridLineWidth = thickness; + this.minorGridLineColor = color; + return this; + } + + public GraphDrawable disableMajorGrid() { + return majorGridLineThickness(0); + } + + public GraphDrawable disableMinorGrid() { + return minorGridLineThickness(0); + } + + public GraphDrawable enableMajorGrid() { + return majorGridLineThickness(0.5f); + } + + public GraphDrawable enableMinorGrid() { + return majorGridLineThickness(0.25f); + } + + public GraphDrawable majorGridLineThickness(float thickness) { + this.gridLineWidth = thickness; + return this; + } + + public GraphDrawable minorGridLineThickness(float thickness) { + this.minorGridLineWidth = thickness; + return this; + } + + public GraphDrawable majorGridLineColor(int color) { + this.gridLineColor = color; + return this; + } + + public GraphDrawable minorGridLineColor(int color) { + this.minorGridLineColor = color; + return this; + } + + public GraphDrawable graphAspectRatio(float aspectRatio) { + this.view.setAspectRatio(aspectRatio); + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphView.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphView.java new file mode 100644 index 00000000000..5ec4b5de883 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/GraphView.java @@ -0,0 +1,141 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import lombok.Getter; +import lombok.Setter; + +public class GraphView { + + @Setter + @Getter + float aspectRatio = 0; + // screen rectangle + float sx0, sy0, sx1, sy1; + // graph rectangle + double gx0, gy0, gx1, gy1; + + float zeroX, zeroY; + + void postResize() { + if (this.aspectRatio > 0) { + float w = sx1 - sx0, h = sy1 - sy0; + float properW = this.aspectRatio * h; + if (w > properW) { + float d = w - properW; + sx0 += d / 2; + sx1 -= d / 2; + } else if (properW > w) { + float properH = w / this.aspectRatio; + float d = h - properH; + sy0 += d / 2; + sy1 -= d / 2; + } + } + this.zeroX = g2sX(0); + this.zeroY = g2sY(0); + } + + boolean setScreen(float x0, float y0, float x1, float y1) { + if (x0 != this.sx0 || y0 != this.sy0 || x1 != this.sx1 || y1 != this.sy1) { + this.sx0 = x0; + this.sy0 = y0; + this.sx1 = x1; + this.sy1 = y1; + return true; + } + return false; + } + + void setGraph(double x0, double y0, double x1, double y1) { + this.gx0 = x0; + this.gy0 = y0; + this.gx1 = x1; + this.gy1 = y1; + this.zeroX = g2sX(0); + this.zeroY = g2sY(0); + } + + public float g2sX(double v) { + return (float) transform(v, gx0, gx1, sx0, sx1); + } + + public float g2sY(double v) { + // gy0 and gy1 inverted on purpose + // screen y0 is top, graph y0 is bottom + return (float) transform(v, gy1, gy0, sy0, sy1); + } + + public double g2sScaleX() { + return scale(gx0, gx1, sx0, sx1); + } + + public double g2sScaleY() { + return scale(gy1, gy0, sy0, sy1); + } + + public double s2gX(float v) { + return transform(v, sx0, sx1, gx0, gx1); + } + + public double s2gY(float v) { + // gy0 and gy1 inverted on purpose + // screen y0 is top, graph y0 is bottom + return transform(v, sy0, sy1, gy1, gy0); + } + + private double transform(double v, double fromMin, double fromMax, double toMin, double toMax) { + v = (v - fromMin) / (fromMax - fromMin); // reverse lerp + return toMin + (toMax - toMin) * v; + } + + private double scale(double fromMin, double fromMax, double toMin, double toMax) { + return (toMax - toMin) / (fromMax - fromMin); + } + + public double getGraphX0() { + return gx0; + } + + public double getGraphX1() { + return gx1; + } + + public double getGraphY0() { + return gy0; + } + + public double getGraphY1() { + return gy1; + } + + public float getScreenX0() { + return sx0; + } + + public float getScreenX1() { + return sx1; + } + + public float getScreenY0() { + return sy0; + } + + public float getScreenY1() { + return sy1; + } + + public float getScreenWidth() { + return sx1 - sx0; + } + + public float getScreenHeight() { + return sy1 - sy0; + } + + public double getGraphWidth() { + return gx1 - gx0; + } + + public double getGraphHeight() { + return gy1 - gy0; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MajorTickFinder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MajorTickFinder.java new file mode 100644 index 00000000000..8eaf332fc2c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MajorTickFinder.java @@ -0,0 +1,9 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface MajorTickFinder { + + double[] find(double min, double max, double[] ticks); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MinorTickFinder.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MinorTickFinder.java new file mode 100644 index 00000000000..325b9a1ac0a --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/MinorTickFinder.java @@ -0,0 +1,9 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +public interface MinorTickFinder { + + double[] find(double min, double max, double[] majorTicks, double[] ticks); +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java new file mode 100644 index 00000000000..e929ae98619 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/graph/Plot.java @@ -0,0 +1,212 @@ +package com.gregtechceu.gtceu.api.mui.drawable.graph; + +import com.gregtechceu.gtceu.api.mui.base.GuiAxis; +import com.gregtechceu.gtceu.api.mui.drawable.GuiDraw; +import com.gregtechceu.gtceu.api.mui.utils.Color; +import com.gregtechceu.gtceu.api.mui.utils.DAM; +import com.gregtechceu.gtceu.api.mui.utils.Interpolations; +import com.gregtechceu.gtceu.client.renderer.GTRenderTypes; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.util.Mth; + +import com.mojang.blaze3d.systems.RenderSystem; +import lombok.Getter; + +public class Plot { + + public static final int[] DEFAULT_PLOT_COLORS = { + Color.BLUE_ACCENT.main, + Color.ORANGE_ACCENT.darker(0), + Color.GREEN.main, + Color.RED.main, + Color.DEEP_PURPLE_ACCENT.main, + Color.BROWN.main, + Color.TEAL.main, + Color.LIME.main + }; + + double[] xs = DAM.EMPTY; + double[] ys = DAM.EMPTY; + @Getter + float thickness = 1f; + boolean defaultColor = true; + @Getter + int color; + + private float[] vertexBuffer; // screen coords need to be way less accurate than graph coords, so float is fine + private boolean dirty = true; + + public void redraw() { + this.dirty = true; + } + + private void redraw(GraphView view) { + float dHalf = thickness * 0.5f; + + int n = xs.length * 4; + this.vertexBuffer = new float[n]; + int vertexIndex = 0; + + // first only calculate the start point vertices + // they are dependent on the first and second point + float x0 = view.g2sX(xs[0]); + float y0 = view.g2sY(ys[0]); + float x1 = view.g2sX(xs[1]); + float y1 = view.g2sY(ys[1]); + // last pos + float lx = x0; + float ly = y0; + + float dx = x1 - x0; + float dy = y1 - y0; + float len = Mth.sqrt(dx * dx + dy * dy); + if (len == 0) throw new IllegalArgumentException("Graph can't handle the same point back to back!"); + dx /= len; + dy /= len; + // perpendicular + float px = -dy; + float py = dx; + // last perpendicular thickness offset + float lpox = px * dHalf; + float lpoy = py * dHalf; + + vertexIndex = storePoints(vertexIndex, view, lx, ly, lpox, lpoy); + + // calculate all points except start and endpoint + // these depend on both their neighbors + for (int i = 1; i < xs.length - 1; i++) { + x0 = view.g2sX(xs[i]); + y0 = view.g2sY(ys[i]); + x1 = view.g2sX(xs[i + 1]); + y1 = view.g2sY(ys[i + 1]); + + dx = x1 - x0; + dy = y1 - y0; + len = Mth.sqrt(dx * dx + dy * dy); + if (len == 0) continue; + dx /= len; + dy /= len; + // perpendicular + px = -dy; + py = dx; + // perpendicular thickness offset + float pox = px * dHalf; + float poy = py * dHalf; + float ox, oy; + if (pox == lpox && poy == lpoy) { // linear + ox = pox; + oy = poy; + } else { + // get the average offset of this and the last point and of this and the next point + ox = Interpolations.lerp(lpox, pox, 0.5f); + oy = Interpolations.lerp(lpoy, poy, 0.5f); + // normalize it + len = Mth.sqrt(ox * ox + oy * oy); + ox /= len; + oy /= len; + // angle between now offset vector and last perpendicular offset vector + float cosAngle = (ox * lpox + oy * lpoy) / (1 * dHalf); + // calc hypotenuse and use it to calculate the actual length of the offset vector + float hypotenuse = this.thickness / cosAngle; + ox *= hypotenuse * 0.5f; + oy *= hypotenuse * 0.5f; + } + + vertexIndex = storePoints(vertexIndex, view, x0, y0, ox, oy); + + lx = x0; + ly = y0; + lpox = pox; + lpoy = poy; + } + + // finally calculate endpoint + // this depends on itself and the point before + int last = this.xs.length - 1; + x0 = lx; + y0 = ly; + x1 = view.g2sX(xs[last]); + y1 = view.g2sY(ys[last]); + + dx = x1 - x0; + dy = y1 - y0; + len = Mth.sqrt(dx * dx + dy * dy); + if (len == 0) throw new IllegalArgumentException("Graph can't handle the same point back to back!"); + dx /= len; + dy /= len; + // perpendicular + px = -dy; + py = dx; + // last perpendicular thickness offset + lpox = px * dHalf; + lpoy = py * dHalf; + + storePoints(vertexIndex, view, x1, y1, lpox, lpoy); + } + + private int storePoints(int index, GraphView view, float sx, float sy, float ox, float oy) { + this.vertexBuffer[index++] = sx - ox; + this.vertexBuffer[index++] = sy - oy; + this.vertexBuffer[index++] = sx + ox; + this.vertexBuffer[index++] = sy + oy; + return index; + } + + public void draw(GuiGraphics graphics, GraphView view) { + if (xs.length == 0) return; + if (xs.length == 1) { + GuiDraw.drawRect(graphics, view.g2sX(xs[0]) - thickness / 2, view.g2sY(ys[0]) - thickness / 2, thickness, + thickness, color); + return; + } + if (this.dirty) { + redraw(view); + this.dirty = false; + } + int r = Color.getRed(color); + int g = Color.getGreen(color); + int b = Color.getBlue(color); + int a = Color.getAlpha(color); + + RenderSystem.setShader(GameRenderer::getPositionColorShader); + var pose = graphics.pose().last().pose(); + var buffer = graphics.bufferSource().getBuffer(GTRenderTypes.guiTriangleStrip()); + for (int i = 0; i < this.vertexBuffer.length; i += 2) { + buffer.vertex(pose, this.vertexBuffer[i], this.vertexBuffer[i + 1], 0).color(r, g, b, a).endVertex(); + } + } + + public double[] getX() { + return xs; + } + + public double[] getY() { + return ys; + } + + public double[] getData(GuiAxis axis) { + return axis.isHorizontal() ? this.xs : this.ys; + } + + public Plot data(double[] x, double[] y) { + if (x.length != y.length) throw new IllegalArgumentException("X and Y must have the same length!"); + this.xs = x; + this.ys = y; + redraw(); + return this; + } + + public Plot thickness(float thickness) { + this.thickness = thickness; + redraw(); + return this; + } + + public Plot color(int color) { + this.color = color; + this.defaultColor = color == 0; + return this; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/AnimatedText.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/AnimatedText.java new file mode 100644 index 00000000000..0e2cca03ede --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/AnimatedText.java @@ -0,0 +1,145 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; +import com.gregtechceu.gtceu.api.mui.theme.WidgetTheme; +import com.gregtechceu.gtceu.api.mui.utils.Alignment; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import lombok.Setter; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.IntSupplier; + +@Accessors(fluent = true, chain = true) +public class AnimatedText extends StyledText { + + private MutableComponent full; + private String fullString; + private String currentString = ""; + private int currentIndex; + /** + * How fast the characters appear + */ + @Setter + private int speed = 40; // ms per char + private long timeLastDraw; + @Setter + private boolean forward = true; + + private boolean isAnimating = false; + + public AnimatedText(IKey key) { + super(key); + } + + @Override + public MutableComponent get() { + return Component.literal(this.currentString); + } + + public void reset() { + this.full = null; + } + + private void advance() { + if (!this.isAnimating || (this.forward && this.currentIndex >= this.fullString.length()) || + (!this.forward && this.currentIndex < 0)) + return; + long time = Util.getMillis(); + int amount = (int) ((time - this.timeLastDraw) / this.speed); + if (amount == 0) return; + if (this.forward) { + int max = Math.min(this.fullString.length() - 1, this.currentIndex + amount); + this.currentIndex = Math.max(this.currentIndex, 0); + for (int i = this.currentIndex; i < max; i++) { + char c = this.fullString.charAt(i); + if (c == ' ') { + max = Math.min(this.fullString.length() - 1, max + 1); + } + // noinspection StringConcatenationInLoop + this.currentString += c; + } + this.currentIndex = max; + } else { + int min = Math.max(0, this.currentIndex - amount); + this.currentIndex = Math.min(this.currentIndex, this.currentString.length() - 1); + for (int i = this.currentIndex; i >= min; i--) { + char c = this.fullString.charAt(i); + if (c == ' ') { + min = Math.max(0, min - 1); + } + this.currentString = this.currentString.substring(0, i); + } + this.currentIndex = min; + } + this.timeLastDraw += (long) amount * this.speed; + } + + @OnlyIn(Dist.CLIENT) + @Override + public void draw(GuiContext context, int x, int y, int width, int height, WidgetTheme widgetTheme) { + if (this.full == null || !this.full.equals(super.get())) { + if (this.isAnimating) { + this.full = super.get(); + this.fullString = this.full.getString(); + this.currentString = this.forward ? "" : this.fullString; + this.currentIndex = this.forward ? 0 : this.fullString.length() - 1; + this.timeLastDraw = Util.getMillis(); + } else { + this.currentString = this.forward ? "" : this.fullString; + } + } + advance(); + if (this.currentString.isEmpty()) return; + super.draw(context, x, y, width, height, widgetTheme); + } + + public AnimatedText startAnimation() { + this.isAnimating = true; + return this; + } + + public AnimatedText stopAnimation() { + this.isAnimating = false; + return this; + } + + @Override + public @NotNull AnimatedText style(ChatFormatting formatting) { + return (AnimatedText) super.style(formatting); + } + + @Override + public @NotNull AnimatedText alignment(Alignment alignment) { + return (AnimatedText) super.alignment(alignment); + } + + @Override + public @NotNull AnimatedText color(int color) { + return color(() -> color); + } + + @Override + public @NotNull AnimatedText color(@Nullable IntSupplier color) { + return (AnimatedText) super.color(color); + } + + @Override + public @NotNull AnimatedText scale(float scale) { + return (AnimatedText) super.scale(scale); + } + + @Override + public @NotNull AnimatedText shadow(@Nullable Boolean shadow) { + return (AnimatedText) super.shadow(shadow); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/BaseKey.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/BaseKey.java new file mode 100644 index 00000000000..6029ace8eeb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/BaseKey.java @@ -0,0 +1,49 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.MutableComponent; + +import lombok.Getter; +import org.apache.commons.lang3.NotImplementedException; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseKey implements IKey { + + @Getter + private @Nullable FormattingState formatting; + + @Override + public MutableComponent getFormatted(@Nullable FormattingState parentFormatting) { + return FontRenderHelper.format(this.formatting, parentFormatting, get()); + } + + @Override + public BaseKey style(@Nullable ChatFormatting formatting) { + if (this.formatting == null) { + this.formatting = new FormattingState(); + } + if (formatting == null) this.formatting.forceDefaultColor(); + else this.formatting.add(formatting, false); + return this; + } + + @Override + public IKey removeStyle() { + if (this.formatting != null) { + this.formatting.reset(); + } + return this; + } + + @Override + public String toString() { + return getFormatted().getString(); + } + + @Override + public int hashCode() { + throw new NotImplementedException("Implement hashCode() in subclasses"); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/ComposedLine.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/ComposedLine.java new file mode 100644 index 00000000000..43153a27c78 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/ComposedLine.java @@ -0,0 +1,109 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.IThemeApi; +import com.gregtechceu.gtceu.api.mui.base.drawable.IHoverable; +import com.gregtechceu.gtceu.api.mui.base.drawable.IIcon; +import com.gregtechceu.gtceu.api.mui.base.drawable.ITextLine; +import com.gregtechceu.gtceu.client.mui.screen.viewport.GuiContext; + +import net.minecraft.client.gui.Font; +import net.minecraft.locale.Language; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.util.FormattedCharSequence; + +import lombok.Getter; + +import java.util.List; + +public class ComposedLine implements ITextLine { + + private final List elements; + @Getter + private final int width; + private final int height; + + private float lastX, lastY; + + public ComposedLine(List elements, int width, int height) { + this.elements = elements; + this.width = width; + this.height = height; + } + + @Override + public int getHeight(Font font) { + return height == font.lineHeight ? height : height + 1; + } + + @Override + public void draw(GuiContext context, Font font, float x, float y, int color, boolean shadow, + int availableWidth, int availableHeight) { + this.lastX = x; + this.lastY = y; + for (Object o : this.elements) { + if (o instanceof String s) { + float drawY = getHeight(font) / 2f - font.lineHeight / 2f; + context.getGraphics().drawString(font, s, x, y + drawY, color, shadow); + x += font.width(s); + } else if (o instanceof FormattedText text) { + float drawY = getHeight(font) / 2f - font.lineHeight / 2f; + FormattedCharSequence charSequence = text instanceof Component component ? + component.getVisualOrderText() : Language.getInstance().getVisualOrder(text); + context.getGraphics().drawString(font, charSequence, x, y + drawY, color, shadow); + x += font.width(text); + } else if (o instanceof FormattedCharSequence s) { + float drawY = getHeight(font) / 2f - font.lineHeight / 2f; + context.getGraphics().drawString(font, s, x, y + drawY, color, shadow); + x += font.width(s); + } else if (o instanceof IIcon icon) { + float drawY = getHeight(font) / 2f - icon.getHeight() / 2f; + int w = icon.getWidth() > 0 ? icon.getWidth() : availableWidth; + icon.draw(context, (int) x, (int) (y + drawY), w, icon.getHeight(), + IThemeApi.get().getDefaultTheme().getFallback().getTheme()); + if (icon instanceof IHoverable hoverable) { + hoverable.setRenderedAt((int) x, (int) (y + drawY)); + } + x += w; + } + } + } + + @Override + public Object getHoveringElement(Font font, int x, int y) { + int h0 = getHeight(font); + if (y < lastY || y > lastY + h0) return null; // is not hovering vertically + if (x < lastX || x > lastX + getWidth()) return Boolean.FALSE; // is not hovering horizontally + float x0 = x - this.lastX; // origin to 0 + float x1 = 0; + float y0 = y - this.lastY; // origin to 0 + for (Object o : this.elements) { + float w, h; + if (o instanceof String s) { + w = font.width(s); + h = font.lineHeight; + } else if (o instanceof Component c) { + w = font.width(c); + h = font.lineHeight; + } else if (o instanceof FormattedCharSequence s) { + w = font.width(s); + h = font.lineHeight; + } else if (o instanceof IIcon icon) { + w = icon.getWidth(); + h = icon.getWidth(); + } else continue; + if (x0 > x1 && x0 < x1 + w) { + // is inside horizontally + if (h < h0) { + // is smaller than line height + int lower = (int) (h0 / 2f - h / 2); + int upper = (int) (h0 / 2f + h / 2 - 1f); + if (y0 < lower || y0 > upper) return Boolean.FALSE; // is outside vertically + } + return o; // found hovering + } + x1 += w; // move to next element + } + return null; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/CompoundKey.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/CompoundKey.java new file mode 100644 index 00000000000..3b023a7f02c --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/CompoundKey.java @@ -0,0 +1,45 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +public class CompoundKey extends BaseKey { + + private static final IKey[] EMPTY = new IKey[0]; + + @Getter + private final IKey[] keys; + + public CompoundKey(IKey... keys) { + this.keys = keys == null || keys.length == 0 ? EMPTY : keys; + } + + @Override + public MutableComponent get() { + return toComponent(false, null); + } + + @Override + public MutableComponent getFormatted(@Nullable FormattingState parentFormatting) { + // formatting is prepended to each key + return toComponent(true, parentFormatting); + } + + private MutableComponent toComponent(boolean formatted, @Nullable FormattingState parentFormatting) { + MutableComponent builder = Component.empty(); + for (IKey key : this.keys) { + if (formatted) { + // merge parent formatting and this formatting to not lose info + builder.append(key.getFormatted(FormattingState.merge(parentFormatting, getFormatting()))); + } else { + builder.append(key.get()); + } + } + return builder; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/DynamicKey.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/DynamicKey.java new file mode 100644 index 00000000000..abb02fb4648 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/DynamicKey.java @@ -0,0 +1,41 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; + +import net.minecraft.network.chat.MutableComponent; + +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +public class DynamicKey extends BaseKey { + + private final Supplier supplier; + + public DynamicKey(Supplier supplier) { + // Objects.requireNonNull(supplier.get(), "IKey returns a null string!"); + this.supplier = supplier; + } + + @Override + public MutableComponent get() { + return toString(false, null); + } + + @Override + public MutableComponent getFormatted(@Nullable FormattingState parentFormatting) { + // formatting is prepended to each key + return toString(true, parentFormatting); + } + + private MutableComponent toString(boolean formatted, @Nullable FormattingState parentFormatting) { + IKey key = this.supplier.get(); + if (key == null) key = IKey.EMPTY; + if (formatted) { + // merge parent formatting and this formatting to no lose info + return key.getFormatted(FormattingState.merge(parentFormatting, getFormatting())); + } else { + return key.get(); + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/FontRenderHelper.java b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/FontRenderHelper.java new file mode 100644 index 00000000000..07ad8d8e084 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/mui/drawable/text/FontRenderHelper.java @@ -0,0 +1,238 @@ +package com.gregtechceu.gtceu.api.mui.drawable.text; + +import com.gregtechceu.gtceu.api.mui.base.MCHelper; +import com.gregtechceu.gtceu.api.mui.base.drawable.IKey; + +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.*; +import net.minecraft.util.FormattedCharSequence; + +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.apache.commons.lang3.mutable.MutableFloat; +import org.apache.commons.lang3.mutable.MutableInt; +import org.apache.commons.lang3.mutable.MutableObject; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class FontRenderHelper { + + private static final int min = '0', max = 'r'; // min = 48, max = 114 + // array to access text formatting by character fast + private static final ChatFormatting[] formattingMap = new ChatFormatting[max - min + 1]; + + static { + for (ChatFormatting formatting : ChatFormatting.values()) { + char c = formatting.getChar(); + formattingMap[c - min] = formatting; + if (Character.isLetter(c)) { + formattingMap[Character.toUpperCase(c) - min] = formatting; + } + } + } + + /** + * Returns the formatting for a character with a fast array lookup. + * + * @param c formatting character + * @return formatting for character or null + */ + @Nullable + public static ChatFormatting getForCharacter(char c) { + if (c < min || c > max) return null; + return formattingMap[c - min]; + } + + public static void addAfter(ChatFormatting[] state, ChatFormatting formatting, boolean removeAllOnReset) { + if (formatting == ChatFormatting.RESET) { + if (removeAllOnReset) Arrays.fill(state, null); + state[0] = formatting; + return; + } + // remove reset + if (removeAllOnReset) state[6] = null; + if (formatting.isFormat()) { + state[formatting.ordinal() - 15] = formatting; + return; + } + // color + state[0] = formatting; + } + + public static MutableComponent format(@Nullable FormattingState state, @Nullable FormattingState parentState, + Component text) { + if (state == null) { + if (parentState == null) return text.copy(); + return parentState.prependText(ChatFormatting.RESET, null).append(text); + } + return state.prependText(ChatFormatting.RESET, parentState).append(text); + } + + public static MutableComponent formatArgs(Object[] args, @Nullable FormattingState parentState, String text, + boolean translate) { + if (args == null || args.length == 0) return translate ? Component.translatable(text) : Component.literal(text); + args = Arrays.copyOf(args, args.length); + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof IKey key) { + // parent format + key format + key text + parent format + args[i] = FormattingState.appendFormat(key.getFormatted(parentState) + .withStyle(ChatFormatting.RESET), parentState); + } + } + return translate ? Component.translatable(text, args) : Component.literal(String.format(text, args)); + } + + public static int getDefaultTextHeight() { + Font fr = MCHelper.getFont(); + return fr != null ? fr.lineHeight : 9; + } + + /** + * Calculates how many formatting characters there are at the given position of the string. + * + * @param s string + * @param start starting index + * @return amount of formatting characters at index + */ + public static int getFormatLength(String s, int start) { + int i = Math.max(0, start); + int l = 0; + for (; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 167) { + if (i + 1 >= s.length()) return l; + if (getForCharacter(c) == null) return l; + l += 2; + i++; + } else { + return l; + } + } + return l; + } + + public static FormattedCharSequence splitAtMax(FormattedCharSequence input, float maxWidth) { + MutableFloat cur = new MutableFloat(); + // split the string at max width. + List output = new ArrayList<>(); + input.accept((pos, style, codePoint) -> { + if (cur.addAndGet(TextRenderer.getWidthProvider().getWidth(codePoint, style)) > maxWidth) { + return false; + } + output.add(new TextRenderer.FormattedChar(codePoint, style)); + return true; + }); + return fromChars(output); + } + + public static boolean isEmpty(FormattedCharSequence input) { + if (input == FormattedCharSequence.EMPTY) { + return true; + } + MutableBoolean value = new MutableBoolean(true); + input.accept((pos, style, codePoint) -> { + value.setFalse(); + return false; + }); + return value.isTrue(); + } + + public static Component getComponentFromCharSequence(FormattedCharSequence input) { + List parts = new ArrayList<>(); + StringBuilder value = new StringBuilder(); + MutableObject