/*
* Copyright 2013 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.input.internal;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.terasology.engine.SimpleUri;
import org.terasology.engine.Time;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.input.ActivateMode;
import org.terasology.input.BindButtonEvent;
import org.terasology.input.BindButtonSubscriber;
import org.terasology.input.BindableButton;
import org.terasology.input.ButtonState;
import org.terasology.input.Input;
import org.terasology.math.geom.Vector3f;
import org.terasology.math.geom.Vector3i;
import java.util.List;
import java.util.Set;
/**
* A BindableButton is pseudo button that is controlled by one or more actual inputs (whether keys, mouse buttons or the
* mouse wheel).
* <br><br>
* When the BindableButton changes state it sends out events like an actual key or button does. It also allows direct
* subscription via the {@link org.terasology.input.BindButtonSubscriber} interface.
*/
public class BindableButtonImpl implements BindableButton {
private SimpleUri id;
private String displayName;
private BindButtonEvent buttonEvent;
private Set<Input> activeInputs = Sets.newHashSet();
private List<BindButtonSubscriber> subscribers = Lists.newArrayList();
private ActivateMode mode = ActivateMode.BOTH;
private boolean repeating;
private int repeatTime;
private long lastActivateTime;
private boolean consumedActivation;
private Time time;
/**
* Creates the button. Package-private, as should be created through the InputSystem
*
* @param id
* @param event
*/
public BindableButtonImpl(SimpleUri id, String displayName, BindButtonEvent event, Time time) {
this.id = id;
this.displayName = displayName;
this.buttonEvent = event;
this.time = time;
}
@Override
public SimpleUri getId() {
return id;
}
@Override
public String getDisplayName() {
return displayName;
}
@Override
public void setMode(ActivateMode mode) {
this.mode = mode;
}
@Override
public ActivateMode getMode() {
return mode;
}
@Override
public void setRepeating(boolean repeating) {
this.repeating = repeating;
}
@Override
public boolean isRepeating() {
return repeating;
}
/**
* Sets the repeat time
*
* @param repeatTimeMs The time between repeat events, in ms
*/
@Override
public void setRepeatTime(int repeatTimeMs) {
this.repeatTime = repeatTimeMs;
}
@Override
public int getRepeatTime() {
return repeatTime;
}
@Override
public ButtonState getState() {
return (activeInputs.isEmpty() || consumedActivation) ? ButtonState.UP : ButtonState.DOWN;
}
/**
* Register a subscriber to this bind
*
* @param subscriber
*/
@Override
public void subscribe(BindButtonSubscriber subscriber) {
subscribers.add(subscriber);
}
/**
* Removes a subscriber from this bind
*
* @param subscriber
*/
@Override
public void unsubscribe(BindButtonSubscriber subscriber) {
subscribers.remove(subscriber);
}
/**
* Updates this bind with the new state of a bound button. This should be done whenever a bound button changes
* state, so that the overall state of the bind can be tracked.
*
* @param pressed Is the changing
* @param delta The length of the current frame
* @param target The current camera target
* @param initialKeyConsumed Has the changing button's event already been consumed
* @return Whether the button's event has been consumed
*/
public boolean updateBindState(Input input,
boolean pressed,
float delta,
EntityRef[] inputEntities,
EntityRef target,
Vector3i targetBlockPos,
Vector3f hitPosition,
Vector3f hitNormal,
boolean initialKeyConsumed) {
boolean keyConsumed = initialKeyConsumed;
if (pressed) {
boolean previouslyEmpty = activeInputs.isEmpty();
activeInputs.add(input);
if (previouslyEmpty && mode.isActivatedOnPress()) {
lastActivateTime = time.getGameTimeInMs();
consumedActivation = keyConsumed;
if (!keyConsumed) {
keyConsumed = triggerOnPress(delta, target);
}
if (!keyConsumed) {
buttonEvent.prepare(id, ButtonState.DOWN, delta);
buttonEvent.setTargetInfo(target, targetBlockPos, hitPosition, hitNormal);
for (EntityRef entity : inputEntities) {
entity.send(buttonEvent);
if (buttonEvent.isConsumed()) {
break;
}
}
keyConsumed = buttonEvent.isConsumed();
}
}
} else if (!activeInputs.isEmpty()) {
activeInputs.remove(input);
if (activeInputs.isEmpty() && mode.isActivatedOnRelease()) {
if (!keyConsumed) {
keyConsumed = triggerOnRelease(delta, target);
}
if (!keyConsumed) {
buttonEvent.prepare(id, ButtonState.UP, delta);
buttonEvent.setTargetInfo(target, targetBlockPos, hitPosition, hitNormal);
for (EntityRef entity : inputEntities) {
entity.send(buttonEvent);
if (buttonEvent.isConsumed()) {
break;
}
}
keyConsumed = buttonEvent.isConsumed();
}
}
}
return keyConsumed;
}
public void update(EntityRef[] inputEntities, float delta, EntityRef target, Vector3i targetBlockPos, Vector3f hitPosition, Vector3f hitNormal) {
long activateTime = this.time.getGameTimeInMs();
if (repeating && getState() == ButtonState.DOWN && mode.isActivatedOnPress() && activateTime - lastActivateTime > repeatTime) {
lastActivateTime = activateTime;
if (!consumedActivation) {
boolean consumed = triggerOnRepeat(delta, target);
if (!consumed) {
buttonEvent.prepare(id, ButtonState.REPEAT, delta);
buttonEvent.setTargetInfo(target, targetBlockPos, hitPosition, hitNormal);
for (EntityRef entity : inputEntities) {
entity.send(buttonEvent);
if (buttonEvent.isConsumed()) {
break;
}
}
}
}
}
}
private boolean triggerOnPress(float delta, EntityRef target) {
for (BindButtonSubscriber subscriber : subscribers) {
if (subscriber.onPress(delta, target)) {
return true;
}
}
return false;
}
private boolean triggerOnRepeat(float delta, EntityRef target) {
for (BindButtonSubscriber subscriber : subscribers) {
if (subscriber.onRepeat(delta, target)) {
return true;
}
}
return false;
}
private boolean triggerOnRelease(float delta, EntityRef target) {
for (BindButtonSubscriber subscriber : subscribers) {
if (subscriber.onRelease(delta, target)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "BindableButtonEventImpl [" + id + ", \"" + displayName + "\", " + buttonEvent + "]";
}
}