2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.gui2
;
21 import com
.googlecode
.lanterna
.TerminalPosition
;
22 import com
.googlecode
.lanterna
.TerminalSize
;
27 * This class is used to keep a 'map' of the usable area and note where all the interact:ables are. It can then be used
28 * to find the next interactable in any direction. It is used inside the GUI system to drive arrow key navigation.
31 public class InteractableLookupMap
{
32 private final int[][] lookupMap
;
33 private final List
<Interactable
> interactables
;
35 InteractableLookupMap(TerminalSize size
) {
36 lookupMap
= new int[size
.getRows()][size
.getColumns()];
37 interactables
= new ArrayList
<Interactable
>();
38 for (int[] aLookupMap
: lookupMap
) {
39 Arrays
.fill(aLookupMap
, -1);
44 interactables
.clear();
45 for (int[] aLookupMap
: lookupMap
) {
46 Arrays
.fill(aLookupMap
, -1);
50 TerminalSize
getSize() {
51 if (lookupMap
.length
==0) { return TerminalSize
.ZERO
; }
52 return new TerminalSize(lookupMap
[0].length
, lookupMap
.length
);
56 * Adds an interactable component to the lookup map
57 * @param interactable Interactable to add to the lookup map
59 @SuppressWarnings("ConstantConditions")
60 public synchronized void add(Interactable interactable
) {
61 TerminalPosition topLeft
= interactable
.toBasePane(TerminalPosition
.TOP_LEFT_CORNER
);
62 TerminalSize size
= interactable
.getSize();
63 interactables
.add(interactable
);
64 int index
= interactables
.size() - 1;
65 for(int y
= topLeft
.getRow(); y
< topLeft
.getRow() + size
.getRows(); y
++) {
66 for(int x
= topLeft
.getColumn(); x
< topLeft
.getColumn() + size
.getColumns(); x
++) {
67 //Make sure it's not outside the map
68 if(y
>= 0 && y
< lookupMap
.length
&&
69 x
>= 0 && x
< lookupMap
[y
].length
) {
70 lookupMap
[y
][x
] = index
;
77 * Looks up what interactable component is as a particular location in the map
78 * @param position Position to look up
79 * @return The {@code Interactable} component at the specified location or {@code null} if there's nothing there
81 public synchronized Interactable
getInteractableAt(TerminalPosition position
) {
82 if(position
.getRow() >= lookupMap
.length
) {
85 else if(position
.getColumn() >= lookupMap
[0].length
) {
88 else if(lookupMap
[position
.getRow()][position
.getColumn()] == -1) {
91 return interactables
.get(lookupMap
[position
.getRow()][position
.getColumn()]);
95 * Starting from a particular {@code Interactable} and going up, which is the next interactable?
96 * @param interactable What {@code Interactable} to start searching from
97 * @return The next {@code Interactable} above the one specified or {@code null} if there are no more
98 * {@code Interactable}:s above it
100 public synchronized Interactable
findNextUp(Interactable interactable
) {
101 return findNextUpOrDown(interactable
, false);
105 * Starting from a particular {@code Interactable} and going down, which is the next interactable?
106 * @param interactable What {@code Interactable} to start searching from
107 * @return The next {@code Interactable} below the one specified or {@code null} if there are no more
108 * {@code Interactable}:s below it
110 public synchronized Interactable
findNextDown(Interactable interactable
) {
111 return findNextUpOrDown(interactable
, true);
114 //Avoid code duplication in above two methods
115 private Interactable
findNextUpOrDown(Interactable interactable
, boolean isDown
) {
116 int directionTerm
= isDown ?
1 : -1;
117 TerminalPosition startPosition
= interactable
.getCursorLocation();
118 if (startPosition
== null) {
119 // If the currently active interactable component is not showing the cursor, use the top-left position
120 // instead if we're going up, or the bottom-left position if we're going down
122 startPosition
= new TerminalPosition(0, interactable
.getSize().getRows() - 1);
125 startPosition
= TerminalPosition
.TOP_LEFT_CORNER
;
129 //Adjust position so that it's at the bottom of the component if we're going down or at the top of the
130 //component if we're going right. Otherwise the lookup might product odd results in certain cases.
132 startPosition
= startPosition
.withRow(interactable
.getSize().getRows() - 1);
135 startPosition
= startPosition
.withRow(0);
138 startPosition
= interactable
.toBasePane(startPosition
);
139 Set
<Interactable
> disqualified
= getDisqualifiedInteractables(startPosition
, true);
140 TerminalSize size
= getSize();
141 int maxShiftLeft
= interactable
.toBasePane(TerminalPosition
.TOP_LEFT_CORNER
).getColumn();
142 maxShiftLeft
= Math
.max(maxShiftLeft
, 0);
143 int maxShiftRight
= interactable
.toBasePane(new TerminalPosition(interactable
.getSize().getColumns() - 1, 0)).getColumn();
144 maxShiftRight
= Math
.min(maxShiftRight
, size
.getColumns() - 1);
145 int maxShift
= Math
.max(startPosition
.getColumn() - maxShiftLeft
, maxShiftRight
- startPosition
.getRow());
146 for (int searchRow
= startPosition
.getRow() + directionTerm
;
147 searchRow
>= 0 && searchRow
< size
.getRows();
148 searchRow
+= directionTerm
) {
150 for (int xShift
= 0; xShift
<= maxShift
; xShift
++) {
151 for (int modifier
: new int[]{1, -1}) {
152 if (xShift
== 0 && modifier
== -1) {
155 int searchColumn
= startPosition
.getColumn() + (xShift
* modifier
);
156 if (searchColumn
< maxShiftLeft
|| searchColumn
> maxShiftRight
) {
160 int index
= lookupMap
[searchRow
][searchColumn
];
161 if (index
!= -1 && !disqualified
.contains(interactables
.get(index
))) {
162 return interactables
.get(index
);
171 * Starting from a particular {@code Interactable} and going left, which is the next interactable?
172 * @param interactable What {@code Interactable} to start searching from
173 * @return The next {@code Interactable} left of the one specified or {@code null} if there are no more
174 * {@code Interactable}:s left of it
176 public synchronized Interactable
findNextLeft(Interactable interactable
) {
177 return findNextLeftOrRight(interactable
, false);
181 * Starting from a particular {@code Interactable} and going right, which is the next interactable?
182 * @param interactable What {@code Interactable} to start searching from
183 * @return The next {@code Interactable} right of the one specified or {@code null} if there are no more
184 * {@code Interactable}:s right of it
186 public synchronized Interactable
findNextRight(Interactable interactable
) {
187 return findNextLeftOrRight(interactable
, true);
190 //Avoid code duplication in above two methods
191 private Interactable
findNextLeftOrRight(Interactable interactable
, boolean isRight
) {
192 int directionTerm
= isRight ?
1 : -1;
193 TerminalPosition startPosition
= interactable
.getCursorLocation();
194 if(startPosition
== null) {
195 // If the currently active interactable component is not showing the cursor, use the top-left position
196 // instead if we're going left, or the top-right position if we're going right
198 startPosition
= new TerminalPosition(interactable
.getSize().getColumns() - 1, 0);
201 startPosition
= TerminalPosition
.TOP_LEFT_CORNER
;
205 //Adjust position so that it's on the left-most side if we're going left or right-most side if we're going
206 //right. Otherwise the lookup might product odd results in certain cases
208 startPosition
= startPosition
.withColumn(interactable
.getSize().getColumns() - 1);
211 startPosition
= startPosition
.withColumn(0);
214 startPosition
= interactable
.toBasePane(startPosition
);
215 Set
<Interactable
> disqualified
= getDisqualifiedInteractables(startPosition
, false);
216 TerminalSize size
= getSize();
217 int maxShiftUp
= interactable
.toBasePane(TerminalPosition
.TOP_LEFT_CORNER
).getRow();
218 maxShiftUp
= Math
.max(maxShiftUp
, 0);
219 int maxShiftDown
= interactable
.toBasePane(new TerminalPosition(0, interactable
.getSize().getRows() - 1)).getRow();
220 maxShiftDown
= Math
.min(maxShiftDown
, size
.getRows() - 1);
221 int maxShift
= Math
.max(startPosition
.getRow() - maxShiftUp
, maxShiftDown
- startPosition
.getRow());
222 for(int searchColumn
= startPosition
.getColumn() + directionTerm
;
223 searchColumn
>= 0 && searchColumn
< size
.getColumns();
224 searchColumn
+= directionTerm
) {
226 for(int yShift
= 0; yShift
<= maxShift
; yShift
++) {
227 for(int modifier
: new int[] { 1, -1 }) {
228 if(yShift
== 0 && modifier
== -1) {
231 int searchRow
= startPosition
.getRow() + (yShift
* modifier
);
232 if(searchRow
< maxShiftUp
|| searchRow
> maxShiftDown
) {
235 int index
= lookupMap
[searchRow
][searchColumn
];
236 if (index
!= -1 && !disqualified
.contains(interactables
.get(index
))) {
237 return interactables
.get(index
);
245 private Set
<Interactable
> getDisqualifiedInteractables(TerminalPosition startPosition
, boolean scanHorizontally
) {
246 Set
<Interactable
> disqualified
= new HashSet
<Interactable
>();
247 if (lookupMap
.length
== 0) { return disqualified
; } // safeguard
249 TerminalSize size
= getSize();
251 //Adjust start position if necessary
252 if(startPosition
.getRow() < 0) {
253 startPosition
= startPosition
.withRow(0);
255 else if(startPosition
.getRow() >= lookupMap
.length
) {
256 startPosition
= startPosition
.withRow(lookupMap
.length
- 1);
258 if(startPosition
.getColumn() < 0) {
259 startPosition
= startPosition
.withColumn(0);
261 else if(startPosition
.getColumn() >= lookupMap
[startPosition
.getRow()].length
) {
262 startPosition
= startPosition
.withColumn(lookupMap
[startPosition
.getRow()].length
- 1);
265 if(scanHorizontally
) {
266 for(int column
= 0; column
< size
.getColumns(); column
++) {
267 int index
= lookupMap
[startPosition
.getRow()][column
];
269 disqualified
.add(interactables
.get(index
));
274 for(int row
= 0; row
< size
.getRows(); row
++) {
275 int index
= lookupMap
[row
][startPosition
.getColumn()];
277 disqualified
.add(interactables
.get(index
));
285 for(int[] row
: lookupMap
) {
286 for(int value
: row
) {
288 System
.out
.print(" ");
290 System
.out
.print(value
);
292 System
.out
.println();
294 System
.out
.println();