package com.yoursway.swt.scrollbar; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; 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 com.yoursway.utils.annotations.SynchronizedWithMonitorOfField; public class CoolScrollBar extends Canvas { private float whole; private float visibleHeight; private float position; private float alpha; private final Color color; private float beginMargin; private float endMargin; private boolean vertical = true; @SynchronizedWithMonitorOfField("animationLock") private boolean runningAnimation = false; private Thread animationThread; private final AnimationRunnable animationRunnable; private final Object animationLock = new Object(); protected Rectangle lastRunnerRect; private class DragginMouseListener implements MouseListener, MouseMoveListener { private boolean dragMode; private float mouseOffset; public void mouseDoubleClick(MouseEvent e) { } public void mouseDown(MouseEvent e) { if (lastRunnerRect != null) { dragMode = true; if (!lastRunnerRect.contains(e.x, e.y)) { //> scrolling by pages float newPosition = positionForClick(e.x, e.y); if (newPosition >= 0) { position = newPosition; redraw(); CoolScrollBar.this.notifyListeners(SWT.Selection, new Event()); } } mouseOffset = e.y - clickForPosition(position); } } public void mouseUp(MouseEvent e) { dragMode = false; } public void mouseMove(MouseEvent e) { if (dragMode) { float newPosition = positionForClick(e.x - mouseOffset, e.y - mouseOffset); if (newPosition >= 0) { position = newPosition; redraw(); CoolScrollBar.this.notifyListeners(SWT.Selection, new Event()); } } } private float positionForClick(float xc, float yc) { float coord = (vertical ? yc : xc) - runnerLength() / 2; if (ratio() > 0.9) // nothing to show return -1; float activeSpace = activeSpace(); if (coord < beginMargin) coord = beginMargin; if (coord > beginMargin + activeSpace) coord = beginMargin + activeSpace; return (whole - visibleHeight) * (coord - beginMargin) / activeSpace; } private float clickForPosition(float position) { return position * activeSpace() / (whole - visibleHeight) + beginMargin + runnerLength() / 2; } private float activeSpace() { return length() - runnerLength() - beginMargin - endMargin; } private float runnerLength() { return Math.max(ratio() * length(), 10); } private float length() { Rectangle clientArea = CoolScrollBar.this.getClientArea(); return vertical ? clientArea.height : clientArea.width; } private float ratio() { return visibleHeight / whole; } } private Rectangle calculateRunnerRect() { float ratio = visibleHeight / whole; if (ratio > 0.9) // nothing to show return null; Rectangle clientArea = CoolScrollBar.this.getClientArea(); int length = vertical ? clientArea.height : clientArea.width; int runnerLength = (int) Math.max(ratio * length, 10); int activeSpace = (int) (length - runnerLength - beginMargin - endMargin); int pos = (int) (activeSpace * (position / (whole - visibleHeight)) + beginMargin); if (vertical) return new Rectangle(3, pos, 7, runnerLength); else return new Rectangle(pos, 3, runnerLength, 7); } public CoolScrollBar(Composite parent, int style, boolean vertical) { this(parent, style, vertical, new Color(Display.getDefault(), 80, 80, 80)); addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { color.dispose(); } }); } public CoolScrollBar(Composite parent, int style, boolean vertical, Color color) { super(parent, style | SWT.DOUBLE_BUFFERED); this.vertical = vertical; alpha = 0; beginMargin = 6; endMargin = 6; this.color = color; this.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE)); addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { Rectangle runnerRect = calculateRunnerRect(); if (runnerRect != null) { GC gc = e.gc; gc.setAlpha((int) (alpha * 255)); gc.setBackground(CoolScrollBar.this.color); gc.fillRoundRectangle(runnerRect.x, runnerRect.y, runnerRect.width, runnerRect.height, 7, 7); } lastRunnerRect = runnerRect; } }); DragginMouseListener dragListener = new DragginMouseListener(); addMouseListener(dragListener); addMouseMoveListener(dragListener); // addMouseWheelListener(new MouseWheelListener() { // // public void mouseScrolled(MouseEvent e) { // scrollable.scrollBy(e.count); // redraw(); // } // // }); animationRunnable = new AnimationRunnable(true, 300); animationThread = new Thread(animationRunnable); } public void setRunnerSize(float whole, float visible) { this.whole = whole; this.visibleHeight = visible; if (position > whole - visible) position = 0; redraw(); } public void setPosition(float position) { // if (position < 0 || position > whole - visible) // throw new IllegalArgumentException("position is out of bounds"); if (position < 0) position = 0; if (position > whole - visibleHeight) position = whole - visibleHeight; this.position = position; redraw(); } public float getPosition() { return position; } public float beginMargin() { return beginMargin; } public float endMargin() { return endMargin; } public void setBeginMargin(float beginMargin) { this.beginMargin = beginMargin; } public void setEndMargin(float endMargin) { this.endMargin = endMargin; } @Override public Point computeSize(int hint, int hint2, boolean changed) { Point computedSize = super.computeSize(hint, hint2, changed); if (vertical) return new Point(14, computedSize.y); else return new Point(computedSize.x, 14); } // animation stuff private class AnimationRunnable implements Runnable { @SynchronizedWithMonitorOfField("animationLock") private boolean show; private final float time; public AnimationRunnable(boolean show, float time) { synchronized (animationLock) { this.show = show; } this.time = time; } public void run() { synchronized (animationLock) { runningAnimation = true; } long delay = 5; float step = delay / time; while (true) { synchronized (animationLock) { if (((show && alpha >= 1) || (!show && alpha <= 0)) || !runningAnimation) { runningAnimation = false; break; } Display.getDefault().syncExec(new Runnable() { public void run() { if (!CoolScrollBar.this.isDisposed()) CoolScrollBar.this.redraw(); } }); try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); break; } if (show) alpha += step; else alpha -= step; } } synchronized (animationLock) { runningAnimation = false; if (show && alpha > 1) alpha = 1; if (!show && alpha < 0) alpha = 0; } } } private boolean show_ = false; private synchronized void animateAlpha(final boolean show) { show_ = show; new Thread(new Runnable() { public void run() { synchronized (animationLock) { animationRunnable.show = show_; if (!runningAnimation) { animationThread = new Thread(animationRunnable); animationThread.start(); } } } }).start(); } public void animateShow() { animateAlpha(true); } public void animateHide() { animateAlpha(false); } public boolean working() { return calculateRunnerRect() != null; } }