PopMenu Custom COMP Examples

From TouchDesigner Documentation
Jump to: navigation, search

This page contains examples of how to set up a popMenu custom component. They assume a basic knowledge of TouchDesigner and Python.

For full documentation of popMenu, see: PopMenu Custom COMP.

Example 1 - Menu Parameters[edit]

Default Pop-up Menu

In this example we will explore the features available via the popMenu parameters. For more details, see popMenu parameters.

Creating a popMenu[edit]

To create a popMenu, drag one from the Palette (Derivative>UI folder) into your network. To test the popMenu, click the Open parameter. You can click the Close parameter or press esc to close the menu. Note: the view in the menu node is not always an accurate depiction of the popMenu window, so it is good to open it while testing.

List Item Parameters[edit]

To add list items to the popMenu, simply add them to the Python list in the Items parameter. To disable items in the list, add them to the Python list in the Disabled Items parameter. Disabled items must match those in the Items list exactly. Highlighted Items works exactly like Disabled Items, but the item has a highlight color and remains enabled. To create a dividing line after an item, add it to the Python List in the Dividers After Items parameters.

There are a couple options for creating checked items in the popMenu using the Checked Items parameter. If you enter a Python list, items in the list will have checks next to them. This method does not allow for creating empty checkboxes, however. For that, use a Python dictionary with keys matching list Items and values of 1 (checked) or 0 (unchecked).

Align and Format Parameters[edit]

To adjust the position that the popMenu appears relative to the mouse, use the Horizontal Align and Vertical Align parameters. The Align Offset parameters let you enter a pixel offset to the alignment.

There are a few basic formatting parameters available as well. The Columns parameter can be used to create a multi-column menu. Note: if you have more than one column, Dividers After Items, Checked Items, and SubMenus (see examples below) will not be available. The Borders parameter turns on and off the outer border of the menu, and the Max Height parameter sets the size at which the menu will display a scrollbar.

Example 2 - A popMenu Network[edit]

A popMenu Network

In this example, we'll set up a simple network that opens a popMenu above a button when it is pressed.

Creating a popMenu[edit]

To create a popMenu, drag one from the Palette (Derivative>UI folder) into your network. For this example, make sure the name of it is popMenu.

PopMenu Inputs and Outputs[edit]

PopMenu input and output network

Setting up a popMenu based on a table is easy. Simply wire it into the input of popMenu. The first column of the table will be used as the popMenu's list items. Other columns will be ignored. To create the example menu, make a single-column Table DAT with 4 rows containing: "Test", "Demo", "Trial", "Example". Wire the table into a popMenu. Press the Open parameter to see your menu in action. TIP: You can use different Input Table parameter settings for multi-column and labeled input tables.


To get the popMenu's results using a CHOP, wire a Null CHOP into the popMenu's CHOP output. When the popMenu is opened, before a selection is made, the selected_cell channel's value will be -1. When a selection is made, selected_cell will be the selected item's index. Use the same technique with a Table DAT and the popMenu's DAT output to get the selected Item text. The table will be empty before a selection is made.

Opening and Reacting to a popMenu Using a Button[edit]

First you'll need a button. Create a Button COMP in your network. Change its Width parameter to 100 and its Button Type parameter to Momentary. To make the button open your popMenu, create a Panel Execute DAT and set its Panel parameter to the new button. Next, enter the following code into the DAT, creating the new onMenuChoice function and replacing the default offToOn function:

def onMenuChoice(info):
	debug(info)
	debug(info['item'])

def offToOn(panelValue):
	op('popMenu').Open(callback=onMenuChoice)
	return

The offToOn callback opens the menu and sets the selection callback to onMenuChoice. The onMenuChoice function prints all the info sent to the callback, then prints just the most commonly used part of that info, the item string that was selected. For more info about callbacks, see: popMenu Callback System.

To test the button menu, open a floating viewer to the button and click it. You will want to use a floating viewer because the node view stretches the button and creates unpredictable menu placement. Your menu opens, but it opens at the mouse location instead of attached to the button. Let's fix that...

Attaching a popMenu to a Button[edit]

To attach the popMenu to the button, use the parameters at the bottom of the menu's Pop Menu parameter page. First, set the Button Comp parameter to your button. Note: although the parameter is called Button Comp because that is its most common use, it can be any panel component. Next, set the Horizontal Attach parameter to Left and the Vertical Attach parameter to Top. These define where the menu's align point will be attached. We want to match that op the bottom left of the popMenu, so set the Vertical Align parameter to Bottom.

Test the button again. Looks good except it would be nicer if the menu size matched the button's size. Go to the menu's Width parameter and notice that it contains the expression me.OptimalWidth. That OptimalWidth member contains the size to wrap the menu's text exactly. Just change the Width to 100 to match the button's width. Testing now will give the same result as the image at the top of this example.

Example 3 - Simple Selection popMenu Created with Script[edit]

Simple Selection popMenu

In this example we will create a pop-up context menu that lets you select the current node in a TouchDesigner network. Unlike the previous examples, we will create this menu using only script.

The System popMenu[edit]

For context pop-up menus, you will generally want to create and open the menu entirely from script. You can use your own popMenu COMP for this, or you can use the system popMenu provided in TouchDesigner. If you use the system popMenu, it should be for context menus and other things that will disappear immediately after use, because it can be invoked by other TouchDesigner features. This system popMenu is located at: op.TDResources.op('popMenu')

Creating the Simple Selection Menu[edit]

To begin, create a Text DAT in a network with some other operators. Enter the following text into it:

def menuChoice(info):
	op(info['item']).current = True

menuItems = [child.name for child in parent().children]

op.TDResources.op('popMenu').Open(
		items=menuItems,
		callback=menuChoice,
		callbackDetails=None,
		disabledItems=[me.name],
		dividersAfterItems=[],
		checkedItems=[],
		subMenuItems=[],
		autoClose=True
	)

The menuChoice function is what will be called when a menu selection is made. It simply sets the chosen operator to be the current node in the network.

The menuItems list is a list of names of all the nodes in the network with your textDAT. It is created using a Python list comprehension. If you're not familiar with this syntax, there is a good tutorial here: Python List Comprehension With Examples.

The last part of the script opens the actual pop-up menu. It is a call to the Open method of the system popMenu. You will recognize most of the arguments as parallels of the parameters on the popMenu COMP. The items argument holds the list items for the menu. In this case we put the list of names we created in the line above, menuItems. The callback argument takes a Python function that will be used as a callback when a choice is made from the menu. In this case, we want to use the menuChoice function at the top of the script. The disabledItems argument corresponds to the Disabled Items parameter, and as an example we provide a list with one element, the name of the script operator. Note: because we are using the system popMenu, all these arguments must be provided even though they are mostly empty values. This is to clear out any values that may be have been used previously. For information on other arguments of popMenu's Open method, see popMenu Methods.

To test the script, zoom out to where you can see other nodes in the script's network, then right-click on the textDAT and choose Run Script. When you choose an Operator name from the menu, a green box will appear around that Operator, indicating that it is the "current" node.

Example 4 - Advanced Selection popMenu[edit]

Simple Selection popMenu

In this example we will expand the previous example to create a pop-up context menu that not only selects a node in a network, but also uses advanced popMenu features to show which node is being rolled over in the menu and which node is currently selected.

Creating the Advanced Selection popMenu Script[edit]

Create a Text DAT in a network with some other operators. Enter the following text into it:

def onMenuChoice(info):
	if info['item'] == 'Close Menu':
		info['menu'].Close()
	else:
		op(info['item']).current = True

def onRollover(info):
	for child in parent().selectedChildren:
		child.selected = False
	if info['item'] == 'Close Menu' or info['item'] is None:
		return
	op(info['item']).selected=True

menuItems = [child.name for child in parent().children]
lastChild = menuItems[-1]
menuItems.append('Close Menu')
op.TDResources.op('popMenu').Open(
		items=menuItems,
		callback=onMenuChoice,
		callbackDetails=None,
		disabledItems=[me.name],
		dividersAfterItems=[lastChild],
		checkedItems='{child.name: child.current for child in op("' \
						+ parent().path + '").children}',
		subMenuItems=[],
		autoClose=False,
		rolloverCallback=onRollover
	)

Test the code by right-clicking on the textDAT and selecting Run Script. Notice that a yellow selection box appears around nodes when you roll over their names in the menu. When you click a node name, it becomes the current node and its name is checked. Click Close Menu to (you guessed it) close the menu.

This script is a lot to take in, so let's take it in sections...

Selection and Rollover Callbacks[edit]
def onMenuChoice(info):
	if info['item'] == 'Close Menu':
		info['menu'].Close()
	else:
		op(info['item']).current = True

The onMenuChoice function is similar to the one in the previous example, except it adds the Close Menu option. As you can see, creating specific actions for menu choices is as simple as an if/else clause.

def onRollover(info):
	for child in parent().selectedChildren:
		child.selected = False
	if info['item'] == 'Close Menu' or info['item'] is None:
		return
	op(info['item']).selected=True

The onRollover callback uses Operators' selected member to create the yellow box you see around nodes when you roll over menu choices. The for loop deselects all the nodes in the network. We then check to make sure we aren't over the Close Menu item or no item at all. Rollover callbacks will be called when the mouse leaves the popMenu, so the item can be None! Finally, if the mouse is over a valid item, set the corresponding Operator's selected member to True.

Advanced Selection popMenu Setup and Checked Items Expression[edit]
menuItems = [child.name for child in parent().children]
lastChild = menuItems[-1]
menuItems.append('Close Menu')
op.TDResources.op('popMenu').Open(
		items=menuItems,
		callback=onMenuChoice,
		callbackDetails=None,
		disabledItems=[me.name],
		dividersAfterItems=[lastChild],
		checkedItems='{child.name: child.current for child in op("' \
						+ parent().path + '").children}',
		subMenuItems=[],
		autoClose=False,
		rolloverCallback=onRollover
	)

This part of the script creates the actual menu. As in the previous example, we create a menuItems list containing all the sibling nodes of the script. We then store the last member of that list in lastChild for use in the Open method. Finally, we append "Close Menu" to the list to create that option.

In the Open method, we use menuItems as the list items. We use the lastChild we stored in the dividersAfterItems argument to create a line between the choices and the Close Menu option.

The checkedItems argument contains an expression which will continuously update the checkboxes in the menu. It uses a Python Dictionary comprehension (which you can learn about here) to create a dictionary keyed by Operator name with data equal to the Operator's current member. This has the result of creating an empty checkbox by every Operator name except the "current" operator, which will have a check. Note: the checkedItems argument will accept a literal dictionary or list as well, but providing an expression causes it to update in real-time.

The autoClose argument is False for this menu because it is meant to stay open until Close Menu is selected. Finally, we set the rolloverCallback argument to the onRollover function we created above.

Example 5 - Color Selector popMenu with Sub-Menus[edit]

Color Selector popMenu

In this example we will create a popMenu with sub-menus that let you choose black or white for the node colors in your network.

Creating the Color Selector popMenu Script[edit]

Create a Text DAT in a network with some other operators. Enter the following text into it:

def onMainMenuChoice(info):
	info['menu'].OpenSubMenu(
			items=['white', 'black'],
			callback=onSubMenuChoice,
			callbackDetails=info['item'],
			disabledItems=[],
			dividersAfterItems=[],
			checkedItems=[],
			subMenuItems=[],
			autoClose=True
		)

def onSubMenuChoice(info):
	menuOp = op(info['details'])
	if info['item'] == 'white':
		menuOp.color = (1,1,1)
	elif info['item'] == 'black':
		menuOp.color = (0,0,0)

menuItems = [child.name for child in parent().children]
op.TDResources.op('popMenu').Open(
		items=menuItems,
		callback=onMainMenuChoice,
		callbackDetails=None,
		disabledItems=[],
		dividersAfterItems=[],
		checkedItems=[],
		subMenuItems=menuItems,
		autoClose=True,
		allowStickySubMenus=True
	)

Test the code by right-clicking on the textDAT and selecting Run Script. When you roll over an Operator name, a sub-menu appears with black and white options. Selecting one of those will change the Operator's color in the network.

In this example, we have activated the sticky sub-menus feature of popMenu. This allows you to click on a sub-menu item (in this case an Operator name) to force the sub-menu to "stick" open. This allows you to click on the sub-menu choices without the menu closing. Try that now. You will see that the Operator name is displayed in a bold font when the sub-menu is set to sticky. You can now switch between black and white without having to re-open the popMenu. You can click on the name again to turn of stickiness. The menu will still close if it loses focus, even if a sub-menu is sticky.

Let's examine each the script in sections...

The onMainMenuChoice Callback[edit]
def onMainMenuChoice(info):
	info['menu'].OpenSubMenu(
			items=['white', 'black'],
			callback=onSubMenuChoice,
			callbackDetails=info['item'],
			disabledItems=[],
			dividersAfterItems=[],
			checkedItems=[],
			subMenuItems=[],
			autoClose=True
		)

This is the most important part of creating a sub-menu. It is in the regular selection callback of the parent menu that you define all sub-menu settings. The OpenSubMenu method is basically identical to the Open method used for opening popMenus. The difference is that you call it from within a selection callback on info['menu'], which holds the parent popMenu.

Notice that we set up all item callbacks just as described in previous examples, and we have a separate selection callback, onSubMenuChoice, to deal with selections from the sub-menu. The other new feature we use here is the details argument. You can use details to pass any extra info you need to the selection callback. In this case, we need to know which main menu item generated the sub-menu, so we pass info['item'].


The onSubMenuChoice Callback[edit]
def subMenuChoice(info):
	menuOp = op(info['details'])
	if info['item'] == 'white':
		menuOp.color = (1,1,1)
	elif info['item'] == 'black':
		menuOp.color = (0,0,0)

The onSubMenuChoice callback is fairly simple. First, we fetch the Operator to affect from the info['details'] received from the onMainMenuChoice callback. We then do a simple check against the selected info['item'] to determine what color to set the operator to.

Color Selector popMenu Setup[edit]
menuItems = [child.name for child in parent().children]
op.TDResources.op('popMenu').Open(
		items=menuItems,
		callback=onMainMenuChoice,
		callbackDetails=None,
		disabledItems=[],
		dividersAfterItems=[],
		checkedItems=[],
		subMenuItems=menuItems,
		autoClose=True,
		allowStickySubMenus=True
	)

The main setup for this popMenu will look familiar to you from the previous examples. The only new thing to notice here is the allowStickySubMenus argument. This enables sticky sub-menus, which are described above.

Important Note: the system popMenu in op.TDResources provides two levels of sub-menus. If you need more than that you will have to create your own set of popMenus. That said, menus with more than two levels of sub-menus tend to be confusing.

Every component contains a network of operators that create and modify data. The operators are connected by wires that define where data is routed after the operator cooks its inputs and generates an output.

The connection of an output of one node to the input of another node in a network. In contrast, see Link.

An Operator Family which operate on Channels (a series of numbers) which are used for animation, audio, mathematics, simulation, logic, UI construction, and many other applications.

An Operator Family that manipulates text strings: multi-line text or tables. Multi-line text is often a command Script, but can be any multi-line text. Tables are rows and columns of cells, each containing a text string.

A set of commands located in a Text DAT that are triggered to run under certain conditions. There are two scripting languages in TouchDesigner: Python and the original Tscript. Scripts and single-line commands can also be run in the Textport.

An Operator Family that contains its own Network inside. There are twelve 3D Object Component and eight 2D Panel Component types. See also Network Path.

Any of the procedural data operators. OPs do all the work in TouchDesigner. They "cook" and output data to other OPs, which ultimately result in new images, data and audio being generated. See Node.

A text string that contains data (string, float, list, boolean, etc.) and operators (+ * < etc) that are evaluated by the node's language (python or Tscript) and returns a string, float list or boolean, etc. Expressions are used in parameters, DATs and in scripts.

Some operators have a DAT docked to them that contains some python functions. These functions, called "callbacks", get called when something in the operator changes.