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 if(startPosition
== null) {
140 // The structure has changed, our interactable is no longer inside the base pane!
143 Set
<Interactable
> disqualified
= getDisqualifiedInteractables(startPosition
, true);
144 TerminalSize size
= getSize();
145 int maxShiftLeft
= interactable
.toBasePane(TerminalPosition
.TOP_LEFT_CORNER
).getColumn();
146 maxShiftLeft
= Math
.max(maxShiftLeft
, 0);
147 int maxShiftRight
= interactable
.toBasePane(new TerminalPosition(interactable
.getSize().getColumns() - 1, 0)).getColumn();
148 maxShiftRight
= Math
.min(maxShiftRight
, size
.getColumns() - 1);
149 int maxShift
= Math
.max(startPosition
.getColumn() - maxShiftLeft
, maxShiftRight
- startPosition
.getRow());
150 for (int searchRow
= startPosition
.getRow() + directionTerm
;
151 searchRow
>= 0 && searchRow
< size
.getRows();
152 searchRow
+= directionTerm
) {
154 for (int xShift
= 0; xShift
<= maxShift
; xShift
++) {
155 for (int modifier
: new int[]{1, -1}) {
156 if (xShift
== 0 && modifier
== -1) {
159 int searchColumn
= startPosition
.getColumn() + (xShift
* modifier
);
160 if (searchColumn
< maxShiftLeft
|| searchColumn
> maxShiftRight
) {
164 int index
= lookupMap
[searchRow
][searchColumn
];
165 if (index
!= -1 && !disqualified
.contains(interactables
.get(index
))) {
166 return interactables
.get(index
);
175 * Starting from a particular {@code Interactable} and going left, which is the next interactable?
176 * @param interactable What {@code Interactable} to start searching from
177 * @return The next {@code Interactable} left of the one specified or {@code null} if there are no more
178 * {@code Interactable}:s left of it
180 public synchronized Interactable
findNextLeft(Interactable interactable
) {
181 return findNextLeftOrRight(interactable
, false);
185 * Starting from a particular {@code Interactable} and going right, which is the next interactable?
186 * @param interactable What {@code Interactable} to start searching from
187 * @return The next {@code Interactable} right of the one specified or {@code null} if there are no more
188 * {@code Interactable}:s right of it
190 public synchronized Interactable
findNextRight(Interactable interactable
) {
191 return findNextLeftOrRight(interactable
, true);
194 //Avoid code duplication in above two methods
195 private Interactable
findNextLeftOrRight(Interactable interactable
, boolean isRight
) {
196 int directionTerm
= isRight ?
1 : -1;
197 TerminalPosition startPosition
= interactable
.getCursorLocation();
198 if(startPosition
== null) {
199 // If the currently active interactable component is not showing the cursor, use the top-left position
200 // instead if we're going left, or the top-right position if we're going right
202 startPosition
= new TerminalPosition(interactable
.getSize().getColumns() - 1, 0);
205 startPosition
= TerminalPosition
.TOP_LEFT_CORNER
;
209 //Adjust position so that it's on the left-most side if we're going left or right-most side if we're going
210 //right. Otherwise the lookup might product odd results in certain cases
212 startPosition
= startPosition
.withColumn(interactable
.getSize().getColumns() - 1);
215 startPosition
= startPosition
.withColumn(0);
218 startPosition
= interactable
.toBasePane(startPosition
);
219 if(startPosition
== null) {
220 // The structure has changed, our interactable is no longer inside the base pane!
223 Set
<Interactable
> disqualified
= getDisqualifiedInteractables(startPosition
, false);
224 TerminalSize size
= getSize();
225 int maxShiftUp
= interactable
.toBasePane(TerminalPosition
.TOP_LEFT_CORNER
).getRow();
226 maxShiftUp
= Math
.max(maxShiftUp
, 0);
227 int maxShiftDown
= interactable
.toBasePane(new TerminalPosition(0, interactable
.getSize().getRows() - 1)).getRow();
228 maxShiftDown
= Math
.min(maxShiftDown
, size
.getRows() - 1);
229 int maxShift
= Math
.max(startPosition
.getRow() - maxShiftUp
, maxShiftDown
- startPosition
.getRow());
230 for(int searchColumn
= startPosition
.getColumn() + directionTerm
;
231 searchColumn
>= 0 && searchColumn
< size
.getColumns();
232 searchColumn
+= directionTerm
) {
234 for(int yShift
= 0; yShift
<= maxShift
; yShift
++) {
235 for(int modifier
: new int[] { 1, -1 }) {
236 if(yShift
== 0 && modifier
== -1) {
239 int searchRow
= startPosition
.getRow() + (yShift
* modifier
);
240 if(searchRow
< maxShiftUp
|| searchRow
> maxShiftDown
) {
243 int index
= lookupMap
[searchRow
][searchColumn
];
244 if (index
!= -1 && !disqualified
.contains(interactables
.get(index
))) {
245 return interactables
.get(index
);
253 private Set
<Interactable
> getDisqualifiedInteractables(TerminalPosition startPosition
, boolean scanHorizontally
) {
254 Set
<Interactable
> disqualified
= new HashSet
<Interactable
>();
255 if (lookupMap
.length
== 0) { return disqualified
; } // safeguard
257 TerminalSize size
= getSize();
259 //Adjust start position if necessary
260 if(startPosition
.getRow() < 0) {
261 startPosition
= startPosition
.withRow(0);
263 else if(startPosition
.getRow() >= lookupMap
.length
) {
264 startPosition
= startPosition
.withRow(lookupMap
.length
- 1);
266 if(startPosition
.getColumn() < 0) {
267 startPosition
= startPosition
.withColumn(0);
269 else if(startPosition
.getColumn() >= lookupMap
[startPosition
.getRow()].length
) {
270 startPosition
= startPosition
.withColumn(lookupMap
[startPosition
.getRow()].length
- 1);
273 if(scanHorizontally
) {
274 for(int column
= 0; column
< size
.getColumns(); column
++) {
275 int index
= lookupMap
[startPosition
.getRow()][column
];
277 disqualified
.add(interactables
.get(index
));
282 for(int row
= 0; row
< size
.getRows(); row
++) {
283 int index
= lookupMap
[row
][startPosition
.getColumn()];
285 disqualified
.add(interactables
.get(index
));
293 for(int[] row
: lookupMap
) {
294 for(int value
: row
) {
296 System
.out
.print(" ");
298 System
.out
.print(value
);
300 System
.out
.println();
302 System
.out
.println();