001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.Gdx; 004import com.badlogic.gdx.graphics.Color; 005import com.badlogic.gdx.graphics.g2d.Batch; 006import com.badlogic.gdx.scenes.scene2d.InputEvent; 007import com.badlogic.gdx.scenes.scene2d.InputListener; 008import squidpony.IColorCenter; 009import squidpony.panel.IColoredString; 010 011import java.util.ArrayList; 012import java.util.List; 013 014/** 015 * A specialized SquidPanel that is meant for displaying messages in a scrolling pane. You primarily use this class by 016 * calling appendMessage() or appendWrappingMessage(), but the full SquidPanel API is available as well, though it isn't 017 * the best idea to use that set of methods with this class in many cases. Messages can be Strings or IColoredStrings. 018 * Height must be at least 3 cells, because clicking/tapping the top or bottom borders (which are part of the grid's 019 * height, which leaves 1 row in the middle for a message) will scroll up or down. 020 * Created by Tommy Ettinger on 12/10/2015. 021 * 022 * @see TextPanel An alternative, which is also designed to write messages (not 023 * in a scrolling pane though), but which is backed up by {@link com.badlogic.gdx.scenes.scene2d.Actor} 024 * instead of {@link SquidPanel} (hence better supports tight variable-width fonts) 025 */ 026public class SquidMessageBox extends SparseLayers { 027 protected ArrayList<IColoredString<Color>> messages = new ArrayList<>(256); 028 protected int messageIndex; 029 /** 030 * Creates a bare-bones panel with all default values for text rendering. 031 * 032 * @param gridWidth the number of cells horizontally 033 * @param gridHeight the number of cells vertically, must be at least 3 034 */ 035 public SquidMessageBox(int gridWidth, int gridHeight) { 036 super(gridWidth, gridHeight); 037 if(gridHeight < 3) 038 throw new IllegalArgumentException("gridHeight must be at least 3, was given: " + gridHeight); 039 appendMessage(""); 040 putBorders(SColor.FLOAT_WHITE, null); 041 } 042 043 /** 044 * Creates a panel with the given grid and cell size. Uses a default square font. 045 * 046 * @param gridWidth the number of cells horizontally 047 * @param gridHeight the number of cells vertically 048 * @param cellWidth the number of horizontal pixels in each cell 049 * @param cellHeight the number of vertical pixels in each cell 050 */ 051 public SquidMessageBox(int gridWidth, int gridHeight, int cellWidth, int cellHeight) { 052 super(gridWidth, gridHeight, cellWidth, cellHeight); 053 if(gridHeight < 3) 054 throw new IllegalArgumentException("gridHeight must be at least 3, was given: " + gridHeight); 055 appendMessage(""); 056 putBorders(SColor.FLOAT_WHITE, null); 057 } 058 059 /** 060 * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images 061 * are being used, a TextCellFactory is still needed to perform sizing and other utility functions. 062 * <p/> 063 * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null 064 * then a default one will be created and initialized. 065 * 066 * @param gridWidth the number of cells horizontally 067 * @param gridHeight the number of cells vertically 068 * @param factory the factory to use for cell rendering 069 */ 070 public SquidMessageBox(int gridWidth, int gridHeight, TextCellFactory factory) { 071 super(gridWidth, gridHeight, factory.initialized() ? factory.width() : 12, 072 factory.initialized() ? factory.height() : 12,factory); 073 if(gridHeight < 3) 074 throw new IllegalArgumentException("gridHeight must be at least 3, was given: " + gridHeight); 075 appendMessage(""); 076 putBorders(SColor.FLOAT_WHITE, null); 077 } 078 079 /** 080 * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images 081 * are being used, a TextCellFactory is still needed to perform sizing and other utility functions. 082 * <p/> 083 * If the TextCellFactory has not yet been initialized, then it will be sized at 12x12 px per cell. If it is null 084 * then a default one will be created and initialized. 085 * 086 * @param gridWidth the number of cells horizontally 087 * @param gridHeight the number of cells vertically 088 * @param factory the factory to use for cell rendering 089 * @param center ignored. 090 */ 091 public SquidMessageBox(int gridWidth, int gridHeight, final TextCellFactory factory, IColorCenter<Color> center) { 092 this(gridWidth, gridHeight, factory); 093 } 094 private void makeBordersClickable() 095 { 096 final float cellH = getHeight() / gridHeight; 097 clearListeners(); 098 addListener(new InputListener(){ 099 @Override 100 public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { 101 if(x >= 0 && x < getWidth()) 102 { 103 if(y < cellH) 104 { 105 nudgeDown(); 106 return true; 107 } 108 else if(y >= getHeight() - cellH * 2) 109 { 110 nudgeUp(); 111 return true; 112 } 113 } 114 return false; 115 } 116 }); 117 } 118 119 /** 120 * The primary way of using this class. Appends a new line to the message listing and scrolls to the bottom. 121 * @param message a String that should be no longer than gridWidth - 2; will be truncated otherwise. 122 */ 123 public void appendMessage(String message) 124 { 125 IColoredString.Impl<Color> truncated = new IColoredString.Impl<>(message, defaultForeground); 126 truncated.setLength(gridWidth - 2); 127 messages.add(truncated); 128 messageIndex = messages.size() - 1; 129 } 130 /** 131 * Appends a new line to the message listing and scrolls to the bottom. If the message cannot fit on one line, 132 * it will be word-wrapped and one or more messages will be appended after it. 133 * @param message a String; this method has no specific length restrictions 134 */ 135 public void appendWrappingMessage(String message) 136 { 137 if(message.length() <= gridWidth - 2) 138 { 139 appendMessage(message); 140 return; 141 } 142 List<IColoredString<Color>> truncated = new IColoredString.Impl<>(message, defaultForeground).wrap(gridWidth - 2); 143 for (IColoredString<Color> t : truncated) 144 { 145 appendMessage(t); 146 } 147 } 148 149 /** 150 * A common way of using this class. Appends a new line as an IColoredString to the message listing and scrolls to 151 * the bottom. 152 * @param message an IColoredString that should be no longer than gridWidth - 2; will be truncated otherwise. 153 */ 154 public void appendMessage(IColoredString<Color> message) 155 { 156 IColoredString.Impl<Color> truncated = new IColoredString.Impl<>(); 157 truncated.append(message); 158 truncated.setLength(gridWidth - 2); 159 messages.add(truncated); 160 messageIndex = messages.size() - 1; 161 } 162 163 /** 164 * Appends a new line as an IColoredString to the message listing and scrolls to the bottom. If the message cannot 165 * fit on one line, it will be word-wrapped and one or more messages will be appended after it. 166 * @param message an IColoredString with type parameter Color; this method has no specific length restrictions 167 */ 168 public void appendWrappingMessage(IColoredString<Color> message) 169 { 170 if(message.length() <= gridWidth - 2) 171 { 172 appendMessage(message); 173 return; 174 } 175 List<IColoredString<Color>> truncated = message.wrap(gridWidth - 2); 176 for (IColoredString<Color> t : truncated) 177 { 178 appendMessage(t); 179 } 180 } 181 182 /** 183 * Used internally to scroll up by one line, but can also be triggered by your code. 184 */ 185 public void nudgeUp() 186 { 187 messageIndex = Math.max(0, messageIndex - 1); 188 } 189 190 /** 191 * Used internally to scroll down by one line, but can also be triggered by your code. 192 */ 193 public void nudgeDown() 194 { 195 messageIndex = Math.min(messages.size() - 1, messageIndex + 1); 196 } 197 198 @Override 199 public void draw(Batch batch, float parentAlpha) { 200 for (int x = 1; x < gridWidth - 1; x++) { 201 for (int y = 1; y < gridHeight - 1; y++) { 202 clear(x, y, 0); 203 } 204 } 205 for (int i = 1; i < gridHeight - 1 && i <= messageIndex; i++) { 206 put(1, gridHeight - 1 - i, messages.get(messageIndex + 1 - i)); 207 } 208 act(Gdx.graphics.getDeltaTime()); 209 super.draw(batch, parentAlpha); 210 } 211 212 /** 213 * Set the x, y position of the lower left corner, plus set the width and height. 214 * ACTUALLY NEEDED to make the borders clickable. It can't know 215 * the boundaries of the clickable area until it knows its own position and bounds. 216 * 217 * @param x x position in pixels or other units that libGDX is set to use 218 * @param y y position in pixels or other units that libGDX is set to use 219 * @param width the width in pixels (usually) of the message box; changes on resize 220 * @param height the height in pixels (usually) of the message box; changes on resize 221 */ 222 @Override 223 public void setBounds(float x, float y, float width, float height) { 224 super.setBounds(x, y, width, height); 225 makeBordersClickable(); 226 } 227}