Overview
Flux is a framework for building general purpose desktop applications with the fwt toolkit. This chapter covers how to use the flux APIs for creating new applications and plugins. See docTools for how to setup and use flux as an IDE.
Flux is based on a browser navigation model. Users navigate Uris which are resolved to resources. Users interact with resources through views which are used to view and edit the resources. The next sections cover resources and views in detail.
The Flux chrome works very much like a traditional browser. It handles navigation, history, and view tabs. It provides a plugin model for adding sidebars which are used independently of views.
Resources
Resources are the objects a user browses, views, and edits in a flux application. Resources are modeled by the Resource
class. The Resource
class typically wraps some other Fantom object to define how it is presented in the user interface such as its name, icon, the available views, and children if tree structured.
The most common resource is FileResource
which is used to wrap a File
instance. FileResource uses MIME type to map files to their icon and available views.
Views
Views are user interface components which subclass from ContentPane
. A view is how a user interacts with a resource for visualizing and/or editing. Views are model by the View
class.
View Lifecycle
The view lifecycle typically involves these steps:
- Construction: when your view's constructor is called you don't have access to the parent frame or resource; so typically you won't do much in your constructor.
- onLoad: the
onLoad
callback is invoked when the view should load its UI state from the resource. At this point both View.frame
and View.resource
are available. However your view has not been mounted into the into the widget tree yet.
- dirty: if your view is an editor, then you should set the
dirty
field to true when the user makes a modification to the resource.
- onSave: the framework will determine when the user wishes to save your view. This might happen from the Save or Save All commands, or might occur from a close prompt. The view should save the UI state back to the actual resource - for example if editing a file, then the view will rewrite the file. Once
onSave
completes, the dirty flag is set back to false.
- onUnload: this callback is invoked when the view is being unloaded, typically because navigating to a new resource or view.
In addition to the above, views receive the onActive
and onInactive
callbacks when the view is selected and unselected as the active tab. The onActive
callback is used to enable predefined view managed commands such as find and replace.
Navigation
When a user navigates to a Uri, the flux runtime performs the following process to resolve the resource and views:
Uri Resolution
The step of navigation is to resolve the Uri to a normal Fantom Obj
using the standard naming facilities. This means that any Uri which has been mapped via a UriScheme
is available for navigation.
Resource Resolution
Uri resolution maps the Uri to a normal Fantom object. But we really want a Resource
instance so that we know how to display the object in our UI. If the object from uri resolution is already an instance of Resource, then we don't need to do anything special.
If the object from uri resolution is not a Resource, then we search the indexed props for a Resource keyed by the flux.resource.{qname}
prop for the object's type (and we search its base classes). The FileResource
class is automatically registered to handle sys::File
. For example to map an instance of AppRec
to AppResource
:
class AppResource : Resource
{
new (Uri uri, AppRecord rec) { this.uri = uri; this.rec = rec }
override Uri uri
override Str name() { ... }
override Image icon() { ... }
AppRecord rec
}
// build.fan
index = ["flux.resource.myPod::AppRec": "myPod::AppResource"]
If you map a Resource subclass, then you need to ensure that your make
constructor takes Uri as its first parameter, and the mapped object as its second parameter.
View Resolution
Once we've resolved the uri to a resource instance, the next step is to discover which views are available to view and edit the resource. The list of available views is deferred to the Resource.views
method. The first view in the list should be default view.
The default strategy for resolving views by Resource.views
is to query index props for any View
keyed by the flux.view.{resource}
using the resource's type. For example to register AppView
on AppResource
:
class AppView : View
{
override Void onLoad() { ... }
}
// build.fan
index = ["flux.view.myPod::AppResource": "myPod::AppView"]
If working with files, then you typically register a view by MIME type
. The follow indexed prop keys are used:
// register on text/*
flux.view.mime.text
// register on text/xml
flux.view.mime.text/xml
internal class MyXmlView : View
// register on multiple MIME types
flux.view.mime.text/xml
flux.view.mime.text/html
View Query
The default view used to work with a resource is the first view in the list returned by Resource.views
. Alternate views are selected in the Uri's query map with a view
key and qualified view typename as the value:
file:/home/file.txt?view=fluxText::TextEditor
Typically the flux chrome will handle displaying the list of available views and allowing the user to choose alternate views.
A SideBar
is a plugin which exists independent of resources and views. Sidebars are widgets which may be opened along the left, bottom, or right edges of the view pane. SideBars are registered with the flux.sideBar
indexed prop. A very simple sidebar:
class ExampleSideBar : SideBar
{
new make() { content = Label {"Example SideBar" } }
}
// build.fan
index = ["flux.sideBar": "myPod::ExampleSideBar"]
There is one instance of a SideBar per registered type for each Frame. The SideBar is instantiated in memory the first time the user shows it within a given frame. After that the instance is mounted and unmounted from the frame using show
and hide
.
Typically the user manages the sidebars via the View menu. The id for each sidebar command on the View menu is that SideBar's type name, and localization is based on the type's pod. You can setup icons and accelerators using the normal conventions via localization.
SideBars receive a set of callbacks to manage their lifecycle and respond to view selections:
- onLoad: called when loaded into memory; good place to load persistent state
- onUnload: called when unloaded from memory; good place to save persistent state
- onShow: called when shown on frame
- onHide: called when hidden on frame
- onActive: called when user links to new resource or selects new tab
- onInactive: called when current view is replaced with new view or tab