1 package com
.googlecode
.lanterna
.gui2
;
3 import com
.googlecode
.lanterna
.Symbols
;
4 import com
.googlecode
.lanterna
.TerminalSize
;
5 import com
.googlecode
.lanterna
.graphics
.ThemeDefinition
;
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.
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}.
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.
26 public class ScrollBar
extends AbstractComponent
<ScrollBar
> {
28 private final Direction direction
;
34 * Creates a new {@code ScrollBar} with a specified direction
35 * @param direction Direction of the scrollbar
37 public ScrollBar(Direction direction
) {
38 this.direction
= direction
;
45 * Returns the direction of this {@code ScrollBar}
46 * @return Direction of this {@code ScrollBar}
48 public Direction
getDirection() {
53 * Sets the maximum value the scrollbar's position (minus the view size) can have
54 * @param maximum Maximum value
57 public ScrollBar
setScrollMaximum(int maximum
) {
59 throw new IllegalArgumentException("Cannot set ScrollBar maximum to " + maximum
);
61 this.maximum
= maximum
;
67 * Returns the maximum scroll value
68 * @return Maximum scroll value
70 public int getScrollMaximum() {
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
80 public ScrollBar
setScrollPosition(int position
) {
81 this.position
= Math
.min(position
, this.maximum
);
87 * Returns the position of the {@code ScrollBar}'s tracker
88 * @return Position of the {@code ScrollBar}'s tracker
90 public int getScrollPosition() {
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
100 public ScrollBar
setViewSize(int viewSize
) {
101 this.viewSize
= viewSize
;
106 * Returns the view size of the scrollbar
107 * @return View size of the scrollbar
109 public int getViewSize() {
113 if(direction
== Direction
.HORIZONTAL
) {
114 return getSize().getColumns();
117 return getSize().getRows();
122 protected ComponentRenderer
<ScrollBar
> createDefaultRenderer() {
123 return new DefaultScrollBarRenderer();
127 * Helper class for making new {@code ScrollBar} renderers a little bit cleaner
129 public static abstract class ScrollBarRenderer
implements ComponentRenderer
<ScrollBar
> {
131 public TerminalSize
getPreferredSize(ScrollBar component
) {
132 return TerminalSize
.ONE
;
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.
142 public static class DefaultScrollBarRenderer
extends ScrollBarRenderer
{
144 private boolean growScrollTracker
;
147 * Default constructor
149 public DefaultScrollBarRenderer() {
150 this.growScrollTracker
= true;
154 * Should tracker automatically grow in size along with the {@code ScrollBar} (default: {@code true})
155 * @param growScrollTracker Automatically grow tracker
157 public void setGrowScrollTracker(boolean growScrollTracker
) {
158 this.growScrollTracker
= growScrollTracker
;
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();
169 if(size
.getRows() == 0 || size
.getColumns() == 0) {
173 //Adjust position if necessary
174 if(position
+ viewSize
>= maximum
) {
175 position
= Math
.max(0, maximum
- viewSize
);
176 component
.setScrollPosition(position
);
179 ThemeDefinition themeDefinition
= graphics
.getThemeDefinition(ScrollBar
.class);
180 graphics
.applyThemeStyle(themeDefinition
.getNormal());
182 if(direction
== Direction
.VERTICAL
) {
183 if(size
.getRows() == 1) {
184 graphics
.setCharacter(0, 0, themeDefinition
.getCharacter("VERTICAL_BACKGROUND", Symbols
.BLOCK_MIDDLE
));
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
));
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
));
198 float ratio
= clampRatio((float)position
/ (float)(maximum
- viewSize
));
199 int scrollTrackerPosition
= (int)(ratio
* (float)(scrollableArea
- scrollTrackerSize
)) + 1;
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
));
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'));
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'));
220 if(size
.getColumns() == 1) {
221 graphics
.setCharacter(0, 0, themeDefinition
.getCharacter("HORIZONTAL_BACKGROUND", Symbols
.BLOCK_MIDDLE
));
223 else if(size
.getColumns() == 2) {
224 graphics
.setCharacter(0, 0, Symbols
.ARROW_LEFT
);
225 graphics
.setCharacter(1, 0, Symbols
.ARROW_RIGHT
);
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
));
235 float ratio
= clampRatio((float)position
/ (float)(maximum
- viewSize
));
236 int scrollTrackerPosition
= (int)(ratio
* (float)(scrollableArea
- scrollTrackerSize
)) + 1;
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
));
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", '>'));
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", '>'));
258 private float clampRatio(float value
) {
262 else if(value
> 1.0f
) {