/*
* Copyright 2016 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.rendering.nui.widgets;
import com.google.common.collect.Lists;
import org.terasology.input.MouseInput;
import org.terasology.math.Border;
import org.terasology.math.geom.Rect2i;
import org.terasology.math.geom.Vector2i;
import org.terasology.rendering.nui.BaseInteractionListener;
import org.terasology.rendering.nui.Canvas;
import org.terasology.rendering.nui.CoreWidget;
import org.terasology.rendering.nui.databinding.Binding;
import org.terasology.rendering.nui.databinding.DefaultBinding;
import org.terasology.rendering.nui.events.NUIMouseClickEvent;
import org.terasology.rendering.nui.events.NUIMouseDoubleClickEvent;
import org.terasology.rendering.nui.itemRendering.ItemRenderer;
import org.terasology.rendering.nui.itemRendering.ToStringTextRenderer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A list widget.
*
* @param <T> the list element type
*/
public class UIList<T> extends CoreWidget {
private final List<ItemInteractionListener> itemListeners = Lists.newArrayList();
private final List<ItemActivateEventListener<T>> activateListeners = Lists.newArrayList();
private final List<ItemSelectEventListener<T>> selectionListeners = Lists.newArrayList();
private Binding<Boolean> interactive = new DefaultBinding<>(true);
private Binding<Boolean> selectable = new DefaultBinding<>(true);
private Binding<T> selection = new DefaultBinding<>();
private Binding<List<T>> list = new DefaultBinding<>(new ArrayList<>());
private ItemRenderer<T> itemRenderer = new ToStringTextRenderer<>();
private Binding<Boolean> canBeFocus = new DefaultBinding<>(true);
public UIList() {
}
public UIList(String id) {
super(id);
}
@Override
public void onDraw(Canvas canvas) {
updateItemListeners();
canvas.setPart("item");
boolean enabled = isEnabled();
Border margin = canvas.getCurrentStyle().getMargin();
int yOffset = 0;
for (int i = 0; i < list.get().size(); ++i) {
T item = list.get().get(i);
Vector2i preferredSize = margin.grow(itemRenderer.getPreferredSize(item, canvas));
Rect2i itemRegion = Rect2i.createFromMinAndSize(0, yOffset, canvas.size().x, preferredSize.y);
ItemInteractionListener listener = itemListeners.get(i);
if (enabled) {
if (Objects.equals(item, selection.get())) {
canvas.setMode(ACTIVE_MODE);
} else if (listener.isMouseOver()) {
canvas.setMode(HOVER_MODE);
} else {
canvas.setMode(DEFAULT_MODE);
}
if (isInteractive()) {
canvas.addInteractionRegion(listener, itemRenderer.getTooltip(item), itemRegion);
}
} else {
canvas.setMode(DISABLED_MODE);
}
canvas.drawBackground(itemRegion);
itemRenderer.draw(item, canvas, margin.shrink(itemRegion));
yOffset += preferredSize.getY();
}
}
private void updateItemListeners() {
while (itemListeners.size() > list.get().size()) {
itemListeners.remove(itemListeners.size() - 1);
}
while (itemListeners.size() < list.get().size()) {
itemListeners.add(new ItemInteractionListener(itemListeners.size()));
}
}
@Override
public boolean canBeFocus() {
return canBeFocus.get();
}
@Override
public Vector2i getPreferredContentSize(Canvas canvas, Vector2i areaHint) {
canvas.setPart("item");
Vector2i result = new Vector2i();
for (T item : list.get()) {
Vector2i preferredSize = canvas.getCurrentStyle().getMargin().grow(itemRenderer.getPreferredSize(item, canvas));
result.x = Math.max(result.x, preferredSize.x);
result.y += preferredSize.y;
}
return result;
}
public void bindList(Binding<List<T>> binding) {
this.list = binding;
}
/**
* @return The list of options.
*/
public List<T> getList() {
return list.get();
}
/**
* @param list The list to display on the buttons.
*/
public void setList(List<T> list) {
this.list.set(list);
}
public void bindSelectable(Binding<Boolean> binding) {
selectable = binding;
}
/**
* @return True if the list is interactive.
*/
public boolean isInteractive() {
return interactive.get();
}
/**
* @return True if the list is selectable.
*/
public boolean isSelectable() {
return selectable.get();
}
/**
* @param value A Boolean indicating the interactivity to set.
*/
public void setInteractive(boolean value) {
interactive.set(value);
}
/**
* @param value A Boolean indicating how selectable the list should be.
*/
public void setSelectable(boolean value) {
selectable.set(value);
}
/**
* @param value A Boolean indicating if it should be focusable.
*/
public void setCanBeFocus(boolean value) {
canBeFocus.set(value);
}
public void bindSelection(Binding<T> binding) {
selection = binding;
}
/**
* @return The value of the selected button.
*/
public T getSelection() {
if (!isSelectable()) {
return null;
}
return selection.get();
}
/**
* @param item The item to be selected
*/
public void setSelection(T item) {
if (isSelectable()) {
selection.set(item);
for (ItemSelectEventListener<T> listener : selectionListeners) {
listener.onItemSelected(this, item);
}
}
}
/**
* Subscribe an event listener to be called upon the list being activated.
*
* @param eventListener The event listener to call.
*/
public void subscribe(ItemActivateEventListener<T> eventListener) {
activateListeners.add(eventListener);
}
/**
* Remove an event listener from being called when the list being activated.
*
* @param eventListener The event listener to remove.
*/
public void unsubscribe(ItemActivateEventListener<T> eventListener) {
activateListeners.remove(eventListener);
}
/**
* Subscribe an event listener to be called then an item is selected.
*
* @param eventListener The event listener to add.
*/
public void subscribeSelection(ItemSelectEventListener<T> eventListener) {
selectionListeners.add(eventListener);
}
/**
* Remove an event listener from being called when a selection is made.
*
* @param eventListener The event listener to remove.
*/
public void unsubscribeSelection(ItemSelectEventListener<T> eventListener) {
selectionListeners.remove(eventListener);
}
/**
* Select an item from the list via index.
*
* @param index The index of the item to select.
*/
public void select(int index) {
if (index >= 0 && index < list.get().size() && isSelectable()) {
T item = list.get().get(index);
setSelection(item);
}
}
/**
* Activate an item from the list via index.
*
* @param index The index of the item to select.
*/
private void activate(int index) {
if (index < list.get().size()) {
T item = list.get().get(index);
for (ItemActivateEventListener<T> listener : activateListeners) {
listener.onItemActivated(this, item);
}
}
}
/**
* @return The item renderer used in the list.
*/
public ItemRenderer<T> getItemRenderer() {
return itemRenderer;
}
/**
* @param itemRenderer The renderer to use.
*/
public void setItemRenderer(ItemRenderer<T> itemRenderer) {
this.itemRenderer = itemRenderer;
}
private class ItemInteractionListener extends BaseInteractionListener {
private int index;
ItemInteractionListener(int index) {
this.index = index;
}
@Override
public boolean onMouseClick(NUIMouseClickEvent event) {
if (event.getMouseButton() == MouseInput.MOUSE_LEFT && isSelectable()) {
select(index);
return true;
}
return false;
}
@Override
public boolean onMouseDoubleClick(NUIMouseDoubleClickEvent event) {
if (event.getMouseButton() == MouseInput.MOUSE_LEFT && isSelectable()) {
activate(index);
return true;
}
return false;
}
}
}