diff --git a/squidlib-gdx/src/main/java/squidpony/squidgrid/gui/gdx/SquidPanel.java b/squidlib-gdx/src/main/java/squidpony/squidgrid/gui/gdx/SquidPanel.java index 1b1cc12e52..d65c4c2f62 100644 --- a/squidlib-gdx/src/main/java/squidpony/squidgrid/gui/gdx/SquidPanel.java +++ b/squidlib-gdx/src/main/java/squidpony/squidgrid/gui/gdx/SquidPanel.java @@ -1,18 +1,21 @@ package squidpony.squidgrid.gui.gdx; +import java.util.ArrayList; +import java.util.LinkedHashSet; + +import squidpony.panel.IColoredString; +import squidpony.panel.ISquidPanel; +import squidpony.squidgrid.Direction; +import squidpony.squidmath.Coord; + import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Group; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.utils.Align; -import squidpony.squidgrid.Direction; -import com.badlogic.gdx.graphics.Color; -import squidpony.squidmath.Coord; - -import java.util.ArrayList; -import java.util.LinkedHashSet; /** * Displays text and images in a grid pattern. Supports basic animations. @@ -25,7 +28,7 @@ * * @author Eben Howard - http://squidpony.com - howard@squidpony.com */ -public class SquidPanel extends Group { +public class SquidPanel extends Group implements ISquidPanel { public float DEFAULT_ANIMATION_DURATION = 0.12F; private int animationCount = 0; @@ -103,7 +106,8 @@ public void put(char[][] chars) { SquidPanel.this.put(0, 0, chars); } - public void put(char[][] chars, Color[][] foregrounds) { + @Override + public void put(char[][] chars, Color[][] foregrounds) { SquidPanel.this.put(0, 0, chars, foregrounds); } @@ -181,18 +185,19 @@ public void put(int xOffset, int yOffset, String string) { SquidPanel.this.put(xOffset, yOffset, string, defaultForeground); } - /** - * Puts the given string horizontally with the first character at the given offset. - * - * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than - * the grid size) will not be shown but will not cause any malfunctions. - * - * @param xOffset the x coordinate of the first character - * @param yOffset the y coordinate of the first character - * @param string the characters to be displayed - * @param foreground the color to draw the characters - */ - public void put(int xOffset, int yOffset, String string, Color foreground) { + @Override + public void put(int xOffset, int yOffset, IColoredString cs) { + int x = xOffset; + for (IColoredString.Bucket fragment : cs) { + final String s = fragment.getText(); + final Color color = fragment.getColor(); + put(x, yOffset, s, color == null ? getDefaultForegroundColor() : color); + x += s.length(); + } + } + + @Override + public void put(int xOffset, int yOffset, String string, Color foreground) { char[][] temp = new char[string.length()][1]; for (int i = 0; i < string.length(); i++) { temp[i][0] = string.charAt(i); @@ -250,21 +255,18 @@ public void erase() { } } - /** - * Removes the contents of this cell, leaving a transparent space. - * - * @param x - * @param y - */ - public void clear(int x, int y) { + @Override + public void clear(int x, int y) { this.put(x, y, Color.CLEAR); } - public void put(int x, int y, Color color) { + @Override + public void put(int x, int y, Color color) { put(x, y, '\0', color); } - public void put(int x, int y, char c) { + @Override + public void put(int x, int y, char c) { put(x, y, c, defaultForeground); } @@ -299,7 +301,8 @@ public void put(int x, int y, char c, int index, ArrayList palette) { * @param c * @param color */ - public void put(int x, int y, char c, Color color) { + @Override + public void put(int x, int y, char c, Color color) { if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { return;//skip if out of bounds } @@ -315,11 +318,13 @@ public int cellHeight() { return cellHeight; } - public int gridHeight() { + @Override + public int gridHeight() { return gridHeight; } - public int gridWidth() { + @Override + public int gridWidth() { return gridWidth; } @@ -350,9 +355,16 @@ public void drawActor(Batch batch, float parentAlpha, AnimatedEntity ae) ae.actor.draw(batch, parentAlpha); } - public void setDefaultForeground(Color defaultForeground) { + @Override + public void setDefaultForeground(Color defaultForeground) { this.defaultForeground = defaultForeground; } + + @Override + public Color getDefaultForegroundColor() { + return defaultForeground; + } + public AnimatedEntity getAnimatedEntityByCell(int x, int y) { for(AnimatedEntity ae : animatedEntities) { @@ -995,4 +1007,15 @@ public boolean hasActiveAnimations() { public LinkedHashSet getAnimatedEntities() { return animatedEntities; } + + @Override + public void refresh() { + /* smelC: should we do something here ? */ + } + + @Override + public ISquidPanel getBacker() { + return this; + } + } diff --git a/squidlib-util/src/main/java/squidpony/panel/IColoredString.java b/squidlib-util/src/main/java/squidpony/panel/IColoredString.java new file mode 100644 index 0000000000..fa9528f739 --- /dev/null +++ b/squidlib-util/src/main/java/squidpony/panel/IColoredString.java @@ -0,0 +1,260 @@ +package squidpony.panel; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; + +import squidpony.annotation.Beta; + +/** + * A {@link String} divided in chunks of different colors. Use the + * {@link Iterable} interface to get the pieces. + * + * @author smelC + * + * @param + * The type of colors; + */ +@Beta +public interface IColoredString extends Iterable> { + + /** + * Mutates {@code this} by appending {@code c} to it. + * + * @param c + * The text to append. + * @param color + * {@code text}'s color. Or {@code null} to let the panel decide. + */ + public void append(char c, /* @Nullable */T color); + + /** + * Mutates {@code this} by appending {@code text} to it. Does nothing if + * {@code text} is {@code null}. + * + * @param text + * The text to append. + * @param color + * {@code text}'s color. Or {@code null} to let the panel decide. + */ + public void append(/* @Nullable */String text, /* @Nullable */T color); + + /** + * Mutates {@code this} by appending {@code i} to it. + * + * @param i + * The int to append. + * @param color + * {@code text}'s color. Or {@code null} to let the panel decide. + */ + public void appendInt(int i, /* @Nullable */T color); + + /** + * Mutates {@code this} by appending {@code other} to it. + * + * @param other + */ + public void append(IColoredString other); + + /** + * Deletes all content after index {@code len} (if any). + * + * @param len + */ + public void setLength(int len); + + /** + * @return The length of text. + */ + public int length(); + + /** + * @return The text that {@code this} represents. + */ + public String present(); + + /** + * A basic implementation of {@link IColoredString}. + * + * @author smelC + * + * @param + * The type of colors + */ + public static class Impl implements IColoredString { + + protected final LinkedList> fragments; + + /** + * An empty instance. + */ + public Impl() { + this.fragments = new LinkedList>(); + } + + @Override + public void append(char c, T color) { + append(String.valueOf(c), color); + } + + @Override + public void append(String text, T color) { + if (text == null || text.isEmpty()) + return; + + if (fragments.isEmpty()) + fragments.add(new Bucket(text, color)); + else { + final Bucket last = fragments.getLast(); + if (equals(last.color, color)) { + /* Append to the last bucket, to avoid extending the list */ + final Bucket novel = last.append(text); + fragments.removeLast(); + fragments.addLast(novel); + } else + fragments.add(new Bucket(text, color)); + } + } + + @Override + public void appendInt(int i, T color) { + append(String.valueOf(i), color); + } + + @Override + /* KISS implementation */ + public void append(IColoredString other) { + for (IColoredString.Bucket ofragment : other) + append(ofragment.getText(), ofragment.getColor()); + } + + @Override + public void setLength(int len) { + int l = 0; + final ListIterator> it = fragments.listIterator(); + while (it.hasNext()) { + final IColoredString.Bucket next = it.next(); + final String ftext = next.text; + final int flen = ftext.length(); + final int nextl = l + flen; + if (nextl < len) + /* Nothing to do */ + continue; + else if (nextl == len) { + /* Delete all next fragments */ + while (it.hasNext()) + it.remove(); + /* We'll exit the outer loop right away */ + } else { + assert len < nextl; + /* Trim this fragment */ + final IColoredString.Bucket trimmed = next.setLength(nextl - l); + /* Replace this fragment */ + it.remove(); + it.add(trimmed); + /* Delete all next fragments */ + while (it.hasNext()) + it.remove(); + /* We'll exit the outer loop right away */ + } + } + + } + + @Override + public int length() { + int result = 0; + for (Bucket fragment : fragments) + result += fragment.getText().length(); + return result; + } + + @Override + public String present() { + final StringBuilder result = new StringBuilder(); + for (Bucket fragment : fragments) + result.append(fragment.text); + return result.toString(); + } + + @Override + public Iterator> iterator() { + return fragments.iterator(); + } + + @Override + public String toString() { + return present(); + } + + protected static boolean equals(Object o1, Object o2) { + if (o1 == null) + return o2 == null; + else + return o1.equals(o2); + } + } + + /** + * A piece of a {@link IColoredString}: a text and its color. + * + * @author smelC + * + * @param + * The type of colors; + */ + public static class Bucket { + + protected final String text; + protected final/* @Nullable */T color; + + public Bucket(String text, /* @Nullable */T color) { + this.text = text == null ? "" : text; + this.color = color; + } + + /** + * @param text + * @return An instance whose text is {@code this.text + text}. Color is + * unchanged. + */ + public Bucket append(String text) { + if (text == null || text.isEmpty()) + /* Let's save an allocation */ + return this; + else + return new Bucket(this.text + text, color); + } + + public Bucket setLength(int l) { + final int here = text.length(); + if (here < l) + return this; + else + return new Bucket(text.substring(0, l), color); + } + + /** + * @return The text that this bucket contains. + */ + public String getText() { + return text; + } + + /** + * @return The color of {@link #getText()}. Or {@code null} if none. + */ + public/* @Nullable */T getColor() { + return color; + } + + @Override + public String toString() { + if (color == null) + return text; + else + return String.format("%s (%s)", text, color); + } + + } +} diff --git a/squidlib-util/src/main/java/squidpony/panel/ICombinedPanel.java b/squidlib-util/src/main/java/squidpony/panel/ICombinedPanel.java new file mode 100644 index 0000000000..0d5a2d477b --- /dev/null +++ b/squidlib-util/src/main/java/squidpony/panel/ICombinedPanel.java @@ -0,0 +1,203 @@ +package squidpony.panel; + +import java.util.LinkedList; +import java.util.List; + +import squidpony.annotation.Beta; + +/** + * The combination of two panels, one to color the background, the other to + * write characters on the foreground. + * + * @author smelC + * + * @param + * The type of colors. + */ +@Beta +public interface ICombinedPanel { + + /** + * Puts the character {@code c} at {@code (x, y)}. + * + * @param x + * @param y + * @param c + */ + public void putFG(int x, int y, char c); + + /** + * Puts the character {@code c} at {@code (x, y)} with some {@code color}. + * + * @param x + * @param y + * @param c + * @param color + */ + public void putFG(int x, int y, char c, T color); + + /** + * Puts the given string horizontally with the first character at the given + * offset. + * + * Does not word wrap. Characters that are not renderable (due to being at + * negative offsets or offsets greater than the grid size) will not be shown + * but will not cause any malfunctions. + * + * @param x + * the x coordinate of the first character + * @param y + * the y coordinate of the first character + * @param string + * the characters to be displayed + * @param color + * the color to draw the characters + */ + public void putFG(int x, int y, String string, T color); + + /** + * Puts the given string horizontally with the first character at the given + * offset. + * + * Does not word wrap. Characters that are not renderable (due to being at + * negative offsets or offsets greater than the grid size) will not be shown + * but will not cause any malfunctions. + * + * @param x + * the x coordinate of the first character + * @param y + * the y coordinate of the first character + * @param cs + * the text to be displayed, with its color. + */ + public void putFG(int x, int y, IColoredString cs); + + /** + * Puts the color {@code c} at {@code (x, y)}. + * + * @param x + * @param y + * @param color + */ + public void putBG(int x, int y, T color); + + /** + * @param margin + * The color to put at this panel's borders. + * @param inside + * The color to put within this panel. + */ + public void fillBG(T margin, T inside); + + public void refresh(); + + /** + * @return The two backers, with the panel at the top (the foreground) + * first. They are instances of {@code SquidPanel}. + */ + public List> getBackers(); + + /** + * A basic implementation of {@link ICombinedPanel}. + * + * @author smelC + * + * @param + * The type of colors. + */ + @Beta + public static class Impl implements ICombinedPanel { + + protected final ISquidPanel bg; + protected final ISquidPanel fg; + + protected final int width; + protected final int height; + + /** + * @param bg + * The backing background panel. + * @param fg + * The backing foreground panel. + * @param width + * The width of this panel, used for + * {@link #fillBG(Object, Object)} (so that it fills within + * {@code [0, width)}). + * @param height + * The height of this panel, used for + * {@link #fillBG(Object, Object)} (so that it fills within + * {@code [0, height)}). + * @throws IllegalStateException + * In various cases of errors regarding sizes of panels. + */ + public Impl(ISquidPanel bg, ISquidPanel fg, int width, int height) { + if (bg.gridWidth() != fg.gridWidth()) + throw new IllegalStateException( + "Cannot build a combined panel with backers of different widths"); + if (bg.gridHeight() != fg.gridHeight()) + throw new IllegalStateException( + "Cannot build a combined panel with backers of different heights"); + + this.bg = bg; + this.fg = fg; + if (width < 0) + throw new IllegalStateException("Cannot create a panel with a negative width"); + this.width = width; + if (height < 0) + throw new IllegalStateException("Cannot create a panel with a negative height"); + this.height = height; + } + + @Override + public void putFG(int x, int y, char c) { + fg.put(x, y, c); + } + + @Override + public void putFG(int x, int y, char c, T color) { + fg.put(x, y, c, color); + } + + @Override + public void putFG(int x, int y, String string, T foreground) { + fg.put(x, y, string, foreground); + } + + @Override + public void putFG(int x, int y, IColoredString cs) { + fg.put(x, y, cs); + } + + @Override + public void putBG(int x, int y, T color) { + bg.put(x, y, color); + } + + @Override + public void fillBG(T margin, T inside) { + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + if (x == 0 || y == 0 || x == width - 1 || y == height - 1) + putBG(x, y, margin); + else + putBG(x, y, inside); + } + } + } + + @Override + public void refresh() { + bg.refresh(); + fg.refresh(); + } + + @Override + public List> getBackers() { + final List> backers = new LinkedList>(); + backers.add(fg.getBacker()); + backers.add(bg.getBacker()); + return backers; + } + + } +} diff --git a/squidlib-util/src/main/java/squidpony/panel/ISquidPanel.java b/squidlib-util/src/main/java/squidpony/panel/ISquidPanel.java new file mode 100644 index 0000000000..564a05c0d1 --- /dev/null +++ b/squidlib-util/src/main/java/squidpony/panel/ISquidPanel.java @@ -0,0 +1,134 @@ +package squidpony.panel; + +import squidpony.annotation.Beta; + +/** + * The abstraction of {@code SquidPanel}s, to abstract from the UI + * implementation (i.e. whether it's awt or libgdx doesn't matter here). + * + * @author smelC - Introduction of this interface, but methods were in + * SquidPanel already. + * + * @param + * The type of colors + * + * @see ICombinedPanel The combination of two panels, one for the background, + * one for the foreground; a frequent use case in roguelikes. + */ +@Beta +public interface ISquidPanel { + + /** + * Puts the character {@code c} at {@code (x, y)}. + * + * @param x + * @param y + * @param c + */ + public void put(int x, int y, char c); + + /** + * Puts {@code color} at {@code (x, y)} (in the cell's entirety, i.e. in the + * background). + * + * @param x + * @param y + * @param color + */ + public void put(int x, int y, T color); + + /** + * Puts the given string horizontally with the first character at the given + * offset. + * + * Does not word wrap. Characters that are not renderable (due to being at + * negative offsets or offsets greater than the grid size) will not be shown + * but will not cause any malfunctions. + * + * @param xOffset + * the x coordinate of the first character + * @param yOffset + * the y coordinate of the first character + * @param string + * the characters to be displayed + * @param foreground + * the color to draw the characters + */ + public void put(int xOffset, int yOffset, String string, T foreground); + + /** + * Puts the given string horizontally with the first character at the given + * offset, using the colors that {@code cs} provides. + * + * Does not word wrap. Characters that are not renderable (due to being at + * negative offsets or offsets greater than the grid size) will not be shown + * but will not cause any malfunctions. + * + * @param xOffset + * the x coordinate of the first character + * @param yOffset + * the y coordinate of the first character + * @param cs + * The string to display, with its colors. + */ + public void put(int xOffset, int yOffset, IColoredString cs); + + /** + * Puts the character {@code c} at {@code (x, y)} with some {@code color}. + * + * @param x + * @param y + * @param c + * @param color + */ + public void put(int x, int y, char c, T color); + + public void put(char[][] foregrounds, T[][] colors); + + /** + * Removes the contents of this cell, leaving a transparent space. + * + * @param x + * @param y + */ + public void clear(int x, int y); + + /** + * Cause everything that has been prepared for drawing (such as with put) to + * actually be drawn. + */ + public void refresh(); + + public int gridWidth(); + + public int gridHeight(); + + /** + * Sets the default foreground color. + * + * @param color + */ + public void setDefaultForeground(T color); + + /** + * @return The default foreground color (if none was set with + * {@link #setDefaultForeground(Object)}), or the last color set + * with {@link #setDefaultForeground(Object)}. Cannot be + * {@code null}. + */ + public T getDefaultForegroundColor(); + + /** + * @return The panel doing the real job, i.e. an instance of + * {@code SquidPanel}. The type of colors is unspecified, as some + * clients have forwarding instances of this class that hides that + * the type of color of the backer differs from the type of color in + * {@code this}. + * + *

+ * Can be {@code this} itself. + *

+ */ + public ISquidPanel getBacker(); + +} diff --git a/squidlib/src/main/java/squidpony/squidgrid/gui/SquidPanel.java b/squidlib/src/main/java/squidpony/squidgrid/gui/SquidPanel.java index c890c4b957..ffbfb2c35c 100644 --- a/squidlib/src/main/java/squidpony/squidgrid/gui/SquidPanel.java +++ b/squidlib/src/main/java/squidpony/squidgrid/gui/SquidPanel.java @@ -13,8 +13,11 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; + import javax.swing.JLayeredPane; +import squidpony.panel.IColoredString; +import squidpony.panel.ISquidPanel; import squidpony.squidgrid.gui.animation.Animation; import squidpony.squidgrid.gui.animation.AnimationManager; import squidpony.squidgrid.gui.animation.BumpAnimation; @@ -33,7 +36,7 @@ * * @author Eben Howard - http://squidpony.com - howard@squidpony.com */ -public class SquidPanel extends JLayeredPane { +public class SquidPanel extends JLayeredPane implements ISquidPanel { private static int DEFAULT_ANIMATION_DURATION = 200; private static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 22); @@ -155,7 +158,8 @@ public void put(char[][] chars) {//TODO - convert this to work with code points SquidPanel.this.put(0, 0, chars); } - public void put(char[][] chars, Color[][] foregrounds) {//TODO - convert this to work with code points + @Override + public void put(char[][] chars, Color[][] foregrounds) {//TODO - convert this to work with code points SquidPanel.this.put(0, 0, chars, foregrounds); } @@ -247,18 +251,8 @@ public void put(int xOffset, int yOffset, String string) { SquidPanel.this.put(xOffset, yOffset, string, defaultForeground); } - /** - * Puts the given string horizontally with the first character at the given offset. - * - * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than - * the grid size) will not be shown but will not cause any malfunctions. - * - * @param xOffset the x coordinate of the first character - * @param yOffset the y coordinate of the first character - * @param string the characters to be displayed - * @param foreground the color to draw the characters - */ - public void put(int xOffset, int yOffset, String string, Color foreground) {//TODO - make this work with code points + @Override + public void put(int xOffset, int yOffset, String string, Color foreground) {//TODO - make this work with code points char[][] temp = new char[string.length()][1]; for (int i = 0; i < string.length(); i++) { temp[i][0] = string.charAt(i); @@ -266,6 +260,17 @@ public void put(int xOffset, int yOffset, String string, Color foreground) {//TO SquidPanel.this.put(xOffset, yOffset, temp, foreground); } + @Override + public void put(int xOffset, int yOffset, IColoredString cs) { + int x = xOffset; + for (IColoredString.Bucket fragment : cs) { + final String s = fragment.getText(); + final Color color = fragment.getColor(); + put(x, yOffset, s, color == null ? getDefaultForegroundColor() : color); + x += s.length(); + } + } + /** * Puts the given string horizontally with the first character at the given offset. * @@ -317,21 +322,18 @@ public void erase() { redraw(); } - /** - * Removes the contents of this cell, leaving a transparent space. - * - * @param x - * @param y - */ - public void clear(int x, int y) { + @Override + public void clear(int x, int y) { this.put(x, y, SColor.TRANSPARENT); } - public void put(int x, int y, Color color) { + @Override + public void put(int x, int y, Color color) { put(x, y, textFactory.getSolid(color)); } - public void put(int x, int y, char c) { + @Override + public void put(int x, int y, char c) { put(x, y, c, defaultForeground); } @@ -346,7 +348,8 @@ public void put(int x, int y, int code) { put(x, y, code, defaultForeground); } - public void put(int x, int y, char c, Color color) { + @Override + public void put(int x, int y, char c, Color color) { put(x, y, (int) c, color); } @@ -382,18 +385,18 @@ public int cellHeight() { return cellHeight; } - public int gridHeight() { + @Override + public int gridHeight() { return gridHeight; } - public int gridWidth() { + @Override + public int gridWidth() { return gridWidth; } - /** - * Cause everything that has been prepared for drawing (such as with put) to actually be drawn. - */ - public void refresh() { + @Override + public void refresh() { trimAnimations(); redraw(); repaint(); @@ -435,17 +438,14 @@ private void redraw() { * @param defaultForeground * A non-{@code null} color. */ + @Override public void setDefaultForeground(Color defaultForeground) { if (defaultForeground == null) throw new NullPointerException("A null default foreground color is forbidden"); this.defaultForeground = defaultForeground; } - /** - * @return The default foreground color (if none was set with - * {@link #setDefaultForeground(Color)}), or the last color set with - * {@link #setDefaultForeground(Color)}. Cannot be {@code null}. - */ + @Override public Color getDefaultForegroundColor() { return defaultForeground; } @@ -581,4 +581,9 @@ public boolean hasActiveAnimations() { return animations == null ? false : !animations.isEmpty(); } + @Override + public ISquidPanel getBacker() { + return this; + } + }