Skip to content

Instantly share code, notes, and snippets.

@nedtwigg
Created April 14, 2017 15:45
Show Gist options
  • Save nedtwigg/ede2fb9537141965fd1cebceb8a4c9fb to your computer and use it in GitHub Desktop.
Save nedtwigg/ede2fb9537141965fd1cebceb8a4c9fb to your computer and use it in GitHub Desktop.
/*
* Copyright (C) 2016 DiffPlug, LLC - All Rights Reserved
* Unauthorized copying of this file via any medium is strictly prohibited.
* Proprietary and confidential.
* Please send any inquiries to Ned Twigg <ned.twigg@diffplug.com>
*/
package org.eclipse.swt.widgets2;
import java.util.function.DoubleConsumer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DelegatingLayout;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Table;
/**
* A table which can be scrolled past its limits (positive and negative).
* Support per-pixel scrolling on all platforms.
*/
abstract class AbstractSmoothTable extends Composite {
protected final Table table;
protected final int rowHeight;
private final int scrollBarWidth;
protected int offset;
protected double topRow;
protected int topPixel;
private int width;
protected int height;
private int tableHeight;
private int itemCount;
static final DoubleConsumer DO_NOTHING = value -> {};
private DoubleConsumer topIndexListener = DO_NOTHING;
private DoubleConsumer numVisibleListener = DO_NOTHING;
protected final boolean extraRow;
protected final boolean hasVScroll;
/** Stuff for things. */
AbstractSmoothTable(Composite parent, int style) {
// if there's a BORDER, apply it to the Composite instead
super(parent, (style & SWT.V_SCROLL) | (style & SWT.BORDER));
if ((style & SWT.H_SCROLL) == SWT.H_SCROLL) {
throw new IllegalArgumentException("Must not set H_SCROLL");
}
// the table can't have the BORDER
table = new Table(this, style & (~SWT.BORDER));
this.setBackground(table.getBackground());
rowHeight = table.getItemHeight();
itemCount = table.getItemCount();
hasVScroll = (SWT.V_SCROLL & style) == SWT.V_SCROLL;
if (hasVScroll) {
this.scrollBarWidth = getVerticalBar().getSize().x;
// setup the vertical scroll bar
getVerticalBar().setVisible(true);
table.addListener(SWT.MouseVerticalWheel, e -> {
setTopPixelSaturated(topPixel - e.count * rowHeight);
e.doit = false;
});
getVerticalBar().addListener(SWT.Selection, e -> {
setTopPixelSaturated(getVerticalBar().getSelection() + minTopPixel);
});
} else {
this.scrollBarWidth = 0;
}
// if we're Unscrollable, then we'll need an extra row
this.extraRow = (this instanceof Unscrollable);
// track our size
this.addListener(SWT.Resize, e -> {
Rectangle clientArea = this.getClientArea();
width = clientArea.width + scrollBarWidth;
if (height != clientArea.height) {
height = clientArea.height;
tableHeight = height + (extraRow ? rowHeight : 0);
numVisibleListener.accept(height / ((double) rowHeight));
}
readjustScrollBar();
});
// set the table's size when we get laidout
super.setLayout(new Layout() {
@Override
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
return table.computeSize(wHint, hHint);
}
@Override
protected void layout(Composite composite, boolean flushCache) {
table.setBounds(0, offset, width, tableHeight);
if (layout != null) {
layout.layout(composite, flushCache);
}
}
});
}
/** Returns the height of rows. */
public int getRowHeight() {
return rowHeight;
}
private DelegatingLayout layout;
@Override
public void setLayout(Layout layout) {
this.layout = new DelegatingLayout(layout);
}
/** Sets the vertical offset of the table. */
protected void setOffset(int offset) {
// save the double so that getOffset() is exact
if (this.offset != offset) {
this.offset = offset;
table.setLocation(0, offset);
}
}
/** Sets the item count. */
public void setItemCount(int items) {
table.setItemCount(items);
itemCount = items;
readjustScrollBar();
}
private int minTopPixel, maxTopPixel;
private void readjustScrollBar() {
int increment = height;
int pageIncrement = height;
int thumb = height;
int selection = round(topRow * rowHeight);
int minimum = 0;
int maximum = itemCount * rowHeight;
minTopPixel = 0;
maxTopPixel = maximum - height;
if (selection < 0) {
// we've scrolled into the negatives
minTopPixel = selection;
if (maximum < height) {
maximum = height - selection;
} else {
maximum -= selection;
}
selection = 0;
} else if (maximum < height) {
// we've scrolled lower than is now possible
maximum = height + selection;
maxTopPixel = selection;
}
maxTopPixel = Math.max(0, maxTopPixel);
if (hasVScroll) {
getVerticalBar().setValues(selection, minimum, maximum, thumb, increment, pageIncrement);
}
}
/** Returns the maximum top row which wouldn't cause us to scroll past the end. */
public double getMaxTopRow() {
double maxTopPixel = Math.max(0, itemCount * rowHeight - height);
return maxTopPixel / rowHeight;
}
private void setTopPixelSaturated(int topPixel) {
double topPixelSat = Math.min(maxTopPixel, Math.max(minTopPixel, topPixel));
setTopRow(topPixelSat / rowHeight);
}
/** Returns the top row. */
public double getTopRow() {
return topRow;
}
/** Sets the top row. */
public void setTopRow(double topRow) {
this.topRow = topRow;
this.topPixel = round(topRow * rowHeight);
readjustScrollBar();
setTopRowImpl();
topIndexListener.accept(topRow);
}
/** Marks the given row as requiring redraw. */
public void redrawRow(int row) {
if (row < itemCount) {
boolean redrawChildren = true;
Rectangle itemBounds = table.getItem(row).getBounds();
table.redraw(0, itemBounds.y, width, rowHeight, redrawChildren);
}
}
/** Sets the listener which will be called when the number of visible rows is changed (can only be set once). */
public void setListenerNumVisible(DoubleConsumer numVisibleListener) {
if (this.numVisibleListener != DO_NOTHING) {
throw new IllegalArgumentException("Can't call twice!");
}
this.numVisibleListener = numVisibleListener;
}
/** Sets the listener which will be called when the top row is changed (can only be set once). */
public void setListenerTopIndex(DoubleConsumer topIndexListener) {
if (this.topIndexListener != DO_NOTHING) {
throw new IllegalArgumentException("Can't call twice!");
}
this.topIndexListener = topIndexListener;
}
/** Sets the fractional top index. */
protected abstract void setTopRowImpl();
static int round(double value) {
return (int) Math.round(value);
}
/** Returns the underlying table. */
public Table getTable() {
return table;
}
/** An implementation of {@link AbstractSmoothTable} which works for platforms which don't support per-pixel scrolling. */
static class Unscrollable extends AbstractSmoothTable {
Unscrollable(Composite parent, int style) {
super(parent, style);
}
@Override
protected void setTopRowImpl() {
int offset, topIndex;
if (topPixel < 0) {
offset = -topPixel;
topIndex = 0;
} else {
topIndex = Math.floorDiv(topPixel, rowHeight);
int maxTopIndex = table.getItemCount() - (height / rowHeight) - 1;
if (topIndex > maxTopIndex) {
topIndex = Math.max(0, maxTopIndex);
offset = (topIndex * rowHeight) - topPixel;
} else {
offset = -Math.floorMod(topPixel, rowHeight);
}
}
if (this.offset != offset) {
setRedraw(false);
table.setTopIndex(topIndex);
setOffset(offset);
setRedraw(true);
} else {
table.setTopIndex(topIndex);
}
}
}
/** An implementation of {@link AbstractSmoothTable} which works for platforms which do support per-pixel scrolling. */
static abstract class Scrollable extends AbstractSmoothTable {
Scrollable(Composite parent, int style) {
super(parent, style);
}
@Override
protected void setTopRowImpl() {
int offset, tableTopPixel;
if (topPixel < 0) {
offset = -topPixel;
tableTopPixel = 0;
} else {
int topIndex = Math.floorDiv(topPixel, rowHeight);
int maxTopIndex = table.getItemCount() - (height / rowHeight) - 1;
if (topIndex > maxTopIndex) {
topIndex = Math.max(0, maxTopIndex);
tableTopPixel = topIndex * rowHeight;
offset = tableTopPixel - topPixel;
} else {
tableTopPixel = topPixel;
offset = 0;
}
}
if (this.offset != offset) {
setRedraw(false);
setTopPixelWithinTable(tableTopPixel);
setOffset(offset);
setRedraw(true);
} else {
setTopPixelWithinTable(tableTopPixel);
}
}
protected abstract void setTopPixelWithinTable(int topPixel);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment