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

698 lines
20 KiB
Java

package speiger.src.coreengine.rendering.gui.components;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.sets.ObjectOpenHashSet;
import speiger.src.collections.objects.sets.ObjectSet;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.math.misc.ColorUtils;
import speiger.src.coreengine.math.misc.Facing;
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.tree.ITreeEntry;
import speiger.src.coreengine.rendering.gui.helper.UIShapes;
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.gui.renderer.buffer.RenderBuffer;
import speiger.src.coreengine.rendering.input.Keyboard;
public class TreeComponent<T extends ITreeEntry> extends GuiComponent implements 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_OPEN = 2;
public static final int UPDATE_MODE_ALL = 3;
public static final int FLAG_NO_BACKGROUND = 1 << 20;
ScrollBarComponent verticalBar = new ScrollBarComponent(ColorUtils.LIGHT_GRAY);
ScrollBarComponent horizontalBar = new ScrollBarComponent(ColorUtils.LIGHT_GRAY).setHorizontal(true);
int color;
int selectedColor;
int hoverColor = ColorUtils.LIGHT_GRAY;
int hoverIndex = -1;
int dragIndex = -1;
int movement;
Vec2i lastMouse = Vec2i.mutable();
IButtonComponent customButton;
protected ObjectSet<T> openNodes = new ObjectOpenHashSet<>();
protected ObjectSet<T> selectedNodes = new ObjectOpenHashSet<>();
protected int selectionMode = 1;
protected int updateMode = 1;
T node;
float entryHeight;
boolean listChange = true;
List<T> visibleNodes = new ObjectArrayList<T>();
RenderBuffer buffer;
public TreeComponent(int color, float entryHeight)
{
super(0F, 0F, 0F, 0F);
this.entryHeight = entryHeight;
this.color = color;
this.selectedColor = color;
}
public TreeComponent(int color, float entryHeight, T entry)
{
super(0F, 0F, 0F, 0F);
this.entryHeight = entryHeight;
getNodes(entry, openNodes, false);
this.color = color;
this.selectedColor = color;
node = entry;
if(entry != null)
{
entry.calculateDebth();
}
}
public TreeComponent(float x, float y, float width, float height, int color, float entryHeight, T entry)
{
super(x, y, width, height);
this.entryHeight = entryHeight;
getNodes(entry, openNodes, false);
this.color = color;
node = entry;
if(entry != null)
{
entry.calculateDebth();
}
}
@Override
public void init()
{
addCloseListener(buffer = getRenderer().createBuffer());
addChild(horizontalBar, Constrains.scrollBar(verticalBar::isInUse, true, 5F));
addChild(verticalBar, Constrains.scrollBar(horizontalBar::isInUse, false, 5F));
List<T> entries = new ObjectArrayList<T>();
getNodes(node, entries, false);
for(int i = 0, m = entries.size();i < m;i++)
{
entries.get(i).init(this, getGui());
}
updateScrollBar();
createArrow();
}
public TreeComponent<T> setColor(int color)
{
if(this.color != color)
{
this.color = color;
onChanged(true);
}
return this;
}
public TreeComponent<T> setHoverColor(int color)
{
hoverColor = color;
return this;
}
public TreeComponent<T> setSelectionColor(int color)
{
selectedColor = color;
return this;
}
public TreeComponent<T> setEntryHeight(float entryHeight)
{
if(this.entryHeight != entryHeight)
{
this.entryHeight = entryHeight;
onChanged(true);
}
return this;
}
public TreeComponent<T> setSelectionMode(int mode)
{
if(mode < 0 || mode > 3)
{
throw new IllegalStateException("Unknown Mode");
}
this.selectionMode = mode;
selectedNodes.clear();
return this;
}
public TreeComponent<T> setUpdateMode(int mode)
{
if(mode < 0 || mode > 3)
{
throw new IllegalStateException("Unknown Mode");
}
updateMode = mode;
return this;
}
public TreeComponent<T> disableBackground(boolean value)
{
setFlag(FLAG_NO_BACKGROUND, value);
return this;
}
@Override
protected void repaint()
{
float scale = getBox().getScale();
List<T> entries = new ObjectArrayList<T>();
getNodes(node, entries, false);
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).updateState(this, scale);
}
updateScrollBar();
createArrow();
}
public TreeComponent<T> setTree(T entry)
{
if(node == entry)
{
return this;
}
node = entry;
if(entry != null)
{
entry.calculateDebth();
if(getGui() != null)
{
List<T> entries = new ObjectArrayList<T>();
getNodes(entry, entries, false);
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).init(this, getGui());
}
}
}
listChange = true;
openNodes.clear();
selectedNodes.clear();
onChanged(true);
return this;
}
public T getTree()
{
return node;
}
public void onTreeChanged()
{
listChange = true;
List<T> entries = new ObjectArrayList<T>();
getNodes(node, entries, false);
for(int i = 0,m=entries.size();i<m;i++)
{
T entry = entries.get(i);
if(!entry.isInit())
{
entry.init(this, getGui());
if(!entry.isLeaf() && isOpen((T)entry.getParent()))
{
openNode(entry, true);
}
}
}
}
public boolean isNodeSelected(T entry)
{
return selectedNodes.contains(entry);
}
public void addSelectedNode(T entry)
{
switch(selectionMode)
{
case SELECTION_MODE_DISABLE: return;
case SELECTION_MODE_SINGLE:
selectedNodes.clear();
}
selectedNodes.add(entry);
}
public void removeSelectedNode(T entry)
{
selectedNodes.remove(entry);
}
public void clearSelectedNodes()
{
selectedNodes.clear();
}
public boolean hasSelectedNodes()
{
return selectedNodes.size() > 0;
}
public T getSelectedNode()
{
return selectedNodes.isEmpty() ? null : selectedNodes.iterator().next();
}
public List<T> getSelectedNodes()
{
return new ObjectArrayList<T>(this.selectedNodes);
}
public List<T> getOpenNodes()
{
return new ObjectArrayList<T>(this.visibleNodes);
}
public List<T> getAllNodes()
{
List<T> list = new ObjectArrayList<T>();
getNodes(node, list, false);
return list;
}
public void openAll()
{
if(node != null)
{
openNode(node, true);
}
}
public void closeAll()
{
if(node != null)
{
closeNode(node, true);
}
}
public void openNode(T entry, boolean childrenIncluded)
{
openNode(entry, childrenIncluded, Integer.MAX_VALUE);
}
public void openNode(T entry, boolean childrenIncluded, int maxLayers)
{
openNodes.add(entry);
if(childrenIncluded)
{
getNodes(entry, openNodes, false, maxLayers);
}
listChange = true;
updateScrollBar();
}
public void toggleNode(T entry, boolean childrenIncluded)
{
if(isOpen(entry))
{
closeNode(entry, childrenIncluded);
return;
}
openNode(entry, childrenIncluded);
}
public boolean isOpen(T entry)
{
return openNodes.contains(entry);
}
public void closeNode(T entry, boolean childrenIncluded)
{
openNodes.remove(entry);
if(childrenIncluded)
{
Set<T> entries = new ObjectOpenHashSet<T>();
getNodes(entry, entries, false);
openNodes.removeAll(entries);
}
listChange = true;
updateScrollBar();
}
protected void createArrow()
{
float pixelSize = (entryHeight * 0.5F);
UIShapes.createArrow(buffer, pixelSize, pixelSize, color, Facing.EAST);
UIShapes.createArrow(buffer, pixelSize, pixelSize, color, Facing.SOUTH);
}
protected void updateScrollBar()
{
if(listChange)
{
listChange = false;
visibleNodes.clear();
getNodes(node, visibleNodes, true);
customButton = null;
}
float width = 0F;
float pixelSize = getBox().getScale() * entryHeight;
for(int i = 0,m=visibleNodes.size();i<m;i++)
{
ITreeEntry entry = visibleNodes.get(i);
width = Math.max(width, entry.getWidth() + (pixelSize * entry.getDebth()));
}
verticalBar.setScrollMax(MathUtils.ceil(visibleNodes.size() * entryHeight));
horizontalBar.setScrollMax((int)(width / getBox().getScale()));
horizontalBar.onChanged(true);
verticalBar.onChanged(true);
}
@Override
protected boolean fixedUpdateSelf()
{
if(updateMode == UPDATE_MODE_ALL)
{
for(int i = 0,m=visibleNodes.size();i<m;i++)
{
visibleNodes.get(i).onFixedUpdate();
}
updateScrollBar();
}
else if(updateMode == UPDATE_MODE_VISIBLE)
{
int min = getStartIndex();
int max = min + getIndexWidth();
for(int i = min,m=visibleNodes.size();i<m&&i<max;i++)
{
visibleNodes.get(i).onFixedUpdate();
}
updateScrollBar();
}
else if(updateMode == UPDATE_MODE_ALL)
{
List<T> entries = new ObjectArrayList<T>();
getNodes(node, entries, false);
for(int i = 0,m=entries.size();i<m;i++)
{
entries.get(i).onFixedUpdate();
}
updateScrollBar();
}
return true;
}
@Override
protected 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();
mouseY -= getBox().getMinY() - (verticalBar.getScroll() * scale);
int index = (int)(mouseY / (entryHeight * scale));
hoverIndex = index < 0 || index >= visibleNodes.size() ? -1 : index;
return true;
}
@Override
protected boolean renderSelf(int mouseX, int mouseY, float particalTicks)
{
float brightness = getActiveBrightness();
UIRenderer render = getRenderer();
int start = getStartIndex();
int end = MathUtils.clamp(0, visibleNodes.size(), start + getIndexWidth());
IGuiBox box = getBox();
float scale = box.getScale();
float minX = horizontalBar.getScroll() * scale;
float minY = verticalBar.getScroll() * scale;
float maxX = box.getWidth() - verticalBar.getRequiredSpace() + (horizontalBar.getScroll() * scale);
float pixelSize = scale * entryHeight;
float offsetSize = pixelSize * 0.8F;
if(isFlagNotSet(FLAG_NO_BACKGROUND))
{
render.setBrightness(brightness).drawQuad(box, color);
}
render.push();
render.translate(box.getMinX(-horizontalBar.getScroll()), box.getMinY(-verticalBar.getScroll()));
enableScissorsBox(box.getMinX(), box.getMinY(), box.getMaxX()-verticalBar.getRequiredSpace(), box.getMaxY()-horizontalBar.getRequiredSpace());
boolean skip = false;
if(hoverIndex != -1 && hoverIndex >= start && hoverIndex < end)
{
T node = visibleNodes.get(hoverIndex);
float xOffset = (offsetSize + (node.getDebth() * offsetSize * 0.6F)) * 0.8F;
if(!node.isLeaf() && mouseX - box.getMinX() >= xOffset - (pixelSize * 0.6F) && mouseX - box.getMinX() <= xOffset)
{
skip = true;
}
if(!skip)
{
int extraX = (int)(minX - getBox().getMinX() - xOffset);
int extraY = (int)(minY - hoverIndex * pixelSize - getBox().getMinY());
if(node instanceof IButtonComponent && ((IButtonComponent)node).isComponentColliding(mouseX + extraX, mouseY + extraY))
{
skip = true;
}
}
float otherMax = node.getHighlightWidth() >= 0F ? xOffset + node.getHighlightWidth() : maxX;
if(mouseX - box.getMinX() >= otherMax) skip = true;
if(!skip && selectionMode != SELECTION_MODE_INTERACT)
{
float offset = pixelSize * hoverIndex;
render.drawQuad(minX, offset, Math.min(otherMax, maxX), offset + pixelSize, 0.01F, hoverColor);
}
}
mouseX -= box.getMinX();
mouseY -= box.getMinY();
if(selectedNodes.size() > 0)
{
render.setBrightness(0.75F * brightness);
for(int i = start;i<end;i++)
{
ITreeEntry entry = visibleNodes.get(i);
if(selectedNodes.contains(entry))
{
float offset = pixelSize * i;
render.drawQuad(minX, offset, maxX, offset + pixelSize, 0.011F, selectedColor);
}
}
}
for(int i = start;i<end;i++)
{
ITreeEntry entry = visibleNodes.get(i);
float offsetY = i * pixelSize;
float offsetX = (offsetSize + (entry.getDebth() * offsetSize * 0.6F)) * 0.8F;
render.translate(offsetX, offsetY, 0.02F);
if(!entry.isLeaf())
{
boolean extra = skip && i == hoverIndex;
render.translate(-(pixelSize / 3F), pixelSize * 0.5F).scale(scale);
render.setBrightness(brightness * (extra ? 0.5F : 1.25F)).drawBuffers(buffer.selectionIterator(openNodes.contains(entry) ? 1 : 0), 0F, 0F);
render.unscale(scale).translate((pixelSize / 3F), -(pixelSize * 0.5F));
}
render.setBrightness(brightness);
entry.onRender(this, isEnabled(), (int)(mouseX + minX - offsetX), (int)(mouseY + minY - offsetY), particalTicks);
render.translate(-offsetX, -offsetY, -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, visibleNodes.size(), start + getIndexWidth());
mouseX -= getBox().getMinX();
mouseY -= getBox().getMinY();
float scale = getBox().getScale();
float minX = horizontalBar.getScroll() * scale;
float minY = verticalBar.getScroll() * scale;
float pixelSize = scale * entryHeight;
float offsetSize = pixelSize * 0.8F;
for(int i = start;i<end;i++)
{
ITreeEntry entry = visibleNodes.get(i);
float offsetY = i * pixelSize;
float offsetX = (offsetSize + (entry.getDebth() * offsetSize * 0.6F)) * 0.8F;
entry.collectTooltips(this, (int)(mouseX + minX - offsetX), (int)(mouseY + minY - offsetY), particalTicks, collector);
}
}
@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)
{
ITreeEntry entry = visibleNodes.get(hoverIndex);
float scale = getBox().getScale();
float offsetSize = entryHeight * scale * 0.8F;
float offsetX = (offsetSize + (entry.getDebth() * offsetSize * 0.6F)) * 0.8F;
if(mouseX - getBox().getMinX() >= offsetX - (entryHeight * scale * 0.6F) && mouseX - getBox().getMinX() <= offsetX)
{
toggleNode(visibleNodes.get(hoverIndex), Keyboard.isShiftDown());
return true;
}
if(visibleNodes.get(hoverIndex) instanceof IButtonComponent)
{
IButtonComponent comp = (IButtonComponent)visibleNodes.get(hoverIndex);
int extraX = (int)((horizontalBar.getScroll() * scale - getBox().getMinX()) - offsetX);
int extraY = (int)(verticalBar.getScroll() * scale - (hoverIndex * scale * entryHeight) - getBox().getMinY());
if(comp.isComponentColliding(mouseX + extraX, mouseY + extraY) && comp.onClick(button, mouseX + extraX, mouseY + extraY))
{
customButton = comp;
return true;
}
}
}
dragIndex = hoverIndex;
if(selectionMode == SELECTION_MODE_INTERACT)
{
dragIndex = -1;
}
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)
{
float scale = getBox().getScale();
float offsetSize = entryHeight * scale * 0.8F;
int extraX = (int)((horizontalBar.getScroll() * scale - getBox().getMinX()) - ((offsetSize + (visibleNodes.get(hoverIndex).getDebth() * offsetSize * 0.6F)) * 0.8F));
int extraY = (int)(verticalBar.getScroll() * scale - (hoverIndex * scale * entryHeight) - getBox().getMinY());
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 true;
}
@Override
public void onRelease(int button, int mouseX, int mouseY)
{
horizontalBar.onRelease(button, mouseX, mouseY);
verticalBar.onRelease(button, mouseX, mouseY);
if(customButton != null && (hoverIndex >= 0 && hoverIndex < visibleNodes.size() && customButton == visibleNodes.get(hoverIndex)))
{
float scale = getBox().getScale();
float offsetSize = entryHeight * scale * 0.8F;
int extraX = (int)((horizontalBar.getScroll() * scale - getBox().getMinX()) - ((offsetSize + (visibleNodes.get(hoverIndex).getDebth() * offsetSize * 0.6F)) * 0.8F));
int extraY = (int)(verticalBar.getScroll() * scale - (hoverIndex * scale * entryHeight) - getBox().getMinY());
customButton.onRelease(button, mouseX + extraX, mouseY + extraY);
customButton = null;
}
else if(dragIndex != -1 && dragIndex == hoverIndex && movement < 2)
{
if(isNodeSelected(visibleNodes.get(hoverIndex)))
{
removeSelectedNode(visibleNodes.get(hoverIndex));
}
else
{
addSelectedNode(visibleNodes.get(hoverIndex));
}
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 && visibleNodes.get(hoverIndex) instanceof IButtonComponent)
{
IButtonComponent comp = (IButtonComponent)visibleNodes.get(hoverIndex);
float scale = getBox().getScale();
float offsetSize = entryHeight * scale * 0.8F;
int extraX = (int)((horizontalBar.getScroll() * scale - getBox().getMinX()) - ((offsetSize + (visibleNodes.get(hoverIndex).getDebth() * offsetSize * 0.6F)) * 0.8F));
int extraY = (int)(verticalBar.getScroll() - (hoverIndex * getBox().getScale() * entryHeight) - getBox().getMinY());
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;
}
public int getStartIndex()
{
return MathUtils.clamp(0, visibleNodes.size(), MathUtils.floor(verticalBar.getScroll() / entryHeight));
}
public int getIndexWidth()
{
return MathUtils.clamp(0, visibleNodes.size(), MathUtils.ceil((getBox().getBaseHeight() - (horizontalBar.getRequiredSpace() / getBox().getScale())) / entryHeight) + 1);
}
protected void getNodes(T entry, Collection<T> collection, boolean openOnly, int layers)
{
if(entry != null && layers >= 0)
{
collection.add(entry);
if(!entry.isLeaf() && (!openOnly || openNodes.contains(entry)))
{
for(int i = 0,m=entry.getChildCount();i<m;i++)
{
getNodes((T)entry.getChild(i), collection, openOnly, layers-1);
}
}
}
}
protected void getNodes(T entry, Collection<T> collection, boolean openOnly)
{
if(entry != null)
{
collection.add(entry);
if(!entry.isLeaf() && (!openOnly || openNodes.contains(entry)))
{
for(int i = 0,m=entry.getChildCount();i<m;i++)
{
getNodes((T)entry.getChild(i), collection, openOnly);
}
}
}
}
}