-
Notifications
You must be signed in to change notification settings - Fork 2
Quick
Quick is a toolkit that was originally designed for creating user interfaces. Its use of the Qonfig and Expresso APIs make it extremely quick, easy, and intuitive for developers to create complicated user interfaces with many responsive, stylish, interacting widgets.
Quick is the end result of a thought process that began more than 15 years ago. The original idea was to create desktop user interfaces by strict XML: similarly to HTML, but easier. The original implementation was an attempt at a brand new user interface implementation from the ground up, based on java's Graphics rendering and nothing else, doing all the eventing, layout, rendering itself.
Eventually I determined it would be better to create an API that could be useful outside of desktop user interfaces, e.g. for web or smartphone apps. Hence I rewrote Quick as an API that compiles XML structures into structures that expose observable model data for each widget, allowing for the possibility of multiple UI implementations that use Quick structures.
This is not necessarily to facilitate write-once-run-anywhere user interfaces that can run without modification on a desktop, web page, or smart phone; a good user interface written for one is likely to be abominable in another. Rather, Quick is an attempt to create a standard user interface language that is extremely fast and easy to work in, which can be used to create user interfaces on many different platforms.
The result is, however, I believe even more than that. I believe that Quick has become a language that can potentially be used to create more than just user interfaces, but potentially interfaces of many kinds, or other kinds of applications altogether. Some other potential uses for Quick may be:
- Binary or REST socket- or HTTP-based server interfaces, with elements representing methods that may be called by remote applications and parameters to those methods. Parallel client interfaces might be developed in Quick as well.
- Software services, which publish observable structures for calling code to use.
For now, the only Quick implementation is Java Swing. Perhaps one day I or others may develop other implementations.
Quick is designed in a series of stages.
Layer Name | Input | Output | Exception(s) |
---|---|---|---|
XML Parsing | Binary or Text stream | StrictXmlReader | IOException, TextParseException |
Qonfig Parsing | StrictXmlReader | QonfigDocument | QonfigParseException |
Qonfig Interpretation | QonfigDocument | ExElement.Def | QonfigInterpretationException |
Expresso/Quick Interpretation | ExElement.Def | ExElement.Interpreted | ExpressoInterpretationException |
Instantiation | ExElement.Interpreted | ExElement | ModelInstantiationException |
Rendering | ExElement | Rendered and displayed UI |
Parses XML from a stream, validating only XML syntax
Validates the XML with Qonfig and produces a Qonfig-structured document that can be easily processed.
Interprets the Qonfig document to produce an element definition. Expressions are parsed for syntax only. No type information is inferred and no reflection done. The output of this stage corresponds to the first layer of the Expresso API.
All the piping for the data flow is put together. Types are resolved, reflection is performed. The final result is capable of producing the structure that will be used in the application, but contains no model value instances. Hence multiple calls to the Instantiation method on a single Interpreted instance with independent inputs will produce independent copies. The output of this stage corresponds to the second layer of the Expresso API.
Produces the structure that will actually be used in the application. This structure contains all the values linked together and initialized correctly. It can be used to power a service or further interpreted to construct some other observable structure or user interface. The output of this stage corresponds to the third layer of the Expresso API.
Before this point, all the different layers and stages contain references to each other. This makes the APIs easier to use, since the source information is readily available. However, there is a clean break between the Interpretation and Instantiation stages, such that the structures produced by instantiation contain no references to the results of the previous stages. This means that after instantiation, the references to the results of the previous structures can be released and garbage-collected. These previous structures tend to be very heavy with all the utility they provide. The instantiated results tend to be much leaner, containing only the minimum number of references to do their simple jobs with as much performance as possible. This clean-up is especially useful because the next and last stage, rendering, may require a lot of memory.
This clean break cannot be enforced, but an attempt was made to clear all structures generated by Quick and Expresso instantiation of references to earlier stages, and testing will be done periodically to ensure this remains the case.
Interprets the structure into a user interface, using all the observable values, collections, etc. as models to the various widgets.
This stage is not actually a part of the Quick API, but rather must be implemented separately for each environment in which Quick is to be run. A Java Swing implementation is provided which renders Quick widgets as widgets in a 2D desktop user interface.
Integral to Quick is the notion of styles. Quick styles share some commonality with Cascading Style Sheets, which are the best way to change the appearance of elements in an HTML document. Like CSS:
- Quick styles may be specified on the element they target or in separate style sheets.
- Quick styles operate on style attributes, like background color or text size.
- Quick styles from style sheets can be targeted to certain element types or declared classes (style-sets in Quick).
Unlike CSS, however, Quick styles:
- Can have values that are expressions, changing with model values in the application
- Can have conditions (also dynamic expressions) that activate or deactivate the style
- Are specified using XML just like Quick itself, instead of in a separate language within XML attributes
Quick styles were designed with user interfaces in mind, but nothing in the Quick Style API is specific to user interfaces. Style attributes can be created for any property so that it can be specified via extremely flexible Quick Styles.
As mentioned, Quick is built on the Qonfig XML validated language. The Qonfig language consists of toolkits, each of which specify a set of elements and add-ons. In Quick toolkits, elements typically (but not always) represent types widgets. There are 3 Quick toolkits that are provided by default:
- Quick-Core, which defines basic types which most other Quick toolkits should use to define their widgets. The core toolkit does not define any actual widgets at all, but rather defines the <widget> type itself and what widget types should look like in Quick toolkits, as well as event listeners, style properties, etc.
- Quick-Base extends Quick-Core and defines a core set of widgets that most Quick implementations should support. These include:
- Label
- Text field
- Text area
- Button
- Check box
- Radio button
- Toggle button
- Combo box
- Spinner
- Slider
- Progress bar
- Table
- Tree
- Tabs
- Scroll pane
- Split pane
- Box (a generic container with a custom layout) and several basic layouts
- Field Panel, a container that lays out contents as fields in a form
- A few dialog types
- Menu bar and menus
- Quick-X extends Quick-Base and defines a few widget types which are not always present in standard widget toolkits (like Java Swing), but which can be very useful in making powerful user interfaces:
- Collapse pane, a panel with a single content widget and a title bar which toggles its content's visibility
- Tree Table, a table whose first column is a tree
- Combo Button, a button that displays a popup list when clicked, allowing the user to select from a set of actions to perform
- Multi Slider, a slider that can have any number of handles (thumbs), also with highly customizable rendering
- Tiled Pane, a widget that displays each value in a collection as a rendered widget, similar to a table with a single column, but with more customizable layout, complex rendering, and intuitive interaction.
User interfaces are built by creating XML documents with elements of these types.
The following is an example of a complete but extremely simple Quick user interface.
<?xml version="1.0" encoding="UTF-8"?>
<!--
Root XML element.
Declare all Quick toolkits to be used with 'xmlns:<name>="Toolkit-Name vM.m"'.
Toolkits extended by declared toolkits will be available as well.
'with-extension="window"' brings in the 'title' attribute (and 'width' and others)
so we can set the title on the window.
Set the 'width' attribute just so the window is big enough to see the title
-->
<quick xmlns:base="Quick-Base v0.1" with-extension="window" title="`My App`" width="240">
<!--
The head section of the document, where we define model data
and other non-widget information such as style-sheets
-->
<head>
<models>
<!-- Define a model named "app" -->
<model name="app">
<!--
Define a value named "text".
The initial value here will set the type of the value (String),
so it doesn't need to be specified explicitly.
-->
<value name="text" init=""This is some text"" />
</model>
</models>
</head>
<!--
The root widget of the application, a box (simple container with a customizable layout)
with an inline-layout (lays out widgets one after another in a single dimension,
in this case vertically)
-->
<box layout="inline-layout" orientation="vertical">
<!-- This label has constant text specified in the value of the element -->
<label>The value you enter in this text field...</label>
<!-- This text field allows the user to edit the 'app.text' model value -->
<text-field value="app.text" />
<label>...will be reproduced in the label below.</label>
<!--
This label's text is the contents of the 'app.text' model value,
updating when the model value is changed
-->
<label value="app.text" />
</box>
</quick>
When executed, this user interface will look like this:
When the user edits the text field (and presses enter or the text field loses focus), the text in the final label will also change.
Very simple Quick applications can run without a line of Java code, but Quick is not designed to handle complex business logic. A pattern for complex Quick application design is to create a Java class to handle complex business logic and interface with it using Quick.
A typical example of this would be to define a model value in the <models> of the <head> section of the Quick document to create an instance of the business logic class that will be used by the Quick application, e.g.
<models>
<model name="app">
<constant name="biz">new MyBusinessImplementation()</constant>
</model>
</models>
The value would then be accessible to expressions in the document via "app.biz". Expressions can interact with the business logic by:
- Calling methods (e.g. "app.biz.doSomething()"
- Accessing fields (e.g. "app.biz.someField")
- Passing values (e.g. "app.biz.setThis(some.expression)")
The business logic can define fields and method parameters and return values that are observable structures (see Observables, ObservableCollections, and Observable(Multi)Maps that Quick will treat as normal model structures, enabling seamless, intuitive coordination between Quick and the business logic in response to events or actions that occur in the UI or the back end.
The Quick format is very simple to write, but it is built using many layers, some of them with great complexity, so that a great deal of code is needed to interpret the configuration of an application and run it. To make this easier, the QuickApp class was created.
QuickApp is a main class whose function is to read a Quick file and perform all the work of parsing, interpreting, instantiating, and rendering it. QuickApp is agnostic of the nature of the type of rendering.
QuickApp is an extension of QonfigApp, and accepts the same <toolkit>, <special-session>, and <interpretation> elements to parse, interpret, and instantiate the Quick data structures.
A least one additional <quick-interpretation> element must be specified, whose text is the name of a class implementing org.observe.quick.QuickInterpretation. This class helps QuickApp configure transformation for Quick data structures into application behaviors.
To use QuickApp, create a file containing references to all toolkits, session implementation classes, and Qonfig and Quick interpretation classes needed to interpret and execute the application. Here is the app file for the simple demo above.
<!--
Reference to the Quick-App Qonfig toolkit and the Quick file defining the application to run.
The Quick file is in the same directory as this file, so no path prefix is needed.
-->
<quick-app xmlns:app="Quick-App v0.1" app-file="wiki-demo.qml">
<!-- References Qonfig toolkits used by the application -->
<toolkit def="/org/qommons/config/simple-qonfig-reference.qtd" />
<toolkit def="/org/observe/expresso/qonfig/expresso-core.qtd">
<!--
The Expresso-Core toolkit defines the "expression" external type,
so we need to specify its implementation
-->
<value-type>org.observe.expresso.qonfig.ExpressionValueType</value-type>
</toolkit>
<toolkit def="/org/observe/expresso/qonfig/expresso-base.qtd" />
<toolkit def="/org/observe/quick/style/quick-style.qtd" />
<toolkit def="/org/observe/quick/quick-core.qtd" />
<toolkit def="/org/observe/quick/base/quick-base.qtd" />
<!-- Default Quick classes all use the same session implementation designed for Expresso -->
<special-session>org.observe.expresso.qonfig.ExpressoSessionImplV0_1</special-session>
<!-- Generally, one Qonfig interpretation per toolkit is needed -->
<interpretation>org.observe.expresso.qonfig.ExpressoBaseV0_1</interpretation>
<interpretation>org.observe.quick.style.QuickStyleInterpretation</interpretation>
<interpretation>org.observe.quick.QuickCoreInterpretation</interpretation>
<interpretation>org.observe.quick.base.QuickBaseInterpretation</interpretation>
<!--
These classes interpret the Quick data structures as application behaviors:
in this case, widgets in a Swing UI
-->
<quick-interpretation>org.observe.quick.swing.QuickCoreSwing</quick-interpretation>
<quick-interpretation>org.observe.quick.swing.QuickBaseSwing</quick-interpretation>
</quick-app>
If a Quick application uses the Quick-X toolkit, the following additional elements would be needed:
<toolkit def="/org/observe/quick/ext/quick-ext.qtd" />
<interpretation>org.observe.quick.ext.QuickXInterpretation</interpretation>
<quick-interpretation>org.observe.quick.swing.QuickXSwing</quick-interpretation>
There are two ways to tell QuickApp to load and execute an application:
- Execute the QuickApp main class with the command-line argument "--quick-app=<quick-app-file>"
- Execute the QuickApp main class with a META-INF/MANIFEST.MF file on the classpath containing a manifest entry of "Quick-App: <quick-app-file>
where <quick-app-file> is a reference to the app file like the one above. The reference can be:
- An absolute classpath entry (prefixed with '/')
- A file path, either absolute or relative to the current directory the application is being executed in
- A URL
The first execution method would by typical of execution in development or by a script. The second is provided for packaging a Quick application as a standalone executable jar file.
The quick-app file generally contains all the information needed for QuickApp to create and execute an application, but it is possible to accept command-line input in a Quick application. To do this, the <models> element in the <head> section of the Quick document should contain an <ext-model> element. This element may contain <value>, <list>, or other model structures which will be populated by command-line input from the user.
The names of the values in the <ext-model> will be converted from camel case (a case system where "words" in a variable name are delimited by an upper-case character, e.g. "thisIsAVariable") to kebab-case (where "words" are delimited by dashes, e.g. "this-is-a-variable"). These variables may then be specified on the command line prefixed by a double-dash and postfixed by an equals sign (e.g. "--this-is-a-variable=someValue". The <ext-model> element may also specify named <ext-model> children containing model variables. In this case, the kebab-case-converted name of the model will be prefixed to the variable name, followed by a dot (e.g. "sub-model.this-is-a-variable").
The types of the <ext-model> values may be:
- String
- boolean (or Boolean)
- int or long (or Integer or Long)
- double (or Double)
- An Enum type
- Duration
- Instant
- File
- BetterFile
Default values may be specified for the command-line arguments via a 'default' attribute. If a default is not provided for a <value>, QuickApp will throw an exception and exit if the value is not specified via command-line. "null" is an acceptable default for non-primitive types. <list> variables will be empty if not provided.
It is also possible to allow specification of arbitrary command-line arguments not matched to any <ext-model> variable by specifying a <list> variable with type "String" in an <ext-model> named "$UNMATCHED$". Command-line arguments that cannot be matched to an <ext-model> variable by name will be available to the application as an element in this list.
Command-line arguments will be parsed and available to the application via the specified model variables in the <ext-model> model.
The ObServe project contains a wealth of simple Quick demo applications using almost all the various widgets, style options, and other tools provided in the Expresso and Quick toolkits. These are located in the src/test/java/org/observe/quick/swing folder. Each demo is accompanied by a quick-app file, so each is ready to run.
Anyone interested in creating applications in Quick should run and play with these demos and examine the code to understand just how easy it is to create complex, beautiful user interfaces with Quick.
The QWYSIWYG application was developed to help developers understand the flow of data in a Quick document. QWYSIWYG loads a Quick document and displays it parallel to a representation of the XML definition of the document. The XML definition is syntax-highlighted and provides information on various parts of the application:
- Hovering over a model value will display the current value in that model
- Ctrl+Clicking on a model value will jump to the declaration of that value
- Hovering over an element name will pop up a description of the element and the role it is fulfilling in its parent
- Hovering over an add-on name will pop up a description of the add-on
- Hovering over an attribute name or an attribute value or element value will pop up a description of the attribute or value's function as well as its current value
- If the qwysiwyg.qtd toolkit and qwysiwyg-default.qss style sheet are imported by the application, hovering over a widget element in the XML will cause the widget in the user interface to be accented.
QWYSIWYG also provides other tools for understanding the application:
- Watch Expressions, which display the current value of an expression on some element, updating as the value changes in the application
- Watch Actions, expressions that cause custom actions to occur when they turn true
- Style inspection. It can sometimes be difficult to understand why a Quick element's style is or is not changing as you intend. This feature lists all style values for a selected style attribute on a selected element from all the various possible sources (specification on the element, inherited from parent element, inherited from a style-sheet), each listed with its source, condition, value expression, and actual value (only when active). The styles are sorted by priority and the active style value is highlighted. Changes to models in the application show in the table. This enables a complete understanding of why an element looks or behaves the way it does, enabling troubleshooting of style issues.
A "Toolkits" view is also provided which contains a view of each Qonfig toolkit loaded by the application. Each value type, add-on, element-def, attribute, child, and element value in each toolkit is represented with its documentation, hyper-linked to references and inherited information.
QWYSIWYG is written in Quick. It's by far the most complex Quick application packaged in ObServe. As such, examining its source can provide a lot of insight into how to create complex applications in Quick with lots of custom functionality and UI interaction.