/*
* Copyright 2014-2016 Cel Skeggs.
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.supercanvas.components.palette;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import ccre.supercanvas.DraggableBoxComponent;
import ccre.supercanvas.Rendering;
import ccre.supercanvas.SuperCanvasComponent;
/**
* A palette of entities - which can be dragged out of the palette into the main
* canvas. This is backed by an iterable, usually managed by a subclass.
*
* @author skeggsc
* @param <T> The type of the backing iterable or collection.
*/
public class PaletteComponent<T extends Iterable<? extends PaletteEntry>> extends DraggableBoxComponent {
private static final long serialVersionUID = 4042918337094646087L;
/**
* The list of entries available in this component.
*/
public final T entries;
private transient int rowHeight, yshift, scroll, maxScroll;
private transient boolean isScrolling = false;
/**
* Create a new PaletteComponent at the specified position with the
* specified iterator backing this element.
*
* @param cx The X coordinate.
* @param cy The Y coordinate.
* @param entries The iterable of entries to include on the list.
*/
public PaletteComponent(int cx, int cy, T entries) {
super(cx, cy, 0, true);
this.entries = entries;
}
@Override
public void render(Graphics2D g, int screenWidth, int screenHeight, FontMetrics fontMetrics, int mouseX, int mouseY) {
calculatePaletteSize(fontMetrics);
Rendering.drawBody(Color.LIGHT_GRAY, g, this);
Shape clip = g.getClip();
g.setClip(new Rectangle(centerX - halfWidth + 5, centerY - halfHeight + 24, halfWidth * 2 - 10, halfHeight * 2 - 36));
int entryCount = drawPaletteEntries(mouseX, mouseY, centerX - halfWidth + 16, centerY - halfHeight + 36 - scroll, g, fontMetrics);
g.setClip(clip);
drawScrollbar(entryCount, fontMetrics, g);
}
private int drawPaletteEntries(int mouseX, int mouseY, int xPos, int initialYPos, Graphics2D g, FontMetrics fontMetrics) {
int yPos = initialYPos;
yshift = fontMetrics.getAscent();
int entryCount = 0;
synchronized (entries) {
for (PaletteEntry ent : entries) {
if (mouseX >= xPos - 5 && mouseX <= xPos + halfWidth * 2 - 28 && mouseY >= yPos - yshift && mouseY < yPos - yshift + rowHeight) {
g.setColor(Color.WHITE);
g.fillRoundRect(xPos - 5, yPos - yshift, halfWidth * 2 - 22, rowHeight, 10, 10);
}
g.setColor(Color.BLACK);
g.drawString(ent.getName(), xPos, yPos);
yPos += rowHeight;
entryCount++;
}
}
return entryCount;
}
private void drawScrollbar(int cnt, FontMetrics fontMetrics, Graphics2D g) {
this.maxScroll = cnt * fontMetrics.getHeight() - halfHeight;
float frac = scroll / (float) maxScroll;
if (frac < 0) {
frac = 0;
} else if (frac > 1) {
frac = 1;
}
g.setColor(scroll == 0 ? Color.GREEN : Color.BLACK);
Rendering.drawScrollbar(g, scroll != 0, centerX - halfWidth + 8, centerY - halfHeight + 12 + (int) ((2 * halfHeight - 24) * frac));
}
private void calculatePaletteSize(FontMetrics fontMetrics) {
int maxWidth = 100, count = 0;
synchronized (entries) {
for (PaletteEntry ent : entries) {
maxWidth = Math.max(maxWidth, fontMetrics.stringWidth(ent.getName()));
count++;
}
}
halfWidth = (maxWidth) / 2 + 20;
rowHeight = fontMetrics.getHeight();
halfHeight = (count * rowHeight + 40) / 2;
if (halfHeight > 200) {
halfHeight = 200;
}
}
/**
* Called when the title bar is interacted with. Defaults to nothing. This
* could, for example, refresh the contents of the palette.
*
* @return true if the event was handled and should not be sent to any other
* components, and false otherwise.
*/
protected boolean onInteractWithTitleBar() {
// Do nothing by default.
return false;
}
@Override
public boolean onInteract(int x, int y) {
if (y < centerY - halfHeight + 24) {
return onInteractWithTitleBar();
}
int xPos = centerX - halfWidth + 16;
int yPos = centerY - halfHeight + 36 - scroll;
for (PaletteEntry ent : entries) {
if (x >= xPos - 5 && x <= xPos + halfWidth * 2 - 28 && y >= yPos - yshift && y < yPos - yshift + rowHeight) {
onInteract(ent);
}
yPos += rowHeight;
}
return false;
}
/**
* Called when the specified PaletteEntry is right-clicked on in the
* palette.
*
* @param ent the entry clicked on.
*/
protected void onInteract(PaletteEntry ent) {
// No default active.
}
@Override
public boolean onSelect(int x, int y) {
int xPos = centerX - halfWidth + 16;
int yPos = centerY - halfHeight + 36 - scroll;
isScrolling = false;
for (PaletteEntry ent : entries) {
if (x < xPos - 5) {
isScrolling = true;
break;
} else if (y >= centerY - halfHeight + 24 && x <= xPos + halfWidth * 2 - 28 && y >= yPos - yshift && y < yPos - yshift + rowHeight) {
SuperCanvasComponent nent = ent.fetch(x, y);
getPanel().add(nent);
getPanel().startDrag(nent, x, y);
return true;
}
yPos += rowHeight;
}
getPanel().startDrag(this, x, y);
return true;
}
@Override
public boolean onMouseMove(int x, int y) {
return true;
}
@Override
public boolean onScroll(int x, int y, int wheelRotation) {
scroll += wheelRotation;
constrainScrolling();
return true;
}
private void constrainScrolling() {
if (scroll < 0) {
scroll = 0;
} else if (scroll > maxScroll) {
scroll = maxScroll;
}
}
@Override
public void moveForDrag(int x, int y) {
if (isScrolling) {
scroll = y * maxScroll / (2 * halfHeight - 24);
constrainScrolling();
} else {
super.moveForDrag(x, y);
}
}
@Override
public boolean canDrop() {
return !isScrolling;
}
@Override
public int getDragRelX(int x) {
return isScrolling ? 0 : super.getDragRelX(x);
}
@Override
public int getDragRelY(int y) {
if (isScrolling) {
return (int) ((2 * halfHeight - 24) * (scroll / (float) maxScroll)) - y;
} else {
return super.getDragRelY(y);
}
}
}