package com.googlecode.lanterna.gui2;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import com.googlecode.lanterna.graphics.Theme;
import com.googlecode.lanterna.input.KeyStroke;
import com.googlecode.lanterna.input.KeyType;
import com.googlecode.lanterna.input.MouseAction;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractBasePane<T extends BasePane> implements BasePane {
protected final ContentHolder contentHolder;
private final CopyOnWriteArrayList<BasePaneListener<T>> listeners;
protected InteractableLookupMap interactableLookupMap;
private Interactable focusedInteractable;
private boolean invalid;
private boolean strictFocusChange;
private boolean enableDirectionBasedMovements;
private Theme theme;
protected AbstractBasePane() {
this.contentHolder = new ContentHolder();
this.listeners = new CopyOnWriteArrayList<BasePaneListener<T>>();
this.interactableLookupMap = new InteractableLookupMap(new TerminalSize(80, 25));
this.invalid = false;
this.strictFocusChange = false;
this.enableDirectionBasedMovements = true;
this.theme = null;
}
@Override
public boolean isInvalid() {
return invalid || contentHolder.isInvalid();
}
@Override
public void invalidate() {
invalid = true;
contentHolder.invalidate();
}
@Override
public void draw(TextGUIGraphics graphics) {
graphics.applyThemeStyle(getTheme().getDefinition(Window.class).getNormal());
graphics.fill(' ');
contentHolder.draw(graphics);
if(!interactableLookupMap.getSize().equals(graphics.getSize())) {
interactableLookupMap = new InteractableLookupMap(graphics.getSize());
} else {
interactableLookupMap.reset();
}
contentHolder.updateLookupMap(interactableLookupMap);
invalid = false;
}
@Override
public boolean handleInput(KeyStroke key) {
AtomicBoolean deliverEvent = new AtomicBoolean(true);
for (BasePaneListener<T> listener : listeners) {
listener.onInput(self(), key, deliverEvent);
}
if (!deliverEvent.get()) {
return true;
}
boolean handled = doHandleInput(key);
if(!handled) {
AtomicBoolean hasBeenHandled = new AtomicBoolean(false);
for(BasePaneListener<T> listener: listeners) {
listener.onUnhandledInput(self(), key, hasBeenHandled);
}
handled = hasBeenHandled.get();
}
return handled;
}
abstract T self();
private boolean doHandleInput(KeyStroke key) {
boolean result = false;
if(key.getKeyType() == KeyType.MouseEvent) {
MouseAction mouseAction = (MouseAction)key;
TerminalPosition localCoordinates = fromGlobal(mouseAction.getPosition());
if (localCoordinates != null) {
Interactable interactable = interactableLookupMap.getInteractableAt(localCoordinates);
if(interactable != null) {
interactable.handleInput(key);
}
}
}
else if(focusedInteractable == null) {
Component baseComponent = getComponent();
Interactable.FocusChangeDirection direction = Interactable.FocusChangeDirection.TELEPORT;
Interactable nextFocus = null;
switch (key.getKeyType()) {
case Tab:
case ArrowRight:
case ArrowDown:
direction = Interactable.FocusChangeDirection.NEXT;
if (baseComponent instanceof Container) {
nextFocus = ((Container) baseComponent).nextFocus(null);
}
else if (baseComponent instanceof Interactable) {
nextFocus = (Interactable) baseComponent;
}
break;
case ReverseTab:
case ArrowUp:
case ArrowLeft:
direction = Interactable.FocusChangeDirection.PREVIOUS;
if (baseComponent instanceof Container) {
nextFocus = ((Container) baseComponent).previousFocus(null);
}
else if (baseComponent instanceof Interactable) {
nextFocus = (Interactable) baseComponent;
}
break;
}
if (nextFocus != null) {
setFocusedInteractable(nextFocus, direction);
result = true;
}
}
else {
Interactable next = null;
Interactable.FocusChangeDirection direction = Interactable.FocusChangeDirection.TELEPORT;
Interactable.Result handleResult = focusedInteractable.handleInput(key);
if(!enableDirectionBasedMovements) {
if(handleResult == Interactable.Result.MOVE_FOCUS_DOWN || handleResult == Interactable.Result.MOVE_FOCUS_RIGHT) {
handleResult = Interactable.Result.MOVE_FOCUS_NEXT;
}
else if(handleResult == Interactable.Result.MOVE_FOCUS_UP || handleResult == Interactable.Result.MOVE_FOCUS_LEFT) {
handleResult = Interactable.Result.MOVE_FOCUS_PREVIOUS;
}
}
switch (handleResult) {
case HANDLED:
result = true;
break;
case UNHANDLED:
Container parent = focusedInteractable.getParent();
while(parent != null) {
if(parent.handleInput(key)) {
return true;
}
parent = parent.getParent();
}
result = false;
break;
case MOVE_FOCUS_NEXT:
next = contentHolder.nextFocus(focusedInteractable);
if(next == null) {
next = contentHolder.nextFocus(null);
}
direction = Interactable.FocusChangeDirection.NEXT;
break;
case MOVE_FOCUS_PREVIOUS:
next = contentHolder.previousFocus(focusedInteractable);
if(next == null) {
next = contentHolder.previousFocus(null);
}
direction = Interactable.FocusChangeDirection.PREVIOUS;
break;
case MOVE_FOCUS_DOWN:
next = interactableLookupMap.findNextDown(focusedInteractable);
direction = Interactable.FocusChangeDirection.DOWN;
if(next == null && !strictFocusChange) {
next = contentHolder.nextFocus(focusedInteractable);
direction = Interactable.FocusChangeDirection.NEXT;
}
break;
case MOVE_FOCUS_LEFT:
next = interactableLookupMap.findNextLeft(focusedInteractable);
direction = Interactable.FocusChangeDirection.LEFT;
break;
case MOVE_FOCUS_RIGHT:
next = interactableLookupMap.findNextRight(focusedInteractable);
direction = Interactable.FocusChangeDirection.RIGHT;
break;
case MOVE_FOCUS_UP:
next = interactableLookupMap.findNextUp(focusedInteractable);
direction = Interactable.FocusChangeDirection.UP;
if(next == null && !strictFocusChange) {
next = contentHolder.previousFocus(focusedInteractable);
direction = Interactable.FocusChangeDirection.PREVIOUS;
}
break;
}
if(next != null) {
setFocusedInteractable(next, direction);
result = true;
}
}
return result;
}
@Override
public Component getComponent() {
return contentHolder.getComponent();
}
@Override
public void setComponent(Component component) {
contentHolder.setComponent(component);
}
@Override
public Interactable getFocusedInteractable() {
return focusedInteractable;
}
@Override
public TerminalPosition getCursorPosition() {
if(focusedInteractable == null) {
return null;
}
TerminalPosition position = focusedInteractable.getCursorLocation();
if(position == null) {
return null;
}
if(position.getColumn() < 0 ||
position.getRow() < 0 ||
position.getColumn() >= focusedInteractable.getSize().getColumns() ||
position.getRow() >= focusedInteractable.getSize().getRows()) {
return null;
}
return focusedInteractable.toBasePane(position);
}
@Override
public void setFocusedInteractable(Interactable toFocus) {
setFocusedInteractable(toFocus,
toFocus != null ?
Interactable.FocusChangeDirection.TELEPORT : Interactable.FocusChangeDirection.RESET);
}
protected void setFocusedInteractable(Interactable toFocus, Interactable.FocusChangeDirection direction) {
if(focusedInteractable == toFocus) {
return;
}
if(toFocus != null && !toFocus.isEnabled()) {
return;
}
if(focusedInteractable != null) {
focusedInteractable.onLeaveFocus(direction, focusedInteractable);
}
Interactable previous = focusedInteractable;
focusedInteractable = toFocus;
if(toFocus != null) {
toFocus.onEnterFocus(direction, previous);
}
invalidate();
}
@Override
public void setStrictFocusChange(boolean strictFocusChange) {
this.strictFocusChange = strictFocusChange;
}
@Override
public void setEnableDirectionBasedMovements(boolean enableDirectionBasedMovements) {
this.enableDirectionBasedMovements = enableDirectionBasedMovements;
}
@Override
public synchronized Theme getTheme() {
if(theme != null) {
return theme;
}
else if(getTextGUI() != null) {
return getTextGUI().getTheme();
}
return null;
}
@Override
public synchronized void setTheme(Theme theme) {
this.theme = theme;
}
protected void addBasePaneListener(BasePaneListener<T> basePaneListener) {
listeners.addIfAbsent(basePaneListener);
}
protected void removeBasePaneListener(BasePaneListener<T> basePaneListener) {
listeners.remove(basePaneListener);
}
protected List<BasePaneListener<T>> getBasePaneListeners() {
return listeners;
}
protected class ContentHolder extends AbstractComposite<Container> {
@Override
public void setComponent(Component component) {
if(getComponent() == component) {
return;
}
setFocusedInteractable(null);
super.setComponent(component);
if(focusedInteractable == null && component instanceof Interactable) {
setFocusedInteractable((Interactable)component);
}
else if(focusedInteractable == null && component instanceof Container) {
setFocusedInteractable(((Container)component).nextFocus(null));
}
}
public boolean removeComponent(Component component) {
boolean removed = super.removeComponent(component);
if (removed) {
focusedInteractable = null;
}
return removed;
}
@Override
public TextGUI getTextGUI() {
return AbstractBasePane.this.getTextGUI();
}
@Override
protected ComponentRenderer<Container> createDefaultRenderer() {
return new ComponentRenderer<Container>() {
@Override
public TerminalSize getPreferredSize(Container component) {
Component subComponent = getComponent();
if(subComponent == null) {
return TerminalSize.ZERO;
}
return subComponent.getPreferredSize();
}
@Override
public void drawComponent(TextGUIGraphics graphics, Container component) {
Component subComponent = getComponent();
if(subComponent == null) {
return;
}
subComponent.draw(graphics);
}
};
}
@Override
public TerminalPosition toGlobal(TerminalPosition position) {
return AbstractBasePane.this.toGlobal(position);
}
@Override
public TerminalPosition toBasePane(TerminalPosition position) {
return position;
}
@Override
public BasePane getBasePane() {
return AbstractBasePane.this;
}
}
}