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. Layouts are defined entirely through configs.
Layouts can be chosen by explicitly calling the layout.restore command, through the ‘’Layout Windows’’ or ‘’layout.createOrClose’ commands, from the Layout menu, or from the Switcher Bar at the top of the window. A layout can also contain a Viewport Group, which is a special viewport type that contains another layout.
A “base” layout represents a kind of idealized version of the layout, and is what you save to the config when you choose ‘’Save Layout’’ from the menu. A layout can be reverted to the base version at any time. An “edit” version is saved automatically when you quit the application or switch to another layout, and stores any changes such as divider positions, viewport options, any changes to which viewports the layout contains, and so on. This edit version is also what is loaded when you switch to this layout again.
‘’’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 or application state, so they are “viewports”. Individual viewports may then reference other constructs. For example, what a Form View displays is determined by the form set in its viewport options.
Viewport types are defined in code. For plug-ins, this is limited to Custom Viewports that wrap a Qt widget, and Tree Views, while all the other viewport types are part of modo itself.
A viewport type and its settings are stored as a ‘’viewport preset’’ in the config. These presets are used both as a way to store the viewports so that you can instance the in other layouts, as well as actually stored by the layouts themselves to represent the state of the viewport in that layout. This makes the term “preset” a bit of a misnomer due to the dual usage.
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 Windows command.
Viewport presets are referenced in their host layout by their ‘’’hash’’’. Hashes are arbitrary case-sensitive strings used to uniquely identify something in the configs (and sometimes elsewhere in the application). There are few rules, other than that a hash be unique and have no spaces and limited special characters (alphanumeric is the best way to go). While often pseudorandom, it is legal to use whatever reasonable string you want, such as words or short sentences (with no spaces), just as long as the string is unique with respect to all other hashes in that class.
The same viewport preset can be used in multiple layouts, in which case both layouts are referencing the same preset – making changes in one layout will save the viewport state to that preset, and loading the other layout will restore that same viewport state. This is useful if you want the same viewport state in multiple layouts.
Note that duplicating a layout assigns new hashes to every viewport and nested layout within the layout hierarchy, and thus completely divorce the new layout from the old one. This is done because it is assumed that you want to create a new layout using the original as a basis, but not reference the original layout in any way.
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.
Descriptions about this behavior and its setup can be found here: Collapsable and Hideable Viewports.
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.
- ‘’’Command System’’’ are plug-ins that are used in multiple ways in modo’s interface:
‘’’Form System’’’ 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 Dialog 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.
‘’’CustomView’’’ 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 System 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 based 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.
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.
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.
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 List example are used to provide a list of other commands to insert into a form.
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 Form Categories and Groups 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.