package com.peterhi.ui.bar2;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.jdesktop.core.animation.timing.Animator;
import org.jdesktop.core.animation.timing.TimingTargetAdapter;
import com.peterhi.runtime.RT;
import com.peterhi.ui.UI;
import com.peterhi.ui.UIImageResource;
public final class StripBar extends Canvas implements Listener {
public static enum State {
DEFAULT,
HOVERED,
PRESSED,
SELECTED;
private static final Map<State, RGB> foregrounds = new HashMap<State, RGB>();
private static final Map<State, RGB> backgrounds = new HashMap<State, RGB>();
static {
Display display = Display.getDefault();
Color color = display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND);
RGB rgb = color.getRGB();
foregrounds.put(DEFAULT, rgb);
foregrounds.put(HOVERED, rgb);
color = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
rgb = color.getRGB();
foregrounds.put(PRESSED, rgb);
foregrounds.put(SELECTED, rgb);
color = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
rgb = color.getRGB();
backgrounds.put(DEFAULT, rgb);
rgb = new RGB(72, 118, 255);
backgrounds.put(HOVERED, rgb);
rgb = new RGB(65, 105, 225);
backgrounds.put(PRESSED, rgb);
rgb = new RGB(67, 110, 238);
backgrounds.put(SELECTED, rgb);
}
public static RGB getForeground(State state) {
return foregrounds.get(state);
}
public static RGB getBackground(State state) {
return backgrounds.get(state);
}
public RGB getForeground() {
return getForeground(this);
}
public RGB getBackground() {
return getBackground(this);
}
}
public static final class Item {
private final String id;
private String text;
private Image image;
private State state;
StripBar parent;
Animator animator;
RGB foreground;
RGB background;
public Item(String id) {
this.id = id;
this.state = State.DEFAULT;
}
public StripBar getParent() {
return parent;
}
void setParent(StripBar value) {
parent = value;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
public void setText(String value) {
text = value;
}
public boolean hasText() {
if (text == null) {
return false;
}
if (text.isEmpty()) {
return false;
}
return true;
}
public Point getTextSize() {
if (!hasText()) {
return null;
}
Display display = Display.getDefault();
GC gc = new GC(display);
Point textSize = gc.stringExtent(text);
gc.dispose();
return textSize;
}
public Rectangle getTextBounds() {
checkParent();
return parent.getTextBounds(this);
}
public Image getImage() {
return image;
}
public void setImage(Image value) {
image = value;
}
public boolean hasImage() {
return image != null;
}
public Point getImageSize() {
if (!hasImage()) {
return null;
}
Rectangle imageBounds = image.getBounds();
return new Point(imageBounds.width, imageBounds.height);
}
public Rectangle getImageBounds() {
checkParent();
return parent.getImageBounds(this);
}
public Rectangle getCloseButtonBounds() {
checkParent();
return parent.getCloseButtonBounds(this);
}
public Rectangle getBounds() {
checkParent();
return parent.getBounds(this);
}
public State getState() {
return state;
}
public void setState(State value) {
if (value == null) {
throw new NullPointerException();
}
checkParent();
if (state == value) {
return;
}
if (animator != null) {
animator.cancel();
animator = null;
}
if ((state == State.DEFAULT && value == State.HOVERED) || (state == State.HOVERED && value == State.DEFAULT)) {
RGB from = background;
if (from == null) {
from = state.getBackground();
}
Fade fade = new Fade(this, from, value.getBackground());
animator = new Animator.Builder().addTarget(fade).setDuration(100, TimeUnit.MILLISECONDS).build();
animator.start();
} else {
foreground = value.getForeground();
background = value.getBackground();
redraw();
}
state = value;
}
public boolean isDefault() {
return state == State.DEFAULT;
}
public boolean isHovered() {
return state == State.HOVERED;
}
public boolean isPressed() {
return state == State.PRESSED;
}
public boolean isSelected() {
return state == State.SELECTED;
}
public int getIndex() {
checkParent();
return parent.indexOf(this);
}
public Item getLast() {
checkParent();
return parent.get(getIndex() - 1);
}
public Item getNext() {
checkParent();
return parent.get(getIndex() + 1);
}
public void dispose() {
checkParent();
parent.remove(this);
}
public void redraw() {
checkParent();
Rectangle bounds = getBounds();
parent.redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Item other = (Item) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return MessageFormat.format("{0} \"{1}\"", id, text);
}
private void checkParent() {
if (parent == null) {
throw new IllegalStateException();
}
}
}
static final class Fade extends TimingTargetAdapter {
private final Item item;
private final RGB from;
private final RGB to;
public Fade(Item item, RGB from, RGB to) {
this.item = item;
this.from = from;
this.to = to;
}
@Override
public void timingEvent(Animator source, double fraction) {
double r = (to.red - from.red) * fraction;
double g = (to.green - from.green) * fraction;
double b = (to.blue - from.blue) * fraction;
item.background = new RGB(0, 0, 0);
item.background.red += from.red;
item.background.green += from.green;
item.background.blue += from.blue;
item.background.red += (int )Math.round(r);
item.background.green += (int )Math.round(g);
item.background.blue += (int )Math.round(b);
item.redraw();
}
@Override
public void end(Animator source) {
item.background = to;
item.redraw();
}
}
public static final int SPACE = 6;
private final List<Item> items = new ArrayList<Item>();
private Item hovered;
private Item pressed;
private final Set<Item> selected = new HashSet<Item>();
public StripBar(Composite parent, int style) {
super(parent, style);
addListener(SWT.Paint, this);
addListener(SWT.MouseEnter, this);
addListener(SWT.MouseExit, this);
addListener(SWT.MouseDown, this);
addListener(SWT.MouseMove, this);
addListener(SWT.MouseUp, this);
}
@Override
public void handleEvent(Event event) {
if (equals(event.widget)) {
if (event.type == SWT.Paint) {
onPaint(event);
}
if (event.type == SWT.MouseEnter || event.type == SWT.MouseExit || event.type == SWT.MouseMove) {
onHover(event);
}
if (event.type == SWT.MouseDown) {
onPress(event);
}
if (event.type == SWT.MouseUp) {
onSelect(event);
}
}
}
private void onPaint(Event event) {
for (Item item : items) {
paint(event.gc, item);
}
}
private void onHover(Event event) {
Item old = hovered;
Item neo = get(event.x, event.y);
if (RT.equals(neo, old)) {
return;
}
if (neo != null) {
neo.setState(State.HOVERED);
}
if (old != null && !old.isSelected()) {
old.setState(State.DEFAULT);
}
hovered = neo;
}
private void onPress(Event event) {
Item neo = get(event.x, event.y);
if (neo != null) {
neo.setState(State.PRESSED);
}
pressed = neo;
}
private void onSelect(Event event) {
if (pressed != null) {
pressed.setState(State.SELECTED);
}
selected.add(pressed);
pressed = null;
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point size = new Point(0, 0);
for (Item item : items) {
Rectangle bounds = item.getBounds();
size.x += bounds.width;
size.y = Math.max(size.y, bounds.height);
}
size.x += SPACE * (items.size() - 1);
size.y += SPACE * 2;
Rectangle boundsWithTrim = computeTrim(0, 0, size.x, size.y);
size.x = boundsWithTrim.width;
size.y = boundsWithTrim.height;
return size;
}
public boolean add(Item item) {
return insert(item, size());
}
public boolean insert(Item item, int index) {
if (item == null) {
throw new NullPointerException();
}
if (contains(item)) {
return false;
}
items.add(index, item);
item.setParent(this);
return true;
}
public boolean remove(Item item) {
if (item == null) {
throw new NullPointerException();
}
if (!contains(item)) {
return false;
}
items.remove(item);
item.setParent(null);
return true;
}
public void remove(int index) {
Item item = items.remove(index);
item.setParent(null);
}
public int size() {
return items.size();
}
public boolean contains(Item item) {
return items.contains(item);
}
public int indexOf(Item item) {
if (item == null) {
throw new NullPointerException();
}
return items.indexOf(item);
}
public Item get(int index) {
return items.get(index);
}
public Item get(int x, int y) {
for (Item item : items) {
Rectangle bounds = item.getBounds();
if (bounds.contains(x, y)) {
return item;
}
}
return null;
}
public Item[] getAll() {
return items.toArray(new Item[items.size()]);
}
public Rectangle getBounds(Item item) {
if (item == null) {
throw new NullPointerException();
}
if (!contains(item)) {
return null;
}
Rectangle bounds = new Rectangle(SPACE, SPACE, 0, 0);
for (Item anItem : items) {
if (anItem.hasImage()) {
bounds.height = Math.max(bounds.height, anItem.getImageSize().y);
}
if (anItem.hasText()) {
bounds.height = Math.max(bounds.height, anItem.getTextSize().y);
}
bounds.height = Math.max(bounds.height, getCloseButtonSize().y);
}
bounds.height += SPACE * 2;
for (Item anItem : items) {
int extent = SPACE;
if (anItem.hasImage()) {
extent += anItem.getImageSize().x;
extent += SPACE;
}
if (anItem.hasText()) {
extent += anItem.getTextSize().x;
extent += SPACE;
}
extent += getCloseButtonSize().x;
extent += SPACE;
if (anItem.equals(item)) {
bounds.width = extent;
break;
} else {
bounds.x += extent;
bounds.x += SPACE;
}
}
return bounds;
}
public Rectangle getImageBounds(Item item) {
Rectangle bounds = getBounds(item);
if (bounds == null) {
return null;
}
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (item.hasImage()) {
Point imageSize = item.getImageSize();
bounds.x += SPACE;
bounds.y -= imageSize.y / 2;
bounds.width = imageSize.x;
bounds.height = imageSize.y;
}
return bounds;
}
public Rectangle getTextBounds(Item item) {
Rectangle bounds = getImageBounds(item);
if (bounds == null) {
return null;
}
bounds.x += bounds.width;
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (item.hasText()) {
Point textSize = item.getTextSize();
bounds.x += SPACE;
bounds.y -= textSize.y / 2;
bounds.width = textSize.x;
bounds.height = textSize.y;
}
return bounds;
}
public Rectangle getCloseButtonBounds(Item item) {
Rectangle bounds = getTextBounds(item);
if (bounds == null) {
return null;
}
Point closeButtonSize = getCloseButtonSize();
bounds.x += bounds.width + SPACE;
bounds.y += bounds.height / 2 - closeButtonSize.y / 2;
bounds.width = closeButtonSize.x;
bounds.height = closeButtonSize.y;
return bounds;
}
private void paint(GC gc, Item item) {
paintBackground(gc, item);
paintImage(gc, item);
paintText(gc, item);
paintCloseButton(gc, item);
paintFrame(gc, item);
}
private void paintBackground(GC gc, Item item) {
Color old = gc.getBackground();
Color neo = null;
if (item.background != null) {
neo = getColor(item.background);
gc.setBackground(neo);
}
Rectangle bounds = item.getBounds();
gc.fillRectangle(bounds);
gc.setBackground(old);
if (neo != null) {
neo.dispose();
}
}
private void paintImage(GC gc, Item item) {
if (!item.hasImage()) {
return;
}
Rectangle imageBounds = item.getImageBounds();
gc.drawImage(item.getImage(), imageBounds.x, imageBounds.y);
}
private void paintText(GC gc, Item item) {
if (!item.hasText()) {
return;
}
Color old = gc.getForeground();
Color neo = null;
if (item.foreground != null) {
neo = getColor(item.foreground);
gc.setForeground(neo);
}
Rectangle textBounds = item.getTextBounds();
gc.drawText(item.getText(), textBounds.x, textBounds.y, true);
gc.setForeground(old);
if (neo != null) {
neo.dispose();
}
}
private void paintCloseButton(GC gc, Item item) {
Rectangle closeButtonBounds = item.getCloseButtonBounds();
gc.drawImage(getCloseButtonImage(), closeButtonBounds.x, closeButtonBounds.y);
}
private void paintFrame(GC gc, Item item) {
Rectangle bounds = item.getBounds();
Color oldForeground = gc.getForeground();
Color newForeground = getColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
gc.setForeground(newForeground);
gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
gc.setForeground(oldForeground);
}
private Image getCloseButtonImage() {
return UIImageResource.T.closeNormal10();
}
private Point getCloseButtonSize() {
Rectangle closeButtonImageBounds = getCloseButtonImage().getBounds();
return new Point(closeButtonImageBounds.width, closeButtonImageBounds.height);
}
private Color getColor(int id) {
return getDisplay().getSystemColor(id);
}
private Color getColor(RGB rgb) {
return new Color(getDisplay(), rgb);
}
public static void main(String[] args) {
UI.initialize();
Display display = Display.getDefault();
Shell shell = new Shell(display);
StripBar bar = new StripBar(shell, SWT.DOUBLE_BUFFERED);
GridLayout layout = new GridLayout();
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
GridData data = new GridData();
data.horizontalAlignment = GridData.FILL;
data.verticalAlignment = GridData.FILL;
data.grabExcessHorizontalSpace = true;
bar.setLayoutData(data);
Item item0 = new Item("item0");
item0.setText("Item 0");
item0.setImage(UIImageResource.T.about16());
Item item1 = new Item("item1");
item1.setText("Item 1");
Item item2 = new Item("item2");
item2.setImage(UIImageResource.T.addContact16());
Item item3 = new Item("item3");
bar.add(item0);
bar.add(item1);
bar.add(item2);
bar.add(item3);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
UI.destroy();
}
}