From 2e228ca33ba4331ab674ef94684cf2ad2be84a63 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 18 Mar 2026 18:43:57 +0100 Subject: [PATCH] feat: added `Position` class --- .../codejive/twinkle/screen/BufferStack.java | 23 +++--- .../codejive/twinkle/screen/util/Rect.java | 52 ++++++++----- .../fluent/commands/CursorCommands.java | 12 +++ .../org/codejive/twinkle/text/Position.java | 63 ++++++++++++++++ .../java/org/codejive/twinkle/text/Size.java | 74 ++++++++++++++++++- 5 files changed, 196 insertions(+), 28 deletions(-) create mode 100644 twinkle-text/src/main/java/org/codejive/twinkle/text/Position.java diff --git a/twinkle-screen/src/main/java/org/codejive/twinkle/screen/BufferStack.java b/twinkle-screen/src/main/java/org/codejive/twinkle/screen/BufferStack.java index 3cfb547..744eabd 100644 --- a/twinkle-screen/src/main/java/org/codejive/twinkle/screen/BufferStack.java +++ b/twinkle-screen/src/main/java/org/codejive/twinkle/screen/BufferStack.java @@ -6,6 +6,7 @@ import java.util.Set; import org.codejive.twinkle.ansi.Style; import org.codejive.twinkle.ansi.util.Printable; +import org.codejive.twinkle.text.Position; import org.codejive.twinkle.text.Size; import org.jspecify.annotations.NonNull; @@ -16,16 +17,14 @@ public class BufferStack implements Printable { public static class BufferElement { public Buffer buffer; - public int xPos; - public int yPos; + public Position pos; public int zIndex; public boolean visible; public String transparancy; - public BufferElement(Buffer buffer, int xPos, int yPos, int zIndex) { + public BufferElement(Buffer buffer, Position pos, int zIndex) { this.buffer = buffer; - this.xPos = xPos; - this.yPos = yPos; + this.pos = pos; this.zIndex = zIndex; this.visible = true; this.transparancy = "\0"; @@ -73,7 +72,7 @@ public void primary(Buffer primary) { public List list() { List elems = list_(); - elems.add(0, new BufferElement(primary, 0, 0, Integer.MIN_VALUE)); + elems.add(0, new BufferElement(primary, Position.ZERO, Integer.MIN_VALUE)); return elems; } @@ -85,13 +84,19 @@ private List list_() { } public BufferElement add(Buffer buffer) { - BufferElement element = new BufferElement(buffer, 0, 0, bufferStack.size()); + BufferElement element = new BufferElement(buffer, Position.ZERO, bufferStack.size()); + bufferStack.add(element); + return element; + } + + public BufferElement add(Buffer buffer, Position pos, int zIndex) { + BufferElement element = new BufferElement(buffer, pos, zIndex); bufferStack.add(element); return element; } public BufferElement add(Buffer buffer, int xPos, int yPos, int zIndex) { - BufferElement element = new BufferElement(buffer, xPos, yPos, zIndex); + BufferElement element = new BufferElement(buffer, Position.of(xPos, yPos), zIndex); bufferStack.add(element); return element; } @@ -121,7 +126,7 @@ public Buffer combined() { for (BufferElement element : list_()) { if (element.visible) { element.buffer.overlayOn( - combined, element.xPos, element.yPos, element.transparancy); + combined, element.pos.x(), element.pos.y(), element.transparancy); } } return combined; diff --git a/twinkle-screen/src/main/java/org/codejive/twinkle/screen/util/Rect.java b/twinkle-screen/src/main/java/org/codejive/twinkle/screen/util/Rect.java index 6c1ca02..8f83bd8 100644 --- a/twinkle-screen/src/main/java/org/codejive/twinkle/screen/util/Rect.java +++ b/twinkle-screen/src/main/java/org/codejive/twinkle/screen/util/Rect.java @@ -1,11 +1,12 @@ package org.codejive.twinkle.screen.util; +import org.codejive.twinkle.text.Position; import org.codejive.twinkle.text.Size; import org.jspecify.annotations.NonNull; public class Rect implements Sized { - private final int left, top; - private final Size size; + private final @NonNull Position pos; + private final @NonNull Size size; public static @NonNull Rect of(int width, int height) { return of(0, 0, width, height); @@ -16,33 +17,44 @@ public class Rect implements Sized { } public static @NonNull Rect of(int left, int top, int width, int height) { - return new Rect(left, top, Size.of(width, height)); + return new Rect(Position.of(left, top), Size.of(width, height)); } public static @NonNull Rect of(int left, int top, @NonNull Size size) { - return new Rect(left, top, size); + return new Rect(Position.of(left, top), size); } - public Rect(int left, int top, @NonNull Size size) { - this.left = left; - this.top = top; + public static @NonNull Rect of(@NonNull Position pos, @NonNull Size size) { + return new Rect(pos, size); + } + + public Rect(@NonNull Position pos, @NonNull Size size) { + this.pos = pos; this.size = size; } + public int x() { + return pos.x(); + } + + public int y() { + return pos.y(); + } + public int left() { - return left; + return pos.x(); } public int right() { - return left + width() - 1; + return left() + width() - 1; } public int top() { - return top; + return pos.y(); } public int bottom() { - return top + height() - 1; + return top() + height() - 1; } public int width() { @@ -53,6 +65,10 @@ public int height() { return size.height(); } + public @NonNull Position position() { + return pos; + } + @Override public @NonNull Size size() { return size; @@ -78,31 +94,31 @@ public boolean overlap(@NonNull Rect other) { public Rect grow(int leftAmount, int topAmount, int rightAmount, int bottomAmount) { return Rect.of( - left - leftAmount, - top - topAmount, + left() - leftAmount, + top() - topAmount, Math.max(width() + leftAmount + rightAmount, 0), Math.max(height() + topAmount + bottomAmount, 0)); } public Rect limited(@NonNull Rect availableRect) { - int l = Math.max(left, availableRect.left()); - int t = Math.max(top, availableRect.top()); + int l = Math.max(left(), availableRect.left()); + int t = Math.max(top(), availableRect.top()); int r = Math.min(right(), availableRect.right()); int b = Math.min(bottom(), availableRect.bottom()); return Rect.of(l, t, r - l + 1, b - t + 1); } public Rect appliedTo(@NonNull Rect rect) { - return of(rect.left() + left, rect.top() + top, size); + return of(rect.left() + left(), rect.top() + top(), size); } @Override public String toString() { return "Rect{" + "left=" - + left + + left() + ", top=" - + top + + top() + ", width=" + width() + ", height=" diff --git a/twinkle-text/src/main/java/org/codejive/twinkle/fluent/commands/CursorCommands.java b/twinkle-text/src/main/java/org/codejive/twinkle/fluent/commands/CursorCommands.java index 45b4410..824d039 100644 --- a/twinkle-text/src/main/java/org/codejive/twinkle/fluent/commands/CursorCommands.java +++ b/twinkle-text/src/main/java/org/codejive/twinkle/fluent/commands/CursorCommands.java @@ -1,6 +1,7 @@ package org.codejive.twinkle.fluent.commands; import org.codejive.twinkle.fluent.Fluent; +import org.codejive.twinkle.text.Position; public interface CursorCommands { /** @@ -10,6 +11,17 @@ public interface CursorCommands { */ Fluent home(); + /** + * Positions the cursor at the specified column (x) and row (y). Coordinates are 0-based (the + * top-left corner is 0,0). + * + * @param pos the position (0-based) + * @return this Fluent instance for chaining + */ + default Fluent at(Position pos) { + return at(pos.x(), pos.y()); + } + /** * Positions the cursor at the specified column (x) and row (y). Coordinates are 0-based (the * top-left corner is 0,0). diff --git a/twinkle-text/src/main/java/org/codejive/twinkle/text/Position.java b/twinkle-text/src/main/java/org/codejive/twinkle/text/Position.java new file mode 100644 index 0000000..edb7d33 --- /dev/null +++ b/twinkle-text/src/main/java/org/codejive/twinkle/text/Position.java @@ -0,0 +1,63 @@ +package org.codejive.twinkle.text; + +import java.util.Objects; +import org.jspecify.annotations.NonNull; + +public class Position { + private final int x; + private final int y; + + public static Position ZERO = new Position(0, 0); + public static Position MAX = new Position(Integer.MAX_VALUE, Integer.MAX_VALUE); + + public static @NonNull Position of(int x, int y) { + if (x == 0 && y == 0) { + return ZERO; + } + if (x == Integer.MAX_VALUE && y == Integer.MAX_VALUE) { + return MAX; + } + return new Position(x, y); + } + + protected Position(int x, int y) { + this.x = x; + this.y = y; + } + + public int x() { + return x; + } + + public int y() { + return y; + } + + /** + * Returns a new position moved by the given amounts in x and y direction. + * + * @param dx the distance to move in the X direction + * @param dy the distance to move in the Y direction + * @return a new position with the updated coordinates + */ + public Position move(int dx, int dy) { + return of(x + dx, y + dy); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Position pos = (Position) o; + return x == pos.x && y == pos.y; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public String toString() { + return "(" + x + "," + y + ")"; + } +} diff --git a/twinkle-text/src/main/java/org/codejive/twinkle/text/Size.java b/twinkle-text/src/main/java/org/codejive/twinkle/text/Size.java index 4b823ed..076d0cc 100644 --- a/twinkle-text/src/main/java/org/codejive/twinkle/text/Size.java +++ b/twinkle-text/src/main/java/org/codejive/twinkle/text/Size.java @@ -7,11 +7,20 @@ public class Size { private final int width; private final int height; + public static Size EMPTY = new Size(0, 0); + public static Size MAX = new Size(Integer.MAX_VALUE, Integer.MAX_VALUE); + public static @NonNull Size of(int width, int height) { + if (width == 0 && height == 0) { + return EMPTY; + } + if (width == Integer.MAX_VALUE && height == Integer.MAX_VALUE) { + return MAX; + } return new Size(width, height); } - public Size(int width, int height) { + protected Size(int width, int height) { assert width >= 0; assert height >= 0; this.width = width; @@ -26,10 +35,73 @@ public int height() { return height; } + /** + * Grows the size by the given size, returning a new Size instance with the updated dimensions. + * + * @param growSize the amount to grow the width + * @return a new size with the updated dimensions + */ + public Size grow(Size growSize) { + return grow(growSize.width(), growSize.height()); + } + + /** + * Grows the size by the given amounts in width and height, returning a new Size instance with + * the updated dimensions. + * + * @param dw the amount to grow the width + * @param dh the amount to grow the height + * @return a new size with the updated dimensions + */ public Size grow(int dw, int dh) { return of(width + dw, height + dh); } + /** + * Shrinks the size by the given size, returning a new Size instance with the updated + * dimensions. + * + * @param shrinkSize the amount to shrink the width + * @return a new size with the updated dimensions + */ + public Size shrink(Size shrinkSize) { + return shrink(shrinkSize.width(), shrinkSize.height()); + } + + /** + * Shrinks the size by the given amounts in width and height, returning a new Size instance with + * the updated dimensions. + * + * @param dw the amount to shrink the width + * @param dh the amount to shrink the height + * @return a new size with the updated dimensions + */ + public Size shrink(int dw, int dh) { + return of(width - dw, height - dh); + } + + /** + * Returns the center position of an area of this size + * + * @return the center position + */ + public Position center() { + return new Position(width / 2, height / 2); + } + + /** + * Returns the position where the other size would be centered, both horizontally as vertically, + * within this size. + * + * @param otherSize the other size to center + * @return the position where the other size would be centered within this size + */ + public Position center(Size otherSize) { + int posX = (width - otherSize.width) / 2; + int posY = (height - otherSize.height) / 2; + return new Position(posX, posY); + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false;