Over the past years I had the opportunity to work with various widget toolkit in java, among them the old AWT and Swing, the new JavaFX or out of curiosity SWT and QtJambi. And we also find web-like widgets toolkit like JSP,JSF,JQuery,GWT…
There are many, some are dead, some are going fine, still none really slove all the simple needs I had.
AWT is very limited and has no android support
Swing is getting old, requires a lot of efforts to extend and has no android support
JavaFX don’t support OpenGL pane, has a desatrous image API, an obscure future and has no android support
SWT and QtJambi are not always friendly to use and require JNI bindings.
Web-like… well no real layouts, messing code html/css/less/js, browser issues, techs changing every 2years, slow and a pain to debug…
Things are always more or less the same, lucky for me I work in a domain which also has a lot of rendering : GIS (Geographic Information System). GIS or Maps unlike computer science are far older and have many decades of experience in semiology and symbology. Specifications related to rendering such as IHO S-52, OGC Symbology encoding or ISO Portrayal Service are way more complex and advanced. I used those as reference to define something new, as a result the Unlicense-Lib widget toolkit doesn’t use CSS but something new which looks pretty much like CSS but far more efficient and adapted for widgets, I called this new format 'Rules-Style' (files in *.rs) which you will discover later in those tutorials.
Without any more waiting here is the Hello world frame code :
Frame frame = FrameManagers.getManager().createFrame(false); frame.setSize(800,600); frame.setTitle(new Chars("Hello World")); frame.setVisible(true);
If you are not familiar with Unlicense-Lib, you must know that the project do not use any API for the standard JVM, everything has been rewrite from scratch, including 'String' which are called 'Chars' here and you will see how this API change can be a really bonus when dealing wih internationalisation.
Widgets all share a set of commun properties and behaviors.
un.engine.ui.Widget is the parent class of all widgets.
The toolkit work like a scenograph,a Widget is a SceneNode, which implies it can have a parent and a NodeTransform from parent to node. Unless the parent is a WContainer with an AbsoluteLayout you should not have to play with the NodeTransform.
The common properties of all widgets are :
The visibility state tells if the widget should be displayed or not, if it is not visible, it can not be interacted with either.
The enable state is used by interactive widgets such as WButton or WTextField to disable user interaction. This propery is also used on other widgets to indicate they are unused or uneffective at this time.
Widget sizes are defined by their View object, the view always compute the optimal minimum, best and maximum sizes. Still it can be necessary to enforce other sizes for various reasons.
Extents extents = new Extents(); extents.setMin(10,10); extents.setBest(100,100); extents.setMax(1000,1000); widget.setOverrideExtents(extents);
Modifying the sizes is discouraged because it does not take in consideration the size properties, such as the font, borders, margins,… The layouts and layoutconstraints and style offer a wide range of configurations which should be enough for all general cases.
The style defines the rendering properties of the widgets and it’s children, For more informations see the >>Style section.
Reserve space only affect the parent layout when the widget is not visible. This property answers to the question : 'Should there be an empty area when the widget is not visible ?' If the reserve space property is true then there will be an empty area, in the other case the parent layout will do as the widget did not exist and will use allocate the area to other widgets.
The layout constraint defines the placement properties of the widget inside the parent WContainer. The parent layout will take in consideration those constraints to properly size and place the widget.
WLabel north = new WLabel(new Chars("North")); north.setLayoutConstraint(BorderConstraint.TOP); WLabel south = new WLabel(new Chars("South")); south.setLayoutConstraint(BorderConstraint.BOTTOM); WContainer parent = new WContainer(new BorderLayout()); parent.children().add(north); parent.children().add(south);
Each layout expect a different layout constraint, if an incorrect constraint is used the layout may use a default placement or ignore it.
The WLabel is the most used widget of all time, it is a text with an optional graphic element, image, glyph, whatever.
WLabel lbl = new WLabel(new Chars("Hello World!"));
With an image associated
Image image = Images.read(Paths.resolve("file>/home/me/my_image.png")); WGraphicImage graphic = new WGraphicImage(image); WLabel lbl = new WLabel(graphic, new Chars("Hello World!"));
The layout API isn’t part of the Widget API therefor it can be used for many other purposes like graphs, charts, UML-like and so on…
A layout works with Positionnable objects and a Widget is a Positionable.
To use a layout you must first have a parent widget which will make use of the layout to place it’s children widgets. This widget is called a WContainer.
WContainer parent = new WContainer(new BorderLayout());
WContainer parent = new WConainer(); parent.setLayout(new BorderLayout());
The BorderLayout organize widgets in five different positions.
Border elements (TOP,BOTTOM,LEFT,RIGHT) will have there best size used and the CENTER will have what remains. If there is not enough space for all widgets to obtain at least there best size then the layout try to evenly allocate space for each widget.
This layout expects widgets to have LayoutConstaints of type BorderConstraint.
WContainer parent = new WContainer(new BorderLayout()); parent.addChild(new WLabel(new Chars("TOP"), BorderConstraint.TOP); parent.addChild(new WLabel(new Chars("BOTTOM"), BorderConstraint.BOTTOM); parent.addChild(new WLabel(new Chars("LEFT"), BorderConstraint.LEFT); parent.addChild(new WLabel(new Chars("RIGHT"), BorderConstraint.RIGHT); parent.addChild(new WLabel(new Chars("CENTER"), BorderConstraint.CENTER);
The GridLayout places widgets on a regular grid.
The grid can have an arbitrary number of columns and rows. Widgets children are place row after row starting from the top until all cells are used. Unused cells will remain empty.
WContainer parent = new WContainer(new GridLayout(2,3)); parent.addChild(new WLabel(new Chars("(0,0)"),null); parent.addChild(new WLabel(new Chars("(1,0)"),null); parent.addChild(new WLabel(new Chars("(0,1)"),null); parent.addChild(new WLabel(new Chars("(1,1)"),null); parent.addChild(new WLabel(new Chars("(0,2)"),null); parent.addChild(new WLabel(new Chars("(1,2)"),null);
It is possible to specify -1 for the number of columns or rows but not for both. The layout will create then use has many columns/rows as needed to fit all widgets.
WContainer parent = new WContainer(new GridLayout(1,-1)); parent.addChild(new WLabel(new Chars("(0,0)"),null); parent.addChild(new WLabel(new Chars("(0,1)"),null); parent.addChild(new WLabel(new Chars("(0,2)"),null); parent.addChild(new WLabel(new Chars("(0,3)"),null); parent.addChild(new WLabel(new Chars("(0,4)"),null);
This layout do not expect any specific LayoutConstaint on the widgets.
The style syntaxe is close to JSON. Styles are divided in two, Rules and Properties.
Here is how we define a margin around a widget :
margin : 10;
to apply it on a widget :
Chars style = new Chars("..."); lbl.getStyle().getSelfRule().setProperties(style);