Collapsable and Hideable Viewports

From The Foundry MODO SDK wiki
Jump to: navigation, search

Collapsing and Hiding Viewports

Starting in modo901, viewports can be hidden or collapsed programatically.

Collapsing Viewports

Most dividers will look like they always have. Some, however, will be wider, with a thick grip and an arrow pointing in one direction on one or both sides of the grip. These are the collapse widgets -- clicking one will collapse the viewports along that divider in that direction. Since we must collapse rectangular regions, this may collapse multiple viewports, possibly outside the bounds you might be expecting. Dragging anywhere in the divider (not just the grip) will resize the viewport as normal.

Collapsed viewports are replaced with a thin rectangular region with a triangle in it; to restore the original viewports, just click on the widget. This collection of viewports is referred to as a set.

Toggling Collapsing Support For A Divider

Any divider can be made wide to support collapsing by right clicking and choosing the collapse direction. Care should be taken with which dividers are made collapsible, as some don't make sense and could result in odd collapsing effects. The menu allows you to choose if a divider can collapsed in zero, one or both directions.

Hiding Viewports

Hiding is like collapsing, but it completely removes the viewport set from the dividers. Hiding and revealing viewports is commonly done through buttons in Form Views.

Under the Hood

Each layout maintains their own lists of collapsed and hidden sets, which are stored in the config between sessions. All sets keep track of where they are in the layout the same way that viewports do, by the dividers they share. This means that they can survive deleted viewports, re-arranged viewports, split viewports and so on, and it is still possible to restore the set’s contents (although if you make enough changes they might not restore in quite where you might expect).

The sets are stored in the layout’s config in a Collapsed list (both for collapsed and hidden sets) in a manner somewhat similar to how viewports are stored in Port entries. The set’s contents (its dividers and viewports) stored in separate CollapsedSet hash entries in the Frame atom, like viewports presets, with the contents being a layout-like list of its dividers and viewport presets. (This all makes sense once you look at a config.)

A simple config hierarchy. The bold bits are the new elements added for collapsed sets:

Frame (atom) Layout (hash)

	    HDiv (list)
	    VDiv (list)
	    Port (list)
	    Collapsed (list)
	      Dividers (atom)
	  Viewport (hash)
	  CollapsedSet (hash)
	    Position (atom)
	    HDiv (list)
	    VDiv (list)
	    Port (list)
	    Collapsed (list)
	      Dividers (atom)

Like viewports, sets have their own hashes, and duplicating a layout generates a new hash for the set. To make sure a command in a form can find the set even if the hash changes, the sets have an expansionKey that does not change. This is detailed further below.

The Commands

Collapsing and hiding use very similar commands:

viewport.collapse show:? viewportFrom:tag viewport:MyTag dir:right expansionKey:MyExpansionKey
viewport.hide show:? viewportFrom:tag viewport:MyTag dir:right expansionKey:MyExpansionKey

The arguments work the same way, so I'll just cover viewport.hide here.

Collapsing/Hiding

When show is false, the viewports will be collapsed/hidden. There are two pieces of information required to find the divider to be collapsed: the viewport’s hash or tag, and a direction to collapse in.

viewportFrom can be set to tag or hash to indicate what the viewport argument represents. Collapsing by clicking a widget in the collapse popover tends to use the viewport’s preset hash, since that’s all we really have to work with. Hiding, especially if a button in the UI, should always use a viewport tag. Using tags ensures that the viewport can always be found, even if its hash changes (which can happen when copying/duplicating a layout, for example).

Searching for a viewport with the hash/tag is done from the current frame (as derived from the currently selected viewport) and its children, then the parent frame hierarchy and its children, and finally all remaining frames. This ensures that if the tag/preset exists in multiple currently open layouts that we’ll probably find the one that you wanted, although you may get incorrect behavior if the same tag/hash is used in multiple visible layouts. If this becomes an issue I can try to look into ways around it, either by allowing the search scope to be more limited or by doing more detailed searches within collapsed sets for the viewports, although I’m not sure what to do about the expansion key (since that only exists if the set is collapsed/hidden, we could find an existing set in the wrong frame unless we support limiting the scope of the search).

After finding the viewport, the dir argument is checked to decide which direction we’re collapsing in. For example, if it is set to left, then the viewport collapses to the left (and thus you clicked on the left-pointing widget on the divider popover over the right edge of the viewport). For viewport.hide, this is opposite the divider that is removed after the viewports in that direction are hidden (ie: hiding to the left would remove the right edge). This makes sense when you see it in action. Just remember it’s always the direction you’re collapsing in, not the side of the viewport that you’re removing.

The expansionKey argument is used to designate a (hopefully) uniquely identifier for this collapsed set so that we can restore it later. As such, it is stored as part of the set itself. This isn’t a dynamic hash, so duplicating the layout will result in the same key existing in the duplicate, and thus your buttons in the forms will still work. This key is defined by the command call itself, and doesn’t already exist in the layout separately.

Expanding/Unhiding

Expanding or unhiding is done with the same command, but this time with show set to true. The viewportFrom/viewport arguments must be set but are ignored, since the viewport won’t exist in the layout at this time. The expansionKey argument is now used to find the previously-collapsed/hidden set, at which point its dividers and viewports are restored into their frame.

Putting It Together

Here’s a simple made-up example of how this might work as a button in a form:

viewport.hide show:? viewportFrom:tag viewport:MyTag dir:right expansionKey:MyExpansionKey

This creates a tool-style toggle button in the form. The state of the button is determined by if the set is in the visible (as indicated by the viewport indicated by viewportFrom/viewport existing) or hidden (via a set with the expansionKey existing). It contains all the information needed to hide or show the set when its button is clicked.

Clicking the button would make a layout where the viewport A has the tag MyTag go from this:

+-------+----------+
|       |          |
|       |          |
|       |     A    |
|       |          |
|       |          |
+-------+----------+

To this:

+------------------+
|                  |
|                  |
|                  |
|                  |
|                  |
+------------------+

The hidden set is attached to the top, right and bottom edges, and will restore from the right edge. If you split the remaining viewport, the hidden set will still be attached to the right edge when it is restored.

Tabs in Tabbed Viewports

The tabTag argument provides an extra layer of functionality. Let’s say you have a layout like this:

+-------+------------+
|       |            |
|       |            |
|       |            |
|   P   |            |
|       |            |
|       |            |
+-------+------------+
| A B C D            |
+--------------------+

P is a panel that you want to hide, but the contents of P can be one of four possible sets of viewports. The buttons A, B, C and D determine which viewports are displayed in P, but you can also click the highlighted button to hide the entire panel. The question is, how do you do this?

The answer is that P is actually a tabbed viewport, but with the tabs hidden (this newly-added ability is supported through the MinHeader config parm on the tabbed viewport, which can be toggled form the UI via the Show Tabs option in its Viewport Controls). Each tab’s viewport must be given a tag, which in turn is referenced by the viewport.hide/viewport.collapse commands in the form:

viewport.hide show:? viewportFrom:tag viewport:MyTag dir:left expansionKey:MyExpansionKey tabTag:MyTabA
viewport.hide show:? viewportFrom:tag viewport:MyTag dir:left expansionKey:MyExpansionKey tabTag:MyTabB
viewport.hide show:? viewportFrom:tag viewport:MyTag dir:left expansionKey:MyExpansionKey tabTag:MyTabC
viewport.hide show:? viewportFrom:tag viewport:MyTag dir:left expansionKey:MyExpansionKey tabTag:MyTabD

When you click one of the buttons, the command first checks to see if the viewport is a tabbed viewport and that the tab with that tag is current. If it is, only then does it hide the set. If the tabbed viewport is visible but the tab with that tag is not the current one, it simply switches to that tab. If the set isn’t visible, it both reveals the set and switching to the desired tab.

This lets you create buttons that you can click to open a panel, then click to switch between different kinds of panels, before finally hiding the panel again by clicking the highlighted button.

Tab Switching without Collapsing/Hiding

If you want to just have a tabbed viewport that is always visible but has no tab bar, and you simply want to switch tabs with buttons in a form, you can use the viewport.tabWithTag command. It supports querying and icons just like the collapse/hide commands.

viewport.tabWithTag tag:string <?isCurrent:boolean>

In a form, it would look like this. The isCurrent argument exist solely so that we have something to query from the form so that the button can be properly highlighted.

viewport.tabWithTag tag:MyTag isCurrent:?

This is a global search for that tag in all tabs in all tabbed viewports, so you should try to make sure it’s unique.

Icons

Viewports now support icon resources through their tags. These are displayed on the buttons created by viewport.hide, viewport.collapse, and viewport.tabWithTag. For the hide/collapse commands, the viewportFrom argument must be tag, and viewport argument must be a tag, as the tag itself is used as the icon resource lookup.

These icons obey standard forms rules, meaning that you should define myTag_20 and myTag_32 in your image resources.

There is no fallback. We have no way to know what viewport this is, so we can’t resort to the viewport type, and the viewport preset could change if the layout is cloned. So tags it is.

Demo Config

To demonstrate this there is simple |a simple test config, which you can unzip and put in user:configs to try it out -- just switch to the Tab Hide Test layout. You can use the buttons to switch to specific tabs, to hide the panel (by clicking the highlighted button), and to show it again (by clicking any button when none are highlighted). It demonstrates how to set up the commands, form and tabbed viewport.

TabHidingTestConfig.zip

Bonus command: viewport.settings

The viewport.settings command can be used to open the settings of the currently-selected viewport. You can also open a specific viewport via viewport tags:

viewport.settings tag:MyViewportTag

If you target a tabbed viewport, it will open the options of the current tab. This can be useful to provide a way for the user to open the viewport options when the tab bar is hidden.