package com.peterhi.ui.bar2;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
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.TimingTarget;
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 StripBar5 extends Canvas {
public static interface StripItemListener extends EventListener {
void hovered(StripItemEvent e);
}
public static abstract class StripItemAdapter implements StripItemListener {
@Override
public void hovered(StripItemEvent e) {
}
}
public static final class StripItemEvent extends EventObject {
private static final long serialVersionUID = -2990774029657941119L;
private final StripItem item;
private final StripItem lastItem;
public StripItemEvent(StripBar5 bar, StripItem item, StripItem lastItem) {
super(bar);
this.item = item;
this.lastItem = lastItem;
}
public StripBar5 getStripBar() {
return (StripBar5 )getSource();
}
public StripItem getItem() {
return item;
}
public StripItem getLastItem() {
return lastItem;
}
}
public static final class StripItem {
public static final RGB[] DEFAULT = new RGB[] {
Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND).getRGB(),
Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND).getRGB()
};
public static final RGB[] HOVERED = new RGB[] {
Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND).getRGB(),
new RGB(72, 118, 255)
};
public static final RGB[] PRESSED = new RGB[] {
Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND).getRGB(),
new RGB(65, 105, 225)
};
public static final RGB[] SELECTED = new RGB[] {
Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB(),
new RGB(67, 110, 238)
};
private final String id;
private String text;
private Image image;
StripBar5 parent;
Animator animator;
RGB foreground;
RGB background;
public StripItem(String id) {
this.id = id;
}
public StripBar5 getParent() {
return parent;
}
void setParent(StripBar5 parent) {
this.parent = parent;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
public void setText(String value) {
text = value;
}
public Image getImage() {
return image;
}
public void setImage(Image value) {
image = value;
}
public Rectangle getBounds() {
checkParent();
return parent.getBounds(this);
}
public Rectangle getImageBounds() {
checkParent();
return parent.getImageBounds(this);
}
public Rectangle getTextBounds() {
checkParent();
return parent.getTextBounds(this);
}
public Rectangle getCloseButtonBounds() {
checkParent();
return parent.getCloseButtonBounds(this);
}
public int getIndex() {
checkParent();
return parent.indexOf(this);
}
public StripItem getPreviousItem() {
checkParent();
return parent.getItem(getIndex() - 1);
}
public StripItem getNextItem() {
checkParent();
return parent.getItem(getIndex() + 1);
}
public void dispose() {
checkParent();
parent.removeItem(this);
}
public boolean isHovered() {
checkParent();
return equals(parent.getHoveredItem());
}
public boolean isPressed() {
checkParent();
return equals(parent.getPressedItem());
}
public boolean isSelected() {
checkParent();
return parent.isSelected(this);
}
public void setSelected(boolean value) {
checkParent();
parent.setSelected(this, value);
}
@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;
StripItem other = (StripItem) 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();
}
}
}
public static final int SPACING = 6;
private final List<StripItem> items = new ArrayList<StripItem>();
private final List<StripItemListener> listeners = new ArrayList<StripItemListener>();
private final Set<StripItem> selected = new HashSet<StripItem>();
private StripItem hovered;
private StripItem pressed;
public StripBar5(Composite parent, int style) {
super(parent, style);
addListener(SWT.Paint, new Listener() {
@Override
public void handleEvent(Event event) {
onPaint(event);
}
});
Listener hoverListener = new Listener() {
@Override
public void handleEvent(Event event) {
onHover(event);
}
};
addListener(SWT.MouseEnter, hoverListener);
addListener(SWT.MouseExit, hoverListener);
addListener(SWT.MouseMove, hoverListener);
addListener(SWT.MouseDown, new Listener() {
@Override
public void handleEvent(Event event) {
onPress(event);
}
});
addListener(SWT.MouseUp, new Listener() {
@Override
public void handleEvent(Event event) {
onSelect(event);
}
});
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point size = new Point(0, 0);
for (StripItem item : items) {
Rectangle bounds = item.getBounds();
size.x += bounds.width;
size.y = Math.max(size.y, bounds.height);
}
size.x += SPACING * (items.size() - 1);
size.y += SPACING * 2;
Rectangle boundsWithTrim = computeTrim(0, 0, size.x, size.y);
size.x = boundsWithTrim.width;
size.y = boundsWithTrim.height;
return size;
}
public void addStripItemListener(StripItemListener listener) {
if (listener == null) {
throw new NullPointerException();
}
listeners.add(listener);
}
public void removeStripItemListener(StripItemListener listener) {
if (listener == null) {
throw new NullPointerException();
}
listeners.remove(listener);
}
public void removeAllStripItemListeners() {
listeners.clear();
}
public boolean addItem(StripItem item) {
return insertItem(item, getItemCount());
}
public boolean insertItem(StripItem item, int index) {
if (item == null) {
throw new NullPointerException();
}
if (items.contains(item)) {
return false;
}
items.add(index, item);
item.setParent(this);
return true;
}
public boolean removeItem(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
return false;
}
boolean result = items.remove(item);
if (result) {
if (item.equals(hovered)) {
hovered = null;
}
item.setParent(null);
}
return result;
}
public int getItemCount() {
return items.size();
}
public StripItem getItem(int index) {
return items.get(index);
}
public StripItem getItem(int x, int y) {
for (StripItem item : items) {
Rectangle bounds = item.getBounds();
if (bounds.contains(x, y)) {
return item;
}
}
return null;
}
public int indexOf(StripItem item) {
return items.indexOf(item);
}
public StripItem[] getItems() {
return items.toArray(new StripItem[items.size()]);
}
public StripItem getHoveredItem() {
return hovered;
}
public StripItem getPressedItem() {
return pressed;
}
public boolean isSelected(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
return selected.contains(item);
}
public void setSelected(StripItem item, boolean value) {
setSelected(item, value, false, true);
}
public void setSelected(StripItem item, boolean value, boolean fromPressedState, boolean checkOthers) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
boolean needsAnimation = false;
if (value) {
if (selected.add(item)) {
needsAnimation = true;
}
} else {
if (selected.remove(item)) {
needsAnimation = true;
}
}
if (needsAnimation) {
Point location = getDisplay().getCursorLocation();
location = toControl(location);
StripItem over = getItem(location.x, location.y);
RGB from = item.background;
RGB to = null;
if (selected.contains(item)) {
if (from == null) {
if (fromPressedState) {
from = StripItem.PRESSED[1];
} else if (item.equals(over)) {
from = StripItem.HOVERED[1];
} else {
from = StripItem.DEFAULT[1];
}
}
item.foreground = StripItem.SELECTED[0];
to = StripItem.SELECTED[1];
} else {
if (from == null) {
from = StripItem.SELECTED[1];
}
if (item.equals(over)) {
item.foreground = StripItem.HOVERED[0];
to = StripItem.HOVERED[1];
} else {
item.foreground = StripItem.DEFAULT[0];
to = StripItem.DEFAULT[1];
}
}
if (from != null && to != null) {
animBackColorChange(item, from, to);
}
}
if (checkOthers && !isMultiSelection()) {
for (StripItem otherItem : items) {
if (otherItem.equals(item)) {
continue;
}
if (otherItem.isSelected()) {
setSelected(otherItem, false, fromPressedState, false);
}
}
}
}
public StripItem[] getSelectedItems() {
return selected.toArray(new StripItem[selected.size()]);
}
public Rectangle getBounds(StripItem destItem) {
if (destItem == null) {
throw new NullPointerException();
}
if (!items.contains(destItem)) {
throw new IllegalArgumentException();
}
GC gc = new GC(this);
Rectangle bounds = new Rectangle(SPACING, SPACING, 0, 0);
Image closeButtonImage = UIImageResource.T.closeNormal10();
Rectangle closeButtonImageBounds = closeButtonImage.getBounds();
for (StripItem srcItem : items) {
if (srcItem.getImage() != null) {
Rectangle imageBounds = srcItem.getImage().getBounds();
bounds.height = Math.max(bounds.height, imageBounds.height);
}
if (srcItem.getText() != null && !srcItem.getText().isEmpty()) {
Point textSize = gc.stringExtent(srcItem.getText());
bounds.height = Math.max(bounds.height, textSize.y);
}
bounds.height = Math.max(bounds.height, closeButtonImageBounds.height);
}
bounds.height += SPACING * 2;
for (StripItem srcItem : items) {
int extent = SPACING;
if (srcItem.getImage() != null) {
Rectangle imageBounds = srcItem.getImage().getBounds();
extent += imageBounds.width;
extent += SPACING;
}
if (srcItem.getText() != null && !srcItem.getText().isEmpty()) {
Point textSize = gc.stringExtent(srcItem.getText());
extent += textSize.x;
extent += SPACING;
}
extent += closeButtonImageBounds.width;
extent += SPACING;
if (srcItem.equals(destItem)) {
bounds.width = extent;
break;
} else {
bounds.x += extent;
bounds.x += SPACING;
}
}
gc.dispose();
return bounds;
}
public Rectangle getImageBounds(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
Rectangle bounds = getBounds(item);
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (item.getImage() != null) {
Rectangle imageBounds = item.getImage().getBounds();
bounds.x += SPACING;
bounds.y -= imageBounds.height / 2;
bounds.width = imageBounds.width;
bounds.height = imageBounds.height;
}
return bounds;
}
public Rectangle getTextBounds(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
Rectangle bounds = getImageBounds(item);
bounds.x += bounds.width;
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (item.getText() != null && !item.getText().isEmpty()) {
GC gc = new GC(this);
Point textSize = gc.stringExtent(item.getText());
bounds.x += SPACING;
bounds.y -= textSize.y / 2;
bounds.width = textSize.x;
bounds.height = textSize.y;
gc.dispose();
}
return bounds;
}
public Rectangle getCloseButtonBounds(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
Rectangle bounds = getTextBounds(item);
bounds.x += bounds.width;
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
Image closeButtonImage = UIImageResource.T.closeNormal10();
Rectangle closeButtonImageBounds = closeButtonImage.getBounds();
bounds.x += SPACING;
bounds.y -= closeButtonImageBounds.height / 2;
bounds.width = closeButtonImageBounds.width;
bounds.height = closeButtonImageBounds.height;
return bounds;
}
public boolean isMultiSelection() {
int style = getStyle();
boolean multi = (style & SWT.MULTI) == SWT.MULTI;
return multi;
}
private void fireHoveredEvent(StripItemEvent e) {
for (StripItemListener listener : listeners) {
listener.hovered(e);
}
}
private void onPaint(Event event) {
for (StripItem item : items) {
paintStripItem(event.gc, item);
}
}
private void paintStripItem(GC gc, StripItem item) {
paintStripItemBackground(gc, item);
paintStripItemImage(gc, item);
paintStripItemText(gc, item);
paintStripItemCloseButton(gc, item);
paintStripItemFrame(gc, item);
}
private void paintStripItemBackground(GC gc, StripItem item) {
Rectangle bounds = item.getBounds();
Color old = gc.getBackground();
Color neo;
if (item.background == null) {
neo = new Color(getDisplay(), StripItem.DEFAULT[1]);
} else {
neo = new Color(getDisplay(), item.background);
}
gc.setBackground(neo);
gc.fillRectangle(bounds);
gc.setBackground(old);
neo.dispose();
}
private void paintStripItemImage(GC gc, StripItem item) {
if (item.getImage() == null) {
return;
}
Rectangle bounds = item.getImageBounds();
gc.drawImage(item.getImage(), bounds.x, bounds.y);
}
private void paintStripItemText(GC gc, StripItem item) {
if (item.getText() == null || item.getText().isEmpty()) {
return;
}
Rectangle bounds = item.getTextBounds();
Color old = gc.getForeground();
Color neo;
if (item.foreground == null) {
neo = new Color(getDisplay(), StripItem.DEFAULT[0]);
} else {
neo = new Color(getDisplay(), item.foreground);
}
gc.setForeground(neo);
gc.drawText(item.getText(), bounds.x, bounds.y, true);
gc.setForeground(old);
neo.dispose();
}
private void paintStripItemCloseButton(GC gc, StripItem item) {
Image closeButtonImage = UIImageResource.T.closeNormal10();
Rectangle bounds = item.getCloseButtonBounds();
gc.drawImage(closeButtonImage, bounds.x, bounds.y);
}
private void paintStripItemFrame(GC gc, StripItem item) {
Color old = gc.getForeground();
Color neo = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
gc.setForeground(neo);
Rectangle bounds = item.getBounds();
gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
gc.setForeground(old);
}
private void onHover(Event event) {
StripItem old = hovered;
StripItem neo = getItem(event.x, event.y);
if (RT.equals(old, neo)) {
return;
}
if (old != null && !old.isPressed() && !old.isSelected()) {
RGB from = old.background;
if (from == null) {
from = StripItem.HOVERED[1];
}
RGB to = StripItem.DEFAULT[1];
animBackColorChange(old, from, to);
}
if (neo != null && !neo.isPressed() && !neo.isSelected()) {
RGB from = neo.background;
if (from == null) {
from = StripItem.DEFAULT[1];
}
RGB to = StripItem.HOVERED[1];
animBackColorChange(neo, from, to);
}
hovered = neo;
fireHoveredEvent(new StripItemEvent(this, neo, old));
}
private void onPress(Event event) {
StripItem item = getItem(event.x, event.y);
if (item != null) {
if (isMultiSelection() || !item.isSelected()) {
Rectangle bounds = item.getBounds();
item.foreground = StripItem.PRESSED[0];
item.background = StripItem.PRESSED[1];
redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
pressed = item;
}
}
}
private void onSelect(Event event) {
if (pressed != null) {
boolean wasSelected = pressed.isSelected();
setSelected(pressed, !wasSelected, true, true);
pressed = null;
}
}
private void animBackColorChange(final StripItem item, final RGB from, final RGB to) {
if (item.animator != null) {
item.animator.cancel();
item.animator = null;
}
TimingTarget tt = new TimingTargetAdapter() {
@Override
public void timingEvent(Animator source, double fraction) {
Rectangle bounds = item.getBounds();
int r = (int )Math.round((to.red - from.red) * fraction);
int g = (int )Math.round((to.green - from.green) * fraction);
int b = (int )Math.round((to.blue - from.blue) * fraction);
item.background = new RGB(from.red + r, from.green + g, from.blue + b);
redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
}
@Override
public void end(Animator source) {
Rectangle bounds = item.getBounds();
item.background = to;
redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
}
};
item.animator = new Animator.Builder().addTarget(tt).setDuration(100, TimeUnit.MILLISECONDS).build();
item.animator.start();
}
public static void main(String[] args) {
UI.initialize();
Display display = Display.getDefault();
Shell shell = new Shell(display);
StripBar5 bar = new StripBar5(shell, SWT.DOUBLE_BUFFERED | SWT.MULTI);
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 = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = true;
bar.setLayoutData(data);
StripItem item0 = new StripItem("item0");
item0.setText("Item 0");
item0.setImage(UIImageResource.T.about16());
StripItem item1 = new StripItem("item1");
item1.setText("Item 1");
StripItem item2 = new StripItem("item2");
item2.setImage(UIImageResource.T.addContact16());
StripItem item3 = new StripItem("item3");
bar.addItem(item0);
bar.addItem(item1);
bar.addItem(item2);
bar.addItem(item3);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
UI.destroy();
}
}