Skip to content

Instantly share code, notes, and snippets.

@fappel
Last active May 7, 2021 07:58
Show Gist options
  • Save fappel/9168399 to your computer and use it in GitHub Desktop.
Save fappel/9168399 to your computer and use it in GitHub Desktop.
Draft of an Custom SWT Layout for Responsive SWT UI Development (depends on SWT and Google Guava)
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
public class Demo {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell( display, SWT.SHELL_TRIM );
shell.setLayout( new FillLayout() );
shell.setBounds( 20, 20, 1200, 600 );
Composite parent = new Composite( shell, SWT.NONE );
parent.setLayout( new ScaleLayout( new ScaleProvider( parent ) ) );
parent.setBackground( getSystemColor( SWT.COLOR_LIST_BACKGROUND ) );
configure( parent );
shell.open();
while( !shell.isDisposed() ) {
if( !shell.getDisplay().readAndDispatch() ) {
shell.getDisplay().sleep();
}
}
}
private static void configure( Composite parent ) {
ScaleData child1 = createChild( parent, "control1", SWT.COLOR_RED );
ScaleData child2 = createChild( parent, "control2", SWT.COLOR_GREEN );
ScaleData child3 = createChild( parent, "control3", SWT.COLOR_BLUE );
ScaleData child4 = createChild( parent, "control4", SWT.COLOR_CYAN );
ScaleData child5 = createChild( parent, "control5", SWT.COLOR_YELLOW );
child1.on( Scale.SUPER_WIDE ).setMargin( 3, 3, 3, 3 );
child2.on( Scale.SUPER_WIDE ).setMargin( 3, 3, 3, 3 );
child3.on( Scale.SUPER_WIDE ).setWidth( 300 ).setMargin( 3, 3, 3, 3 );
child4.on( Scale.SUPER_WIDE ).setMargin( 3, 3, 3, 3 );
child5.on( Scale.SUPER_WIDE ).setMargin( 3, 3, 3, 3 );
child1.on( Scale.WIDE ).setMargin( 3, 3, 3, 3 );
child2.on( Scale.WIDE ).setMargin( 3, 3, 3, 3 );
child3.on( Scale.WIDE ).setWidth( 300 ).setMargin( 3, 3, 3, 3 );
child4.on( Scale.WIDE ).tie( child5 ).setHeight( 200 ).setMargin( 3, 3, 3, 3 );
child5.on( Scale.WIDE ).setMargin( 3, 3, 3, 3 );
child1.on( Scale.STANDARD ).setMargin( 3, 3, 3, 3 );
child2.on( Scale.STANDARD ).tie( child3 ).setMargin( 3, 3, 3, 3 );
child3.on( Scale.STANDARD ).setWidth( 300 ).setMargin( 3, 3, 3, 3 );
child4.on( Scale.STANDARD ).tie( child5 ).setMargin( 3, 3, 3, 3 );
child5.on( Scale.STANDARD ).setMargin( 3, 3, 3, 3 );
child1.on( Scale.COMPACT ).setMargin( 3, 3, 3, 3 );
child2.on( Scale.COMPACT ).tie( child3 ).setMargin( 3, 3, 3, 3 );
child3.on( Scale.COMPACT ).tie( child4 ).setSize( 300, 60 ).setMargin( 3, 3, 3, 3 );
child4.on( Scale.COMPACT ).tie( child5 ).setMargin( 3, 3, 3, 3 );
child5.on( Scale.COMPACT ).setMargin( 3, 3, 3, 3 );
child1.on( Scale.SUPER_COMPACT ).tie( child2 ).setMargin( 3, 3, 3, 3 );
child2.on( Scale.SUPER_COMPACT ).tie( child3 ).setMargin( 3, 3, 3, 3 );
child3.on( Scale.SUPER_COMPACT ).tie( child4 ).setHeight( 60 ).setMargin( 3, 3, 3, 3 );
child4.on( Scale.SUPER_COMPACT ).tie( child5 ).setMargin( 3, 3, 3, 3 );
child5.on( Scale.SUPER_COMPACT ).setMargin( 3, 3, 3, 3 );
}
static ScaleData createChild( Composite parent, String label, int color ) {
return new ScaleData( createChildControl( parent, label, color ) );
}
private static Control createChildControl( Composite parent, String label, int color ) {
Label result = new Label( parent, SWT.WRAP );
result.setText( label );
result.setBackground( getSystemColor( color ) );
return result;
}
private static Color getSystemColor( int colorIndex ) {
return Display.getCurrent().getSystemColor( colorIndex );
}
}
public enum Scale {
SUPER_WIDE, WIDE, STANDARD, COMPACT, SUPER_COMPACT;
static final int UPPER_BOUND_SUPER_COMPACT = 480;
static final int UPPER_BOUND_COMPACT = 640;
static final int UPPER_BOUND_STANDARD = 800;
static final int UPPER_BOUND_WIDE = 1024;
public static Scale valueOf( int width ) {
Scale result = Scale.SUPER_WIDE;
if( width < UPPER_BOUND_SUPER_COMPACT ) {
result = SUPER_COMPACT;
} else if( width < UPPER_BOUND_COMPACT ) {
result = COMPACT;
} else if( width < UPPER_BOUND_STANDARD ) {
result = STANDARD;
} else if( width < UPPER_BOUND_WIDE ) {
result = WIDE;
}
return result;
}
}
import static com.google.common.collect.Maps.newHashMap;
import java.util.HashMap;
import org.eclipse.swt.widgets.Control;
public class ScaleData {
private final HashMap<Scale, ScaleDetail> scales;
private final Control control;
public ScaleData( Control control ) {
this.scales = newHashMap();
this.control = control;
control.setLayoutData( this );
}
public Control getControl() {
return control;
}
public ScaleDetail on( Scale scale ) {
if( !scales.containsKey( scale ) ) {
scales.put( scale, new ScaleDetail( this, scale ) );
}
return scales.get( scale );
}
}
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
public class ScaleDetail {
private final ScaleData current;
private final Scale scale;
private ScaleData predecessor;
private ScaleData successor;
private Rectangle margin;
private int width;
private int height;
ScaleDetail( ScaleData current, Scale scale ) {
this.current = current;
this.scale = scale;
this.width = SWT.DEFAULT;
this.height = SWT.DEFAULT;
this.margin = new Rectangle( 0, 0, 0, 0 );
}
public int getWidth() {
return width;
}
public ScaleDetail setWidth( int width ) {
this.width = width;
return this;
}
public int getHeight() {
return height;
}
public ScaleDetail setHeight( int height ) {
this.height = height;
return this;
}
public ScaleDetail setSize( int width, int height ) {
setWidth( width );
setHeight( height );
return this;
}
public Rectangle getMargin() {
return margin;
}
public ScaleDetail setMargin( int left, int top, int right, int bottom ) {
this.margin = new Rectangle( left, top, right, bottom );
return this;
}
public ScaleDetail tie( ScaleData successor ) {
if( this.successor != null ) {
this.successor.on( scale ).setPredecessor( null );
}
this.successor = successor;
if( successor != null ) {
successor.on( scale ).setPredecessor( current );
}
return this;
}
public ScaleData getSuccessor() {
return successor;
}
public boolean hasSuccessor() {
return successor != null;
}
boolean hasPredecessor() {
return predecessor != null;
}
private void setPredecessor( ScaleData predecessor ) {
this.predecessor = predecessor;
}
}
import org.eclipse.swt.widgets.Composite;
public class ScaleEvent {
private final Composite source;
private final Scale newScale;
private final Scale oldScale;
ScaleEvent( Composite source, Scale newScale, Scale oldScale ) {
this.source = source;
this.newScale = newScale;
this.oldScale = oldScale;
}
public Composite getSource() {
return source;
}
public Scale getNewScale() {
return newScale;
}
public Scale getOldScale() {
return oldScale;
}
}
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newLinkedList;
import static java.lang.Math.max;
import java.util.List;
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.Control;
import org.eclipse.swt.widgets.Layout;
public class ScaleLayout extends Layout {
private final ScaleProvider scaleProvider;
public ScaleLayout( ScaleProvider scaleProvider ) {
checkArgument( scaleProvider != null, "Parameter 'scaleProvider must not be null." );
this.scaleProvider = scaleProvider;
}
public ScaleProvider getScaleProvider() {
return scaleProvider;
}
@Override
protected Point computeSize( Composite composite, int wHint, int hHint, boolean flushCache ) {
Scale scale = scaleProvider.getScale();
Point result = newSize( 0, 0, wHint, hHint );
for( ScaleData columnHead : getColumnHeads( scale, composite ) ) {
Point columnSize = computeColumnSize( scale, columnHead );
result = new Point( result.x + columnSize.x, max( result.y, columnSize.y ) );
}
return newSize( result.x, result.y, wHint, hHint );
}
private static Point newSize( int width, int height, int wHint, int hHint ) {
return new Point( wHint == SWT.DEFAULT ? width : wHint, hHint == SWT.DEFAULT ? height : hHint );
}
private static Point computeColumnSize( Scale scale, ScaleData columnHead ) {
Point result = new Point( 0, 0 );
for( ScaleData element : getTiedElements( scale, columnHead ) ) {
Point size = computeSize( scale, element );
Rectangle margin = element.on( scale ).getMargin();
int x = max( result.x, size.x + margin.x + margin.width );
int y = result.y + size.y + margin.y + margin.height;
result = new Point( x, y );
}
return result;
}
@Override
protected void layout( Composite composite, boolean flushCache ) {
Scale scale = scaleProvider.getScale();
int xPos = composite.getClientArea().x;
for( ScaleData columnHead : getColumnHeads( scale, composite ) ) {
int columnWidth = layoutColumn( scale, columnHead, composite, xPos );
xPos += columnWidth;
}
}
private static int layoutColumn( Scale scale, ScaleData head, Composite composite, int xPos ) {
int result = calculateColumnWidth( scale, head );
int yPos = composite.getClientArea().y;
for( ScaleData element : getTiedElements( scale, head ) ) {
Rectangle bounds = calculateElementBounds( scale, element, xPos, yPos, result );
bounds.height = adjustHeightIfBottomAttached( scale, element, bounds, head, composite );
bounds.width = adjustWidthIfRightAttached( scale, element, bounds, head, composite );
element.getControl().setBounds( bounds );
Rectangle margin = element.on( scale ).getMargin();
yPos += bounds.height + margin.y + margin.height;
}
return result;
}
private static Rectangle calculateElementBounds(
Scale scale, ScaleData element, int x, int y, int width )
{
int height = computeSize( scale, element ).y;
Rectangle margin = element.on( scale ).getMargin();
return new Rectangle( x + margin.x, y + margin.y, width - margin.x - margin.width, height );
}
private static int adjustHeightIfBottomAttached(
Scale scale, ScaleData element, Rectangle bounds, ScaleData columnHead, Composite composite )
{
int result = bounds.height;
List<ScaleData> elements = getTiedElements( scale, columnHead );
if( isLastElementInList( element, elements ) && !hasHeightHint( scale, element ) ) {
Rectangle margin = element.on( scale ).getMargin();
result = composite.getClientArea().height - bounds.y - margin.height;
}
return result;
}
private static int adjustWidthIfRightAttached(
Scale scale, ScaleData element, Rectangle bounds, ScaleData head, Composite composite )
{
int result = bounds.width;
List<ScaleData> columnHeads = getColumnHeads( scale, composite );
if( isLastElementInList( head, columnHeads ) && !hasWidthHint( scale, element ) ) {
Rectangle margin = element.on( scale ).getMargin();
result = composite.getClientArea().width - bounds.x - margin.width;
}
return result;
}
private static List<ScaleData> getColumnHeads( Scale scale, Composite composite ) {
List<ScaleData> result = newLinkedList();
for( Control control : composite.getChildren() ) {
ScaleData data = ( ScaleData )control.getLayoutData();
if( data != null && !data.on( scale ).hasPredecessor() ) {
result.add( data );
}
}
return result;
}
private static int calculateColumnWidth( Scale scale, ScaleData head ) {
int result = 0;
for( ScaleData element : getTiedElements( scale, head ) ) {
Rectangle margin = element.on( scale ).getMargin();
result = max( result, computeSize( scale, element ).x + margin.x + margin.width );
}
return result;
}
private static List<ScaleData> getTiedElements( Scale scale, ScaleData firstElement ) {
List<ScaleData> result = newLinkedList();
result.add( firstElement );
if( firstElement.on( scale ).hasSuccessor() ) {
ScaleData successor = firstElement.on( scale ).getSuccessor();
result.addAll( getTiedElements( scale, successor ) );
}
return result;
}
private static Point computeSize( Scale scale, ScaleData element ) {
ScaleDetail detail = element.on( scale );
Control control = element.getControl();
return control.computeSize( detail.getWidth(), detail.getHeight(), true );
}
private static boolean isLastElementInList( ScaleData element, List<ScaleData> elements ) {
return element == elements.get( elements.size() - 1 );
}
private static boolean hasHeightHint( Scale scale, ScaleData element ) {
return element.on( scale ).getHeight() != SWT.DEFAULT;
}
private static boolean hasWidthHint( Scale scale, ScaleData element ) {
return element.on( scale ).getWidth() != SWT.DEFAULT;
}
}
public interface ScaleListener {
void scaleChanged( ScaleEvent event );
}
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.toArray;
import java.util.Collection;
import org.eclipse.swt.widgets.Composite;
import com.google.common.collect.Sets;
public class ScaleProvider {
final Composite composite;
final Collection<ScaleListener> listeners;
Scale scale;
public ScaleProvider( Composite composite ) {
checkArgument( composite != null, "Parameter 'composite' must not null." );
this.listeners = Sets.newHashSet();
this.composite = composite;
}
public Scale getScale() {
Scale oldScale = scale;
scale = Scale.valueOf( composite.getSize().x );
if( scale != oldScale ) {
notifyListeners( composite, scale, oldScale );
}
return scale;
}
public void addScaleListener( ScaleListener listener ) {
listeners.add( listener );
}
public void removeScaleListener( ScaleListener listener ) {
listeners.remove( listener );
}
private void notifyListeners( Composite composite, Scale newScale, Scale oldScale ) {
ScaleEvent event = new ScaleEvent( composite, newScale, oldScale );
for( ScaleListener scaleListener : toArray( listeners, ScaleListener.class ) ) {
scaleListener.scaleChanged( event );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment