package gminers.glasspane.component;
import gminers.glasspane.event.ComponentActivateEvent;
import gminers.glasspane.event.ComponentAddedEvent;
import gminers.glasspane.event.ComponentParentChangeEvent;
import gminers.glasspane.event.ComponentRemovedEvent;
import gminers.glasspane.event.FocusGainedEvent;
import gminers.glasspane.event.FocusLostEvent;
import gminers.glasspane.event.KeyTypedEvent;
import gminers.glasspane.event.MouseDownEvent;
import gminers.glasspane.event.MouseUpEvent;
import gminers.glasspane.event.MouseWheelEvent;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.input.Keyboard;
import com.google.common.collect.Lists;
/**
* Base class for all components that can have components added to them.
*
* @author Aesen Vismea
*
*/
public abstract class PaneContainer
extends PaneComponent
implements Focusable {
protected List<PaneComponent> components = new CopyOnWriteArrayList<PaneComponent>();
private boolean focusableComponentPresent = false;
/**
* The component that currently has the focus.
*/
@Getter @Setter protected PaneComponent focusedComponent = null;
/**
* Whether or not to allow using Tab and Shift+Tab to cycle the currently focused component.
*/
@Getter @Setter protected boolean cycleFocusOnTabPress = true;
/**
* Adds multiple PaneComponents to this container, with their positions defined by the current PaneLayoutManager.<br/>
* <b>TODO</b>: Actually add layout managers. For now, all containers act as if they have an AbsoluteLayout and obey the X,Y coords of
* the component, relative to the container.
*
* @param c
* Components to add
*/
public void add(final PaneComponent... c) {
for (final PaneComponent co : c) {
add(co);
}
}
/**
* Adds a PaneComponent to this container, with it's position defined by the current PaneLayoutManager.<br/>
* <b>TODO</b>: Actually add layout managers. For now, all containers act as if they have an AbsoluteLayout and obey the X,Y coords of
* the component, relative to the container.
*
* @param c
* Component to add
*/
public void add(final PaneComponent c) {
fireEvent(ComponentAddedEvent.class, this, c);
final PaneContainer oldParent = c.parent;
c.parent = this;
c.fireEvent(ComponentParentChangeEvent.class, c, oldParent);
if (c instanceof Focusable) {
focusableComponentPresent = true;
}
components.add(c);
}
/**
* Removes multiple PaneComponents from this container.
*
* @param c
* Component to remove
*/
public void remove(final PaneComponent... c) {
for (final PaneComponent co : c) {
remove(co);
}
}
/**
* Removes a PaneComponent from this container.
*
* @param c
* Component to remove
*/
public void remove(final PaneComponent c) {
fireEvent(ComponentRemovedEvent.class, this, c);
if (components.contains(c)) {
c.parent = null;
c.fireEvent(ComponentParentChangeEvent.class, c, this);
if (focusedComponent == c) {
focusedComponent = null;
}
components.remove(c);
focusableComponentPresent = false;
for (final PaneComponent co : components) {
if (co instanceof Focusable) {
focusableComponentPresent = true;
break;
}
}
}
}
/**
* Removes all PaneComponents from this container.
*/
public void clear() {
for (final PaneComponent c : components) {
fireEvent(ComponentRemovedEvent.class, this, c);
c.parent = null;
c.fireEvent(ComponentParentChangeEvent.class, c, this);
}
focusableComponentPresent = false;
focusedComponent = null;
components.clear();
}
/**
* Gets a list of all PaneComponents in this container.
*
* @return A list of components in this container.
*/
public List<PaneComponent> getComponents() {
return Lists.newArrayList(components);
}
@Override
protected void doRender(final int mouseX, final int mouseY, final float partialTicks) {
final int pX = getPX();
final int pY = getPY();
for (final PaneComponent pc : components) {
pc.render(mouseX - pX, mouseY - pY, partialTicks);
}
}
protected int getPY() {
return getY();
}
protected int getPX() {
return getX();
}
@Override
protected void mouseDown(final int mouseX, final int mouseY, final int button) {
if (!isVisible()) return;
boolean clickedAFocusable = false;
final int pX = getPX();
final int pY = getPY();
for (final PaneComponent c : components) {
if (!c.isVisible()) {
continue;
}
if (c.withinBounds(mouseX - pX, mouseY - pY)) {
if (c instanceof Focusable && button == 0) {
clickedAFocusable = true;
if (c.isVisible() && focusedComponent != c) {
final PaneComponent oldFocused = focusedComponent;
focusedComponent = c;
final FocusGainedEvent fge = focusedComponent.fireEvent(FocusGainedEvent.class,
focusedComponent);
if (fge != null) {
if (fge.isConsumed()) {
focusedComponent = oldFocused;
continue;
}
}
if (oldFocused != null) {
oldFocused.fireEvent(FocusLostEvent.class, oldFocused);
}
}
if (c.isActivatedOnClick()) {
c.fireEvent(ComponentActivateEvent.class, c);
}
}
c.fireEvent(MouseDownEvent.class, c, mouseX - pX, mouseY - pY, button);
}
}
if (!clickedAFocusable) {
focusedComponent = null;
if (parent != null) {
parent.focusedComponent = null;
}
} else if (parent != null) {
parent.focusedComponent = this;
}
}
@Override
protected void mouseUp(final int mouseX, final int mouseY, final int button) {
if (!isVisible()) return;
final int pX = getPX();
final int pY = getPY();
for (final PaneComponent c : components) {
if (!c.isVisible()) {
continue;
}
if (c.withinBounds(mouseX - pX, mouseY - pY)) {
c.fireEvent(MouseUpEvent.class, c, mouseX - pX, mouseY - pY, button);
}
}
}
@Override
protected void mouseWheel(final int mouseX, final int mouseY, final int distance) {
if (!isVisible()) return;
final int pX = getPX();
final int pY = getPY();
for (final PaneComponent c : components) {
if (!c.isVisible()) {
continue;
}
if (c.withinBounds(mouseX - pX, mouseY - pY)) {
c.fireEvent(MouseWheelEvent.class, c, mouseX - pX, mouseY - pY, distance);
}
}
}
@Override
protected void keyPressed(final char keyChar, final int keyCode) {
if (keyCode == Keyboard.KEY_TAB && cycleFocusOnTabPress) {
if (Keyboard.isKeyDown(Keyboard.KEY_RSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
focusPrev(-1);
} else {
focusNext(-1);
}
} else if (keyCode == Keyboard.KEY_RETURN) {
if (focusedComponent != null) {
focusedComponent.fireEvent(ComponentActivateEvent.class, focusedComponent);
}
}
final int pX = getPX();
final int pY = getPY();
for (final PaneComponent c : components) {
if (!c.isVisible()) {
continue;
}
if (c == focusedComponent || c.withinBounds(mouseX - pX, mouseY - pY)) {
c.fireEvent(KeyTypedEvent.class, c, keyChar, keyCode);
}
}
}
/**
* Gives the focus to the previous component in this container that can be focused.
*/
public void focusPrev(final int index) {
// make sure we actually have a component to find
if (components.isEmpty() || !focusableComponentPresent) return;
// store the currently focused component
final PaneComponent oldFocused = focusedComponent;
// get the index of the currently focused component, or the passed index
int idx = index == -1 ? components.indexOf(focusedComponent) : index;
boolean outOfBounds = false;
do {
// if we're out of bounds, wrap around
if (idx - 1 < 0) {
idx = components.size();
if (outOfBounds) {
break; // if we've already gone out of bounds once, break out of the loop
}
outOfBounds = true;
}
// set the focused component
focusedComponent = components.get(idx - 1);
// decrement the index
idx--;
} while (!(focusedComponent instanceof Focusable));
// did we change?
if (oldFocused != focusedComponent) {
// fire a gain event to the new component
final FocusGainedEvent fge = focusedComponent.fireEvent(FocusGainedEvent.class, focusedComponent);
// if it consumed it, continue searching for a component
if (fge != null) {
if (fge.isConsumed() || !focusedComponent.isVisible()) {
focusedComponent = oldFocused;
focusPrev(idx);
return;
}
}
// and send a focus lost event to the old component
if (oldFocused != null) {
oldFocused.fireEvent(FocusLostEvent.class, oldFocused);
}
}
}
/**
* Gives the focus to the next component in this container that can be focused.
*/
public void focusNext(final int index) {
// make sure we actually have a component to find
if (components.isEmpty() || !focusableComponentPresent) return;
// store the currently focused component
final PaneComponent oldFocused = focusedComponent;
// get the index of the currently focused component, or the passed index
int idx = index == -1 ? components.indexOf(focusedComponent) : index;
boolean outOfBounds = false;
do {
// if we're out of bounds, wrap around
if (idx + 1 >= components.size()) {
idx = -1;
if (outOfBounds) {
break; // if we've already gone out of bounds once, break out of the loop
}
outOfBounds = true;
}
// set the focused component
focusedComponent = components.get(idx + 1);
// increment the index
idx++;
} while (!(focusedComponent instanceof Focusable));
// did we change?
if (oldFocused != focusedComponent) {
// fire a gain event to the new component
final FocusGainedEvent fge = focusedComponent.fireEvent(FocusGainedEvent.class, focusedComponent);
// if it consumed it, continue searching for a component
if (fge != null) {
if (fge.isConsumed() || !focusedComponent.isVisible()) {
focusedComponent = oldFocused;
focusNext(idx);
return;
}
}
// and send a focus lost event to the old component
if (oldFocused != null) {
oldFocused.fireEvent(FocusLostEvent.class, oldFocused);
}
}
}
@Override
protected void winch(final int oldWidth, final int oldHeight, final int newWidth, final int newHeight) {
for (final PaneComponent c : components) {
if (c.isAutoResizeWidth()) {
c.setWidth((int) (newWidth * c.getRelativeWidth()) + c.getRelativeWidthOffset());
}
if (c.isAutoResizeHeight()) {
c.setHeight((int) (newHeight * c.getRelativeHeight()) + c.getRelativeHeightOffset());
}
if (c.isAutoPositionX()) {
c.setX((int) (newWidth * c.getRelativeX()) + c.getRelativeXOffset());
}
if (c.isAutoPositionY()) {
c.setY((int) (newHeight * c.getRelativeY()) + c.getRelativeYOffset());
}
}
}
}