001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package griffon.javafx.scene.layout;
019
020 import javafx.application.Platform;
021 import javafx.beans.property.IntegerProperty;
022 import javafx.beans.property.ObjectProperty;
023 import javafx.beans.property.ReadOnlyIntegerProperty;
024 import javafx.beans.property.ReadOnlyObjectProperty;
025 import javafx.beans.property.SimpleIntegerProperty;
026 import javafx.beans.property.SimpleObjectProperty;
027 import javafx.collections.ListChangeListener;
028 import javafx.scene.Node;
029 import javafx.scene.layout.Region;
030 import javafx.scene.layout.StackPane;
031
032 import javax.annotation.Nonnull;
033 import javax.annotation.Nullable;
034 import java.util.ArrayList;
035 import java.util.List;
036 import java.util.concurrent.atomic.AtomicBoolean;
037
038 import static griffon.util.GriffonClassUtils.requireState;
039 import static java.util.Objects.requireNonNull;
040
041 /**
042 * @author Andres Almiray
043 * @since 2.11.0
044 */
045 public class IndexedCardPane extends StackPane {
046 private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
047
048 private final List<Node> nodes = new ArrayList<>();
049 private final IntegerProperty selectedIndex = new SimpleIntegerProperty(this, "selectedIndex", -1);
050 private final ObjectProperty<Node> selectedNode = new SimpleObjectProperty<>(this, "selectedNode");
051 private final AtomicBoolean adjusting = new AtomicBoolean(false);
052
053 public IndexedCardPane() {
054 getStyleClass().add("indexed-cardpane");
055
056 getChildren().addListener((ListChangeListener<Node>) c -> {
057 if (adjusting.get()) {
058 return;
059 }
060
061 while (c.next()) {
062 if (c.wasAdded()) {
063 Node lastAddedNode = null;
064 for (Node node : c.getAddedSubList()) {
065 if (!nodes.contains(node)) {
066 nodes.add(node);
067 lastAddedNode = node;
068 }
069 }
070
071 if (lastAddedNode != null) {
072 doShow(indexOf(lastAddedNode));
073 }
074 } else if (c.wasRemoved()) {
075 int selectedIndex = getSelectedIndex();
076 for (Node node : c.getRemoved()) {
077 if (!nodes.contains(node)) {
078 continue;
079 }
080
081 int removedIndex = indexOf(node);
082 nodes.remove(node);
083
084 if (removedIndex <= selectedIndex) {
085 selectedIndex = -1;
086 }
087 }
088
089 doShow(selectedIndex);
090 }
091 }
092 });
093
094 widthProperty().addListener((observable, oldValue, newValue) -> updateBoundsInChildren());
095 heightProperty().addListener((observable, oldValue, newValue) -> updateBoundsInChildren());
096 }
097
098 protected void updateBoundsInChildren() {
099 for (Node node : nodes) {
100 updateChildBounds(node);
101 }
102 layout();
103 }
104
105 protected void updateChildBounds(Node node) {
106 if (node instanceof Region) {
107 Region child = (Region) node;
108 child.setPrefWidth(getWidth());
109 child.setPrefHeight(getHeight());
110 }
111 }
112
113 @Nonnull
114 public ReadOnlyIntegerProperty selectedIndexProperty() {
115 return selectedIndex;
116 }
117
118 @Nonnull
119 public ReadOnlyObjectProperty<Node> selectedNodeProperty() {
120 return selectedNode;
121 }
122
123 public int getSelectedIndex() {
124 return selectedIndex.get();
125 }
126
127 @Nullable
128 public Node getSelectedNode() {
129 return selectedNode.get();
130 }
131
132 public boolean isEmpty() {
133 return nodes.isEmpty();
134 }
135
136 public int size() {
137 return nodes.size();
138 }
139
140 public void clear() {
141 nodes.clear();
142 getChildren().clear();
143 selectedIndex.set(-1);
144 selectedNode.set(null);
145 }
146
147 public int indexOf(@Nonnull Node node) {
148 requireNonNull(node, ERROR_NODE_NULL);
149 return nodes.indexOf(node);
150 }
151
152 public void add(@Nonnull Node node) {
153 requireNonNull(node, ERROR_NODE_NULL);
154 if (!nodes.contains(node)) {
155 adjusting.set(true);
156 nodes.add(node);
157 adjusting.set(false);
158 }
159 show(indexOf(node));
160 }
161
162 public void remove(@Nonnull Node node) {
163 requireNonNull(node, ERROR_NODE_NULL);
164 if (!nodes.contains(node)) {
165 return;
166 }
167
168 int selectedIndex = getSelectedIndex();
169 int removedIndex = indexOf(node);
170 adjusting.set(true);
171 nodes.remove(node);
172 adjusting.set(false);
173
174 if (removedIndex <= selectedIndex) {
175 doShow(selectedIndex - 1);
176 }
177 }
178
179 public void first() {
180 if (!nodes.isEmpty()) {
181 show(0);
182 }
183 }
184
185 public void last() {
186 int size = nodes.size();
187 if (size > 0) {
188 show(size - 1);
189 }
190 }
191
192 public void next() {
193 int size = nodes.size();
194 if (size > 0) {
195 int index = getSelectedIndex() + 1;
196 show(index >= size ? 0 : index);
197 }
198 }
199
200 public void previous() {
201 int size = nodes.size();
202 if (size > 0) {
203 int index = getSelectedIndex() - 1;
204 show(index < 0 ? size - 1 : index);
205 }
206 }
207
208 public void show(int index) {
209 requireState(index > -1, "Argument 'index' must be greater than -1");
210 requireState(index < nodes.size(), "Argument 'index' must be less than " + nodes.size());
211 doShow(index);
212 }
213
214 protected void doShow(final int index) {
215 Platform.runLater(() -> {
216 if (index < 0) {
217 adjusting.set(true);
218 getChildren().clear();
219 adjusting.set(false);
220 selectedIndex.set(-1);
221 selectedNode.set(null);
222 } else {
223 Node node = nodes.get(index);
224 adjusting.set(true);
225 getChildren().setAll(node);
226 adjusting.set(false);
227 selectedIndex.set(index);
228 selectedNode.set(node);
229 }
230 });
231 }
232 }
|