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.beans.value.ChangeListener;
23 import javafx.collections.ListChangeListener;
24 import javafx.collections.MapChangeListener;
25 import javafx.css.CssMetaData;
26 import javafx.css.StyleOrigin;
27 import javafx.css.Styleable;
28 import javafx.css.StyleableIntegerProperty;
29 import javafx.css.StyleableObjectProperty;
30 import javafx.css.StyleableProperty;
31 import javafx.css.converter.PaintConverter;
32 import javafx.css.converter.SizeConverter;
33 import javafx.scene.Node;
34 import javafx.scene.layout.StackPane;
35 import javafx.scene.paint.Color;
36 import javafx.scene.paint.Paint;
37 import org.kordamp.ikonli.Ikon;
38
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.List;
42
43 import static java.util.Collections.unmodifiableList;
44 import static java.util.Objects.requireNonNull;
45
46
47
48
49 public class StackedFontIcon extends StackPane implements Icon {
50 private static final String KEY_STACKED_FONT_ICON_SIZE = StackedFontIcon.class.getName() + ".iconSize";
51
52 private StyleableIntegerProperty iconSize;
53 private StyleableObjectProperty<Paint> iconColor;
54 private double[] iconSizes = new double[0];
55
56 private ChangeListener<Number> iconSizeChangeListener = (v, o, n) -> setIconSizeOnChildren(n.intValue());
57 private ChangeListener<Paint> iconColorChangeListener = (v, o, n) -> setIconColorOnChildren(n);
58
59 public static void setIconSize(Node icon, double size) {
60 if (icon != null && size >= 0d && size <= 1.0d) {
61 icon.getProperties().put(KEY_STACKED_FONT_ICON_SIZE, size);
62 }
63 }
64
65 public static double getIconSize(Node icon) {
66 if (icon != null) {
67 Object value = icon.getProperties().get(KEY_STACKED_FONT_ICON_SIZE);
68 if (value instanceof Number) {
69 return ((Number) value).doubleValue();
70 }
71 }
72 return 1.0d;
73 }
74
75 private class NodeSizeListener implements MapChangeListener<Object, Object> {
76 private Node node;
77
78 private NodeSizeListener(Node node) {
79 this.node = node;
80 }
81
82 @Override
83 public void onChanged(Change<?, ?> change) {
84 if (KEY_STACKED_FONT_ICON_SIZE.equals(String.valueOf(change.getKey()))) {
85 int size = getChildren().size();
86 for (int i = 0; i < size; i++) {
87 if (node == getChildren().get(i)) {
88 double value = 0;
89 Object valueAdded = change.getValueAdded();
90 if (valueAdded instanceof Number) {
91 value = ((Number) valueAdded).doubleValue();
92 } else {
93 value = Double.parseDouble(String.valueOf(valueAdded));
94 }
95 iconSizes[i] = value;
96 return;
97 }
98 }
99 }
100 }
101 }
102
103 public StackedFontIcon() {
104 getStyleClass().setAll("stacked-ikonli-font-icon");
105
106 final String propertiesListenerKey = StackedFontIcon.class.getName() + "-" + System.identityHashCode(this);
107
108 getChildren().addListener(new ListChangeListener<Node>() {
109 @Override
110 public void onChanged(Change<? extends Node> c) {
111 while (c.next()) {
112 if (c.wasAdded()) {
113 int size = c.getTo() - c.getFrom();
114
115 iconSizes = Arrays.copyOf(iconSizes, iconSizes.length + size);
116
117 for (int i = c.getFrom(); i < c.getTo(); i++) {
118 iconSizes[i] = getIconSize(c.getList().get(i));
119 }
120 for (Node node : c.getAddedSubList()) {
121 node.getProperties().put(propertiesListenerKey, new NodeSizeListener(node));
122 }
123 } else if (c.wasRemoved()) {
124 int size = c.getTo() - c.getFrom();
125
126 double[] newIconSizes = new double[iconSizes.length - size];
127
128 int index = 0;
129 for (int i = 0; i < c.getFrom(); i++) {
130 newIconSizes[index++] = iconSizes[i];
131 }
132
133 for (int i = c.getTo(); i < iconSizes.length; i++) {
134 newIconSizes[index++] = iconSizes[i];
135 }
136 iconSizes = newIconSizes;
137 for (Node node : c.getRemoved()) {
138 node.getProperties().remove(propertiesListenerKey);
139 }
140 } else if (c.wasPermutated()) {
141 double[] newIconSizes = Arrays.copyOf(iconSizes, iconSizes.length);
142 for (int i = c.getFrom(); i <= c.getTo(); i++) {
143 newIconSizes[i] = c.getPermutation(i);
144 }
145 iconSizes = newIconSizes;
146 }
147 }
148 }
149 });
150 }
151
152 public IntegerProperty iconSizeProperty() {
153 if (iconSize == null) {
154 iconSize = new StyleableIntegerProperty(16) {
155 @Override
156 public CssMetaData getCssMetaData() {
157 return StyleableProperties.ICON_SIZE;
158 }
159
160 @Override
161 public Object getBean() {
162 return StackedFontIcon.this;
163 }
164
165 @Override
166 public String getName() {
167 return "iconSize";
168 }
169
170 @Override
171 public StyleOrigin getStyleOrigin() {
172 return StyleOrigin.USER_AGENT;
173 }
174 };
175 iconSize.addListener(iconSizeChangeListener);
176 }
177 return iconSize;
178 }
179
180 public ObjectProperty<Paint> iconColorProperty() {
181 if (iconColor == null) {
182 iconColor = new StyleableObjectProperty<Paint>(Color.BLACK) {
183 @Override
184 public CssMetaData getCssMetaData() {
185 return StyleableProperties.ICON_COLOR;
186 }
187
188 @Override
189 public Object getBean() {
190 return StackedFontIcon.this;
191 }
192
193 @Override
194 public String getName() {
195 return "iconColor";
196 }
197
198 @Override
199 public StyleOrigin getStyleOrigin() {
200 return StyleOrigin.USER_AGENT;
201 }
202 };
203 iconColor.addListener(iconColorChangeListener);
204 }
205 return iconColor;
206 }
207
208 public void setIconSize(int size) {
209 if (size <= 0) {
210 throw new IllegalStateException("Argument 'size' must be greater than zero.");
211 }
212 iconSizeProperty().set(size);
213 }
214
215 public int getIconSize() {
216 return iconSizeProperty().get();
217 }
218
219 public void setIconColor(Paint paint) {
220 requireNonNull(paint, "Argument 'paint' must not be null");
221 iconColorProperty().set(paint);
222 }
223
224 public Paint getIconColor() {
225 return iconColorProperty().get();
226 }
227
228 public void setIconCodes(Ikon... iconCodes) {
229 getChildren().clear();
230 initializeSizesIfNeeded(iconCodes);
231 updateIconCodes(iconCodes);
232 }
233
234 public void setIconCodeLiterals(String... iconCodes) {
235 getChildren().clear();
236 Ikonl#Ikon">Ikon[] codes = new Ikon[iconCodes.length];
237 for (int i = 0; i < iconCodes.length; i++) {
238 codes[i] = IkonResolver.getInstance().resolve(iconCodes[i]).resolve(iconCodes[i]);
239 }
240 initializeSizesIfNeeded(iconCodes);
241 updateIconCodes(codes);
242 }
243
244
245
246
247
248
249 public void setIconSizes(double... iconSizes) {
250 this.iconSizes = iconSizes;
251 setIconSizeOnChildren(getIconSize());
252 }
253
254 public void setColors(Paint... iconColors) {
255 int i = 0;
256 for (Node node : getChildren()) {
257 if (node instanceof Icon) {
258 ((Icon) node).setIconColor(iconColors[i++]);
259 }
260 }
261 }
262
263 private void initializeSizesIfNeeded(Object[] array) {
264 if (iconSizes.length == 0 || iconSizes.length != array.length) {
265 iconSizes = new double[array.length];
266 Arrays.fill(iconSizes, 1d);
267 }
268 }
269
270 private void updateIconCodes(Ikon[] iconCodes) {
271 for (int index = 0; index < iconCodes.length; index++) {
272 getChildren().add(createFontIcon(iconCodes[index], index));
273 }
274 }
275
276 private FontIcon createFontIcon(Ikon iconCode, int index) {
277 FontIconntIcon.html#FontIcon">FontIcon icon = new FontIcon(iconCode);
278 icon.setIconSize(getIconSize());
279 icon.setIconColor(getIconColor());
280 int size = icon.getIconSize();
281 applySizeToIcon(size, icon, index);
282 return icon;
283 }
284
285 private static class StyleableProperties {
286 private static final CssMetaData<StackedFontIcon, Number> ICON_SIZE =
287 new CssMetaData<StackedFontIcon, Number>("-fx-icon-size",
288 SizeConverter.getInstance(), 16.0) {
289
290 @Override
291 public boolean isSettable(StackedFontIcon fontIcon) {
292 return true;
293 }
294
295 @Override
296 public StyleableProperty<Number> getStyleableProperty(StackedFontIcon icon) {
297 return (StyleableProperty<Number>) icon.iconSizeProperty();
298 }
299 };
300
301 private static final CssMetaData<StackedFontIcon, Paint> ICON_COLOR =
302 new CssMetaData<StackedFontIcon, Paint>("-fx-icon-color",
303 PaintConverter.getInstance(), Color.BLACK) {
304
305 @Override
306 public boolean isSettable(StackedFontIcon node) {
307 return true;
308 }
309
310 @Override
311 public StyleableProperty<Paint> getStyleableProperty(StackedFontIcon icon) {
312 return (StyleableProperty<Paint>) icon.iconColorProperty();
313 }
314 };
315
316 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
317
318 static {
319 final List<CssMetaData<? extends Styleable, ?>> styleables =
320 new ArrayList<CssMetaData<? extends Styleable, ?>>(StackPane.getClassCssMetaData());
321 styleables.add(ICON_SIZE);
322 styleables.add(ICON_COLOR);
323 STYLEABLES = unmodifiableList(styleables);
324 }
325 }
326
327 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
328 return StyleableProperties.STYLEABLES;
329 }
330
331 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
332 return StackedFontIcon.getClassCssMetaData();
333 }
334
335 private void setIconSizeOnChildren(int size) {
336 int i = 0;
337 for (Node node : getChildren()) {
338 if (node instanceof Icon) {
339 applySizeToIcon(size, (Icon) node, i++);
340 }
341 }
342 }
343
344 private void applySizeToIcon(int size, Icon icon, int index) {
345 double childPercentageSize = iconSizes[index];
346 double newSize = size * childPercentageSize;
347 icon.setIconSize((int) newSize);
348 }
349
350 private void setIconColorOnChildren(Paint color) {
351 for (Node node : getChildren()) {
352 if (node instanceof Icon) {
353 ((Icon) node).setIconColor(color);
354 }
355 }
356 }
357 }