SimpleJavaEngine/src/main/java/speiger/src/coreengine/rendering/gui/components/ListComponent.java

687 lines
18 KiB
Java

package speiger.src.coreengine.rendering.gui.components;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import speiger.src.collections.ints.collections.IntCollection;
import speiger.src.collections.ints.lists.IntArrayList;
import speiger.src.collections.ints.lists.IntList;
import speiger.src.collections.ints.sets.IntLinkedOpenHashSet;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.collections.objects.utils.ObjectIterators;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.math.misc.ColorUtils;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.rendering.gui.GuiComponent;
import speiger.src.coreengine.rendering.gui.base.IButtonComponent;
import speiger.src.coreengine.rendering.gui.components.list.IListEntry;
import speiger.src.coreengine.rendering.gui.helper.box.IGuiBox;
import speiger.src.coreengine.rendering.gui.helper.constrains.Constrains;
import speiger.src.coreengine.rendering.gui.renderer.UIRenderer;
import speiger.src.coreengine.rendering.input.Keyboard;
public class ListComponent<T extends IListEntry> extends GuiComponent
implements Iterable<T>, IButtonComponent
{
public static final int SELECTION_MODE_DISABLE = 0;
public static final int SELECTION_MODE_SINGLE = 1;
public static final int SELECTION_MODE_MULTI = 2;
public static final int SELECTION_MODE_INTERACT = 3;
public static final int UPDATE_MODE_DISABLED = 0;
public static final int UPDATE_MODE_VISIBLE = 1;
public static final int UPDATE_MODE_ALL = 2;
public static final int FLAG_DISABLE_BACKGROUND = 1024;
public static final int FLAG_START_AT_BOTTOM = 2048;
protected int color;
protected int hoverColor = ColorUtils.LIGHT_GRAY;
protected int hoverIndex = -1;
protected int dragIndex = -1;
protected int movement = 0;
protected IButtonComponent customButton;
protected IntLinkedOpenHashSet selectedIndexes = new IntLinkedOpenHashSet();
protected ObjectList<T> entries = new ObjectArrayList<T>();
protected int selectionMode = 1;
protected int updateMode = 1;
protected float entryHeight;
protected float cachedWidth = 0F;
protected ScrollBarComponent verticalBar = new ScrollBarComponent(ColorUtils.LIGHT_GRAY);
protected ScrollBarComponent horizontalBar = new ScrollBarComponent(ColorUtils.LIGHT_GRAY).setHorizontal(true);
protected Vec2i lastMouse = Vec2i.mutable();
public ListComponent()
{
super(0F, 0F, 0F, 0F);
}
public ListComponent(int color, float entryHeight)
{
super(0F, 0F, 0F, 0F);
this.color = color;
this.entryHeight = entryHeight;
}
public ListComponent(float x, float y, float width, float height, int color, float entryHeight)
{
super(x, y, width, height);
this.color = color;
this.entryHeight = entryHeight;
}
@Override
public void init()
{
addChild(horizontalBar, Constrains.scrollBar(verticalBar::isInUse, true, 5F));
addChild(verticalBar, Constrains.scrollBar(horizontalBar::isInUse, false, 5F));
for(int i = 0, m = entries.size();i < m;i++)
{
entries.get(i).init(this, getGui());
}
updateScrollBar();
}
@Override
public void onClosed()
{
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).onClosed();
}
super.onClosed();
}
public ScrollBarComponent getHorizontalBar()
{
return horizontalBar;
}
public ScrollBarComponent getVerticalBar()
{
return verticalBar;
}
public ListComponent<T> disableBackground()
{
setFlag(FLAG_DISABLE_BACKGROUND);
return this;
}
public ListComponent<T> setStartAtBottom(boolean value)
{
setFlag(FLAG_START_AT_BOTTOM, value);
return this;
}
public boolean isStartAtBottom()
{
return isFlagSet(FLAG_START_AT_BOTTOM);
}
public ListComponent<T> setColor(int color)
{
this.color = color;
return this;
}
public ListComponent<T> setHoverColor(int color)
{
hoverColor = color;
return this;
}
public ListComponent<T> setEntryHeight(float entryHeight)
{
if(this.entryHeight != entryHeight)
{
this.entryHeight = entryHeight;
onChanged(true);
}
return this;
}
public ListComponent<T> setSelectionMode(int mode)
{
if(mode < 0 || mode > 3)
{
throw new IllegalStateException("Unknown Mode");
}
this.selectionMode = mode;
selectedIndexes.clear();
return this;
}
public ListComponent<T> setUpdateMode(int mode)
{
if(mode < 0 || mode > 2)
{
throw new IllegalStateException("Unknown Mode");
}
updateMode = mode;
return this;
}
public ListComponent<T> setSelectedIndex(int index)
{
switch(selectionMode)
{
case 0:
throw new IllegalStateException("Selection is not allowed");
case 1:
selectedIndexes.clear();
case 2:
if(index >= 0 && index < entries.size())
{
if(selectionMode == SELECTION_MODE_MULTI && Keyboard.isShiftDown())
{
for(int i = selectedIndexes.lastInt();i<index;i++)
{
selectedIndexes.add(i);
}
for(int i = selectedIndexes.lastInt();i>index;i--)
{
selectedIndexes.add(i);
}
}
selectedIndexes.add(index);
}
break;
}
return this;
}
public boolean isSelected(int index)
{
return selectedIndexes.contains(index);
}
public T removeSelectedIndex(int index)
{
return selectedIndexes.remove(index) ? entries.get(index) : null;
}
public boolean hasSelected()
{
return !selectedIndexes.isEmpty();
}
public int getSelectedIndex()
{
return selectedIndexes.isEmpty() ? -1 : selectedIndexes.iterator().nextInt();
}
public T getSelectedValue()
{
return selectedIndexes.isEmpty() ? null : entries.get(selectedIndexes.iterator().nextInt());
}
public void clearSelection()
{
selectedIndexes.clear();
}
public IntList getSelection()
{
return new IntArrayList(selectedIndexes);
}
public List<T> getSelectedValues()
{
List<T> entries = new ObjectArrayList<T>();
for(int index : selectedIndexes)
{
entries.add(this.entries.get(index));
}
return entries;
}
public ListComponent<T> add(T entry)
{
entries.add(entry);
if(getGui() != null)
{
entry.init(this, getGui());
updateScrollBar();
}
return this;
}
public ListComponent<T> addAll(Collection<T> entries)
{
this.entries.addAll(entries);
if(getGui() != null)
{
for(T entry : entries)
{
entry.init(this, getGui());
}
updateScrollBar();
}
return this;
}
public int size()
{
return entries.size();
}
public T get(int index)
{
return entries.get(index);
}
public boolean removeIf(Predicate<T> filter)
{
if(entries.isEmpty()) return false;
selectedIndexes.clear();
return entries.removeIf(K -> {
if(filter.test(K)) {
K.onClosed();
return true;
}
return false;
});
}
public <K> List<K> map(Function<T, K> mapper)
{
List<K> list = new ObjectArrayList<>(entries.size());
for(int i = 0,m=entries.size();i<m;list.add(mapper.apply(entries.get(i++))));
return list;
}
public void moveUp(int index)
{
if(index > 0)
{
entries.add(index - 1, entries.remove(index));
if(isSelected(index))
{
removeSelectedIndex(index);
setSelectedIndex(index-1);
}
}
}
public void moveDown(int index)
{
if(index + 1 < entries.size())
{
entries.add(index + 1, entries.remove(index));
if(isSelected(index))
{
removeSelectedIndex(index);
setSelectedIndex(index+1);
}
}
}
public void move(int fromIndex, int toIndex)
{
entries.add(toIndex, entries.remove(fromIndex));
}
public int indexOf(T value)
{
return entries.indexOf(value);
}
public T remove(int index)
{
if(index < 0 || index >= entries.size())
{
return null;
}
T value = entries.remove(index);
if(value != null)
{
value.onClosed();
selectedIndexes.remove(index);
}
return value;
}
public boolean removeAll(IntCollection values)
{
List<T> result = new ObjectArrayList<T>();
for(int index : values)
{
if(index < 0 || index >= entries.size())
{
continue;
}
selectedIndexes.remove(index);
T entry = entries.get(index);
entry.onClosed();
result.add(entry);
}
if(result.size() > 0)
{
entries.removeAll(result);
return true;
}
return false;
}
public boolean remove(T value)
{
return remove(indexOf(value)) != null;
}
public void clear()
{
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).onClosed();
}
entries.clear();
clearSelection();
}
@Override
public Iterator<T> iterator()
{
return ObjectIterators.unmodifiable(entries.iterator());
}
protected Iterator<T> rangeIterator(int start, int end)
{
return new Iterator<T>() {
int index = start;
@Override
public boolean hasNext()
{
return index < end && entries.size() > index;
}
@Override
public T next()
{
return entries.get(index++);
}
};
}
public Iterator<T> visibleIterator()
{
int start = getStartIndex();
return rangeIterator(start, MathUtils.clamp(0, entries.size() - 1, start + getIndexWidth()));
}
public float getCachedWidth()
{
return cachedWidth;
}
@Override
protected void repaint()
{
float scale = getBox().getScale();
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).updateState(this, scale);
}
updateScrollBar();
}
protected void updateScrollBar()
{
float width = 0F;
for(int i = 0,m=entries.size();i<m;i++)
{
width = Math.max(width, entries.get(i).getWidth());
}
this.cachedWidth = width;
boolean lastVertical = this.verticalBar.isInUse();
boolean lastHorizontal = this.horizontalBar.isInUse();
verticalBar.setScrollMax(MathUtils.ceil(this.entries.size() * entryHeight));
horizontalBar.setScrollMax((int)(width / getBox().getScale()));
if(lastHorizontal != horizontalBar.isInUse())
{
horizontalBar.onChanged(true);
}
if(lastVertical != verticalBar.isInUse())
{
verticalBar.onChanged(true);
}
}
@Override
protected boolean fixedUpdateSelf()
{
if(updateMode == UPDATE_MODE_ALL)
{
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).onFixedUpdate();
}
updateScrollBar();
}
else if(updateMode == UPDATE_MODE_VISIBLE)
{
int min = getStartIndex();
int max = min + getIndexWidth();
for(int i = min,m=entries.size();i<m&&i<max;i++)
{
entries.get(i).onFixedUpdate();
}
updateScrollBar();
}
return true;
}
@Override
public boolean updateSelf(int mouseX, int mouseY, float particalTicks)
{
if(selectionMode == SELECTION_MODE_DISABLE || !isTopHovered(mouseX, mouseY) || verticalBar.isComponentColliding(mouseX, mouseY) || horizontalBar.isComponentColliding(mouseX, mouseY))
{
hoverIndex = -1;
return true;
}
float scale = getBox().getScale();
boolean bottom = !verticalBar.isInUse() && isStartAtBottom();
float pixelSize = scale * entryHeight;
mouseY -= getBox().getMinY() - (verticalBar.getScroll() * scale) + (bottom ? (((getBox().getHeight() / pixelSize) - MathUtils.clamp(0, entries.size(), getIndexWidth())) * pixelSize) : 0);
int index = (int)(mouseY / pixelSize);
hoverIndex = index < 0 || index >= entries.size() ? -1 : index;
return true;
}
@Override
public boolean renderSelf(int mouseX, int mouseY, float particalTicks)
{
float brightness = getActiveBrightness();
UIRenderer render = getRenderer();
int start = getStartIndex();
int end = MathUtils.clamp(0, entries.size(), start + getIndexWidth());
IGuiBox box = getBox();
float scale = box.getScale();
float pixelSize = scale * entryHeight;
render.setBrightness(brightness);
if(isFlagNotSet(FLAG_DISABLE_BACKGROUND))
{
render.drawQuad(box, color);
}
render.push();
render.translate(box.getMinX(-horizontalBar.getScroll()), box.getMinY(-verticalBar.getScroll()));
float minX = horizontalBar.getScroll() * scale;
float maxX = box.getWidth() - verticalBar.getRequiredSpace() + (horizontalBar.getScroll() * scale);
enableScissorsBox(box.getMinX(), box.getMinY(), box.getMaxX()-verticalBar.getRequiredSpace(), box.getMaxY()-horizontalBar.getRequiredSpace());
boolean bottom = !verticalBar.isInUse() && isStartAtBottom();
if(hoverIndex != -1 && selectionMode != SELECTION_MODE_INTERACT && hoverIndex >= start && hoverIndex < end)
{
float offset = bottom ? box.getHeight()-(end-hoverIndex) * pixelSize : hoverIndex * pixelSize;
render.drawQuad(minX, offset, maxX, offset + pixelSize, 0.01F, hoverColor);
}
if(selectedIndexes.size() > 0)
{
render.setBrightness(0.75F * brightness);
for(int index : selectedIndexes)
{
if(index >= start && index < end)
{
float offset = bottom ? box.getHeight()-(end-index) * pixelSize : index * pixelSize;
render.drawQuad(minX, offset, maxX, offset + pixelSize, 0.011F, color);
}
}
}
mouseX -= box.getMinX();
mouseY -= box.getMinY();
for(int i = start;i<end;i++)
{
float offset = bottom ? box.getHeight()-(end-i) * pixelSize : i * pixelSize;
render.setBrightness(brightness).translate(0F, offset, 0.02F);
entries.get(i).onRender(this, isEnabled(), (int)(mouseX + horizontalBar.getScroll() * scale), (int)((mouseY + verticalBar.getScroll() * scale) - offset), particalTicks);
render.translate(0F, -offset, -0.02F);
}
render.pop();
disableScissors();
return true;
}
@Override
public void collectTooltips(int mouseX, int mouseY, float particalTicks, Map<UUID, GuiComponent> collector)
{
super.collectTooltips(mouseX, mouseY, particalTicks, collector);
int start = getStartIndex();
int end = MathUtils.clamp(0, entries.size(), start + getIndexWidth());
IGuiBox box = getBox();
float scale = box.getScale();
float pixelSize = scale * entryHeight;
mouseX -= box.getMinX();
mouseY -= box.getMinY();
boolean bottom = !verticalBar.isInUse() && isStartAtBottom();
for(int i = start;i<end;i++)
{
float offset = bottom ? box.getHeight()-(end-i) * pixelSize : i * pixelSize;
entries.get(i).collectTooltips(this, (int)(mouseX + horizontalBar.getScroll() * scale), (int)((mouseY + verticalBar.getScroll() * scale) - offset), particalTicks, collector);
}
}
public int getStartIndex()
{
return MathUtils.clamp(0, entries.size(), MathUtils.floor(verticalBar.getScroll() / entryHeight));
}
public int getIndexWidth()
{
return MathUtils.clamp(0, entries.size(), MathUtils.ceil((getBox().getBaseHeight() - (horizontalBar.getRequiredSpace() / getBox().getScale())) / entryHeight) + 1);
}
protected int getExtraY()
{
boolean bottom = !verticalBar.isInUse() && isStartAtBottom();
float pixelSize = getBox().getScale() * entryHeight;
return (int)(verticalBar.getScroll() * getBox().getScale() - (bottom ? getBox().getHeight()-(MathUtils.clamp(0, entries.size(), getStartIndex() + getIndexWidth()) - hoverIndex) * pixelSize : hoverIndex * pixelSize) - getBox().getMinY());
}
@Override
public boolean onClick(int button, int mouseX, int mouseY)
{
if(horizontalBar.isInUse() && horizontalBar.isComponentColliding(mouseX, mouseY))
{
return horizontalBar.onClick(button, mouseX, mouseY);
}
if(verticalBar.isInUse() && verticalBar.isComponentColliding(mouseX, mouseY))
{
return verticalBar.onClick(button, mouseX, mouseY);
}
if(hoverIndex != -1 && get(hoverIndex) instanceof IButtonComponent)
{
IButtonComponent comp = (IButtonComponent)get(hoverIndex);
int extraX = (int)(horizontalBar.getScroll() * getBox().getScale() - getBox().getMinX());
int extraY = getExtraY();
if(comp.isComponentColliding(mouseX + extraX, mouseY + extraY) && comp.onClick(button, mouseX + extraX, mouseY + extraY))
{
customButton = comp;
return true;
}
}
dragIndex = hoverIndex;
lastMouse.set(mouseX, mouseY);
return true;
}
@Override
public boolean onDrag(int mouseX, int mouseY)
{
if(horizontalBar.onDrag(mouseX, mouseY) || verticalBar.onDrag(mouseX, mouseY))
{
return true;
}
if(customButton != null)
{
int extraX = (int)(horizontalBar.getScroll() * getBox().getScale() - getBox().getMinX());
int extraY = getExtraY();
return customButton.onDrag(mouseX + extraX, mouseY + extraY);
}
horizontalBar.addScroll(lastMouse.getX() - mouseX);
verticalBar.addScroll(lastMouse.getY() - mouseY);
movement += Math.abs(lastMouse.getX() - mouseX) + Math.abs(lastMouse.getY() - mouseY);
lastMouse.set(mouseX, mouseY);
return false;
}
@Override
public void onRelease(int button, int mouseX, int mouseY)
{
horizontalBar.onRelease(button, mouseX, mouseY);
verticalBar.onRelease(button, mouseX, mouseY);
if(customButton != null)
{
int extraX = (int)(horizontalBar.getScroll() * getBox().getScale() - getBox().getMinX());
int extraY = getExtraY();
customButton.onRelease(button, mouseX + extraX, mouseY + extraY);
customButton = null;
}
else if(dragIndex != -1 && dragIndex == hoverIndex && movement < 2)
{
if(isSelected(dragIndex))
{
removeSelectedIndex(dragIndex);
}
else
{
setSelectedIndex(dragIndex);
}
notifyListeners(LISTENER_USER_ACTION);
dragIndex = -1;
}
movement = 0;
}
@Override
public boolean onScroll(int scroll, int mouseX, int mouseY)
{
if((horizontalBar.isComponentColliding(mouseX, mouseY) && horizontalBar.onScroll(scroll, mouseX, mouseY)) || (verticalBar.isComponentColliding(mouseX, mouseY) && verticalBar.onScroll(scroll, mouseX, mouseY)))
{
movement = 100;
return true;
}
if(hoverIndex != -1 && get(hoverIndex) instanceof IButtonComponent)
{
IButtonComponent comp = (IButtonComponent)get(hoverIndex);
int extraX = (int)(horizontalBar.getScroll() - getBox().getMinX());
int extraY = getExtraY();
if(comp.isComponentColliding(mouseX + extraX, mouseY + extraY) && comp.onScroll(scroll, mouseX + extraX, mouseY + extraY))
{
return true;
}
}
if(verticalBar.isInUse())
{
verticalBar.addScroll(-(int)(scroll * 5F * getBox().getScale()));
return true;
}
return false;
}
}