Infrastructure
ControlWrapper- create custom widgets which properly encapsulate their base control.Coat- a functional interface for populating an empty Composite.CoatMux- a mechanism for layering and swapping Coats.SwtExec- anExecutorServicewhich executes on the SWT thread.SwtExec.Guarded- anExecutorServicewhich is tied to the lifetime of an SWT widget. Say goodbye toSWTException: Widget is disposedforever! It can also subscribe to any kind of observable (Guava's ListenableFuture or RxJava's Observable), see DurianRx for more info.
SwtExec.async().guardOn(textBox).subscribe(serverResponse, txt -> { textBox.setText(txt); });
Fluent builders
Layouts- all the layouts you'll need in SWT
void textOkCanel(Composite cmp) { Layouts.setGrid(cmp).numColumns(3); // instructions fill the full width Text text = new Text(cmp, SWT.WRAP); Layouts.setGridData(text).horizontalSpan(3).grabAll(); // right-justified ok / cancel buttons Layouts.newGridPlaceholder(cmp).grabHorizontal(); Button btnOk = new Button(cmp, SWT.PUSH); Layouts.setGridData(btn).widthHint(SwtMisc.defaultButtonWidth()); Button btnCancel = new Button(cmp, SWT.PUSH); Layouts.setGridData(btn).widthHint(SwtMisc.defaultButtonWidth()); }
Shells- dialogs without boilerplate
Shells.builder(SWT.DIALOG_TRIM, this::textOkCanel) .setTitle("Confirm operation") .setSize(SwtMisc.defaultDialogWidth(), 0) // set the width, pack height to fit contents .openOnDisplayBlocking();
-
Actions- builder and one-liner:Actions.create("Redo", this::redo); -
LabelProviders- builder and one-liner:LabelProviders.createWithText(Person::getName) -
ColumnFormatandColumnViewerFormat- tables and trees without boilerplate
ColumnViewerFormat<Person> format = ColumnViewerFormat.builder(); format.setStyle(SWT.SINGLE | SWT.FULL_SELECTION); format.addColumn().setText("First").setLabelProviderText(Person::getFirstName); format.addColumn().setText("Last").setLabelProviderText(Person::getLastName); format.addColumn().setText("Age").setLabelProviderText(p -> Integer.toString(p.getAge())).setLayoutPixel(3 * SwtMisc.systemFontWidth()); TableViewer table = format.buildTable(parent); TreeViewer tree = format.buildTree(parent);
Resource management
OnePerWidget- a cache tied to the lifetime of an SWT Widget.ColorPool- a pool of colors tied to the lifetime of a widget.ColorPool.forWidget(widget).getColor(rgbValue)ImageDescriptors- use ImageDescriptors with proper resource sharing.ImageDescriptors.set(btn, imageDescriptor)
Interactive testing
Ideally, all UI code would have fully automated UI testing, but
such tests are time-consuming to write, so they often just don't
get written at all. InteractiveTest
bridges the gap by making it easy to write user-in-the-loop guided tests. Furthermore,
these tests can even be run in a headless enviroment on a CI server, where the test UI
will be opened, then automatically closed after a timeout. This ensures that the tests
are all in working order and ready for a human tester to do final validation.
From ViewerMiscTest.java:
String message = StringPrinter.buildStringFromLines( "- The table and the tree should keep their selection in sync.", "- The table and the tree should not allow multi-selection.", "- The categories in the tree should not be selectable."); InteractiveTest.testCoat(message, cmp -> { TableAndTree tableAndTree = new TableAndTree(cmp, SWT.SINGLE); // get the selection of the tree RxBox<Optional<TreeNode<String>>> treeSelection = ViewerMisc.<TreeNode<String>> singleSelection(tableAndTree.tree) // only names can be selected - not categories .enforce(opt -> opt.map(val -> isName(val) ? val : null)); // sync the tree and the table RxOptional<TreeNode<String>> tableSelection = ViewerMisc.singleSelection(tableAndTree.table); Rx.subscribe(treeSelection, tableSelection::set); Rx.subscribe(tableSelection, treeSelection::set); });
Miscellaneous stuff
SwtMisc- useful static methods.blockForError,blockForSuccess,blockForQuestion, etc. - opens a dialog and blocks for the user's response, can be called from any thread.loopUntil,loopUntilDisposed,loopUntilGet- spins the SWT display loop until some condition is satisfied.systemFontHeight/Width,scaleByFont,scaleByFontHeight- resolution-independent sizes.treeDefControl,treeDefComposite- aTreeDeffor traversing UI elements.setEnabledDeep- sets the enabled status of every child, grandchild, etc. of the given composite.
SwtRx- methods for converting SWT events and models to RxJava Observables.SwtDebug- utilities for debugging SWT events.OS,Arch, andSwtPlatform- detect things about the running system, and manipulate the SWT jars for build tools.- These do not require SWT or JFace, so you can add DurianSwt to your gradle or maven dependencies without needing to also figure out the SWT messiness.
- You can also just copy-paste these straight into your own code - they have no external dependencies.
String installerExtension = OS.getNative().winMacLinux("exe","dmg","sh"); String helperBinary = "driver_" + Arch.getNative().x86x64("", "_64") + ".dll"; String swtJarName = "org.eclipse.swt." + SwtPlatform.getRunning();
ViewerMisc- useful static methods for JFace viewers.singleSelection,multiSelection- returns an RxBox for listening to and setting the selection of a viewer.setTreeContentProvider,setLazyTreeContentProvider- uses a TreeDef to provide the content of a TreeViewer.
Requirements
Durian requires:
- Java 8
- Durian and DurianRx
- Guava and RxJava
- SWT and JFace from Eclipse 4.4+
- SWT and JFace are not included in the Maven POM, but everything else is.
Acknowledgements
- Thanks to David Karnok for contributing an SwtScheduler that honors the Scheduler/Worker contracts.
- Thanks to Moritz Post for his fluent layout idea.
- Formatted by spotless, as such.
- Bugs found by findbugs, as such.
- OSGi metadata generated by JRuyi's [osgibnd-gradle-plugin] (https://github.com/jruyi/osgibnd-gradle-plugin), which leverages Peter Kriens' bnd.
- Scripts in the
.cifolder are inspired by Ben Limmer's work. - Built by gradle.
- Tested by junit.
- Maintained by DiffPlug.
