package com.peterhi.ui.stripbar2;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
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 StripBar extends Canvas implements Listener, PropertyChangeListener {
public static final int SPACE = 5;
private final List<StripItemController> controllers = new ArrayList<StripItemController>();
private StripItemController hovered;
private StripItemController pressed;
private final Set<StripItemController> selected = new HashSet<StripItemController>();
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);
} else if (event.type == SWT.MouseEnter) {
onMouseEnter(event);
} else if (event.type == SWT.MouseExit) {
onMouseExit(event);
} else if (event.type == SWT.MouseDown) {
onMouseDown(event);
} else if (event.type == SWT.MouseMove) {
onMouseMove(event);
} else if (event.type == SWT.MouseUp) {
onMouseUp(event);
}
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getSource() instanceof StripItem) {
if (event.getPropertyName().equals(StripItem.PROPID_TEXT)) {
onItemTextChange(event);
} else if (event.getPropertyName().equals(StripItem.PROPID_IMAGE)) {
onItemImageChange(event);
}
}
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point preferredSize = new Point(0, 0);
for (StripItemController controller : controllers) {
Rectangle bounds = getBounds(controller.getItem());
preferredSize.x += bounds.width;
preferredSize.y = Math.max(preferredSize.y, bounds.height);
}
preferredSize.x += SPACE * (size() - 1);
preferredSize.y += SPACE * 2;
Rectangle boundsWithTrim = computeTrim(0, 0, preferredSize.x, preferredSize.y);
preferredSize.x = boundsWithTrim.width;
preferredSize.y = boundsWithTrim.height;
return preferredSize;
}
public boolean add(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
StripItemController controller = new StripItemController(item);
if (controllers.contains(controller)) {
return false;
}
if (controllers.add(controller)) {
onAdd(controller);
return true;
}
return false;
}
public boolean add(StripItem item, int index) {
if (item == null) {
throw new NullPointerException();
}
StripItemController controller = new StripItemController(item);
if (controllers.contains(controller)) {
return false;
}
controllers.add(index, controller);
onAdd(controller);
return true;
}
public boolean remove(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
StripItemController controller = new StripItemController(item);
if (controllers.remove(controller)) {
onRemove(controller);
return true;
}
return false;
}
public StripItem remove(int index) {
StripItemController controller = controllers.remove(index);
onRemove(controller);
return controller.getItem();
}
public int size() {
return controllers.size();
}
public StripItem get(int index) {
return controllers.get(index).getItem();
}
public StripItem get(int x, int y) {
StripItemController controller = getControllerAt(x, y);
if (controller == null) {
return null;
}
return controller.getItem();
}
public int indexOf(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
for (int i = 0; i < controllers.size(); i++) {
StripItemController controller = controllers.get(i);
if (controller.getItem().equals(item)) {
return i;
}
}
return -1;
}
public boolean contains(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
for (StripItemController controller : controllers) {
if (controller.getItem().equals(item)) {
return true;
}
}
return false;
}
public StripItem[] toArray() {
List<StripItem> items = new ArrayList<StripItem>();
for (StripItemController controller : controllers) {
items.add(controller.getItem());
}
return items.toArray(new StripItem[items.size()]);
}
public Rectangle getBounds(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!contains(item)) {
throw new IllegalArgumentException();
}
Rectangle bounds = new Rectangle(SPACE, SPACE, 0, 0);
for (StripItemController aController : controllers) {
if (hasImage(aController.getItem())) {
bounds.height = Math.max(bounds.height, getImageSize(aController.getItem()).y);
}
if (hasText(aController.getItem())) {
bounds.height = Math.max(bounds.height, getTextSize(aController.getItem()).y);
}
bounds.height = Math.max(bounds.height, getCloseButtonSize().y);
}
bounds.height += SPACE * 2;
for (StripItemController aController : controllers) {
int extent = SPACE;
if (hasImage(aController.getItem())) {
extent += getImageSize(aController.getItem()).x;
extent += SPACE;
}
if (hasText(aController.getItem())) {
extent += getTextSize(aController.getItem()).x;
extent += SPACE;
}
extent += getCloseButtonSize().x;
extent += SPACE;
if (aController.getItem().equals(item)) {
bounds.width = extent;
break;
} else {
bounds.x += extent;
bounds.x += SPACE;
}
}
return bounds;
}
public boolean hasImage(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
return item.getImage() != null;
}
public Point getImageSize(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!hasImage(item)) {
return new Point(0, 0);
}
Rectangle imageBounds = item.getImage().getBounds();
Point imageSize = new Point(imageBounds.width, imageBounds.height);
return imageSize;
}
public Rectangle getImageBounds(StripItem item) {
Rectangle bounds = getBounds(item);
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (hasImage(item)) {
Point imageSize = getImageSize(item);
bounds.x += SPACE;
bounds.y -= imageSize.y / 2;
bounds.width = imageSize.x;
bounds.height = imageSize.y;
}
return bounds;
}
public boolean hasText(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (item.getText() == null) {
return false;
}
if (item.getText().isEmpty()) {
return false;
}
return true;
}
public Point getTextSize(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!hasText(item)) {
return new Point(0, 0);
}
GC gc = new GC(this);
Point textSize = gc.stringExtent(item.getText());
gc.dispose();
return textSize;
}
public Rectangle getTextBounds(StripItem item) {
Rectangle bounds = getImageBounds(item);
bounds.x += bounds.width;
bounds.y += bounds.height / 2;
bounds.width = 0;
bounds.height = 0;
if (hasText(item)) {
Point textSize = getTextSize(item);
bounds.x += SPACE;
bounds.y -= textSize.y / 2;
bounds.width = textSize.x;
bounds.height = textSize.y;
}
return bounds;
}
public Image getCloseButtonImage() {
return UIImageResource.T.closeNormal10();
}
public Point getCloseButtonSize() {
Image closeButtonImage = getCloseButtonImage();
Rectangle closeButtonBounds = closeButtonImage.getBounds();
Point closeButtonSize = new Point(closeButtonBounds.width, closeButtonBounds.height);
return closeButtonSize;
}
public Rectangle getCloseButtonBounds(StripItem item) {
Point closeButtonSize = getCloseButtonSize();
Rectangle bounds = getTextBounds(item);
bounds.x += bounds.width + SPACE;
bounds.y += bounds.height / 2 - closeButtonSize.y / 2;
bounds.width = closeButtonSize.x;
bounds.height = closeButtonSize.y;
return bounds;
}
public boolean isHovered() {
return getHoveredItem() != null;
}
public boolean isHovered(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (hovered == null) {
return false;
}
return hovered.getItem().equals(item);
}
public StripItem getHoveredItem() {
if (hovered == null) {
return null;
}
return hovered.getItem();
}
public boolean isPressed() {
return getPressedItem() != null;
}
public boolean isPressed(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (pressed == null) {
return false;
}
return pressed.getItem().equals(item);
}
public StripItem getPressedItem() {
if (pressed == null) {
return null;
}
return pressed.getItem();
}
public boolean isSelected(StripItem item) {
for (StripItemController controller : selected) {
if (controller.getItem().equals(item)) {
return true;
}
}
return false;
}
public StripItem[] getSelectedItems() {
List<StripItem> items = new ArrayList<StripItem>();
for (StripItemController controller : selected) {
items.add(controller.getItem());
}
return items.toArray(new StripItem[items.size()]);
}
public boolean isMulti() {
int style = getStyle();
boolean multi = (style & SWT.MULTI) == SWT.MULTI;
return multi;
}
protected StripItemController getControllerAt(int x, int y) {
for (StripItemController controller : controllers) {
if (getBounds(controller.getItem()).contains(x, y)) {
return controller;
}
}
return null;
}
protected void onAdd(StripItemController controller) {
if (controller == null) {
throw new NullPointerException();
}
controller.getItem().addPropertyChangeListener(this);
}
protected void onRemove(StripItemController controller) {
if (controller == null) {
throw new NullPointerException();
}
controller.getItem().removePropertyChangeListener(this);
if (controller.equals(hovered)) {
hovered = null;
}
if (controller.equals(pressed)) {
pressed = null;
}
selected.remove(controller);
}
protected void onPaint(Event event) {
for (StripItemController controller : controllers) {
paint(event.gc, controller);
}
}
protected void onMouseEnter(Event event) {
performHover(event.x, event.y);
}
protected void onMouseExit(Event event) {
performHover(event.x, event.y);
}
protected void onMouseDown(Event event) {
performPress(event.x, event.y);
}
protected void onMouseMove(Event event) {
performHover(event.x, event.y);
}
protected void onMouseUp(Event event) {
performSelect(event.x, event.y);
}
protected void onItemTextChange(PropertyChangeEvent event) {
redraw();
}
protected void onItemImageChange(PropertyChangeEvent event) {
redraw();
}
private void performHover(int x, int y) {
StripItemController old = hovered;
StripItemController neo = getControllerAt(x, y);
if (RT.equals(old, neo)) {
return;
}
if (old != null && !isPressed() && !isSelected(old.getItem())) {
if (!old.hasBackground()) {
old.setForeground(StripItemState.HOVERED.getForeground());
old.setBackground(StripItemState.HOVERED.getBackground());
}
animBackgroundChange(old, old.getBackground(), StripItemState.NONE.getBackground());
}
if (neo != null && !isPressed() && !isSelected(neo.getItem())) {
if (!neo.hasBackground()) {
neo.setForeground(StripItemState.NONE.getForeground());
neo.setBackground(StripItemState.NONE.getBackground());
}
animBackgroundChange(neo, neo.getBackground(), StripItemState.HOVERED.getBackground());
}
if (!isPressed()) {
hovered = neo;
}
}
private void performPress(int x, int y) {
StripItemController old = pressed;
StripItemController neo = getControllerAt(x, y);
if (RT.equals(old, neo)) {
return;
}
if (selected.contains(neo)) {
return;
}
if (old != null) {
old.setForeground(StripItemState.NONE.getForeground());
old.setBackground(StripItemState.NONE.getBackground());
repaint(old);
}
if (neo != null) {
neo.setForeground(StripItemState.PRESSED.getForeground());
neo.setBackground(StripItemState.PRESSED.getBackground());
repaint(neo);
}
pressed = neo;
}
private void performSelect(int x, int y) {
if (pressed == null) {
return;
}
if (!isMulti()) {
for (StripItemController controller : selected) {
controller.setForeground(StripItemState.NONE.getForeground());
if (!controller.hasBackground()) {
controller.setBackground(StripItemState.SELECTED.getBackground());
}
animBackgroundChange(controller, controller.getBackground(), StripItemState.NONE.getBackground());
}
selected.clear();
}
if (isSelected(pressed.getItem())) {
if (selected.remove(pressed)) {
pressed.setForeground(StripItemState.HOVERED.getForeground());
pressed.setBackground(StripItemState.HOVERED.getBackground());
} else {
pressed.setForeground(StripItemState.SELECTED.getForeground());
pressed.setBackground(StripItemState.SELECTED.getBackground());
}
} else {
if (selected.add(pressed)) {
pressed.setForeground(StripItemState.SELECTED.getForeground());
pressed.setBackground(StripItemState.SELECTED.getBackground());
} else {
pressed.setForeground(StripItemState.HOVERED.getForeground());
pressed.setBackground(StripItemState.HOVERED.getBackground());
}
}
repaint(pressed);
pressed = null;
}
private void animBackgroundChange(final StripItemController controller, final RGB from, final RGB to) {
if (controller.hasAnimator()) {
controller.getAnimator().cancel();
controller.setAnimator(null);
}
TimingTarget tt = new TimingTargetAdapter() {
@Override
public void timingEvent(Animator source, double fraction) {
double r = to.red - from.red;
double g = to.green - from.green;
double b = to.blue - from.blue;
r *= fraction;
g *= fraction;
b *= fraction;
r += from.red;
g += from.green;
b += from.blue;
controller.setBackground(new RGB((int )r, (int )g, (int )b));
repaint(controller);
}
@Override
public void end(Animator source) {
controller.setBackground(to);
repaint(controller);
}
};
Animator animator = new Animator.Builder().addTarget(tt).setDuration(100, TimeUnit.MILLISECONDS).build();
controller.setAnimator(animator);
animator.start();
}
private void repaint(StripItemController controller) {
if (controller == null) {
throw new NullPointerException();
}
Rectangle bounds = getBounds(controller.getItem());
redraw(bounds.x, bounds.y, bounds.width, bounds.height, true);
}
private void paint(GC gc, StripItemController controller) {
paintBackground(gc, controller);
paintImage(gc, controller);
paintText(gc, controller);
paintCloseButton(gc, controller);
paintFrame(gc, controller);
}
private void paintBackground(GC gc, StripItemController controller) {
if (!controller.hasBackground()) {
return;
}
Color old = gc.getBackground();
Color neo = null;
if (controller.getBackground() != null) {
neo = newColor(controller.getBackground());
gc.setBackground(neo);
}
Rectangle bounds = getBounds(controller.getItem());
gc.fillRectangle(bounds);
gc.setBackground(old);
if (neo != null) {
neo.dispose();
}
}
private void paintImage(GC gc, StripItemController controller) {
if (!hasImage(controller.getItem())) {
return;
}
Rectangle imageBounds = getImageBounds(controller.getItem());
gc.drawImage(controller.getItem().getImage(), imageBounds.x, imageBounds.y);
}
private void paintText(GC gc, StripItemController controller) {
if (!hasText(controller.getItem())) {
return;
}
Color old = gc.getForeground();
Color neo = null;
if (controller.getForeground() != null) {
neo = newColor(controller.getForeground());
gc.setForeground(neo);
}
Rectangle textBounds = getTextBounds(controller.getItem());
gc.drawText(controller.getItem().getText(), textBounds.x, textBounds.y, true);
gc.setForeground(old);
if (neo != null) {
neo.dispose();
}
}
private void paintCloseButton(GC gc, StripItemController controller) {
Rectangle closeButtonBounds = getCloseButtonBounds(controller.getItem());
gc.drawImage(getCloseButtonImage(), closeButtonBounds.x, closeButtonBounds.y);
}
private void paintFrame(GC gc, StripItemController controller) {
Rectangle bounds = getBounds(controller.getItem());
Color old = gc.getForeground();
Color neo = getColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
gc.setForeground(neo);
gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
gc.setForeground(old);
}
private Color getColor(int id) {
Display display = getDisplay();
return display.getSystemColor(id);
}
private Color newColor(RGB rgb) {
if (rgb == null) {
throw new NullPointerException();
}
Display display = getDisplay();
return new Color(display, 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 = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.grabExcessHorizontalSpace = true;
bar.setLayoutData(data);
StripItem item0 = new StripItem("item0");
StripItem item1 = new StripItem("item1");
StripItem item2 = new StripItem("item2");
StripItem item3 = new StripItem("item3");
item0.setText("Item 0");
item1.setText("Item 1");
item0.setImage(UIImageResource.T.about16());
item2.setImage(UIImageResource.T.addContact16());
bar.add(item0);
bar.add(item1);
bar.add(item2);
bar.add(item3);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
UI.destroy();
}
}