Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / gui2 / ScrollBar.java
1 package com.googlecode.lanterna.gui2;
2
3 import com.googlecode.lanterna.Symbols;
4 import com.googlecode.lanterna.TerminalSize;
5 import com.googlecode.lanterna.graphics.ThemeDefinition;
6
7 /**
8 * Classic scrollbar that can be used to display where inside a larger component a view is showing. This implementation
9 * is not interactable and needs to be driven externally, meaning you can't focus on the scrollbar itself, you have to
10 * update its state as part of another component being modified. {@code ScrollBar}s are either horizontal or vertical,
11 * which affects the way they appear and how they are drawn.
12 * <p>
13 * This class works on two concepts, the min-position-max values and the view size. The minimum value is always 0 and
14 * cannot be changed. The maximum value is 100 and can be adjusted programmatically. Position value is whever along the
15 * axis of 0 to max the scrollbar's tracker currently is placed. The view size is an important concept, it determines
16 * how big the tracker should be and limits the position so that it can only reach {@code maximum value - view size}.
17 * <p>
18 * The regular way to use the {@code ScrollBar} class is to tie it to the model-view of another component and set the
19 * scrollbar's maximum to the total height (or width, if the scrollbar is horizontal) of the model-view. View size
20 * should then be assigned based on the current size of the view, meaning as the terminal and/or the GUI changes and the
21 * components visible space changes, the scrollbar's view size is updated along with it. Finally the position of the
22 * scrollbar should be equal to the scroll offset in the component.
23 *
24 * @author Martin
25 */
26 public class ScrollBar extends AbstractComponent<ScrollBar> {
27
28 private final Direction direction;
29 private int maximum;
30 private int position;
31 private int viewSize;
32
33 /**
34 * Creates a new {@code ScrollBar} with a specified direction
35 * @param direction Direction of the scrollbar
36 */
37 public ScrollBar(Direction direction) {
38 this.direction = direction;
39 this.maximum = 100;
40 this.position = 0;
41 this.viewSize = 0;
42 }
43
44 /**
45 * Returns the direction of this {@code ScrollBar}
46 * @return Direction of this {@code ScrollBar}
47 */
48 public Direction getDirection() {
49 return direction;
50 }
51
52 /**
53 * Sets the maximum value the scrollbar's position (minus the view size) can have
54 * @param maximum Maximum value
55 * @return Itself
56 */
57 public ScrollBar setScrollMaximum(int maximum) {
58 if(maximum < 0) {
59 throw new IllegalArgumentException("Cannot set ScrollBar maximum to " + maximum);
60 }
61 this.maximum = maximum;
62 invalidate();
63 return this;
64 }
65
66 /**
67 * Returns the maximum scroll value
68 * @return Maximum scroll value
69 */
70 public int getScrollMaximum() {
71 return maximum;
72 }
73
74
75 /**
76 * Sets the scrollbar's position, should be a value between 0 and {@code maximum - view size}
77 * @param position Scrollbar's tracker's position
78 * @return Itself
79 */
80 public ScrollBar setScrollPosition(int position) {
81 this.position = Math.min(position, this.maximum);
82 invalidate();
83 return this;
84 }
85
86 /**
87 * Returns the position of the {@code ScrollBar}'s tracker
88 * @return Position of the {@code ScrollBar}'s tracker
89 */
90 public int getScrollPosition() {
91 return position;
92 }
93
94 /**
95 * Sets the view size of the scrollbar, determining how big the scrollbar's tracker should be and also affecting the
96 * maximum value of tracker's position
97 * @param viewSize View size of the scrollbar
98 * @return Itself
99 */
100 public ScrollBar setViewSize(int viewSize) {
101 this.viewSize = viewSize;
102 return this;
103 }
104
105 /**
106 * Returns the view size of the scrollbar
107 * @return View size of the scrollbar
108 */
109 public int getViewSize() {
110 if(viewSize > 0) {
111 return viewSize;
112 }
113 if(direction == Direction.HORIZONTAL) {
114 return getSize().getColumns();
115 }
116 else {
117 return getSize().getRows();
118 }
119 }
120
121 @Override
122 protected ComponentRenderer<ScrollBar> createDefaultRenderer() {
123 return new DefaultScrollBarRenderer();
124 }
125
126 /**
127 * Helper class for making new {@code ScrollBar} renderers a little bit cleaner
128 */
129 public static abstract class ScrollBarRenderer implements ComponentRenderer<ScrollBar> {
130 @Override
131 public TerminalSize getPreferredSize(ScrollBar component) {
132 return TerminalSize.ONE;
133 }
134 }
135
136 /**
137 * Default renderer for {@code ScrollBar} which will be used unless overridden. This will draw a scrollbar using
138 * arrows at each extreme end, a background color for spaces between those arrows and the tracker and then the
139 * tracker itself in three different styles depending on the size of the tracker. All characters and colors are
140 * customizable through whatever theme is currently in use.
141 */
142 public static class DefaultScrollBarRenderer extends ScrollBarRenderer {
143
144 private boolean growScrollTracker;
145
146 /**
147 * Default constructor
148 */
149 public DefaultScrollBarRenderer() {
150 this.growScrollTracker = true;
151 }
152
153 /**
154 * Should tracker automatically grow in size along with the {@code ScrollBar} (default: {@code true})
155 * @param growScrollTracker Automatically grow tracker
156 */
157 public void setGrowScrollTracker(boolean growScrollTracker) {
158 this.growScrollTracker = growScrollTracker;
159 }
160
161 @Override
162 public void drawComponent(TextGUIGraphics graphics, ScrollBar component) {
163 TerminalSize size = graphics.getSize();
164 Direction direction = component.getDirection();
165 int position = component.getScrollPosition();
166 int maximum = component.getScrollMaximum();
167 int viewSize = component.getViewSize();
168
169 if(size.getRows() == 0 || size.getColumns() == 0) {
170 return;
171 }
172
173 //Adjust position if necessary
174 if(position + viewSize >= maximum) {
175 position = Math.max(0, maximum - viewSize);
176 component.setScrollPosition(position);
177 }
178
179 ThemeDefinition themeDefinition = graphics.getThemeDefinition(ScrollBar.class);
180 graphics.applyThemeStyle(themeDefinition.getNormal());
181
182 if(direction == Direction.VERTICAL) {
183 if(size.getRows() == 1) {
184 graphics.setCharacter(0, 0, themeDefinition.getCharacter("VERTICAL_BACKGROUND", Symbols.BLOCK_MIDDLE));
185 }
186 else if(size.getRows() == 2) {
187 graphics.setCharacter(0, 0, themeDefinition.getCharacter("UP_ARROW", Symbols.ARROW_UP));
188 graphics.setCharacter(0, 1, themeDefinition.getCharacter("DOWN_ARROW", Symbols.ARROW_DOWN));
189 }
190 else {
191 int scrollableArea = size.getRows() - 2;
192 int scrollTrackerSize = 1;
193 if(growScrollTracker) {
194 float ratio = clampRatio((float) viewSize / (float) maximum);
195 scrollTrackerSize = Math.max(1, (int) (ratio * (float) scrollableArea));
196 }
197
198 float ratio = clampRatio((float)position / (float)(maximum - viewSize));
199 int scrollTrackerPosition = (int)(ratio * (float)(scrollableArea - scrollTrackerSize)) + 1;
200
201 graphics.setCharacter(0, 0, themeDefinition.getCharacter("UP_ARROW", Symbols.ARROW_UP));
202 graphics.drawLine(0, 1, 0, size.getRows() - 2, themeDefinition.getCharacter("VERTICAL_BACKGROUND", Symbols.BLOCK_MIDDLE));
203 graphics.setCharacter(0, size.getRows() - 1, themeDefinition.getCharacter("DOWN_ARROW", Symbols.ARROW_DOWN));
204 if(scrollTrackerSize == 1) {
205 graphics.setCharacter(0, scrollTrackerPosition, themeDefinition.getCharacter("VERTICAL_SMALL_TRACKER", Symbols.SOLID_SQUARE_SMALL));
206 }
207 else if(scrollTrackerSize == 2) {
208 graphics.setCharacter(0, scrollTrackerPosition, themeDefinition.getCharacter("VERTICAL_TRACKER_TOP", (char)0x28c));
209 graphics.setCharacter(0, scrollTrackerPosition + 1, themeDefinition.getCharacter("VERTICAL_TRACKER_BOTTOM", 'v'));
210 }
211 else {
212 graphics.setCharacter(0, scrollTrackerPosition, themeDefinition.getCharacter("VERTICAL_TRACKER_TOP", (char)0x28c));
213 graphics.drawLine(0, scrollTrackerPosition + 1, 0, scrollTrackerPosition + scrollTrackerSize - 2, themeDefinition.getCharacter("VERTICAL_TRACKER_BACKGROUND", ' '));
214 graphics.setCharacter(0, scrollTrackerPosition + (scrollTrackerSize / 2), themeDefinition.getCharacter("VERTICAL_SMALL_TRACKER", Symbols.SOLID_SQUARE_SMALL));
215 graphics.setCharacter(0, scrollTrackerPosition + scrollTrackerSize - 1, themeDefinition.getCharacter("VERTICAL_TRACKER_BOTTOM", 'v'));
216 }
217 }
218 }
219 else {
220 if(size.getColumns() == 1) {
221 graphics.setCharacter(0, 0, themeDefinition.getCharacter("HORIZONTAL_BACKGROUND", Symbols.BLOCK_MIDDLE));
222 }
223 else if(size.getColumns() == 2) {
224 graphics.setCharacter(0, 0, Symbols.ARROW_LEFT);
225 graphics.setCharacter(1, 0, Symbols.ARROW_RIGHT);
226 }
227 else {
228 int scrollableArea = size.getColumns() - 2;
229 int scrollTrackerSize = 1;
230 if(growScrollTracker) {
231 float ratio = clampRatio((float) viewSize / (float) maximum);
232 scrollTrackerSize = Math.max(1, (int) (ratio * (float) scrollableArea));
233 }
234
235 float ratio = clampRatio((float)position / (float)(maximum - viewSize));
236 int scrollTrackerPosition = (int)(ratio * (float)(scrollableArea - scrollTrackerSize)) + 1;
237
238 graphics.setCharacter(0, 0, themeDefinition.getCharacter("LEFT_ARROW", Symbols.ARROW_LEFT));
239 graphics.drawLine(1, 0, size.getColumns() - 2, 0, themeDefinition.getCharacter("HORIZONTAL_BACKGROUND", Symbols.BLOCK_MIDDLE));
240 graphics.setCharacter(size.getColumns() - 1, 0, themeDefinition.getCharacter("RIGHT_ARROW", Symbols.ARROW_RIGHT));
241 if(scrollTrackerSize == 1) {
242 graphics.setCharacter(scrollTrackerPosition, 0, themeDefinition.getCharacter("HORIZONTAL_SMALL_TRACKER", Symbols.SOLID_SQUARE_SMALL));
243 }
244 else if(scrollTrackerSize == 2) {
245 graphics.setCharacter(scrollTrackerPosition, 0, themeDefinition.getCharacter("HORIZONTAL_TRACKER_LEFT", '<'));
246 graphics.setCharacter(scrollTrackerPosition + 1, 0, themeDefinition.getCharacter("HORIZONTAL_TRACKER_RIGHT", '>'));
247 }
248 else {
249 graphics.setCharacter(scrollTrackerPosition, 0, themeDefinition.getCharacter("HORIZONTAL_TRACKER_LEFT", '<'));
250 graphics.drawLine(scrollTrackerPosition + 1, 0, scrollTrackerPosition + scrollTrackerSize - 2, 0, themeDefinition.getCharacter("HORIZONTAL_TRACKER_BACKGROUND", ' '));
251 graphics.setCharacter(scrollTrackerPosition + (scrollTrackerSize / 2), 0, themeDefinition.getCharacter("HORIZONTAL_SMALL_TRACKER", Symbols.SOLID_SQUARE_SMALL));
252 graphics.setCharacter(scrollTrackerPosition + scrollTrackerSize - 1, 0, themeDefinition.getCharacter("HORIZONTAL_TRACKER_RIGHT", '>'));
253 }
254 }
255 }
256 }
257
258 private float clampRatio(float value) {
259 if(value < 0.0f) {
260 return 0.0f;
261 }
262 else if(value > 1.0f) {
263 return 1.0f;
264 }
265 else {
266 return value;
267 }
268 }
269 }
270 }