/
SimpleTheme.java
406 lines (365 loc) · 17.4 KB
/
SimpleTheme.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/*
* This file is part of lanterna (https://github.com/mabe02/lanterna).
*
* lanterna is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2010-2020 Martin Berglund
*/
package com.googlecode.lanterna.graphics;
import com.googlecode.lanterna.SGR;
import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.gui2.*;
import com.googlecode.lanterna.gui2.table.Table;
import java.util.*;
/**
* Very basic implementation of {@link Theme} that allows you to quickly define a theme in code. It is a very simple
* implementation that doesn't implement any intelligent fallback based on class hierarchy or package names. If a
* particular class has not been defined with an explicit override, it will get the default theme style definition.
*
* @author Martin
*/
public class SimpleTheme implements Theme {
/**
* Helper method that will quickly setup a new theme with some sensible component overrides.
* @param activeIsBold Should focused components also use bold SGR style?
* @param baseForeground The base foreground color of the theme
* @param baseBackground The base background color of the theme
* @param editableForeground Foreground color for editable components, or editable areas of components
* @param editableBackground Background color for editable components, or editable areas of components
* @param selectedForeground Foreground color for the selection marker when a component has multiple selection states
* @param selectedBackground Background color for the selection marker when a component has multiple selection states
* @param guiBackground Background color of the GUI, if this theme is assigned to the {@link TextGUI}
* @return Assembled {@link SimpleTheme} using the parameters from above
*/
public static SimpleTheme makeTheme(
boolean activeIsBold,
TextColor baseForeground,
TextColor baseBackground,
TextColor editableForeground,
TextColor editableBackground,
TextColor selectedForeground,
TextColor selectedBackground,
TextColor guiBackground) {
SGR[] activeStyle = activeIsBold ? new SGR[]{SGR.BOLD} : new SGR[0];
SimpleTheme theme = new SimpleTheme(baseForeground, baseBackground);
theme.getDefaultDefinition().setSelected(baseBackground, baseForeground, activeStyle);
theme.getDefaultDefinition().setActive(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(AbstractBorder.class, baseForeground, baseBackground)
.setSelected(baseForeground, baseBackground, activeStyle);
theme.addOverride(AbstractListBox.class, baseForeground, baseBackground)
.setSelected(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(Button.class, baseForeground, baseBackground)
.setActive(selectedForeground, selectedBackground, activeStyle)
.setSelected(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(CheckBox.class, baseForeground, baseBackground)
.setActive(selectedForeground, selectedBackground, activeStyle)
.setPreLight(selectedForeground, selectedBackground, activeStyle)
.setSelected(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(CheckBoxList.class, baseForeground, baseBackground)
.setActive(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(ComboBox.class, baseForeground, baseBackground)
.setActive(editableForeground, editableBackground, activeStyle)
.setPreLight(editableForeground, editableBackground);
theme.addOverride(DefaultWindowDecorationRenderer.class, baseForeground, baseBackground)
.setActive(baseForeground, baseBackground, activeStyle);
theme.addOverride(GUIBackdrop.class, baseForeground, guiBackground);
theme.addOverride(RadioBoxList.class, baseForeground, baseBackground)
.setActive(selectedForeground, selectedBackground, activeStyle);
theme.addOverride(Table.class, baseForeground, baseBackground)
.setActive(editableForeground, editableBackground, activeStyle)
.setSelected(baseForeground, baseBackground);
theme.addOverride(TextBox.class, editableForeground, editableBackground)
.setActive(editableForeground, editableBackground, activeStyle)
.setSelected(editableForeground, editableBackground, activeStyle);
theme.setWindowPostRenderer(new WindowShadowRenderer());
return theme;
}
private final Definition defaultDefinition;
private final Map<Class<?>, Definition> overrideDefinitions;
private WindowPostRenderer windowPostRenderer;
private WindowDecorationRenderer windowDecorationRenderer;
/**
* Creates a new {@link SimpleTheme} object that uses the supplied constructor arguments as the default style
* @param foreground Color to use as the foreground unless overridden
* @param background Color to use as the background unless overridden
* @param styles Extra SGR styles to apply unless overridden
*/
public SimpleTheme(TextColor foreground, TextColor background, SGR... styles) {
this.defaultDefinition = new Definition(new DefaultMutableThemeStyle(foreground, background, styles));
this.overrideDefinitions = new HashMap<Class<?>, Definition>();
this.windowPostRenderer = null;
this.windowDecorationRenderer = null;
}
@Override
public synchronized Definition getDefaultDefinition() {
return defaultDefinition;
}
@Override
public synchronized Definition getDefinition(Class<?> clazz) {
Definition definition = overrideDefinitions.get(clazz);
if(definition == null) {
return getDefaultDefinition();
}
return definition;
}
/**
* Adds an override for a particular class, or overwrites a previously defined override.
* @param clazz Class to override the theme for
* @param foreground Color to use as the foreground color for this override style
* @param background Color to use as the background color for this override style
* @param styles SGR styles to apply for this override
* @return The newly created {@link Definition} that corresponds to this override.
*/
public synchronized Definition addOverride(Class<?> clazz, TextColor foreground, TextColor background, SGR... styles) {
Definition definition = new Definition(new DefaultMutableThemeStyle(foreground, background, styles));
overrideDefinitions.put(clazz, definition);
return definition;
}
@Override
public synchronized WindowPostRenderer getWindowPostRenderer() {
return windowPostRenderer;
}
/**
* Changes the {@link WindowPostRenderer} this theme will return. If called with {@code null}, the theme returns no
* post renderer and the GUI system will use whatever is the default.
* @param windowPostRenderer Post-renderer to use along with this theme, or {@code null} to remove
* @return Itself
*/
public synchronized SimpleTheme setWindowPostRenderer(WindowPostRenderer windowPostRenderer) {
this.windowPostRenderer = windowPostRenderer;
return this;
}
@Override
public synchronized WindowDecorationRenderer getWindowDecorationRenderer() {
return windowDecorationRenderer;
}
/**
* Changes the {@link WindowDecorationRenderer} this theme will return. If called with {@code null}, the theme
* returns no decoration renderer and the GUI system will use whatever is the default.
* @param windowDecorationRenderer Decoration renderer to use along with this theme, or {@code null} to remove
* @return Itself
*/
public synchronized SimpleTheme setWindowDecorationRenderer(WindowDecorationRenderer windowDecorationRenderer) {
this.windowDecorationRenderer = windowDecorationRenderer;
return this;
}
public interface RendererProvider<T extends Component> {
ComponentRenderer<T> getRenderer(Class<T> type);
}
/**
* Internal class inside {@link SimpleTheme} used to allow basic editing of the default style and the optional
* overrides.
*/
public static class Definition implements ThemeDefinition {
private final ThemeStyle normal;
private ThemeStyle preLight;
private ThemeStyle selected;
private ThemeStyle active;
private ThemeStyle insensitive;
private final Map<String, ThemeStyle> customStyles;
private final Properties properties;
private final Map<String, Character> characterMap;
private final Map<Class<?>, RendererProvider<?>> componentRendererMap;
private boolean cursorVisible;
private Definition(ThemeStyle normal) {
this.normal = normal;
this.preLight = null;
this.selected = null;
this.active = null;
this.insensitive = null;
this.customStyles = new HashMap<String, ThemeStyle>();
this.properties = new Properties();
this.characterMap = new HashMap<String, Character>();
this.componentRendererMap = new HashMap<Class<?>, RendererProvider<?>>();
this.cursorVisible = true;
}
@Override
public synchronized ThemeStyle getNormal() {
return normal;
}
@Override
public synchronized ThemeStyle getPreLight() {
if(preLight == null) {
return normal;
}
return preLight;
}
/**
* Sets the theme definition style "prelight"
* @param foreground Foreground color for this style
* @param background Background color for this style
* @param styles SGR styles to use
* @return Itself
*/
public synchronized Definition setPreLight(TextColor foreground, TextColor background, SGR... styles) {
this.preLight = new DefaultMutableThemeStyle(foreground, background, styles);
return this;
}
@Override
public synchronized ThemeStyle getSelected() {
if(selected == null) {
return normal;
}
return selected;
}
/**
* Sets the theme definition style "selected"
* @param foreground Foreground color for this style
* @param background Background color for this style
* @param styles SGR styles to use
* @return Itself
*/
public synchronized Definition setSelected(TextColor foreground, TextColor background, SGR... styles) {
this.selected = new DefaultMutableThemeStyle(foreground, background, styles);
return this;
}
@Override
public synchronized ThemeStyle getActive() {
if(active == null) {
return normal;
}
return active;
}
/**
* Sets the theme definition style "active"
* @param foreground Foreground color for this style
* @param background Background color for this style
* @param styles SGR styles to use
* @return Itself
*/
public synchronized Definition setActive(TextColor foreground, TextColor background, SGR... styles) {
this.active = new DefaultMutableThemeStyle(foreground, background, styles);
return this;
}
@Override
public synchronized ThemeStyle getInsensitive() {
if(insensitive == null) {
return normal;
}
return insensitive;
}
/**
* Sets the theme definition style "insensitive"
* @param foreground Foreground color for this style
* @param background Background color for this style
* @param styles SGR styles to use
* @return Itself
*/
public synchronized Definition setInsensitive(TextColor foreground, TextColor background, SGR... styles) {
this.insensitive = new DefaultMutableThemeStyle(foreground, background, styles);
return this;
}
@Override
public synchronized ThemeStyle getCustom(String name) {
return customStyles.get(name);
}
@Override
public synchronized ThemeStyle getCustom(String name, ThemeStyle defaultValue) {
ThemeStyle themeStyle = customStyles.get(name);
if(themeStyle == null) {
return defaultValue;
}
return themeStyle;
}
/**
* Adds a custom definition style to the theme using the supplied name. This will be returned using the matching
* call to {@link Definition#getCustom(String)}.
* @param name Name of the custom style
* @param foreground Foreground color for this style
* @param background Background color for this style
* @param styles SGR styles to use
* @return Itself
*/
public synchronized Definition setCustom(String name, TextColor foreground, TextColor background, SGR... styles) {
customStyles.put(name, new DefaultMutableThemeStyle(foreground, background, styles));
return this;
}
@Override
public synchronized boolean getBooleanProperty(String name, boolean defaultValue) {
return Boolean.parseBoolean(properties.getProperty(name, Boolean.toString(defaultValue)));
}
/**
* Attaches a boolean value property to this {@link SimpleTheme} that will be returned if calling
* {@link Definition#getBooleanProperty(String, boolean)} with the same name.
* @param name Name of the property
* @param value Value to attach to the property name
* @return Itself
*/
public synchronized Definition setBooleanProperty(String name, boolean value) {
properties.setProperty(name, Boolean.toString(value));
return this;
}
@Override
public synchronized boolean isCursorVisible() {
return cursorVisible;
}
/**
* Sets the value that suggests if the cursor should be visible or not (it's still up to the component renderer
* if it's going to honour this or not).
* @param cursorVisible If {@code true} then this theme definition would like the text cursor to be displayed,
* {@code false} if not.
* @return Itself
*/
public synchronized Definition setCursorVisible(boolean cursorVisible) {
this.cursorVisible = cursorVisible;
return this;
}
@Override
public synchronized char getCharacter(String name, char fallback) {
Character character = characterMap.get(name);
if(character == null) {
return fallback;
}
return character;
}
/**
* Stores a character value in this definition under a specific name. This is used to customize the appearance
* of certain components. It is returned with call to {@link Definition#getCharacter(String, char)} with the
* same name.
* @param name Symbolic name for the character
* @param character Character to attach to the symbolic name
* @return Itself
*/
public synchronized Definition setCharacter(String name, char character) {
characterMap.put(name, character);
return this;
}
@SuppressWarnings("unchecked")
@Override
public synchronized <T extends Component> ComponentRenderer<T> getRenderer(Class<T> type) {
RendererProvider<T> rendererProvider = (RendererProvider<T>)componentRendererMap.get(type);
if(rendererProvider == null) {
return null;
}
return rendererProvider.getRenderer(type);
}
/**
* Registered a callback to get a custom {@link ComponentRenderer} for a particular class. Use this to make a
* certain component (built-in or external) to use a custom renderer.
* @param type Class for which to invoke the callback and return the {@link ComponentRenderer}
* @param rendererProvider Callback to invoke when getting a {@link ComponentRenderer}
* @param <T> Type of class
* @return Itself
*/
public synchronized <T extends Component> Definition setRenderer(Class<T> type, RendererProvider<T> rendererProvider) {
if(rendererProvider == null) {
componentRendererMap.remove(type);
}
else {
componentRendererMap.put(type, rendererProvider);
}
return this;
}
}
}