1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.kordamp.ikonli.javafx;
19
20 import javafx.beans.property.IntegerProperty;
21 import javafx.beans.property.ObjectProperty;
22 import javafx.css.CssMetaData;
23 import javafx.css.StyleOrigin;
24 import javafx.css.Styleable;
25 import javafx.css.StyleableIntegerProperty;
26 import javafx.css.StyleableObjectProperty;
27 import javafx.css.StyleableProperty;
28 import javafx.css.converter.PaintConverter;
29 import javafx.css.converter.SizeConverter;
30 import javafx.scene.paint.Color;
31 import javafx.scene.paint.LinearGradient;
32 import javafx.scene.paint.Paint;
33 import javafx.scene.paint.RadialGradient;
34 import javafx.scene.text.Font;
35 import javafx.scene.text.Text;
36 import org.kordamp.ikonli.Ikon;
37 import org.kordamp.ikonli.IkonHandler;
38
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.Objects;
42
43 import static java.util.Collections.unmodifiableList;
44 import static java.util.Objects.requireNonNull;
45
46
47
48
49 public class FontIcon extends Text implements Icon {
50 private static final double EPSILON = 0.000001d;
51 protected StyleableIntegerProperty iconSize;
52 protected StyleableObjectProperty<Paint> iconColor;
53 private StyleableObjectProperty<Ikon> iconCode;
54
55 public FontIcon() {
56 getStyleClass().setAll("ikonli-font-icon");
57 setIconSize(8);
58 setIconColor(Color.BLACK);
59
60 fontProperty().addListener((v, o, n) -> {
61 int size = (int) n.getSize();
62 if (size != getIconSize()) {
63 setIconSize(size);
64 }
65 });
66
67 fillProperty().addListener((v, o, n) -> {
68 Paint fill = getIconColor();
69 if (!Objects.equals(fill, n)) {
70 setIconColor(n);
71 }
72 });
73
74 iconCodeProperty().addListener((v, o, n) -> {
75 if (n != null) {
76 IkonHandler ikonHandler = IkonResolver.getInstance().resolve(n.getDescription());
77 setStyle(normalizeStyle(getStyle(), "-fx-font-family", "'" + ikonHandler.getFontFamily() + "'"));
78 int code = n.getCode();
79 if (code <= '\uFFFF') {
80 setText(String.valueOf((char) code));
81 } else {
82 char[] charPair = Character.toChars(code);
83 String symbol = new String(charPair);
84 setText(symbol);
85 }
86 }
87 });
88 }
89
90 public FontIcon(String iconCode) {
91 this();
92 setIconLiteral(iconCode);
93 }
94
95 public FontIcon(Ikon iconCode) {
96 this();
97 setIconCode(iconCode);
98 }
99
100 @Override
101 public String toString() {
102 Ikon iconCode = getIconCode();
103 return (iconCode != null ? iconCode.getDescription() : "<undef>") + ":" + getIconSize() + ":" + getIconColor();
104 }
105
106 @Override
107 public IntegerProperty iconSizeProperty() {
108 if (iconSize == null) {
109 iconSize = new StyleableIntegerProperty(8) {
110 @Override
111 public CssMetaData getCssMetaData() {
112 return StyleableProperties.ICON_SIZE;
113 }
114
115 @Override
116 public Object getBean() {
117 return FontIcon.this;
118 }
119
120 @Override
121 public String getName() {
122 return "iconSize";
123 }
124
125 @Override
126 public StyleOrigin getStyleOrigin() {
127 return StyleOrigin.USER_AGENT;
128 }
129 };
130 iconSize.addListener((v, o, n) -> {
131 Font font = FontIcon.this.getFont();
132 if (Math.abs(font.getSize() - n.doubleValue()) >= EPSILON) {
133 FontIcon.this.setFont(Font.font(font.getFamily(), n.doubleValue()));
134 FontIcon.this.setStyle(normalizeStyle(getStyle(), "-fx-font-size", n.intValue() + "px"));
135 }
136 });
137 }
138 return iconSize;
139 }
140
141 @Override
142 public ObjectProperty<Paint> iconColorProperty() {
143 if (iconColor == null) {
144 iconColor = new StyleableObjectProperty<>(Color.BLACK) {
145 @Override
146 public CssMetaData getCssMetaData() {
147 return StyleableProperties.ICON_COLOR;
148 }
149
150 @Override
151 public Object getBean() {
152 return FontIcon.this;
153 }
154
155 @Override
156 public String getName() {
157 return "iconColor";
158 }
159
160 @Override
161 public StyleOrigin getStyleOrigin() {
162 return StyleOrigin.USER_AGENT;
163 }
164 };
165 iconColor.addListener((v, o, n) -> FontIcon.this.setFill(n));
166 }
167 return iconColor;
168 }
169
170 public ObjectProperty<Ikon> iconCodeProperty() {
171 if (iconCode == null) {
172 iconCode = new StyleableObjectProperty<>() {
173 @Override
174 public CssMetaData getCssMetaData() {
175 return StyleableProperties.ICON_CODE;
176 }
177
178 @Override
179 public Object getBean() {
180 return FontIcon.this;
181 }
182
183 @Override
184 public String getName() {
185 return "iconCode";
186 }
187
188 @Override
189 public StyleOrigin getStyleOrigin() {
190 return StyleOrigin.USER_AGENT;
191 }
192 };
193
194 iconCode.addListener((v, o, n) -> {
195 if (!iconCode.isBound()) {
196 FontIcon.this.setIconCode(n);
197 }
198 });
199 }
200 return iconCode;
201 }
202
203 @Override
204 public int getIconSize() {
205 return iconSizeProperty().get();
206 }
207
208 @Override
209 public void setIconSize(int size) {
210 if (size <= 0) {
211 throw new IllegalStateException("Argument 'size' must be greater than zero.");
212 }
213 iconSizeProperty().set(size);
214 }
215
216 @Override
217 public Paint getIconColor() {
218 return iconColorProperty().get();
219 }
220
221 @Override
222 public void setIconColor(Paint paint) {
223 iconColorProperty().set(requireNonNull(paint, "Argument 'paint' must not be null"));
224 }
225
226 public Ikon getIconCode() {
227 return iconCodeProperty().get();
228 }
229
230 public void setIconCode(Ikon iconCode) {
231 iconCodeProperty().set(requireNonNull(iconCode, "Argument 'code' must not be null"));
232 }
233
234 private String normalizeStyle(String style, String key, String value) {
235 int start = style.indexOf(key);
236 if (start != -1) {
237 int end = style.indexOf(";", start);
238 end = end >= start ? end : style.length() - 1;
239 style = style.substring(0, start) + style.substring(end + 1);
240 }
241 return style + key + ": " + value + ";";
242 }
243
244 public String getIconLiteral() {
245 Ikon ikon = iconCodeProperty().get();
246 return ikon != null ? ikon.getDescription() : null;
247 }
248
249 public void setIconLiteral(String iconCode) {
250 String[] parts = iconCode.split(":");
251 setIconCode(org.kordamp.ikonli.javafx.IkonResolver.getInstance().resolve(parts[0]).resolve(parts[0]));
252 resolveSize(iconCode, parts);
253 resolvePaint(iconCode, parts);
254 }
255
256 @Override
257 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
258 return FontIcon.getClassCssMetaData();
259 }
260
261 private void resolveSize(String iconCode, String[] parts) {
262 if (parts.length > 1) {
263 try {
264 setIconSize(Integer.parseInt(parts[1]));
265 } catch (NumberFormatException e) {
266 throw invalidDescription(iconCode, e);
267 }
268 }
269 }
270
271 private void resolvePaint(String iconCode, String[] parts) {
272 if (parts.length > 2) {
273 Paint paint = resolvePaintValue(iconCode, parts[2]);
274 if (paint != null) {
275 setIconColor(paint);
276 }
277 }
278 }
279
280 public static FontIcon of(Ikon ikon) {
281 return of(ikon, 8, Color.BLACK);
282 }
283
284 public static FontIcon of(Ikon ikon, int iconSize) {
285 return of(ikon, iconSize, Color.BLACK);
286 }
287
288 public static FontIcon of(Ikon ikon, Color iconColor) {
289 return of(ikon, 8, iconColor);
290 }
291
292 public static FontIcon of(Ikon iconCode, int iconSize, Color iconColor) {
293 FontIconntIcon.html#FontIcon">FontIcon icon = new FontIcon();
294 icon.setIconCode(iconCode);
295 icon.setIconSize(iconSize);
296 icon.setIconColor(iconColor);
297 return icon;
298 }
299
300 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
301 return StyleableProperties.STYLEABLES;
302 }
303
304 private static Paint resolvePaintValue(String iconCode, String value) {
305 try {
306 return Color.valueOf(value);
307 } catch (IllegalArgumentException e1) {
308 try {
309 return LinearGradient.valueOf(value);
310 } catch (IllegalArgumentException e2) {
311 try {
312 return RadialGradient.valueOf(value);
313 } catch (IllegalArgumentException e3) {
314 throw invalidDescription(iconCode, e3);
315 }
316 }
317 }
318 }
319
320 public static IllegalArgumentException invalidDescription(String description, Exception e) {
321 throw new IllegalArgumentException("Description " + description + " is not a valid icon description", e);
322 }
323
324 private static class StyleableProperties {
325 private static final CssMetaData<FontIcon, Number> ICON_SIZE =
326 new CssMetaData<FontIcon, Number>("-fx-icon-size",
327 SizeConverter.getInstance(), 8) {
328
329 @Override
330 public boolean isSettable(FontIcon icon) {
331 return true;
332 }
333
334 @Override
335 public StyleableProperty<Number> getStyleableProperty(FontIcon icon) {
336 return (StyleableProperty<Number>) icon.iconSizeProperty();
337 }
338 };
339
340 private static final CssMetaData<FontIcon, Paint> ICON_COLOR =
341 new CssMetaData<FontIcon, Paint>("-fx-icon-color",
342 PaintConverter.getInstance(), Color.BLACK) {
343
344 @Override
345 public boolean isSettable(FontIcon node) {
346 return true;
347 }
348
349 @Override
350 public StyleableProperty<Paint> getStyleableProperty(FontIcon icon) {
351 return (StyleableProperty<Paint>) icon.iconColorProperty();
352 }
353 };
354
355 private static final CssMetaData<FontIcon, Ikon> ICON_CODE =
356 new CssMetaData<FontIcon, Ikon>("-fx-icon-code",
357 FontIconConverter.getInstance(), null) {
358
359 @Override
360 public boolean isSettable(FontIcon node) {
361 return true;
362 }
363
364 @Override
365 public StyleableProperty<Ikon> getStyleableProperty(FontIcon icon) {
366 return (StyleableProperty<Ikon>) icon.iconCodeProperty();
367 }
368 };
369
370 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
371
372 static {
373 final List<CssMetaData<? extends Styleable, ?>> styleables =
374 new ArrayList<>(Text.getClassCssMetaData());
375 styleables.add(ICON_SIZE);
376 styleables.add(ICON_COLOR);
377 styleables.add(ICON_CODE);
378 STYLEABLES = unmodifiableList(styleables);
379 }
380 }
381 }