• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 package com.badlogic.gdx.scenes.scene2d.utils;
3 
4 import com.badlogic.gdx.scenes.scene2d.Actor;
5 import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
6 import com.badlogic.gdx.utils.Array;
7 import com.badlogic.gdx.utils.OrderedSet;
8 import com.badlogic.gdx.utils.Pools;
9 
10 import java.util.Iterator;
11 
12 /** Manages selected objects. Optionally fires a {@link ChangeEvent} on an actor. Selection changes can be vetoed via
13  * {@link ChangeEvent#cancel()}.
14  * @author Nathan Sweet */
15 public class Selection<T> implements Disableable, Iterable<T> {
16 	private Actor actor;
17 	final OrderedSet<T> selected = new OrderedSet();
18 	private final OrderedSet<T> old = new OrderedSet();
19 	boolean isDisabled;
20 	private boolean toggle;
21 	boolean multiple;
22 	boolean required;
23 	private boolean programmaticChangeEvents = true;
24 	T lastSelected;
25 
26 	/** @param actor An actor to fire {@link ChangeEvent} on when the selection changes, or null. */
setActor(Actor actor)27 	public void setActor (Actor actor) {
28 		this.actor = actor;
29 	}
30 
31 	/** Selects or deselects the specified item based on how the selection is configured, whether ctrl is currently pressed, etc.
32 	 * This is typically invoked by user interaction. */
choose(T item)33 	public void choose (T item) {
34 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
35 		if (isDisabled) return;
36 		snapshot();
37 		try {
38 			if ((toggle || (!required && selected.size == 1) || UIUtils.ctrl()) && selected.contains(item)) {
39 				if (required && selected.size == 1) return;
40 				selected.remove(item);
41 				lastSelected = null;
42 			} else {
43 				boolean modified = false;
44 				if (!multiple || (!toggle && !UIUtils.ctrl())) {
45 					if (selected.size == 1 && selected.contains(item)) return;
46 					modified = selected.size > 0;
47 					selected.clear();
48 				}
49 				if (!selected.add(item) && !modified) return;
50 				lastSelected = item;
51 			}
52 			if (fireChangeEvent()) revert();
53 		} finally {
54 			cleanup();
55 		}
56 	}
57 
hasItems()58 	public boolean hasItems () {
59 		return selected.size > 0;
60 	}
61 
isEmpty()62 	public boolean isEmpty () {
63 		return selected.size == 0;
64 	}
65 
size()66 	public int size () {
67 		return selected.size;
68 	}
69 
items()70 	public OrderedSet<T> items () {
71 		return selected;
72 	}
73 
74 	/** Returns the first selected item, or null. */
first()75 	public T first () {
76 		return selected.size == 0 ? null : selected.first();
77 	}
78 
snapshot()79 	void snapshot () {
80 		old.clear();
81 		old.addAll(selected);
82 	}
83 
revert()84 	void revert () {
85 		selected.clear();
86 		selected.addAll(old);
87 	}
88 
cleanup()89 	void cleanup () {
90 		old.clear(32);
91 	}
92 
93 	/** Sets the selection to only the specified item. */
set(T item)94 	public void set (T item) {
95 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
96 		if (selected.size == 1 && selected.first() == item) return;
97 		snapshot();
98 		selected.clear();
99 		selected.add(item);
100 		if (programmaticChangeEvents && fireChangeEvent())
101 			revert();
102 		else
103 			lastSelected = item;
104 		cleanup();
105 	}
106 
setAll(Array<T> items)107 	public void setAll (Array<T> items) {
108 		boolean added = false;
109 		snapshot();
110 		selected.clear();
111 		for (int i = 0, n = items.size; i < n; i++) {
112 			T item = items.get(i);
113 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
114 			if (selected.add(item)) added = true;
115 		}
116 		if (added && programmaticChangeEvents && fireChangeEvent())
117 			revert();
118 		else
119 			lastSelected = items.peek();
120 		cleanup();
121 	}
122 
123 	/** Adds the item to the selection. */
add(T item)124 	public void add (T item) {
125 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
126 		if (!selected.add(item)) return;
127 		if (programmaticChangeEvents && fireChangeEvent())
128 			selected.remove(item);
129 		else
130 			lastSelected = item;
131 	}
132 
addAll(Array<T> items)133 	public void addAll (Array<T> items) {
134 		boolean added = false;
135 		snapshot();
136 		for (int i = 0, n = items.size; i < n; i++) {
137 			T item = items.get(i);
138 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
139 			if (selected.add(item)) added = true;
140 		}
141 		if (added && programmaticChangeEvents && fireChangeEvent())
142 			revert();
143 		else
144 			lastSelected = items.peek();
145 		cleanup();
146 	}
147 
remove(T item)148 	public void remove (T item) {
149 		if (item == null) throw new IllegalArgumentException("item cannot be null.");
150 		if (!selected.remove(item)) return;
151 		if (programmaticChangeEvents && fireChangeEvent())
152 			selected.add(item);
153 		else
154 			lastSelected = null;
155 	}
156 
removeAll(Array<T> items)157 	public void removeAll (Array<T> items) {
158 		boolean removed = false;
159 		snapshot();
160 		for (int i = 0, n = items.size; i < n; i++) {
161 			T item = items.get(i);
162 			if (item == null) throw new IllegalArgumentException("item cannot be null.");
163 			if (selected.remove(item)) removed = true;
164 		}
165 		if (removed && programmaticChangeEvents && fireChangeEvent())
166 			revert();
167 		else
168 			lastSelected = null;
169 		cleanup();
170 	}
171 
clear()172 	public void clear () {
173 		if (selected.size == 0) return;
174 		snapshot();
175 		selected.clear();
176 		if (programmaticChangeEvents && fireChangeEvent())
177 			revert();
178 		else
179 			lastSelected = null;
180 		cleanup();
181 	}
182 
183 	/** Fires a change event on the selection's actor, if any. Called internally when the selection changes, depending on
184 	 * {@link #setProgrammaticChangeEvents(boolean)}.
185 	 * @return true if the change should be undone. */
fireChangeEvent()186 	public boolean fireChangeEvent () {
187 		if (actor == null) return false;
188 		ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
189 		try {
190 			return actor.fire(changeEvent);
191 		} finally {
192 			Pools.free(changeEvent);
193 		}
194 	}
195 
contains(T item)196 	public boolean contains (T item) {
197 		if (item == null) return false;
198 		return selected.contains(item);
199 	}
200 
201 	/** Makes a best effort to return the last item selected, else returns an arbitrary item or null if the selection is empty. */
getLastSelected()202 	public T getLastSelected () {
203 		if (lastSelected != null) {
204 			return lastSelected;
205 		} else if (selected.size > 0) {
206 			return selected.first();
207 		}
208 		return null;
209 	}
210 
iterator()211 	public Iterator<T> iterator () {
212 		return selected.iterator();
213 	}
214 
toArray()215 	public Array<T> toArray () {
216 		return selected.iterator().toArray();
217 	}
218 
toArray(Array<T> array)219 	public Array<T> toArray (Array<T> array) {
220 		return selected.iterator().toArray(array);
221 	}
222 
223 	/** If true, prevents {@link #choose(Object)} from changing the selection. Default is false. */
setDisabled(boolean isDisabled)224 	public void setDisabled (boolean isDisabled) {
225 		this.isDisabled = isDisabled;
226 	}
227 
isDisabled()228 	public boolean isDisabled () {
229 		return isDisabled;
230 	}
231 
getToggle()232 	public boolean getToggle () {
233 		return toggle;
234 	}
235 
236 	/** If true, prevents {@link #choose(Object)} from clearing the selection. Default is false. */
setToggle(boolean toggle)237 	public void setToggle (boolean toggle) {
238 		this.toggle = toggle;
239 	}
240 
getMultiple()241 	public boolean getMultiple () {
242 		return multiple;
243 	}
244 
245 	/** If true, allows {@link #choose(Object)} to select multiple items. Default is false. */
setMultiple(boolean multiple)246 	public void setMultiple (boolean multiple) {
247 		this.multiple = multiple;
248 	}
249 
getRequired()250 	public boolean getRequired () {
251 		return required;
252 	}
253 
254 	/** If true, prevents {@link #choose(Object)} from selecting none. Default is false. */
setRequired(boolean required)255 	public void setRequired (boolean required) {
256 		this.required = required;
257 	}
258 
259 	/** If false, only {@link #choose(Object)} will fire a change event. Default is true. */
setProgrammaticChangeEvents(boolean programmaticChangeEvents)260 	public void setProgrammaticChangeEvents (boolean programmaticChangeEvents) {
261 		this.programmaticChangeEvents = programmaticChangeEvents;
262 	}
263 
toString()264 	public String toString () {
265 		return selected.toString();
266 	}
267 }
268