Working with Tables
Adding a Button Column
This time we’re going to use an extra parameter on the lister COMP so we can use a referenced input table DAT rather than connecting our table directly to lister. We’re also going to set lister so it will automatically update the contents of our input table.
Looking at our colDefine Table, the first two columns should look very similar to our pervious configurations. In this case our third column is the one to pay closer attention to. Here we’re adding a column that doesn’t currently exist in our input table, setting it’s contents to be a constant value of “X” and setting the sourceDataMode to be “button”.
Our resulting lister with the configuration above should look like this:
Here in the lister callbacks we’ll make a few changes. What I want to do is to delete a row from our lister when we click the “X” button. To do this we need to know a few things - where is the lister that holds all of the data, which column have I clicked on, and which row do I want to delete. It’s not too hard to figure out these pieces. We can determine which lister we’re working on by looking at which component our listerConfig base is docked to. We can then find the row and column from the info object that’s passed to the lister callbacks. Finally we can use the DeleteRows() method from the lister extension to remove a row from our lister. Here’s the handful of python we’ll need to do this:
The result should be a lister than we we click the “X” it removes the selected row:
A quick overview of what’s happening in the callback - here we’re using the “onClick” method from the available callbacks. Unlike TouchDesigner’s built in ops, that start by showing you all of the available callbacks, for lister you need to check the documentation to see all of the available callbacks. When we click on a cell in lister we start by setting a few variables: our owner COMP, what row was clicked, and what column was clicked. Next we do a quick test to make sure the column that the command came from was the close column. This name comes from our colDefine Table. If the cell we clicked on is in the close column - which is what holds our buttons, the we use the provided method with lister “DeleteRows()” to remove.
Creating a Button Table
Our lister pars are also a little different for this example:
Our colDefine table will hold a large portion of our configuration information for this approach. The set-up here looks like:
I took some time to style my button TOPs - though this works even if you don’t style them:
Our resulting lister of buttons looks like this:
Let’s add one extra step so we can change a table when we press a button. Let’s add a table DAT to our network and call it table_info. Let’s set it up to have three rows and two columns. The rows should be named: row, col, text.
We’ll open up our callbacks again, and this time we want to update our table_info with the row, column, and text that we’ve clicked on. We can get this information from the info object. Here’s a look at the python we’ll need to make this work:
Now when we click on a cell in our lister our table updates with the changes.
TOP Path Example 1 - Press and Roll Changes
We’ll start with a simple table DAT that has some information we’d like to display:
Next, let’s think about what kind of button interaction we want to create. For this example, let’s just look at how we might change the view of our button when we interact with lister - just a visual cue that we’ve clicked on a button.
So just like with our previous example that used a TOP Path to change the contents of a cell with the provided TOPs, we can create our own TOPs for this. Let’s start by creating a button look, a roll, and a press look. Here’s what my network looks like:
Here that’s a top called “btnImage” along with tops that start with that same name and are appended with “Roll” and “Press.” This is important, because lister relies on the names of our TOPs to order to know which to display at what time.
Next let’s look at the colDefine Table:
Here we can see that our sourceDataMode is empty - which is exactly what we want in this case. We don’t want to display anything in particular, just our image. Next in our topPath row we can see that ‘btnImage’ is what’s describing which TOPs lister is going to use to fill in these cells. One final note on formatting here - when working with TOPs that have images in them I generally prefer to make sure that I don’t allow my cells to stretch. This can make sure that you know the exact dimensions of your cell, and can format your texture operators accordingly. In this case, the dimensions for our cells are going to be the height of the top called master - which is used to define a number of characteristics about our lister, and from the colDefine table row called “width”.
The result from our configuration here is a lister whose buttons change color when we mouse over them and when we click them:
But what if you don’t want to just use Roll and Press?!
That’s a great question, and there’s a solution here. Let’s look at a static example first, and then we’ll start to update our lister’s images.
One approach is to think about putting data in a column in a DAT table that you don’t need to display, but can instead use a reference for the state of your row. For example, let’s look at a table where we’ve added another column called ‘state’. In this column I’m just going to set state to be True if col2 is greater than 3.
In the colDefine table we’re going to make a few adjustments. Here we’ll again use a topPath but we want to make sure that our sourceDataMode is ‘blank.’ This will keep the lister cell empty, even though it holds some data that we’re going to use. Next in our topPath we’re going to use ‘btnImage*’. In this case the * is going to act as a pattern matching mechanism; this matches the name of our tops to the contents of that column. Our state column had values that were “True” and “False” - this means that if I create TOPs with the names ‘btnImageTrue’ and ‘btnImageFalse’ they’ll be matched to display in the cells.
Let’s first look at the TOPs we need in our listerConfig:
Between our colDefine table and our additional TOPs, what we end up with should look like this:
This is great for a scenario where you have some internal logic that’s updating a table that you want to see in a list, but don’t want to change directly. This could be online / offline status, or any number of other kinds of information.
But… what if you do want your interaction with the table to update it’s display? Let’s look at that next.
TOP Path Example 2 - Updating a Single Cell
We’ll start by setting up our input table:
This approach works great for a toggle style system, where any given row can be on or off. But… what about if only one row can be on? We’ll use all the same mechanics, and look at that next!
TOP Path Example 2 - Updating a Whole Column
The real fun will come with our callbacks:
Unlike our previous approach where we just changed one cell, here we pull the whole state column. We then make sure the only cell that has a “True” value is the one we clicked on, then we overwrite the column in the original table. This wouldn’t be great for DATs with lots of rows, but for small - medium sized lists this might help you map out that “only one active” use case.
Working with Lists…
A Single List
On lister’s Advanced page there’s a parameter called Raw Data:
We can fill that parameter in directly, or we can write a little script to fill in that parameter. For practice, let’s write a little script that’s going to do that work for us. In a text DAT let’s write the following:
Here we start by making a simple list, and then we set the Rawdata parameter of our lister to be this list. Easy.
Let’s set-up the colDefine table for our lister to work with our python list:
Because our raw data doesn’t have a header, here in our colDefine we can set an internal name for this column of data, as well as a display label for this column. We should now see a lister that looks like this:
A List of Lists
Let’s think about tables for a moment. We often think of tables as a 2D array, in fact in TouchDesigner we use this idea all of the time to describe how we want to either retrieve or set contents in a table - we need to know the cell’s address as a row and column location. If we need to transform the contents of a table into a python object we might start to pull apart the best way to structure this. As a data structure, lists have an inherent order - so we might use that idea transforming a table into something more pythonic. One way to organize this data would be to make a list of rows - where each row is also a list.
Let’s look at that as an image rather than just words. The idea here is that a table organized like this in TouchDesigner:
Could be arranged like this as pure python:
Okay… so with that little bit of data organization unpacked, we can set up lister with a list of lists as a data source. Let’s use a little script again to set the parameter for Raw Data in lister:
Next let’s set up our colDefine table. This is where things start to get a little different from our previous work. Since our list of lists doesn’t have a “header” like our table might, we need to do a little more work to make sure that this will behave correctly. We need to specify the names for our columns in our lister, and we should set the sourceData mode for our lister. Lister will do it’s best to figure this out for us, but we’ll ultimately be happier if we set these manually as we’re learning. When working with a list of lists the sourceData is the index in our lists of rows that we want to display - it’s like a column in our constructed array of data.
The resulting lister should look like this:
A List of Dictionaries
Here all of the dictionaries contain the same keys. We’re going to use the Keys in our dictionary to be the columns for our lister, and the values that correspond to those keys as the contents of each cell in the rows of our lister.
I want to have little icons for my example, so I’m going to add a few extra TOPs that will act as buttons in my listerConfig:
Finally, let’s update our colDefine table for our lister. Here it’s important to note that the sourceData for our columns refers to a key in the dictionaries of our list’s items:
The result should be a lister that looks like this:
A List of Python Objects
We can accomplish this example by using modules on demand in TouchDesigner, but this is likely a better candidate for working with an extension. The big idea is that we’ll have a class that acts as the manager of our list objects. Those list objects will all be instances of a class themselves. We’ll create an instance of our manager class with another text DAT so we’re not continuously re-initializing the class when we interact with lister.
My objects are going to have a handful of attributes - I’m going to define these in a dictionary for now, but you might construct these on the fly. This might be looks, presets, scenes, or any other type of collection of data.
Looking at this we’ll see that there are two classes - our first class is our MangerMod - this holds a list of our objects, and does the work of filling the list of objects. Next we can see the myObj class. Right now this only sets the attributes of the objects.
Next we create an instance of our manager class in a text DAT:
We can now target the myObjs member of our instance in the Raw Data parameter of our lister:
Okay… now we’re ready to have some fun. Let’s look at our colDefine table in lister:
Notice how in sourceData we now use the name of the attribute of our python object. Our resulting lister looks like:
At this point you might be rightly wondering why on earth you’d want to do this much work if you could just use a table. That’s a fair question, part of that is difficult to show in simple examples. That said, it’s not hard to imagine a situation where all of your critical data is stored in an extension, and rather than writing that to a table DAT (and then keeping it synced with your extension) it would be easier to just reference that in a lister directly. That’s part of the magic of this variation on using lister - you can use this to directly display pythonic pieces that might otherwise be more frustrating or time consuming to write out into a UI friendly data structure.
This week we looked at a TON of variations in using lister - from simple alterations, to more complex uses of python. You can find the examples from list post here:
Next week we’ll hear from the puppet master himself - Ivan - about the Dark Dank secrets of lister.
Happy programming - and Lister...ing