User Interface and Plug-Ins
modo's user interface is very likely built a fair bit differently than what you might expect from other applications. Instead of directly adding widgets to the interface, they are primarily built by adding commands to forms, with the control type inferred by the command's argument datatypes. It is possible to create full on Qt widgets (as of modo 901) and put them in the UI, but for most basic controls you'll write commands.
The root-level interface unit in modo is a window, just like in every other application. There are a few kinds of windows:
- Modal Dialogs exist for specific purposes:
- Command dialog, which are modal dialogs used by the user to fill in arguments to a command.
- System dialogs, which are also modal dialogs but defined by the OS, commonly used for for loading and saving files.
- Layout Windows contains a collection of resizable viewports split by dividers:
- Normal windows, such as the main window. These are non-modal and have normal window decoration. Only the main window has a menu bar on Windows/Linux.
- Palette windows are similar to normal windows, but they have slightly different window decoration. On OS X, palettes are hidden when switching away from the application. There is also a keyboard shortcut (the backtick/tilde key) to hide all palettes.
- Popover Windows are special constructs that have unique, minimal window decoration. They can also be "pointed at" the control that was clicked to open them.
- Popover forms contain a form, and are automatically sized to fit the contents of the form. They automatically dismiss when the user clicks off of them, but can be pinned via a button in the top-right corner to keep them open. If the form has no visible controls, the popover is automatically hidden.
- Popover layouts are somewhat similar to normal windows and palette windows, and cannot be pinned. They are used for special cases like the Color Picker.
- Popup Windows are used for context menus and popups (called "dropdowns" in some other UI toolkits), and are created automatically from forms and certain command arguments.
A layout contains any number of viewports split by dividers, and is what you put in a normal window, palette window, or popover layout window. The layout system itself is sometimes called "frames" or the "frame system", as it represents the structure that viewports are attached to.
Viewports are defined as any view in the layout, be it a 3D view, a Form View, an Item List, or anything else. All of these are views on the scene data. Individual viewports may then reference other constructs. For example, what a Form View displays is determined by the form set in its viewport options.
A newly-created window contains a single "none" viewport. This viewport can be split to create a new viewport, which can then be changed to a different viewport type. .Repeating this process allows you to build complex layouts. It is very common to save a layout and instance it into a newly created window, most often by using the layout.window command.
There are two special viewport types that contain other viewports:
- Viewport Groups (aka "subframes") are a viewport that contains another layout. This can provide powerful nesting features.
- Tabbed Viewports show only one viewport at a time, but which viewport is shown can be changed by using a tab bar at the top of the view. This bar can be hidden if you to use other controls to switch tabs. Tabbed viewports cannot be directly nested (you can't have tabs of tabs), and cannot contain more than one viewport, but you can add a Viewport Group to a tab to work around both of these limitations.
Viewports can be resized by dragging their dividers, or the dividers can be locked in situations where resizing doesn't make sense. Many viewports detect when they are below or above a certain size and can significantly change their layouts to better represent their data for the available space.
The user can maximize a viewport, temporarily replacing all other viewports in the layout with just that viewport. This is most useful with viewport groups, and for that reason you'll find that modo's standard layouts often contain multiple viewport groups.
Due to all the extra borders that viewport groups create, there is a Tidy Layouts preference that makes the layouts look cleaner by hiding the viewport group borders. It is advisable to turn this off if you're going to make a number of changes to the layout so that you can better see its true structure.
It is also possible to hide/show viewports in a layout with commands, and collapse them down to a simple header that can be clicked to expand. Dividers can be marked as supporting collapsing in particular directions, thus giving the user the option to collapse particular viewports themselves. Due to the complex nature of the layouts that can be created, this functionality needs to be used carefully or you can hit a pathological case that can break the layout.
Methods for Creating Interfaces
There are a few mechanisms for creating user interfaces in modo. Note that all of these involve writing some code (usually commands) and then using configs to place them in the interface:
- Layouts are stored in configs and populate normal windows, palette windows and popover windows.
- Commands are plug-ins that are used in multiple ways in modo's interface:
- Forms are defined in configs and built from command strings. Forms are used to construct context menus and popover forms (via the attr.formPopover command) , popup menus (when embedded in anotherr form), and toolbars and properties views (when in a Form View placed inside of a layout). If the command doesn't contain any queriable arguments, it is displayed as a button. If one of the queriable arguments is marked with a question mark (?) in the command string, a control will be created in the form based on the argument's datatype. This allows color controls, edit fields, check boxes and other kinds of controls to be created in a form.
- Command dialogs open when executing a command that does not have all of its required arguments set, or when using the "?" command prefix. This modal dialog allows the user to set the arguments in a user-friendly manner. The command's DialogFormatting() method can be used to re-order the arguments in the dialog, gang edit fields, insert dividers and hide arguments that don't need to be shown in the dialog. There are also a series of commands explicitly for creating certain kinds of dialogs, in addition to SDK calls to do the same thing.
- Keyboard shortcuts are set up through the Input Editor with command strings similar to the ones used by forms. These can simply execute a command directly, open a command's dialog, or toggle or increment the value of a command's argument before executing it.
- TreeViews are a kind of plug-in viewport, and can do all the same things that the application's native trees can do.
- Custom Views allow Qt widgets to be created as viewport that can then be placed inside layouts. These are considered an option of last resort, or if you need to do something that doesn't fit into forms or trees, or in cases where a common interface is used across multiple applications and consistency with them is desired. Qt widgets do not have exactly the same look-and-feel as modo's native interface, and do not support input remapping, user customization and other modo constructs, which is why they tend to be discouraged for shipping products, but sometimes they're the only real option.
Command and the User interface
Commands are by far the most common way a user interface is built in modo. At their simplest, they can be put in Form Views to create buttons, or mapped to keys, and will simply perform an action when clicked. By adding required arguments to the command, it can open a dialog from which the user can fill in the arguments in a friendly way. Commands with queriable arguments can be added to forms to create different control types baed on the argument datatype. Commands are also commonly used by scripts, and implicitly make your plug-in scriptable.
Because of how widely commands are used in modo, you should expect to write a number of them. In some cases you will just use existing commands when creating new forms, such as tool.attr for tool properties, and item.channel for item properties. Preferences and modal dialogs requesting a single value are often created through User Values and their associated commands.
Commands are intended to be self-contained. Command Help configs provide user strings for the command and arguments, which are presented in the command's dialog, the Command History viewport and in Form Views and context menus. Commands also expose notifiers, which are used to tell forms when their control needs to be updated with a new value or enable state. These all ensure that no matter what context a command is placed in, user-friendly strings are always available and that it behaves as expected.
Forms are the primary way of getting information from the user in modo. You do not explicitly layout the contents of a form, individual placing controls a specific pixel locations or setting up struts and springs. You also do not define forms in code; they are defined entirely through configs. This is because the user is able to customize forms through the Form Editor, adding controls to the form or putting your commands in their own form, or mapping them to keys as they see fit.
Adding Controls to Forms
Forms are populated with commands. The Form Editor provides a way to create forms and add commands to them. Each form also contains hints indicating how it should be laid out, such as creating a horizontal toolbar, a vertical toolbar or a properties form, and control higher-level options like if icons are shown for buttons and the size of the icons. Sub-forms can be used to further organize controls, allowing horizontal toolbars to be nested inside vertical toolbars, and to gang edit fields together.
The kind of control created from a command string depends on the command string. If no arguments are marked for query (i.e.: it doesn't contain a "?"), it create a simple button. Arguments that are marked for query create controls based on their datatype. For example, a queried "boolean" datatype argument will show a checkbox, while a "distance" argument will show an edit field showing distances, and a "color" argument will show a color control.
Dividers can also be inserted into forms, either with or without labels. Labeled dividers can be clicked to collapse all controls to the next labeled divider.
Vertical tabs provide a way to create groups of controls. When the root form displayed in a Form View is set to the vertical tabs style, the forms it contains will each be considered a separate tab.
Embedding Viewports in Forms
Some viewports can be embedded into forms. Prime examples are the Embedded Gradient Editor and the Preset Browser. Some viewports can be embedded directly, if they have the appropriate support, while others can only be embedded by wrapping them in a viewport preset first. Viewports that support embedding implement a number of extra methods to better handle being in a form, such as drawing a label and presenting arguments as controls in the Form Editor.
Entire forms can be made context sensitive by applying filters to them, although the existing filter system is somewhat limited and arcane and only useful for pre-existing use cases like preferences, tool properties and item properties. Using commands as filters (modo 10.2v1 and above) provides more flexibility, where any command's enable state can be used to decide if the form is shown, or a command with a queried boolean argument that is both enabled and returns true. Individual controls can also be hidden and shown based on their command's enable state by toggling the Show When Disabled option.
End User Modification
Forms can be modified by the user. Since any change to a form results in the entire form being saved to the user's config, it is not advised that you ship alternate versions of a form with your plug-in. It is also illegal to modify forms with code, including the various attr.??? commands. However, in certain situations procedurally-generated lists of controls is useful, such as user channels and item tags. For these situations, Form Command Lists are used to provide a list of other commands to insert into a form.
Extending Forms Without Modifying Them
If you want to add your own form to an existing form, you shouldn't modify the form. Instead, you should add your form to the head or tail category of the target form. This does not make any changes to the form itself, but rather declares that your form should be at the top or bottom of the other form.
Forms can be displayed in a few ways:
- Form Views are the most common, where a viewport is placed inside of a layout in a window and is set to display a particular form.
- Popover forms, which are forms presented in an automatically-sized popover window, are created with the attr.formPopover command.
- Popup forms, including context menus, are also created with attr.formPopover when the form's style is set to popup.
- Popup menus and popover forms can be created just by putting a form inside of another form to create popup control or button by setting that form to the appropriate style.
It's worth noting that the main menu bar is also defined through a special, and behaves basically like a popup.