diff --git a/src/main/java/dev/cigarette/gui/CategoryInstance.java b/src/main/java/dev/cigarette/gui/CategoryInstance.java index f398f137..99239487 100644 --- a/src/main/java/dev/cigarette/gui/CategoryInstance.java +++ b/src/main/java/dev/cigarette/gui/CategoryInstance.java @@ -6,11 +6,30 @@ import java.util.HashSet; +/** + * A category that groups modules together to be rendered by the GUI. + */ public class CategoryInstance { + /** + * The root widget that contains each module's top-level widget for rendering. + */ public final ScrollableWidget> widget; + /** + * The list of each module attached to this category. + */ public final HashSet> children = new HashSet<>(); + /** + * Whether this category is expanded or collapsed. + */ public boolean expanded = false; + /** + * Creates a new category to group modules. + * + * @param displayName The text to display in the header of the category. + * @param x The initial X position of this category + * @param y The initial Y position of this category + */ public CategoryInstance(String displayName, int x, int y) { this.widget = new ScrollableWidget<>(x, y); this.widget.setHeader(displayName, () -> { @@ -19,6 +38,11 @@ public CategoryInstance(String displayName, int x, int y) { this.widget.alphabetic(); } + /** + * Attaches a list of modules to this category. + * + * @param children The list of modules to attach + */ public void attach(BaseModule... children) { BaseWidget[] childWidgets = new BaseWidget[children.length]; for (int i = 0; i < children.length; i++) { diff --git a/src/main/java/dev/cigarette/gui/CigaretteScreen.java b/src/main/java/dev/cigarette/gui/CigaretteScreen.java index afaf6625..0d084078 100644 --- a/src/main/java/dev/cigarette/gui/CigaretteScreen.java +++ b/src/main/java/dev/cigarette/gui/CigaretteScreen.java @@ -5,6 +5,7 @@ import dev.cigarette.gui.widget.BaseWidget; import dev.cigarette.gui.widget.KeybindWidget; import dev.cigarette.gui.widget.ScrollableWidget; +import dev.cigarette.gui.widget.ToggleKeybindWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.Element; @@ -16,32 +17,88 @@ import java.util.Stack; public class CigaretteScreen extends Screen { + /** + * Primary feature color. (Orange) + */ public static final int PRIMARY_COLOR = 0xFFFE5F00; + /** + * Secondary feature color. (Brown) + */ public static final int SECONDARY_COLOR = 0xFFC44700; + /** + * Primary text color. (White) + */ public static final int PRIMARY_TEXT_COLOR = 0xFFFFFFFF; + /** + * Primary background color. (Dark Gray) + */ public static final int BACKGROUND_COLOR = 0xFF1A1A1A; + /** + * Dark background color usually used in gradients with {@link #BACKGROUND_COLOR}. (Black) + */ public static final int DARK_BACKGROUND_COLOR = 0xFF000000; + /** + * Color of enabled things. (Bright Green) + */ public static final int ENABLED_COLOR = 0xFF3AFC3A; + /** + * Reference to the hovered widget. + */ public static @Nullable Object hoverHandled = null; + /** + * An ordered list of the widgets on the screen. Ordered by time of focus descending. Event propagation starts with the most recent focused to the last focused. + */ private final Stack> priority = new Stack<>(); + /** + * The screen that was being rendered before this GUI was opened. + */ private Screen parent = null; + /** + * Whether the GUI is in process of opening. + */ private boolean begin = false; + /** + * The time at which the GUI was opened. + */ private long openStartNanos = 0L; + /** + * Whether the GUI is in process of closing. + */ private boolean closing = false; + /** + * The time at which the GUI was closed. + */ private long closeStartNanos = 0L; + /** + * The length of the opening animation in seconds. + */ private static final double OPEN_DURATION_S = 0.4; private static final double OPEN_STAGGER_S = 0.06; private static final int OPEN_DISTANCE_PX = 24; + /** + * The length of the closing animation as a multiplier of {@link #OPEN_DURATION_S}. + */ private static final double CLOSE_DURATION_FACTOR = 0.6; private static final double CLOSE_STAGGER_FACTOR = 0.6; + /** + * The total number of categories in the GUI. + */ private int categoryCount = 0; + /** + * Reference to a {@link KeybindWidget} or {@link ToggleKeybindWidget} that is actively listening for keys to bind. + */ public static @Nullable KeybindWidget bindingKey = null; protected CigaretteScreen() { super(Text.literal("Cigarette Client")); } + /** + * Called when the GUI is being opened to set the previous screen. + * + * @param parent The parent screen that will be reverted to on close + */ public void setParent(@Nullable Screen parent) { this.parent = parent; } @@ -120,6 +177,9 @@ public void mouseMoved(double mouseX, double mouseY) { } } + /** + * Called when the GUI should be closed. + */ @Override public void close() { assert client != null; @@ -143,6 +203,12 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return true; } + /** + * {@return whether the provided widget can be hovered} If so, that widget is set as the hovered widget. + *

A widget must call {@link BaseWidget#captureHover() captureHover()} to be hoverable.

+ * + * @param obj The widget to check if it can be hovered + */ public static boolean isHoverable(Object obj) { if (hoverHandled == null) { hoverHandled = obj; @@ -151,6 +217,14 @@ public static boolean isHoverable(Object obj) { return hoverHandled == obj; } + /** + * Replaces the built-in {@link Screen#render(DrawContext, int, int, float) Screen.render()} method. Handles animations and category rendering for the GUI. + * + * @param context The current draw context + * @param mouseX Current mouse X position + * @param mouseY Current mouse Y position + * @param deltaTicks Current delta ticks + */ @Override public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) { this.renderBackground(context, mouseX, mouseY, deltaTicks); diff --git a/src/main/java/dev/cigarette/gui/RenderUtil.java b/src/main/java/dev/cigarette/gui/RenderUtil.java index d03ab3d9..dd1cf0e9 100644 --- a/src/main/java/dev/cigarette/gui/RenderUtil.java +++ b/src/main/java/dev/cigarette/gui/RenderUtil.java @@ -2,15 +2,26 @@ import com.mojang.blaze3d.systems.RenderSystem; +/** + * Custom utilities for screen rendering. + */ public final class RenderUtil { private RenderUtil() {} + /** + * Set the opacity of the renderer. + * + * @param alpha The opacity to set, between 0 and 1 + */ public static void pushOpacity(float alpha) { if (alpha < 0f) alpha = 0f; if (alpha > 1f) alpha = 1f; RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, alpha); } + /** + * Reset the opacity of the renderer. + */ public static void popOpacity() { RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); } diff --git a/src/main/java/dev/cigarette/gui/Scissor.java b/src/main/java/dev/cigarette/gui/Scissor.java index ab657057..c59c4243 100644 --- a/src/main/java/dev/cigarette/gui/Scissor.java +++ b/src/main/java/dev/cigarette/gui/Scissor.java @@ -4,14 +4,44 @@ import java.util.Stack; +/** + * Custom scissor utility for screen rendering. + */ public class Scissor { + /** + * An exclusive (non-additive) scissor. Use {@link #apply()} to activate and {@link #remove()} to deactivate. + */ private static class Bound { + /** + * The draw context this scissor is attached to. + */ DrawContext context; + /** + * The left edge of this bounding box. + */ int left; + /** + * The top edge of this bounding box. + */ int top; + /** + * The right edge of this bounding box. + */ int right; + /** + * The bottom edge of this bounding box. + */ int bottom; + /** + * Creates a new exclusive scissor. + * + * @param context The draw context to apply the scissor to + * @param left The left edge of the bounding box + * @param top The top edge of the bounding box + * @param right The right edge of the bounding box + * @param bottom The bottom edge of the bounding box + */ Bound(DrawContext context, int left, int top, int right, int bottom) { this.context = context; this.left = left; @@ -20,11 +50,17 @@ private static class Bound { this.bottom = bottom; } + /** + * Activate this scissor. This does not deactivate any other scissors that are applied. + */ void apply() { if (this.context == null) return; this.context.enableScissor(left, top, right, bottom); } + /** + * Deactivate this scissor. + */ void remove() { if (this.context == null) return; this.context.disableScissor(); @@ -33,6 +69,15 @@ void remove() { private static final Stack exclusiveScissors = new Stack<>(); + /** + * Push an exclusive (non-additive) scissor to a {@link DrawContext}. + * + * @param context The draw context to activate the scissor on + * @param left The left edge of the bounding box + * @param top The top edge of the bounding box + * @param right The right edge of the bounding box + * @param bottom The bottom edge of the bounding box + */ public static void pushExclusive(DrawContext context, int left, int top, int right, int bottom) { Bound bound = new Bound(context, left, top, right, bottom); @@ -45,6 +90,9 @@ public static void pushExclusive(DrawContext context, int left, int top, int rig bound.apply(); } + /** + * Pop the top-most exclusive scissor from the stack. + */ public static void popExclusive() { if (exclusiveScissors.isEmpty()) return; Bound latest = exclusiveScissors.pop(); diff --git a/src/main/java/dev/cigarette/gui/widget/BaseWidget.java b/src/main/java/dev/cigarette/gui/widget/BaseWidget.java index 8c0d476e..947158aa 100644 --- a/src/main/java/dev/cigarette/gui/widget/BaseWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/BaseWidget.java @@ -12,33 +12,87 @@ import java.util.function.Consumer; +/** + * Extends the base widget providing default functionality for saving/loading custom state from the config. + * + * @param The custom state this widget stores. Use {@link BaseWidget.Stateless} for widgets that should not hold state. + */ public abstract class BaseWidget extends ClickableWidget { + /** + * The default state of this widget for resetting. + */ private StateType defaultState; + /** + * The current state of this widget. + */ private StateType state; + /** + * Whether this widget is focused by the user. + */ protected boolean focused = false; + /** + * Whether this widget is capturing hover events to render tooltips. + */ protected boolean captureHover = false; + /** + * Whether this widget is currently hovered by the mouse. + */ protected boolean hovered = false; + /** + * The registered config key for saving and loading this widget's state. + */ protected String configKey; + /** + * The tooltip state of this widget. + */ private final TooltipState tooltip = new TooltipState(); + /** + * A callback triggered when the state changes. For registered widgets, this is used to write the new state to the config. + */ protected @Nullable Consumer stateCallback = null; + /** + * A callback triggered when the config is loaded setting the state. This widget must be registered before the config loads. + */ protected @Nullable Consumer fsCallback = null; + /** + * A callback triggered when the state changes. Used primarily to trigger module {@code whenEnabled} and {@code whenDisabled} events. + */ protected @Nullable Consumer moduleCallback = null; + /** + * Create a basic widget that holds custom state and saves/loads it from the cigarette config. + * + * @param message The text to display when this widget is rendered + * @param tooltip The text to display when this widget is hovered + */ public BaseWidget(String message, @Nullable String tooltip) { super(0, 0, 0, 0, message == null ? Text.empty() : Text.literal(message)); if (tooltip != null) this.setTooltip(Tooltip.of(Text.literal(tooltip))); } + /** + * {@return whether this widget is holding custom state} + */ public boolean isStateless() { return this.state instanceof Stateless; } + /** + * Sets the state of this widget and calls any attached module and state callbacks. Those callbacks trigger module {@code whenEnabled} and {@code whenDisabled} events (given that this is a boolean widget), and writes the new state to the config. + * + * @param state The new state to set to this widget + */ public final void setRawState(StateType state) { this.state = state; if (moduleCallback != null) moduleCallback.accept(this.state); if (stateCallback != null) stateCallback.accept(this.state); } + /** + * Toggles the state of this widget and calls any attached module and state callbacks. Those callbacks trigger module {@code whenEnabled} and {@code whenDisabled} events, and writes the new state to the config. + * + * @throws IllegalStateException If this widget is not of a boolean state type + */ @SuppressWarnings("unchecked") public final void toggleRawState() { if (this.state instanceof Boolean booleanState) { @@ -48,15 +102,31 @@ public final void toggleRawState() { throw new IllegalStateException("Cannot toggle state from a non-boolean component."); } + /** + * {@return the custom state} + * + * @throws IllegalStateException If this widget is a stateless widget + */ public final StateType getRawState() { if (this.state instanceof Stateless) throw new IllegalStateException("Cannot get state from a stateless component."); return this.state; } + /** + * Binds a callback that is triggered when the state is set. Most often used in modules to trigger {@code whenEnabled} and {@code whenDisabled} events. + *

Note that there are no checks on whether the new state is different from the previous state.

+ * + * @param callback The callback that accepts the newest state + */ public void registerModuleCallback(Consumer callback) { this.moduleCallback = callback; } + /** + * The default callback attached to this widget when it is initially registered to the config. This callback receives the state of this widget from the config once it is loaded by the client. Triggers the module state callback which trigger module {@code whenEnabled} and {@code whenDisabled} events. + * + * @param newState The new state to set + */ @SuppressWarnings("unchecked") private void defaultFSCallback(Object newState) { try { @@ -67,6 +137,11 @@ private void defaultFSCallback(Object newState) { } } + /** + * Registers this widget to the config so it can save/load its state. + * + * @param key The key in the config to store the value of this widgets state under + */ public void registerConfigKey(String key) { if (this.state instanceof Stateless) return; if (this.configKey != null) throw new IllegalStateException("Cannot configure a config key more than once."); @@ -76,6 +151,12 @@ public void registerConfigKey(String key) { FileSystem.registerUpdate(key, this.fsCallback); } + /** + * Registers this widget to the config so it can save/load its state. This method has an additional callback that can be defined which will receive the state of the widget once it is loaded by the client. + * + * @param key The key in the config to store the value of this widgets state under + * @param loadedState A callback which receives the state from the config once loaded by the client + */ public void registerConfigKeyAnd(String key, Consumer loadedState) { if (this.state instanceof Stateless) return; if (this.configKey != null) throw new IllegalStateException("Cannot configure a config key more than once."); @@ -88,46 +169,96 @@ public void registerConfigKeyAnd(String key, Consumer loadedState) { FileSystem.registerUpdate(key, this.fsCallback); } + /** + * Sets this widget to capture hovering events. This is required to be set for tooltips to be rendered. + * + * @return This widget for method chaining + */ protected BaseWidget captureHover() { this.captureHover = true; return this; } + /** + * Sets this widget's focused state. + * + * @param state The focused state to set + */ public void setFocused(boolean state) { this.focused = state; } + /** + * Sets this widget to be focused. + */ public void setFocused() { this.focused = true; } + /** + * Sets this widget to be unfocused. + */ public void unfocus() { this.focused = false; } + /** + * Sets the hover tooltip of this widget. The tooltip will only be rendered if {@link #captureHover()} was called allowing this widget to capture hovering events. + *

For basic tooltips, you can use {@code Tooltip.of(Text.literal(String))} to convert a String to a Tooltip.

+ * + * @param tooltip The tooltip + */ @Override public void setTooltip(Tooltip tooltip) { this.tooltip.setTooltip(tooltip); } + /** + * Sets the position of this widget. + * + * @param x The distance from the left edge of the screen + * @param y The distance from the top edge of the screen + * @return This widget for method chaining + */ public BaseWidget withXY(int x, int y) { this.setX(x); this.setY(y); return this; } + /** + * Sets the width and height of this widget. + * + * @param w The width of the widget + * @param h The height of the widget + * @return This widget for method chaining + */ public BaseWidget withWH(int w, int h) { this.setWidth(w); this.setHeight(h); return this; } + /** + * Sets the state and stored default state of this widget. + * + * @param state The default state to set + * @return This widget for method chaining + */ public BaseWidget withDefault(StateType state) { this.defaultState = state; this.state = state; return this; } + /** + * Replacement method for the {@link ClickableWidget#render(DrawContext, int, int, float) ClickableWidget.render()} method that cannot be overridden. Automatically handles rendering tooltips when this widget is hovered and the top-most widget. Also pulls the bounding box of this widget and supplies the values to the {@link #render(DrawContext, boolean, int, int, float, int, int, int, int) render()} method for cleaner code. + * + * @param context The draw context to pass through + * @param mouseX The mouse X position to pass through + * @param mouseY The mouse Y position to pass through + * @param deltaTicks The delta ticks to pass through + */ public void _render(DrawContext context, int mouseX, int mouseY, float deltaTicks) { if (!this.visible) return; this.hovered = captureHover && isMouseOver(mouseX, mouseY) && CigaretteScreen.isHoverable(this); @@ -135,17 +266,42 @@ public void _render(DrawContext context, int mouseX, int mouseY, float deltaTick if (this.hovered) this.tooltip.render(true, this.isFocused(), this.getNavigationFocus()); } + /** + * Alias that calls {@link #_render(DrawContext, int, int, float) _render()} but looks nicer. + *

Automatically handles rendering tooltips when this widget is hovered and the top-most widget. Also pulls the bounding box of this widget and supplies the values to the {@link #render(DrawContext, boolean, int, int, float, int, int, int, int) render()} method for cleaner code.

+ * + * @param context The draw context to pass through + * @param mouseX The mouse X position to pass through + * @param mouseY The mouse Y position to pass through + * @param deltaTicks The delta ticks to pass through + */ @Override protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) { this._render(context, mouseX, mouseY, deltaTicks); } + /** + * The custom rendering method that replaces the built-in {@link ClickableWidget#render(DrawContext, int, int, float) ClickableWidget.render()} method. + * + * @param context The current draw context + * @param hovered Whether this widget is hovered by the mouse + * @param mouseX Current mouse X position + * @param mouseY Current mouse Y position + * @param deltaTicks Current delta ticks + * @param left Distance to the left edge of this widget from the left of the screen + * @param top Distance to the top edge of this widget from the top of the screen + * @param right Distance to the right edge of this widget from the left of the screen + * @param bottom Distance to the bottom edge of this widget from the top of the screen + */ protected abstract void render(DrawContext context, boolean hovered, int mouseX, int mouseY, float deltaTicks, int left, int top, int right, int bottom); @Override protected void appendClickableNarrations(NarrationMessageBuilder builder) { } + /** + * Empty class for widgets that do not hold any custom state. + */ public static class Stateless { } } diff --git a/src/main/java/dev/cigarette/gui/widget/ColorDropdownWidget.java b/src/main/java/dev/cigarette/gui/widget/ColorDropdownWidget.java index 55763ee9..281fbdf1 100644 --- a/src/main/java/dev/cigarette/gui/widget/ColorDropdownWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/ColorDropdownWidget.java @@ -4,25 +4,60 @@ import net.minecraft.client.gui.DrawContext; import org.jetbrains.annotations.Nullable; +/** + * An extension on {@link DropdownWidget} pre-made for color configuration. + * + * @param The type of children this widget stores. Use {@code Widget extends BaseWidget} to allow any types as children. + * @param The custom state this widget stores. Use {@link BaseWidget.Stateless} for widgets that should not hold state. + */ public class ColorDropdownWidget, StateType> extends DropdownWidget { + /** + * Additional header widget to render the color state from the sliders. + */ private final ColorSquareWidget colorSquare = new ColorSquareWidget(); + /** + * The slider that controls the red content in the color. + */ private final SliderWidget sliderRed = new SliderWidget("Red").withBounds(0, 255, 255); + /** + * The slider that controls the green content in the color. + */ private final SliderWidget sliderGreen = new SliderWidget("Green").withBounds(0, 255, 255); + /** + * The slider that controls the blue content in the color. + */ private final SliderWidget sliderBlue = new SliderWidget("Blue").withBounds(0, 255, 255); + /** + * The slider that controls the alpha content in the color. + */ private final SliderWidget sliderAlpha = new SliderWidget("Alpha").withBounds(0, 255, 255); + /** + * {@return the color state in ARGB format} + */ public int getStateARGB() { return this.colorSquare.getRawState(); } + /** + * {@return the color state in RGBA format} + */ public int getStateRGBA() { return ((this.colorSquare.getRawState() & 0xFFFFFF) << 8) + ((this.colorSquare.getRawState() >> 24) & 0xFF); } + /** + * {@return the color state in RGB format} + */ public int getStateRGB() { return this.colorSquare.getRawState() & 0xFFFFFF; } + /** + * {@return the toggled state of this widget} + * + * @throws IllegalStateException If the header of this widget is not an instance of {@link ToggleWidget} + */ public boolean getToggleState() { if (this.header instanceof ToggleWidget) { return ((ToggleWidget) this.header).getRawState(); @@ -30,6 +65,12 @@ public boolean getToggleState() { throw new IllegalStateException("Cannot get boolean state on a stateless header in a dropdown widget."); } + /** + * Creates a dropdown widget pre-made for color configuration. Has a {@link ToggleWidget} and {@link ColorSquareWidget} header, and {@link SliderWidget} children for customizing the selected color. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ @SuppressWarnings("unchecked") public ColorDropdownWidget(String message, @Nullable String tooltip) { super(message, tooltip); @@ -38,6 +79,12 @@ public ColorDropdownWidget(String message, @Nullable String tooltip) { this.attachChildren().captureHover(); } + /** + * Sets the color state and stored default color state of this widget. + * + * @param argb The default state to set in ARGB format + * @return This widget for method chaining + */ public ColorDropdownWidget withDefaultColor(int argb) { this.colorSquare.withDefault(argb); sliderAlpha.withDefault((double) ((argb >> 24) & 0xFF)); @@ -47,17 +94,34 @@ public ColorDropdownWidget withDefaultColor(int argb) { return this; } + /** + * Sets the state and stored default state of the heading {@link ToggleWidget}. + * + * @param state The default state to set + * @return This widget for method chaining + */ public ColorDropdownWidget withDefaultState(StateType state) { this.header.withDefault(state); return this; } + /** + * Sets whether this widget should include an alpha slider. + * + * @param alpha Whether an alpha slider should be included + * @return This widget for method chaining + */ public ColorDropdownWidget withAlpha(boolean alpha) { this.sliderAlpha.disabled = !alpha; if (!alpha) this.sliderAlpha.withDefault(255d); return this; } + /** + * Attaches the children to the dropdown menu and binds callbacks when sliders are moved. + * + * @return This widget for method chaining + */ private ColorDropdownWidget attachChildren() { this.container.setChildren(this.sliderRed, this.sliderGreen, this.sliderBlue, this.sliderAlpha); this.sliderRed.stateCallback = ((newColor -> { @@ -79,6 +143,13 @@ private ColorDropdownWidget attachChildren() { return this; } + /** + * Generator for modules using this as a top-level widget. Creates a togglable {@link ColorDropdownWidget}. + * + * @param displayName The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + * @return A {@link BaseModule.GeneratedWidgets} object for use in {@link BaseModule} constructing + */ public static BaseModule.GeneratedWidgets module(String displayName, @Nullable String tooltip) { ColorDropdownWidget wrapper = new ColorDropdownWidget<>(displayName, tooltip); ToggleWidget widget = new ToggleWidget(displayName, tooltip); @@ -86,6 +157,13 @@ public static BaseModule.GeneratedWidgets module(String d return new BaseModule.GeneratedWidgets<>(wrapper, widget); } + /** + * Creates and returns a new togglable {@link ColorDropdownWidget}. + * + * @param displayName The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + * @return the new widget with a {@link ToggleWidget} attached as the header + */ public static ColorDropdownWidget buildToggle(String displayName, @Nullable String tooltip) { ColorDropdownWidget wrapper = new ColorDropdownWidget<>(displayName, tooltip); ToggleWidget widget = new ToggleWidget(displayName, tooltip); @@ -93,6 +171,13 @@ public static ColorDropdownWidget buildToggle(String disp return wrapper; } + /** + * Creates and returns a new {@link ColorDropdownWidget} with only the color configuration, no toggling. + * + * @param displayName The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + * @return thw new widget with a {@link TextWidget} attached as the header + */ public static ColorDropdownWidget buildText(String displayName, @Nullable String tooltip) { ColorDropdownWidget wrapper = new ColorDropdownWidget<>(displayName, tooltip); TextWidget widget = new TextWidget(displayName, tooltip).centered(false); diff --git a/src/main/java/dev/cigarette/gui/widget/ColorSquareWidget.java b/src/main/java/dev/cigarette/gui/widget/ColorSquareWidget.java index 96e80591..f4cdd60b 100644 --- a/src/main/java/dev/cigarette/gui/widget/ColorSquareWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/ColorSquareWidget.java @@ -2,7 +2,13 @@ import net.minecraft.client.gui.DrawContext; +/** + * A widget that only renders a colored square. + */ public class ColorSquareWidget extends BaseWidget { + /** + * Creates a widget whose only purpose is to render a colored square. + */ public ColorSquareWidget() { super("", null); this.withDefault(0xFFFFFFFF); diff --git a/src/main/java/dev/cigarette/gui/widget/DraggableWidget.java b/src/main/java/dev/cigarette/gui/widget/DraggableWidget.java index 8d147930..eee326ae 100644 --- a/src/main/java/dev/cigarette/gui/widget/DraggableWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/DraggableWidget.java @@ -11,6 +11,9 @@ import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; +/** + * A widget that can be dragged around the screen. + */ public class DraggableWidget extends BaseWidget { public interface DragCallback { void updateParentPosition(int newX, int newY, int deltaX, int deltaY); @@ -20,28 +23,80 @@ public interface ClickCallback { void onClick(double mouseX, double mouseY, int button); } + /** + * Whether this widget is actively being dragged by the user. + */ private boolean dragging = false; + /** + * The starting X position of the drag on screen. + */ private int startingX = 0; + /** + * The starting Y position of the drag on screen. + */ private int startingY = 0; + /** + * The starting mouse X position that initiated the drag. + */ private double startingMouseX = 0; + /** + * The starting mouse Y position that initiated the drag. + */ private double startingMouseY = 0; + /** + * Callback triggered when this widget is moved as a result of a drag. + */ private @Nullable DragCallback dragCallback = null; + /** + * Callback triggered when this widget is right-clicked. + */ private @Nullable ClickCallback clickCallback = null; + /** + * If this widget is responsible for a {@link ScrollableWidget}'s visibility, this signals whether that widget is collapsed. Used for rounding the bottom corners of this widget when collapsed. + */ public boolean expanded = false; + /** + * The current number of ticks the collapse animation has progressed through. + */ private int ticksOnCollapse = 0; + /** + * The max number of ticks the collapse animation lasts. + */ private static final int MAX_TICKS_ON_COLLAPSE = 10; + /** + * Creates a widget that can be dragged and clicked. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + */ public DraggableWidget(int x, int y, int width, int height, String message) { super(message, null); this.captureHover().withXY(x, y).withWH(width, height); } + /** + * Creates a widget that can be dragged and clicked. + * + * @param message The text to display inside this widget + */ public DraggableWidget(String message) { super(message, null); this.captureHover(); } + /** + * Captures a mouse click to initiate dragging and trigger click callbacks. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return Whether this widget handled the click + */ @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOver(mouseX, mouseY)) { @@ -68,6 +123,16 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } + /** + * Captures a mouse drag to update the position of this widget. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @param ignored the mouse delta X + * @param ignored_ the mouse delta Y + * @return Whether this widget handled the drag + */ @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double ignored, double ignored_) { if (dragging) { @@ -90,26 +155,42 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double ign return dragging; } + /** + * Captures a mouse release to stop the dragging of this widget. + *

Does not prevent this event from propagating to other elements.

+ * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return {@code false} + */ @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { dragging = false; return false; } + /** + * Attaches a callback that is triggered when this widget is dragged to a new position. + * + * @param callback The callback to trigger on drag + */ public void onDrag(DragCallback callback) { this.dragCallback = callback; } + /** + * Attaches a callback that is triggered when this widget is clicked and not dragged. + * + * @param callback The callback to trigger on click + */ public void onClick(ClickCallback callback) { this.clickCallback = callback; } - - - @Override public void render(DrawContext context, boolean hovered, int mouseX, int mouseY, float deltaTicks, int left, - int top, int right, int bottom) { + int top, int right, int bottom) { TextRenderer textRenderer = Cigarette.REGULAR; int bgColor = Color.color(left, top); if (!this.expanded) { diff --git a/src/main/java/dev/cigarette/gui/widget/DropdownWidget.java b/src/main/java/dev/cigarette/gui/widget/DropdownWidget.java index 7a31a5eb..99e32571 100644 --- a/src/main/java/dev/cigarette/gui/widget/DropdownWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/DropdownWidget.java @@ -9,24 +9,72 @@ import org.joml.Quaternionf; import org.lwjgl.glfw.GLFW; +/** + * A dropdown widget which can show and hide children widgets. + * + * @param The type of children this widget stores. Use {@code Widget extends BaseWidget} to allow any types as children. + * @param The custom state this widget stores. Use {@link BaseWidget.Stateless} for widgets that should not hold state. + */ public class DropdownWidget, StateType> extends PassthroughWidget, BaseWidget.Stateless> { + /** + * The heading widget. + */ protected Widget header; + /** + * The child widgets inside the dropdown menu. + */ protected ScrollableWidget> container; + /** + * Whether the child widgets in this dropdown should be visible. + */ private boolean dropdownVisible = false; + /** + * Whether a dropdown indicator should be rendered. + */ private boolean dropdownIndicator = true; + /** + * The time at which the rotation started. + */ private long rotateStartMillis = 0L; + /** + * The current rotation of the dropdown indicator. + */ private double rotateAngleRad = 0.0; + /** + * The current rotation of the dropdown indicator whilst the dropdown is open. + */ private double rotateOffsetRad = 0.0; + /** + * The time it takes in milliseconds for a full rotation of the dropdown indicator. + */ private static final int ROTATION_PERIOD_MS = 2000; + /** + * Whether the dropdown menu is currently animating. + */ private boolean animating = false; + /** + * Whether the dropdown menu is in progress of opening. + */ private boolean opening = false; + /** + * The time at which the animation started. + */ private long animStartMillis = 0L; + /** + * The maximum animation runtime on opening/closing the dropdown. + */ private static final int TOGGLE_ANIM_MS = 220; + /** + * Creates a widget that can expand like a dropdown menu to show child widgets. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public DropdownWidget(String message, @Nullable String tooltip) { super(message, tooltip); this.withDefault(new BaseWidget.Stateless()); @@ -34,16 +82,34 @@ public DropdownWidget(String message, @Nullable String tooltip) { super.children.put("0", this.container); } + /** + * Sets this widgets header widget. The dropdown menu opens when this widget is right-clicked and this widget is always visible. + * + * @param header The widget to use as the header + * @return This widget for method chaining + */ public DropdownWidget setHeader(Widget header) { this.header = header; return this; } + /** + * Sets this widget's children. These widgets will become visible when the dropdown menu is opened. + * + * @param children The children to attach + * @return This widget for method chaining + */ public DropdownWidget setChildren(@Nullable BaseWidget... children) { this.container.setChildren(children); return this; } + /** + * Sets whether this widget should render a dropdown indicator over the heading widget. + * + * @param indicator Whether the indicator should be rendered + * @return This widget for method chaining + */ public DropdownWidget withIndicator(boolean indicator) { this.dropdownIndicator = indicator; return this; @@ -215,6 +281,16 @@ protected void render(DrawContext context, boolean hovered, int mouseX, int mous } } + /** + * Render the clients logo at a specific location and rotation. Used as the dropdown indicator. + * + * @param context The draw context to draw on + * @param x The X position to draw at + * @param y The Y position to draw at + * @param w The width of the texture + * @param h The height of the texture + * @param angle The rotation of the texture + */ public static void cigaretteOnlyAt(DrawContext context, int x, int y, int w, int h, int angle) { context.getMatrices().push(); float cx = x + w / 2f; diff --git a/src/main/java/dev/cigarette/gui/widget/KeybindWidget.java b/src/main/java/dev/cigarette/gui/widget/KeybindWidget.java index a0222d6e..cf168ccb 100644 --- a/src/main/java/dev/cigarette/gui/widget/KeybindWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/KeybindWidget.java @@ -16,10 +16,25 @@ import java.util.UUID; import java.util.function.Consumer; +/** + * A widget that lets the user bind a key. + */ public class KeybindWidget extends BaseWidget { + /** + * The internal Minecraft KeyBinding. + */ private final KeyBinding keyBinding; + /** + * The KeyBindings actual key for rendering and configuration. + */ private InputUtil.Key utilKey; + /** + * Creates a widget that stores a keybind and allows it to be configured. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public KeybindWidget(String message, @Nullable String tooltip) { super(message, tooltip); this.utilKey = InputUtil.UNKNOWN_KEY; @@ -27,6 +42,12 @@ public KeybindWidget(String message, @Nullable String tooltip) { KeyBindingHelper.registerKeyBinding(this.keyBinding); } + /** + * Sets the key and stored default key of this widget. + * + * @param key The default key to set + * @return This widget for method chaining + */ public KeybindWidget withDefaultKey(int key) { utilKey = InputUtil.fromKeyCode(key, 0); keyBinding.setBoundKey(utilKey); @@ -34,24 +55,41 @@ public KeybindWidget withDefaultKey(int key) { return this; } + /** + * Update the stored key of this widget from an input event. + * + * @param key The new key to set to this widget + */ public void setBoundKey(@Nullable InputUtil.Key key) { utilKey = key == null ? InputUtil.UNKNOWN_KEY : key; keyBinding.setBoundKey(utilKey); this.setRawState(utilKey.getCode()); } + /** + * {@return the internal Minecraft KeyBinding} + */ public KeyBinding getKeybind() { return this.keyBinding; } + /** + * Stops this widget from capturing keys to update binding. + */ protected void clearBinding() { CigaretteScreen.bindingKey = null; } + /** + * Toggles whether this widget is currently binding and capturing keys. + */ protected void toggleBinding() { CigaretteScreen.bindingKey = isBinding() ? null : this; } + /** + * {@return whether this widget is currently being bound by the user} + */ protected boolean isBinding() { return CigaretteScreen.bindingKey == this; } @@ -74,6 +112,14 @@ public void registerConfigKeyAnd(String key, Consumer loadedState) { }); } + /** + * Captures a mouse click to toggle whether the keybind is being bound. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return Whether this widget handled the click + */ @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOver(mouseX, mouseY)) { @@ -85,6 +131,14 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } + /** + * Captures a key press to bind to the internal KeyBinding. + * + * @param keyCode the named key code of the event as described in the {@link org.lwjgl.glfw.GLFW GLFW} class + * @param scanCode the unique/platform-specific scan code of the keyboard input + * @param modifiers a GLFW bitfield describing the modifier keys that are held down (see GLFW Modifier key flags) + * @return Whether this widget handled the key press + */ @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (!isBinding()) return false; @@ -100,6 +154,13 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return true; } + /** + * Renders the key or binding state of this widget at a specific location. + * + * @param context The draw context to draw on + * @param top The upper-bounding Y position to draw within + * @param right The right-bounding X position to draw within + */ public void renderKeyText(DrawContext context, int top, int right) { TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; diff --git a/src/main/java/dev/cigarette/gui/widget/PassthroughWidget.java b/src/main/java/dev/cigarette/gui/widget/PassthroughWidget.java index c7b9b861..3cf9d35d 100644 --- a/src/main/java/dev/cigarette/gui/widget/PassthroughWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/PassthroughWidget.java @@ -7,27 +7,66 @@ import java.util.Map; import java.util.TreeMap; +/** + * Extends the passthrough widget which holds children widgets and automatically forwards events and rendering to them. + * + * @param The type of children this widget stores. Use {@code Widget extends BaseWidget} to allow any types as children. + * @param The custom state this widget stores. Use {@link BaseWidget.Stateless} for widgets that should not hold state. + */ public abstract class PassthroughWidget, StateType> extends BaseWidget { + /** + * The children that this widget is the parent of, mapped by String to the actual child reference for sorting capabilities. + *

There is no default methods for adding children as defined in {@link PassthroughWidget}, so check the subclasses for implementation details.

+ */ protected Map children = new LinkedHashMap<>(); + /** + * Left offsetting for dropdown continuation for widgets that may render scrollbars or have inconsistent sizing. + */ protected int childLeftOffset = 0; + /** + * Creates the super class. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + */ public PassthroughWidget(int x, int y, int width, int height, String message) { super(message, null); this.withXY(x, y).withWH(width, height); } + /** + * Creates the super class. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public PassthroughWidget(String message, @Nullable String tooltip) { super(message, tooltip); } + /** + * Creates the super class. + * + * @param message The text to display inside this widget + */ public PassthroughWidget(String message) { super(message, null); } + /** + * Switches the {@link Map} type of {@link #children} to a {@link TreeMap} which automatically sorts by the {@link String} keys. + */ public void alphabetic() { this.children = new TreeMap<>(this.children); } + /** + * Sets this widget and all of its children to be unfocused. + */ @Override public void unfocus() { for (BaseWidget child : children.values()) { @@ -36,6 +75,12 @@ public void unfocus() { } } + /** + * Forwards mouse move events to all the children of this widget. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + */ @Override public void mouseMoved(double mouseX, double mouseY) { if (children.isEmpty()) return; @@ -45,6 +90,15 @@ public void mouseMoved(double mouseX, double mouseY) { } } + /** + * Forwards mouse clicked events to all the children of this widget. + *

The first child that handles the event will be focused and all subsequent children will be unfocused and will not receive the event.

+ * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return Whether a child handled the click + */ @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (children.isEmpty()) return false; @@ -62,6 +116,14 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return wasHandled; } + /** + * Forwards mouse released events to all the children of this widget. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return {@code false} + */ @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { if (children.isEmpty()) return false; @@ -72,6 +134,16 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { return false; } + /** + * Forwards mouse dragged events to all the children of this widget. + * + * @param mouseX the current X coordinate of the mouse + * @param mouseY the current Y coordinate of the mouse + * @param button the mouse button number + * @param deltaX the difference of the current X with the previous X coordinate + * @param deltaY the difference of the current Y with the previous Y coordinate + * @return {@code false} + */ @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { if (children.isEmpty()) return false; @@ -82,6 +154,16 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del return false; } + /** + * Forwards mouse scrolled events to all the children of this widget. + *

This returns after the first child that handles the event.

+ * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param horizontalAmount the horizontal scroll amount + * @param verticalAmount the vertical scroll amount + * @return Whether a child handled the scroll + */ @Override public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { if (children.isEmpty()) return false; @@ -93,6 +175,15 @@ public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmou return false; } + /** + * Forwards key pressed events to all the children of this widget. + *

This returns after the first child that handles the event.

+ * + * @param keyCode the named key code of the event as described in the {@link org.lwjgl.glfw.GLFW GLFW} class + * @param scanCode the unique/platform-specific scan code of the keyboard input + * @param modifiers a GLFW bitfield describing the modifier keys that are held down (see GLFW Modifier key flags) + * @return Whether a child handled the key press + */ @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (children.isEmpty()) return false; @@ -104,6 +195,15 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return false; } + /** + * Forwards the key released events to all the children of this widget. + *

This returns after the first child that handles the event.

+ * + * @param keyCode the named key code of the event as described in the {@link org.lwjgl.glfw.GLFW GLFW} class + * @param scanCode the unique/platform-specific scan code of the keyboard input + * @param modifiers a GLFW bitfield describing the modifier keys that are held down (see GLFW Modifier key flags) + * @return Whether a child handled the key release + */ @Override public boolean keyReleased(int keyCode, int scanCode, int modifiers) { if (children.isEmpty()) return false; @@ -115,6 +215,14 @@ public boolean keyReleased(int keyCode, int scanCode, int modifiers) { return false; } + /** + * Forwards the char typed events to all the children of this widget. + *

This returns after the first child that handles the event.

+ * + * @param chr the captured character + * @param modifiers a GLFW bitfield describing the modifier keys that are held down (see GLFW Modifier key flags) + * @return Whether a child handled the char type + */ @Override public boolean charTyped(char chr, int modifiers) { if (children.isEmpty()) return false; diff --git a/src/main/java/dev/cigarette/gui/widget/ScrollableWidget.java b/src/main/java/dev/cigarette/gui/widget/ScrollableWidget.java index cebf76b7..f386b9aa 100644 --- a/src/main/java/dev/cigarette/gui/widget/ScrollableWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/ScrollableWidget.java @@ -11,24 +11,82 @@ import java.util.Collection; import java.util.Objects; +/** + * A scrollable widget which provides scrolling functionality. + * + * @param The type of children this widget stores. Use {@code Widget extends BaseWidget} to allow any types as children. + */ public class ScrollableWidget> extends PassthroughWidget, BaseWidget.Stateless> { + /** + * Multiplier applied to the vertical scrolling delta to determine distance to actually scroll. + */ private static final int VERTICAL_SCROLL_MULTIPLIER = 6; + /** + * The default width of this widget. + */ private static final int DEFAULT_WIDTH = 100; + /** + * The default height of this widget. + */ private static final int DEFAULT_HEIGHT = 200; + /** + * The default scrollbar width of this widget. + */ private static final int DEFAULT_SCROLLBAR_WIDTH = 3; + /** + * The height of the bottom padding for the rounded effect. + */ private static final int BOTTOM_ROUNDED_RECT_HEIGHT = 6; + /** + * The height of each row inside this widget. + */ private final int rowHeight = 20; + /** + * Whether this widget is supposed to be scrollable or not. + */ private boolean shouldScroll = false; + /** + * The current scroll position. + */ private double scrollPosition = 0D; + /** + * The optional header that can move this widget when dragged. + */ private @Nullable DraggableWidget header; + /** + * The category offset index. + */ private int categoryOffsetIndex = 0; + /** + * Whether this widget is expanded, revealing its children, or not. + */ public boolean expanded = true; + /** + * Partial ticks when the widget is expanded. + */ private int ticksOnOpen = 0; + /** + * Max ticks to complete the expanding animation. + */ private static final int MAX_TICKS_ON_OPEN = 20; + /** + * Callback triggered when the header is clicked to toggle the {@link #expanded} state of this widget. + */ private @Nullable Runnable onToggleExpand; + /** + * Whether to show the bottom rounding effect or not. + */ private boolean showBottomRoundedRect = true; + /** + * Creates a widget that renders children in a scrollable window. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param headerText The text to display as the header at the top of this widget + * @param children The children to attach to this widget + */ @SafeVarargs public ScrollableWidget(int x, int y, @Nullable String headerText, @Nullable Widgets... children) { super(x, y, DEFAULT_WIDTH + DEFAULT_SCROLLBAR_WIDTH, DEFAULT_HEIGHT, null); @@ -36,6 +94,13 @@ public ScrollableWidget(int x, int y, @Nullable String headerText, @Nullable Wid this.setChildren(children).setHeader(headerText, null); } + /** + * Creates a widget that renders children in a scrollable window. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param children The children to attach to this widget + */ @SafeVarargs public ScrollableWidget(int x, int y, @Nullable Widgets... children) { super(x, y, DEFAULT_WIDTH + DEFAULT_SCROLLBAR_WIDTH, DEFAULT_HEIGHT, null); @@ -43,11 +108,24 @@ public ScrollableWidget(int x, int y, @Nullable Widgets... children) { this.setChildren(children); } + /** + * Creates a widget that renders children in a scrollable window. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + */ public ScrollableWidget(int x, int y) { super(x, y, DEFAULT_WIDTH + DEFAULT_SCROLLBAR_WIDTH, DEFAULT_HEIGHT, null); this.withDefault(new BaseWidget.Stateless()); } + /** + * Creates a widget that renders children in a scrollable window. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param showBottomRoundedRect Whether to show the bottom rounded effect + */ public ScrollableWidget(int x, int y, boolean showBottomRoundedRect) { super(x, y, DEFAULT_WIDTH + DEFAULT_SCROLLBAR_WIDTH, DEFAULT_HEIGHT, null); this.withDefault(new BaseWidget.Stateless()); @@ -56,17 +134,26 @@ public ScrollableWidget(int x, int y, boolean showBottomRoundedRect) { @Override public BaseWidget withXY(int x, int y) { - if(this.header != null) this.header.withXY(x, y); + if (this.header != null) this.header.withXY(x, y); super.withXY(x, y); return this; } + /** + * {@return whether the container should be scrollable} Updates {@link #shouldScroll}. + */ private boolean updateShouldScroll() { this.shouldScroll = ((children == null ? 0 : children.size()) + (header != null ? 1 : 0)) * rowHeight > this.height; return this.shouldScroll; } + /** + * Sets the children attached to this widget. + * + * @param children The children to attach + * @return This widget for method chaining + */ @SafeVarargs public final ScrollableWidget setChildren(@Nullable Widgets... children) { for (Widgets widget : children) { @@ -77,10 +164,23 @@ public final ScrollableWidget setChildren(@Nullable Widgets... children return updateChildrenSizing(); } + /** + * Sets the header text of this widget. This creates a {@link DraggableWidget} that renders at the top of the widget that also controls its position. + * + * @param headerText The text to display + * @return This widget for method chaining + */ public ScrollableWidget setHeader(@Nullable String headerText) { return this.setHeader(headerText, null); } + /** + * Sets the header text of this widget. This creates a {@link DraggableWidget} that renders at the top of the widget that also controls its position. + * + * @param headerText The text to display + * @param onToggleExpand The callback to run when the header is right-clicked + * @return This widget for method chaining + */ public ScrollableWidget setHeader(@Nullable String headerText, @Nullable Runnable onToggleExpand) { this.onToggleExpand = onToggleExpand; if (headerText == null) { @@ -105,6 +205,11 @@ public ScrollableWidget setHeader(@Nullable String headerText, @Nullabl return updateChildrenSizing(); } + /** + * Sets all the children width and heights. This also triggers {@link #updateShouldScroll()} to set whether there needs to be a scrollbar. + * + * @return This widget for method chaining + */ private ScrollableWidget updateChildrenSizing() { if (this.children != null) { int rightMargin = this.updateShouldScroll() ? DEFAULT_SCROLLBAR_WIDTH : 0; @@ -121,14 +226,27 @@ private ScrollableWidget updateChildrenSizing() { return this; } + /** + * {@return the category offset index} + */ public int getCategoryOffsetIndex() { return this.categoryOffsetIndex; } + /** + * Sets the category offset index. + * + * @param index The category offset index + */ public void setCategoryOffsetIndex(int index) { this.categoryOffsetIndex = Math.max(0, index); } + /** + * Sets whether this widget is expanded. + * + * @param expanded Whether this widget should be expanded + */ public void setExpanded(boolean expanded) { if (this.expanded == expanded) return; diff --git a/src/main/java/dev/cigarette/gui/widget/SliderWidget.java b/src/main/java/dev/cigarette/gui/widget/SliderWidget.java index a0afa7e2..04456b5a 100644 --- a/src/main/java/dev/cigarette/gui/widget/SliderWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/SliderWidget.java @@ -10,15 +10,45 @@ import java.util.function.Consumer; +/** + * A widget that holds a value that be adjusted by a slider. + */ public class SliderWidget extends BaseWidget { + /** + * The amount of padding on the left and right side of the sliders bounding box to prevent the slider bar from reaching the border. + */ private static final int SLIDER_PADDING = 4; + /** + * Callback triggered when this widget's state changes. + */ private @Nullable Consumer sliderCallback = null; + /** + * The maximum value this slider can hold. + */ private double maxState = 0; + /** + * The minimum value this slider can hold. + */ private double minState = 0; + /** + * The number of decimal places to include when getting this widgets state. + */ private int decimalPlaces = 0; + /** + * Whether the slider head is actively being dragged by the user. + */ private boolean dragging = false; + /** + * Whether this slider is disabled. + */ public boolean disabled = false; + /** + * Updates the state of the slider and triggers the state change callback. + *

This returns prematurely if the state is not within bounds.

+ * + * @param state The new state to set to this widget + */ public void setState(double state) { if (state > maxState) return; if (state < minState) return; @@ -26,6 +56,12 @@ public void setState(double state) { if (sliderCallback != null) sliderCallback.accept(state); } + /** + * Updates the raw state of the slider do the {@link #decimalPlaces} degree of accuracy. + *

This returns prematurely if the state is not within bounds. Does not trigger any callbacks by itself.

+ * + * @param state The new state to set to this widget + */ protected void setAccurateState(double state) { if (state > maxState) return; if (state < minState) return; @@ -33,6 +69,11 @@ protected void setAccurateState(double state) { this.setRawState(Math.round(state * mult) / mult); } + /** + * Updates the stored state based on the position of the slider. + * + * @param mouseX the X coordinate of the mouse + */ private void setStateFromDrag(double mouseX) { int left = this.getX() + SLIDER_PADDING; int width = this.getWidth() - 2 * SLIDER_PADDING; @@ -41,26 +82,64 @@ private void setStateFromDrag(double mouseX) { this.setState(value); } + /** + * Creates a widget with a slider to adjust its value. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public SliderWidget(int x, int y, int width, int height, String message, @Nullable String tooltip) { super(message, tooltip); this.captureHover().withXY(x, y).withWH(width, height).withDefault(0d); } + /** + * Creates a widget with a slider to adjust its value. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + */ public SliderWidget(int x, int y, int width, int height, String message) { super(message, null); this.captureHover().withXY(x, y).withWH(width, height).withDefault(0d); } + /** + * Creates a widget with a slider to adjust its value. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public SliderWidget(String message, String tooltip) { super(message, tooltip); this.captureHover().withDefault(0d); } + /** + * Creates a widget with a slider to adjust its value. + * + * @param message The text to display inside this widget + */ public SliderWidget(String message) { super(message, null); this.captureHover().withDefault(0d); } + /** + * Sets the min, max, and default state of this widget. + * + * @param min The minimum value the state can be + * @param def The default state to set + * @param max The maximum value the state can be + * @return This widget for method chaining + */ public SliderWidget withBounds(double min, double def, double max) { this.minState = min; this.maxState = max; @@ -68,11 +147,25 @@ public SliderWidget withBounds(double min, double def, double max) { return this; } + /** + * Sets how many decimal places will be reported when getting the state. + * + * @param decimalPlaces The number of decimal places + * @return This widget for method chaining + */ public SliderWidget withAccuracy(int decimalPlaces) { this.decimalPlaces = decimalPlaces; return this; } + /** + * Captures a mouse click to initiate dragging on the slider. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return Whether this widget handled the click + */ @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.disabled) return false; @@ -82,6 +175,16 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return true; } + /** + * Captures a mouse drag to update the state and position of the slider. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @param ignored the mouse delta X + * @param ignored_ the mouse delta Y + * @return Whether this widget handled the drag + */ @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double ignored, double ignored_) { if (this.disabled) return false; @@ -90,6 +193,15 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double ign return true; } + /** + * Captures a mouse release to stop the dragging of the slider. + *

Does not prevent this event from propagating to other elements.

+ * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return {@code false} + */ @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { if (this.disabled) return false; diff --git a/src/main/java/dev/cigarette/gui/widget/TextWidget.java b/src/main/java/dev/cigarette/gui/widget/TextWidget.java index 8b2558ef..8ae7a603 100644 --- a/src/main/java/dev/cigarette/gui/widget/TextWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/TextWidget.java @@ -6,35 +6,85 @@ import net.minecraft.client.gui.DrawContext; import org.jetbrains.annotations.Nullable; +/** + * A widget that only renders text. + */ public class TextWidget extends BaseWidget { + /** + * Whether a line should be rendered at the bottom of this widget's bounding box. + */ private boolean underlined = false; + /** + * Whether the text should be centered inside this widget's bounding box. + */ private boolean centered = true; + /** + * Creates a widget whose only purpose is to render text and a tooltip. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public TextWidget(int x, int y, int width, int height, String message, @Nullable String tooltip) { super(message, tooltip); this.captureHover().withXY(x, y).withWH(width, height); } + /** + * Creates a widget whose only purpose is to render text and a tooltip. + * + * @param x The initial X position of this widget + * @param y The initial Y position of this widget + * @param width The initial width of this widget + * @param height The initial height of this widget + * @param message The text to display inside this widget + */ public TextWidget(int x, int y, int width, int height, String message) { super(message, null); this.captureHover().withXY(x, y).withWH(width, height); } + /** + * Creates a widget whose only purpose is to render text and a tooltip. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public TextWidget(String message, @Nullable String tooltip) { super(message, tooltip); this.captureHover(); } + /** + * Creates a widget whose only purpose is to render text and a tooltip. + * + * @param message The text to display inside this widget + */ public TextWidget(String message) { super(message, null); this.captureHover(); } + /** + * Sets this widget to render a line at the bottom of its bounding box. + * + * @return This for method chaining + */ public TextWidget withUnderline() { underlined = true; return this; } + /** + * Sets whether this widget should render its text centered in its bounding box. + * + * @param center Whether the text should be centered + * @return This for method chaining + */ public TextWidget centered(boolean center) { this.centered = center; return this; diff --git a/src/main/java/dev/cigarette/gui/widget/ToggleKeybindWidget.java b/src/main/java/dev/cigarette/gui/widget/ToggleKeybindWidget.java index 6be151b3..3af56ca7 100644 --- a/src/main/java/dev/cigarette/gui/widget/ToggleKeybindWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/ToggleKeybindWidget.java @@ -8,9 +8,21 @@ import java.util.function.Consumer; +/** + * A widget that can be toggled by the user and lets a key be bound. + */ public class ToggleKeybindWidget extends ToggleWidget { + /** + * The {@link KeybindWidget} for handling configuration of the keybind. + */ protected KeybindWidget widget; + /** + * Creates a widget that stores a keybind and can be toggled. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public ToggleKeybindWidget(String message, @Nullable String tooltip) { super(message, tooltip); this.widget = new KeybindWidget(message, null); @@ -21,26 +33,52 @@ public ToggleKeybindWidget withDefaultState(boolean state) { return this; } + /** + * Sets the key and stored default key of this widget. + * + * @param keyCode The default key to set + * @return This widget for method chaining + */ public ToggleKeybindWidget withDefaultKey(int keyCode) { this.widget.withDefaultKey(keyCode); return this; } + /** + * {@return the internal Minecraft KeyBinding} + */ public KeyBinding getKeybind() { return this.widget.getKeybind(); } + /** + * Generator for modules using this as a top-level widget. + * + * @param displayName The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + * @return A {@link BaseModule.GeneratedWidgets} object for use in {@link BaseModule} constructing + */ public static BaseModule.GeneratedWidgets keybindModule(String displayName, @Nullable String tooltip) { ToggleKeybindWidget widget = new ToggleKeybindWidget(displayName, tooltip); return new BaseModule.GeneratedWidgets<>(null, widget); } + /** + * Sets the width of this widget. + * + * @param width The widget of the widget + */ @Override public void setWidth(int width) { super.setWidth(width); this.widget.setWidth(width); } + /** + * Sets the height of this widget. + * + * @param height The height of the widget + */ @Override public void setHeight(int height) { super.setHeight(height); diff --git a/src/main/java/dev/cigarette/gui/widget/ToggleWidget.java b/src/main/java/dev/cigarette/gui/widget/ToggleWidget.java index 13887077..9cab2628 100644 --- a/src/main/java/dev/cigarette/gui/widget/ToggleWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/ToggleWidget.java @@ -12,14 +12,37 @@ import java.util.function.Consumer; +/** + * A widget that can be toggled by the user. + */ public class ToggleWidget extends BaseWidget { + /** + * The max number of ticks the hover animation runs for. + */ private static final int MAX_HOVER_TICKS = 35; + /** + * The current number of ticks the hover animation has run for. + */ private int ticksOnHover = 0; private @Nullable Consumer callback = null; + /** + * The max number of ticks the enable animation runs for. + */ private static final float MAX_ENABLE_TICKS = 5f; + /** + * The current number of ticks the enable animation has run for. + */ private float ticksOnEnable = 0f; + /** + * Smoothly transition from color {@code a} to color {@code b} in {@code t} partial ticks. + * + * @param a The starting ARGB color to transition from + * @param b The new ARGB color to transition to + * @param t Partial ticks + * @return The current ARGB color at {@code t} partial ticks + */ private static int lerpColor(int a, int b, float t) { t = Math.max(0f, Math.min(1f, t)); int aA = (a >> 24) & 0xFF; @@ -40,16 +63,35 @@ private static int lerpColor(int a, int b, float t) { return (rA & 0xFF) << 24 | (rR & 0xFF) << 16 | (rG & 0xFF) << 8 | (rB & 0xFF); } + /** + * Creates a togglable widget. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public ToggleWidget(String message, @Nullable String tooltip) { super(message, tooltip); this.captureHover().withDefault(false); } + /** + * Sets the state and stored default state of this widget. + * + * @param state The default state to set + * @return This widget for method chaining + */ public ToggleWidget withDefaultState(boolean state) { this.withDefault(state); return this; } + /** + * Generator for modules using this as a top-level widget. + * + * @param displayName The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + * @return A {@link BaseModule.GeneratedWidgets} object for use in {@link BaseModule} constructing + */ public static BaseModule.GeneratedWidgets module(String displayName, @Nullable String tooltip) { DropdownWidget wrapper = new DropdownWidget<>(displayName, tooltip); ToggleWidget widget = new ToggleWidget(displayName, tooltip); @@ -57,6 +99,14 @@ public static BaseModule.GeneratedWidgets module(String d return new BaseModule.GeneratedWidgets<>(wrapper, widget); } + /** + * Captures a mouse click to switch the state. + * + * @param mouseX the X coordinate of the mouse + * @param mouseY the Y coordinate of the mouse + * @param button the mouse button number + * @return Whether this widget handled the click + */ @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!isMouseOver(mouseX, mouseY)) return false; @@ -100,6 +150,12 @@ protected void render(DrawContext context, boolean hovered, int mouseX, int mous } public class ToggleWidgetDisabled extends ToggleWidget { + /** + * Creates a togglable widget whose state can not be changed and renders disabled. + * + * @param message The text to display inside this widget + * @param tooltip The tooltip to render when this widget is hovered + */ public ToggleWidgetDisabled(String message, @Nullable String tooltip) { super(message, tooltip); }