package com.peterhi.ui.stripbar4;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
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 org.jdesktop.swt.animation.timing.sources.SWTTimingSource;
import com.peterhi.runtime.RT;
import com.peterhi.ui.UI;
import com.peterhi.ui.UIImageResource;
import com.peterhi.ui.obsolete.Bounds;
public class StripBar extends Composite implements Listener {
public static final int MARGIN_LEFT = 5;
public static final int MARGIN_RIGHT = 5;
public static final int MARGIN_TOP = 5;
public static final int MARGIN_BOTTOM = 5;
public static final int ITEM_MARGIN_LEFT = 8;
public static final int ITEM_MARGIN_RIGHT = 8;
public static final int ITEM_MARGIN_TOP = 5;
public static final int ITEM_MARGIN_BOTTOM = 5;
public static final int ITEM_SPACING = 5;
public static final int ITEM_TEXT_INDENT = 5;
public static final int ITEM_CLOSE_BUTTON_INDENT = 10;
public static final int ITEM_ROUND_ARC = 6;
public static final int ITEM_DRAG_THRESHOLD = 5;
private List<StripItem> items = new ArrayList<StripItem>();
private StripItem mouseItem;
private Point mouseInitialLocation;
private Point mouseInitialOffset;
private Point mouseLastLocation;
private Map<StripItem, Rectangle[]> boundsMap;
public StripBar(Composite parent, int style) {
super(parent, style);
addListener(SWT.Dispose, this);
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.Dispose) {
onDispose(event);
} else 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 Point computeSize(int wHint, int hHint, boolean changed) {
Point size = computeSize();
Rectangle bounds = computeTrim(0, 0, size.x, size.y);
return new Point(bounds.width, bounds.height);
}
@Override
public void layout(boolean changed) {
for (StripItem item : items) {
Rectangle bounds = computeBounds(item);
item.setBounds(bounds, false, null);
}
redraw();
}
@Override
public void setLayout(Layout layout) {
throw new UnsupportedOperationException();
}
@Override
public void setLayoutDeferred(boolean defer) {
throw new UnsupportedOperationException();
}
public StripItem getItem(Point value) {
if (value == null) {
throw new NullPointerException();
}
return getItem(value.x, value.y);
}
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 StripItem getItem(int state, boolean exact) {
StripItem[] items = getItems(state, exact);
if (items.length == 0) {
return null;
}
return items[0];
}
public int indexOf(StripItem item) {
return items.indexOf(item);
}
public StripItem[] getItems() {
return items.toArray(new StripItem[items.size()]);
}
public StripItem[] getItems(int state, boolean exact) {
List<StripItem> itemsWithState = new ArrayList<StripItem>();
for (StripItem item : items) {
if (RT.isFlagged(item.getState(), state, exact)) {
itemsWithState.add(item);
}
}
return itemsWithState.toArray(new StripItem[itemsWithState.size()]);
}
public boolean isMultiSelect() {
int style = getStyle();
return RT.isFlagged(style, SWT.MULTI, false);
}
protected void onDispose(Event event) {
StripItem[] items = getItems();
for (StripItem item : items) {
item.dispose();
}
}
protected void onPaint(Event event) {
for (StripItem item : items) {
if (item.isBoundsAnimating()) {
continue;
}
if (RT.isFlagged(item.getState(), StripItem.DRAG, false)) {
continue;
}
if (RT.isFlagged(item.getState(), StripItem.OUTSIDE, false)) {
continue;
}
paint(event.gc, item);
}
StripItem[] boundsAnimatingItems = getBoundsAnimatingItems();
for (StripItem boundsAnimatingItem : boundsAnimatingItems) {
if (RT.isFlagged(boundsAnimatingItem.getState(), StripItem.OUTSIDE, false)) {
continue;
}
paint(event.gc, boundsAnimatingItem);
}
StripItem[] draggedItems = getItems(StripItem.DRAG, false);
for (StripItem draggedItem : draggedItems) {
if (RT.isFlagged(draggedItem.getState(), StripItem.OUTSIDE, false)) {
continue;
}
paint(event.gc, draggedItem);
}
}
protected void onMouseEnter(Event event) {
processFocus(event.x, event.y);
}
protected void onMouseExit(Event event) {
processFocus(event.x, event.y);
}
protected void onMouseDown(Event event) {
if (event.button == 1) {
if (getBoundsAnimatingItems().length <= 0) {
if (mouseItem == null) {
mouseItem = getItem(event.x, event.y);
if (mouseItem != null) {
mouseItem.setState(mouseItem.getState() | StripItem.PRESS, false, null);
Rectangle bounds = mouseItem.getBounds();
mouseInitialLocation = new Point(event.x, event.y);
mouseInitialOffset = new Point(event.x - bounds.x, event.y - bounds.y);
mouseLastLocation = new Point(event.x, event.y);
boundsMap = new HashMap<StripItem, Rectangle[]>();
for (StripItem anItem : items) {
if (anItem.equals(mouseItem)) {
boundsMap.put(anItem, new Rectangle[] { mouseItem.getBounds(), mouseItem.getBounds() });
}
if (indexOf(anItem) < indexOf(mouseItem)) {
Rectangle smaller = anItem.getBounds();
Rectangle bigger = anItem.getBounds();
bigger.x += bounds.width + ITEM_SPACING;
boundsMap.put(anItem, new Rectangle[] { smaller, bigger });
} else {
Rectangle bigger = anItem.getBounds();
Rectangle smaller = anItem.getBounds();
smaller.x -= bounds.width + ITEM_SPACING;
boundsMap.put(anItem, new Rectangle[] { smaller, bigger });
}
}
}
}
}
}
}
protected void onMouseMove(Event event) {
processFocus(event.x, event.y);
StripItem item = getItem(StripItem.PRESS, false);
if (item != null) {
if (Math.abs(event.x - mouseInitialLocation.x) > ITEM_DRAG_THRESHOLD) {
int state = item.getState();
state &= ~StripItem.PRESS;
state |= StripItem.DRAG;
item.setState(state, false, null);
}
}
if (mouseItem != null) {
if (RT.isFlagged(mouseItem.getState(), StripItem.DRAG, false)) {
Display display = getDisplay();
Point mouseLocation = display.getCursorLocation();
Rectangle clientArea = getClientArea();
Rectangle screenArea = UI.toDisplay(this, clientArea);
if (!RT.isFlagged(mouseItem.getState(), StripItem.OUTSIDE, false) && (!screenArea.contains(mouseLocation.x, mouseLocation.y))) {
mouseItem.setState(mouseItem.getState() | StripItem.OUTSIDE, false, null);
StripItem[] itemsBehind = getItemsBehind(mouseItem);
for (StripItem itemBehind : itemsBehind) {
itemBehind.setBounds(boundsMap.get(itemBehind)[0], true, null);
}
} else if (RT.isFlagged(mouseItem.getState(), StripItem.OUTSIDE, false) && screenArea.contains(mouseLocation.x, mouseLocation.y)) {
int state = mouseItem.getState();
state &= ~StripItem.OUTSIDE;
mouseItem.setState(state, false, null);
StripItem[] itemsBehind = getItemsBehind(mouseItem);
for (StripItem itemBehind : itemsBehind) {
itemBehind.setBounds(boundsMap.get(itemBehind)[1], true, null);
}
} else if ((!RT.isFlagged(mouseItem.getState(), StripItem.OUTSIDE, false)) && screenArea.contains(mouseLocation.x, mouseLocation.y)) {
StripItem[] affectedItems = getAffectedItems(mouseItem, event.x - mouseLastLocation.x);
for (StripItem affectedItem : affectedItems) {
if (event.x - mouseLastLocation.x > 0) { // delta > 0
Rectangle affectedBounds = boundsMap.get(affectedItem)[0];
affectedItem.setBounds(affectedBounds, true, null);
} else {
Rectangle affectedBounds = boundsMap.get(affectedItem)[1];
affectedItem.setBounds(affectedBounds, true, null);
}
}
}
Rectangle oldBounds = mouseItem.getBounds();
Rectangle newBounds = new Rectangle(oldBounds.x, oldBounds.y, oldBounds.width, oldBounds.height);
newBounds.x = event.x - mouseInitialOffset.x;
mouseItem.setBounds(newBounds, false, null);
}
}
mouseLastLocation = new Point(event.x, event.y);
}
protected void onMouseUp(Event event) {
StripItem item = getItem(StripItem.PRESS, false);
if (item != null) {
int state = item.getState();
state &= ~StripItem.PRESS;
if (RT.isFlagged(state, StripItem.SELECT, false)) {
if (isMultiSelect()) {
state &= ~StripItem.SELECT;
}
} else {
state |= StripItem.SELECT;
if (!isMultiSelect()) {
StripItem[] items = getItems(StripItem.SELECT, false);
for (StripItem anItem : items) {
if (anItem.equals(item)) {
continue;
}
int aState = anItem.getState();
aState &= ~StripItem.SELECT;
anItem.setState(aState, true, null);
}
}
}
item.setState(state, false, null);
}
if (mouseItem != null) {
StripItem insertItem = getInsertItem(mouseItem);
int index = indexOf(insertItem);
int oldIndex = indexOf(mouseItem);
int newIndex = index + 1;
items.add(newIndex, mouseItem);
items.remove(oldIndex);
Rectangle bounds = mouseItem.getBounds();
int x = MARGIN_LEFT;
if (insertItem != null) {
x = insertItem.getBounds().x + insertItem.getBounds().width + ITEM_SPACING;
}
bounds.x = x;
mouseItem.setBounds(bounds, true, new TimingTargetAdapter() {
@Override
public void timingEvent(Animator source, double fraction) {
Point location = toControl(getDisplay().getCursorLocation());
processFocus(location.x, location.y);
}
@Override
public void end(Animator source) {
Point location = toControl(getDisplay().getCursorLocation());
processFocus(location.x, location.y);
}
});
int state = mouseItem.getState();
state &= ~StripItem.DRAG;
mouseItem.setState(state, false, null);
boundsMap = null;
mouseItem = null;
mouseInitialLocation = null;
mouseInitialOffset = null;
mouseLastLocation = null;
}
}
void add(int index, StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (items.contains(item)) {
throw new IllegalArgumentException();
}
if (index < -1 || index > items.size()) {
throw new IllegalArgumentException();
}
if (index == -1) {
index = items.size();
}
items.add(index, item);
}
void remove(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
if (!items.contains(item)) {
throw new IllegalArgumentException();
}
items.remove(item);
}
private Image getCloseButtonImageNormal() {
return UIImageResource.T.closeNormal10();
}
private Image getCLoseButtonImageFocus() {
return UIImageResource.T.closeHighlight10();
}
private Point getCloseButtonSize() {
Rectangle closeButtonBounds = getCloseButtonImageNormal().getBounds();
return new Point(closeButtonBounds.width, closeButtonBounds.height);
}
private Point computeSize() {
Point size = new Point(0, 0);
for (StripItem item : items) {
Point itemSize = computeSize(item);
size.x += itemSize.x;
size.y = Math.max(size.y, itemSize.y);
}
size.x += MARGIN_LEFT;
size.x += ITEM_SPACING * (items.size() - 1);
size.x += MARGIN_RIGHT;
size.y += MARGIN_TOP;
size.y += MARGIN_BOTTOM;
return size;
}
private Point computeSize(StripItem item) {
Point size = new Point(0, 0);
if (item.hasImage()) {
Point imageSize = item.getImageSize();
size.x += imageSize.x;
}
if (item.hasText()) {
if (item.hasImage()) {
size.x += ITEM_TEXT_INDENT;
}
Point textSize = item.getTextSize();
size.x += textSize.x;
}
if (item.hasImage() || item.hasText()) {
size.x += ITEM_CLOSE_BUTTON_INDENT;
}
Point closeButtonSize = getCloseButtonSize();
size.x += closeButtonSize.x;
size.x += ITEM_MARGIN_LEFT;
size.x += ITEM_MARGIN_RIGHT;
for (StripItem anItem : items) {
if (anItem.hasText()) {
Point textSize = anItem.getTextSize();
size.y = Math.max(size.y, textSize.y);
}
if (anItem.hasImage()) {
Point imageSize = anItem.getImageSize();
size.y = Math.max(size.y, imageSize.y);
}
size.y = Math.max(size.y, closeButtonSize.y);
}
size.y += ITEM_MARGIN_TOP;
size.y += ITEM_MARGIN_BOTTOM;
return size;
}
private Rectangle computeBounds(StripItem item) {
if (item == null) {
throw new NullPointerException();
}
Rectangle bounds = new Rectangle(MARGIN_LEFT, MARGIN_TOP, 0, 0);
for (StripItem anItem : items) {
Point itemSize = computeSize(anItem);
if (anItem.equals(item)) {
bounds.width = itemSize.x;
break;
} else {
bounds.x += itemSize.x;
bounds.x += ITEM_SPACING;
}
}
for (StripItem anItem : items) {
Point itemSize = computeSize(anItem);
bounds.height = Math.max(bounds.height, itemSize.y);
}
return bounds;
}
private Rectangle computeImageBounds(StripItem item, Rectangle bounds) {
Rectangle imageBounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
imageBounds.x += ITEM_MARGIN_LEFT;
imageBounds.y += bounds.height / 2;
imageBounds.width = 0;
imageBounds.height = 0;
if (item.hasImage()) {
Point imageSize = item.getImageSize();
imageBounds.y -= imageSize.y / 2;
imageBounds.width = imageSize.x;
imageBounds.height = imageSize.y;
}
return imageBounds;
}
private Rectangle computeTextBounds(StripItem item, Rectangle bounds) {
Rectangle textBounds = computeImageBounds(item, bounds);
textBounds.x += textBounds.width;
textBounds.y += textBounds.height / 2;
textBounds.width = 0;
textBounds.height = 0;
if (item.hasText()) {
if (item.hasImage()) {
textBounds.x += ITEM_TEXT_INDENT;
}
Point textSize = item.getTextSize();
textBounds.y -= textSize.y / 2;
textBounds.width = textSize.x;
textBounds.height = textSize.y;
}
return textBounds;
}
private Rectangle computeCloseButtonBounds(StripItem item, Rectangle bounds) {
Rectangle closeButtonBounds = computeTextBounds(item, bounds);
Point closeButtonSize = getCloseButtonSize();
closeButtonBounds.x += closeButtonBounds.width;
closeButtonBounds.y += closeButtonBounds.height / 2 - closeButtonSize.y / 2;
closeButtonBounds.width = closeButtonSize.x;
closeButtonBounds.height = closeButtonSize.y;
if (item.hasText() || item.hasImage()) {
closeButtonBounds.x += ITEM_CLOSE_BUTTON_INDENT;
}
return closeButtonBounds;
}
private void paint(GC gc, StripItem item) {
paintBackground(gc, item);
paintImage(gc, item);
paintText(gc, item);
paintCloseButton(gc, item);
paintFrame(gc, item);
}
private void paintBackground(GC gc, StripItem item) {
Color oldBackground = gc.getBackground();
Color newBackground = UI.newColor(item.getBackground());
gc.setBackground(newBackground);
Rectangle bounds = item.getBounds();
gc.fillRoundRectangle(bounds.x, bounds.y, bounds.width, bounds.height, ITEM_ROUND_ARC, ITEM_ROUND_ARC);
gc.setBackground(oldBackground);
newBackground.dispose();
}
private void paintImage(GC gc, StripItem item) {
if (!item.hasImage()) {
return;
}
Rectangle bounds = item.getBounds();
Rectangle imageBounds = computeImageBounds(item, bounds);
gc.drawImage(item.getImage(), imageBounds.x, imageBounds.y);
}
private void paintText(GC gc, StripItem item) {
if (!item.hasText()) {
return;
}
Color oldForeground = gc.getForeground();
Color newForeground = UI.newColor(item.getForeground());
gc.setForeground(newForeground);;
Rectangle bounds = item.getBounds();
Rectangle textBounds = computeTextBounds(item, bounds);
gc.drawText(item.getText(), textBounds.x, textBounds.y, true);
gc.setForeground(oldForeground);
newForeground.dispose();
}
private void paintCloseButton(GC gc, StripItem item) {
Rectangle bounds = item.getBounds();
Rectangle closeButtonBounds = computeCloseButtonBounds(item, bounds);
gc.drawImage(getCloseButtonImageNormal(), closeButtonBounds.x, closeButtonBounds.y);;
}
private void paintFrame(GC gc, StripItem item) {
Color oldForeground = gc.getForeground();
Color newForeground = UI.getColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
gc.setForeground(newForeground);
Rectangle bounds = item.getBounds();
gc.drawRoundRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1, ITEM_ROUND_ARC, ITEM_ROUND_ARC);
gc.setForeground(oldForeground);
}
private void processFocus(int x, int y) {
if (getItem(StripItem.DRAG, false) != null) {
return;
}
StripItem oldItem = getItem(StripItem.FOCUS, false);
StripItem newItem = getItem(x, y);
if (RT.equals(oldItem, newItem)) {
return;
}
if (oldItem != null) {
int state = oldItem.getState();
state &= ~StripItem.FOCUS;
oldItem.setState(state, true, null);
}
if (newItem != null) {
int state = newItem.getState();
state |= StripItem.FOCUS;
newItem.setState(state, true, null);
}
}
private StripItem[] getBoundsAnimatingItems() {
List<StripItem> animatingItems = new ArrayList<StripItem>();
for (StripItem item : items) {
if (item.isBoundsAnimating()) {
animatingItems.add(item);
}
}
return animatingItems.toArray(new StripItem[animatingItems.size()]);
}
private StripItem[] getAffectedItems(StripItem dragItem, int delta) {
List<StripItem> affectedItems = new ArrayList<StripItem>();
Bounds dragBounds = Bounds.fromRectangle(dragItem.getBounds());
for (StripItem anItem : items) {
if (anItem.equals(dragItem)) {
continue;
}
Bounds aBounds = Bounds.fromRectangle(anItem.getBounds());
if (delta > 0) {
if (dragBounds.getLeft() <= aBounds.getCenterX() && dragBounds.getRight() + delta >= aBounds.getCenterX()) {
affectedItems.add(anItem);
}
} else {
if (dragBounds.getRight() >= aBounds.getCenterX() && dragBounds.getLeft() + delta <= aBounds.getCenterX()) {
affectedItems.add(anItem);
}
}
}
return affectedItems.toArray(new StripItem[affectedItems.size()]);
}
private void reorderItems() {
Collections.sort(items, new Comparator<StripItem>() {
@Override
public int compare(StripItem item, StripItem otherItem) {
Rectangle bounds = item.getBounds();
Rectangle otherBounds = otherItem.getBounds();
return (bounds.x + bounds.width / 2) - (otherBounds.x + otherBounds.width / 2);
}
});
}
private StripItem[] getItemsBehind(StripItem targetItem) {
List<StripItem> itemsBehind = new ArrayList<StripItem>();
Rectangle targetBounds = targetItem.getBounds();
for (StripItem anItem : items) {
if (targetItem.equals(anItem)) {
continue;
}
Rectangle aBounds = anItem.getBounds();
if (aBounds.x + aBounds.width >= targetBounds.x + targetBounds.width / 2) {
itemsBehind.add(anItem);
}
}
return itemsBehind.toArray(new StripItem[itemsBehind.size()]);
}
private StripItem getInsertItem(StripItem targetItem) {
for (int i = 0; i < items.size() - 1; i++) {
StripItem cur = items.get(i);
if (cur.equals(targetItem)) {
continue;
}
StripItem next = items.get(i + 1);
if (next.getBounds().x - (cur.getBounds().x + cur.getBounds().width) > ITEM_SPACING) {
return cur;
}
}
return null;
}
public static void main(String[] args) {
Display display = Display.getDefault();
Shell shell = new Shell(display);
StripBar stripBar = new StripBar(shell, SWT.DOUBLE_BUFFERED | SWT.BORDER);
GridLayout layout = new GridLayout();
GridData layoutData = new GridData();
Animator.setDefaultTimingSource(new SWTTimingSource(UI.MSPF, TimeUnit.MILLISECONDS, display));
Animator.getDefaultTimingSource().init();
layoutData.horizontalAlignment = SWT.FILL;
layoutData.verticalAlignment = SWT.FILL;
layoutData.grabExcessHorizontalSpace = true;
stripBar.setLayoutData(layoutData);
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
final StripItem item1 = new StripItem(stripBar, SWT.NONE);
final StripItem item2 = new StripItem(stripBar, SWT.NONE);
final StripItem item3 = new StripItem(stripBar, SWT.NONE);
final StripItem item0 = new StripItem(stripBar, SWT.NONE, 0);
item1.setText("Item 1");
item2.setImage(UIImageResource.T.addContact16());
item0.setText("Item 0");
item0.setImage(UIImageResource.T.about16());
stripBar.layout();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
}