View Single Post
Old 06-10-2016, 09:50 PM   #64
Human being with feelings
Lokasenna's Avatar
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,021

Surprise! I came up with another lesson.

13. Having your script interact with the GUI elements

Note: The tutorial version of my GUI library we've been using up to this point won't work for this lesson, so you'll have to grab the release version found here: Lokasenna_GUI on the ReaTeam Github repository. Be aware that since the library file's name has changed, your script's reference to it will have to change as well. Have a look at the included example script for an, um, example. In exchange for going through this herculean effort, you may be pleased to know that there are a couple of new elements to play with (Range slider and Menu box) as well as even more thorough commenting and documentation for each class.


Anyway, I realized today that aside from drawing pretty pictures, all we've actually learned how to *do* so far is run a function when someone clicks your Button. Boooooorrriiiiinnnnngggggg. Luckily I designed the library with this exact situation in mind.

As part of the reaper.defer() loop that keeps everything going, you can have Lokasenna_GUI run a function of your own by passing that function to GUI.func:
local function do_stuff_on_each_loop()

	...blah blah blah...


GUI.func = do_stuff_on_each_loop
GUI.freq = 0

The other variable there, GUI.freq, sets how often (in seconds) to run your function. Setting it to 0 as we have here will call your function on every loop, which is fine for most things. However, if your GUI is interacting with the Reaper window in any significant way - changing the time selection to match a pair of sliders, for instance - running it too often will annihilate your CPU usage in the same way a hangover breakfast at Taco Bell will annihilate your apartment's plumbing.

What sorts of exciting things can we put in this function? Glad you asked.

Linking two elements together
Let's say you're making a track inspector window - everyone loves those. As part of this project, you want to have both a pan knob (which we'll call "knb_pan" for obvious reasons) and a textbox ("txt_pan") where the user can type in a value themselves. When they adjust the knob the textbox needs to change as well, and vice versa.

	local focus = GUI.elms.text.focus
	local knb = GUI.Val("knb_pan")
	if last_focus == true and focus == false and GUI.char == 13 then
		GUI.Val("knb_pan", tonumber(GUI.Val("text")))
	elseif last_knb ~= knb then
		GUI.Val("text", knb)
	last_knb = knb
	last_focus = focus
- We find out if the textbox is focused or not.
- GUI.Val() is a new function in this version of the library, used here to grab the knob's value.

GUI.Val(elm[, newval]) - returns the given element's value or, if a new value is given, sets the element's value instead

- If the textbox was previously in focus, but isn't anymore, and the user pressed Enter, we use GUI.Val() again to update the knob.

- Otherwise, if the knob has been changed, we update the textbox instead.

- Lastly, we store both elements' current values to compare with on the next loop.

Re-labeling a knob
If you're using a knob to let the user choose note lengths or grid sizes, you probably want the options listed in note lengths. Right? Right.

GUI.elms.note_len.output = {
	[0] = "1/64",
This has to be placed after you've created the knob, or it will be erased by the :New method. If you look at Knobraw(), you'll see that it checks for the existence of an output table prior to drawing each value. If the table is there, it uses the strings from there instead of numbers.

And what if you want to know which value, in note lengths, the knob is set to? Just place the following in your user function and then do whatever you want with it:
local value = GUI.elms.note_len.output[GUI.Val("note_len")]
GUI.Val() tells us what the knob is set to, which we hand right over to the output table to get the matching string.

Appending text to a Slider
Numbers really are boring by themselves, aren't they? This example adds "db" to the displayed value on a volume slider.

	local vol = GUI.Val("vol_sldr")
	if vol ~= last_vol then
		GUI.elms.vol_sldr.output = vol.."db"
	last_vol = vol
Just like the Knob, the Slider has an optional output parameter that will override the internal value (Range sliders have output_a and output_b). We grab the value, see if it's changed, and update output accordingly.

It's worth noting that both here and in the element-linking example we're only changing the target value if we need to; if the slider hasn't moved, we don't need to change the output because it's already been set to what we want. It doesn't save a ton of resources here, but if you had a script automatically setting track layouts based on their name it would literally crash Reaper unless you performed the same check. Seriously, I tried it once.

As you were.
Lokasenna is online now   Reply With Quote