Old 05-15-2016, 02:51 PM   #41
WyattRice
Human being with feelings
 
WyattRice's Avatar
 
Join Date: Sep 2009
Location: Virginia
Posts: 1,913
Default

Lokasenna,
Many thanks! This is a great tutorial.
I have so many questions.
Anyway, thanks for the demo of the textbox.
Later, Wyatt
WyattRice is offline   Reply With Quote
Old 05-15-2016, 05:39 PM   #42
WyattRice
Human being with feelings
 
WyattRice's Avatar
 
Join Date: Sep 2009
Location: Virginia
Posts: 1,913
Default

Just one more question for now.

How can I get a return on the "Enter Key" when pressed
From what I read it's char 13.

e.g.
local function Add_Marker()

local myStr = GUI.elms.TxtBox.retval
if char == 13 then ?

local CursorPosition = reaper.GetCursorPosition()

reaper.AddProjectMarker(0, false, CursorPosition, 0, myStr, -1)

end
WyattRice is offline   Reply With Quote
Old 05-15-2016, 07:08 PM   #43
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

At the moment you can't; if you have a look at TxtBox:ontype() you'll see that it doesn't list character 13 anywhere. You could easily add an "elseif char == 13 then" function yourself, and it's on my list eventually.

However, in the interest of not altering the library code itself, I think this could be a good use for one of LS GUI's other features.

- If you read the comments in the usage demo, you'll see that it's possible to have the Main() loop run a user function every time it comes around.

- In the list of TxtBox's parameters, notice that there's a property called focus, which is set to true when the text box is active. You can check it the same way we got text previously.

- In the library's Main() function, the keyboard state for each loop is saved in GUI.char

I haven't tried, but I'm pretty sure you could write a function that would check if the textbox is in box AND the enter key has been pressed and then perform your action, tell Main() to run that function, and Bob's your uncle. Note, however, that checking the state of the Enter key would have to be done just like we checked the mouse button in the Button lesson, otherwise it would also be picking up the Enter key if it's still down and hasn't been let up yet.
Lokasenna is online now   Reply With Quote
Old 05-16-2016, 11:03 AM   #44
eugen2777
Human being with feelings
 
eugen2777's Avatar
 
Join Date: Aug 2012
Posts: 271
Default

Lokasenna, It looks very good, thank you.
__________________
ReaScripts
eugen2777 is offline   Reply With Quote
Old 05-16-2016, 02:32 PM   #45
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

8. Tables, classes, and methods

Note: This lesson refers to the LS GUI library provided in part 6. If you haven't downloaded those files yet, see this post: 6. Introducing the LS GUI library

Let's take a closer look at how everything in LS GUI is set up. I'm not going to have much code in this lesson, since it's all in the library file, so you may want to have both this post and the library open at the same time. For those with at least Windows 7 (I think), you can drag a window all the way to left or right edge of the screen and it will be resized to take up only that side - it's really handy.

Tables

(For those familiar with other programming languages, tables are Lua's version of arrays. Sort of. They have slightly different rules, and are often much more flexible.)

Tables, as the name suggests, are an easy way to store multiple things in one variable, perform operations on all of them, sort them, etc, while keeping things really tidy. Here's a good article covering how tables are created, how to fill them, and how to work with them: lua-users.org Table Tutorial

As you'll see, everything in LS GUI relies on tables. Seriously, the entire file is one big table - the first line of code is this:
Code:
local GUI = {}
and the last line is this:
Code:
return GUI
Everything we do in between - the color presets, the miscellaneous functions, the Main loop, and call of the GUI elements - is stored in that table:
Code:
local function do_stuff()
end
GUI.do_stuff = do_stuff
This creates a perfect copy of do_stuff within the table. By placing the entire library in a table and returning it to the parent script we accomplish two important things:

1. The parent script can use LS GUI's functions and access its variables.
2. Nothing in LS GUI will affect the parent script or be affected by it, so there are no questions of variable scope or having to write the parent script in a specific way to have everything work properly.

If we think of the parent script as a lady sitting at a desk, LS GUI is a coworker under her supervision. She says "draw me a button" and LS GUI just makes it happen, no fuss.

For comparison, if we remove the table and just place everything in the library at "top level"...
Code:
GUI.fonts --> fonts
GUI.mouse --> mouse
GUI.Main --> Main
GUI.Lbl --> Lbl
...those are common enough names for variables and functions that we'd probably interfere the parent script, so the user would to be very careful not to duplicate any of them. Even worse, anything the library does to mouse would happen to the parent script's mouse as well, so you're not only making the user do things your way but you're potentially screwing with their data at the same time.

In this case, LS GUI is more like one of those coworkers that's always using your stuff, never puts it back in the right place, spills coffee on an important report and then says "wow, you really screwed up this time" before walking away happily back to his own desk. Nobody likes that guy.

Classes

Declaring variables all the time gets really, really old, especially when all you want is five or six of basically the same thing. The same goes for writing functions in many languages, where working with normal variables might force you to write five of the same function to work with each of those five things.

If only there was a way to simplify things... oh wait, there is. It's known as "object-oriented programming" - rather than working simply with variables, we create objects that contain multiple variables. For instance, a person would contain variables like their name, height, weight, favorite shitty reality TV show, whether or not they've paid for their Reaper license, etc.

By creating what's known as a "class", we can build a prototype of one thing and then produce copies of it on-demand, each with their own name, rank, and serial number. As a result, rather than five functions doing the same thing, you just write it once and provide it the name of the object you'd like it to work on.

All of this fancypants stuff happens in the various :new() functions. Here's a simple one, for the Lbl element:

Code:
local Lbl = {}
Just like everything else in LS GUI, we'll start by making ourselves a table.

Code:
function Lbl:new(x, y, caption, shadow)
We'll call this function every time we want to create a new Lbl, giving it the values provided just like a rich parent giving their kid a Ferrari when they turn 16. Calling it Lbl:new rather than, say, new_label tells Lua to store the function inside Lbl{}.

The function's name uses that : to do another neat Lua trick. As I said above, functions written for use with objects from a class are always going to want the object's name. You could include the name as a parameter in all your functions if you wanted to:

Code:
function Lbl.new(lbl_name, x, y, caption, shadow)
...but the : serves as a shortcut to do the same thing. Lbl:new creates a function in Lbl{} that will automatically check the object's ID and store it in a variable called self. If you scroll down to some of the other functions in LS GUI, you'll see that self shows up all over the place. It's just a timesaver, really, like tapping your credit card on the machine rather than swiping it, typing in your PIN, waiting, etc.

The function then creates its own temporary table, label{} and stores the given parameters in it, along with a couple of other values that we'll need down the road.

It's worth noting what one line is doing:
Code:
label.shadow = shadow or 0
This is an easy way to see if shadow was specified, and setting it to 0 if it wasn't. I'm not sure exactly how it works, but it does, so you'll see it here and there in the library. It's a nice time-saver.

The magic that turns our wooden doll of a function into a real live boy is at the end:
Code:
   setmetatable(label, self)
   self.__index = self 
   return label
Again, my understanding of this bit is a little shaky, but here's a rough idea of what it's doing:

1. setmetatable tells label{}, to use self as its blueprint. Remember that self is standing in for Lbl{}.

2. Normally, if you look a value up in a table and there's nothing there Lua will return nil. __index tells the table to, instead, try looking in its metatable to see if there's an appropriate value there.

3. By having the function return label{}, which includes the metatable and __index stuff, we can create a variable, direct it to the :new function, and that variable will become a "child" of the "parent" object, inheriting all of its properties and functions:

Code:
my_new_label = Lbl:new(50, 50, "I'm a label", 0)
Because my_new_label is only a copy we can do things to it without affecting the parent, but if it needs help it can still phone up Mommy to ask "hey, so how do I do my own laundry?"

Methods

Moving on in the library file, you can see Lbl has the following functions:

Code:
:new()
:draw()
:onmousedown()
:onmouseup()
:ondoubleclick()
:ondrag()
:ontype()
Functions like these that are meant to work with a class of objects are known as "methods" of that class, and as their names suggest they're a list of things our sweet lil' Lbl has been taught how to do. Again, they're all stored in Lbl{}, so all of our child objects know how to use them, and because they all have that : in their names they'll have a self parameter that can be used to get information about the object that called them. i.e.
Code:
my_new_label:print_your_name()
would theoretically spit out
Code:
"Hi guys, I'm my_new_label. Does anyone here like Pokemon? I LOVE Pokemon. Guys? Hello?"
Lastly, we copy Lbl{} into GUI{}:
Code:
GUI.Lbl = Lbl
After all of that, my_new_label now exists with the values we specified in its blueprint, and has been sent off into the world with a little instruction booklet covering what to do if someone wants to draw it, or click on it, or type at it.

*sigh*....they grow up so fast.

Last edited by Lokasenna; 05-16-2016 at 02:40 PM.
Lokasenna is online now   Reply With Quote
Old 05-16-2016, 03:58 PM   #46
heda
Human being with feelings
 
heda's Avatar
 
Join Date: Jun 2012
Location: Spain
Posts: 5,481
Default

Lokasenna, where where you when I was learning one year ago? I suffered a lot! haha

Very good! Thank you
heda is offline   Reply With Quote
Old 05-16-2016, 06:53 PM   #47
run_esc
Human being with feelings
 
Join Date: Mar 2016
Location: Victoria, BC
Posts: 197
Default

This is bloody awesome for a relative newb like me. Thanks! (from a fellow Western Canadian!)
run_esc is offline   Reply With Quote
Old 05-16-2016, 11:40 PM   #48
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,061
Default

Objects... It's getting serious now :P

------
Code:
label.shadow = shadow or 0
I didn't know that !

I would have made this :
Code:
 if shadow then lbl.shadow = shadow else lbl.shadow = 0 end
and before that, I was also making comparaison with nil, which is complelty unecessary in most cases in lua ^^.

Nice !
X-Raym is offline   Reply With Quote
Old 05-17-2016, 04:11 AM   #49
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

You can also do this:
Code:
a = b and c or d
instead of this:
Code:
if b then
    a = c
else
    a = d
end
Looks like it will misbehave if you give it nil arguments though.
Lokasenna is online now   Reply With Quote
Old 05-17-2016, 03:58 PM   #50
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

9. Working with strings

Note: This lesson refers to the LS GUI library provided in part 6. If you haven't downloaded those files yet, see this post: 6. Introducing the LS GUI library

Just a short lesson today. We're going to look at two classes that are almost, but not quite, doing the same thing: OptLst, commonly known as "radio buttons", and ChkLst, a series of checkboxes. OptLst lets you choose one of the listed options, whereas ChkLst can have each option turned on or off separately.

As you can see in the library script they use the same parameters for creation - our old buddies x,y,w,h, and three new ones:

Code:
caption			Title / question
opts			String separated by commas, just like for GetUserInputs().
				ex: "Alice,Bob,Charlie,Denise,Edward"
pad				Padding between the caption and each option
opts is the one we'll be focusing on here. By using some of Lua's built-in features for doing work on strings, a list of options, or values, or whatever can be stored as one continuous string. The same thing could be accomplished with a table, but for educational purposes I've opted to do things this way.

As you can see from the example in the code box above, the list of options is simply written out in the form "Option A,Option B,Option C,...". Comma-separated strings, or CSVs, are fairly common in programming and databases, so it's good to be familiar with them; for instance the Reaper function reaper.GetUserInputs() includes two CSVs in its parameters and returns the user's responses in a third.

The first thing we do with our options is - surprise! - put them into a table:
Code:
	-- Parse the string of options into a table
	opt_lst.optarray = {}
	local tempidx = 1
	for word in string.gmatch(opts, '([^,]+)') do
		opt_lst.optarray[tempidx] = word
		tempidx = tempidx + 1
	end
	
	opt_lst.numopts = tempidx - 1
string.gmatch() is really powerful. Almost... TOO powerful... . That crazy jumble of symbols that looks like someone was holding the Shift key down while they typed a number? It's a pattern that Lua will look for in our string (opts). As you can probably guess, that pattern translates to "Find a group of characters that are before a comma", and in this case we're going to use that group in a for loop:
Code:
for each group of characters, which we'll call word, do this:
	put the word into our table using the current index number
	ChkLst only: use the same index number to set that option's initial state
	increase the index number
repeat with the next word
The index number is then used to figure out how many options there are, and therefore how many buttons/boxes to draw.

The :draw() methods for both are pretty similar aside from one drawing circles and the other drawing squares. OptLst also only has one value telling it which option is selected, while ChkLst uses a table to store each option's state individually.

Likewise, their mouse methods are virtually identical. OptLst has a little extra code in :onmouseup so that the user can drag over the options and have the bubble follow their mouse, but reset to the initial state if the mouse is moved off the option list before they let the button up. Just a visual thing, not necessary by any means.

As with the other interactive elements, both OptLst and ChkLst store their current selection in the variable retval. For ChkLst, retval is a table storing option 1's status at index 1, etc.

We can find a few more string functions further down, in the TxtBox class. For instance, it's a fairly standard convention that double-clicking on a text box will select all of its contents. To do that, our TxtBox:ondoubleclick() method needs to know how long the string is:
Code:
local len = string.len(self.retval)
string.len() simply returns the length in characters of a given string. Child's play.

Moving on to :ontype() we can find another new function, in the code for Backspace:
Code:
text = string.sub(text, 1, caret - 1)..(string.sub(text, caret + 1))
string.sub(text, start, end) takes a string, text, and returns a portion of it from start to end. As you can see here, we're using it twice to get the text before and after the character we just backspaced. The .. in between tells Lua to join two strings together.

A little further down, in the code for typing a character, we can see strings being joined in a different way:
Code:
text = string.format("%s%c%s", string.sub(text, 1, caret), char, string.sub(text, caret + 1))
string.format(formatting, str1, str2...) uses some fancy symbols in its first argument to make sure the remaining arguments fit into a certain format. %s is just a string, while %c converts the char value that was typed from an ASCII number into a character.

We'll take a longer look at the TxtBox in another lesson.

For a more thorough explanation of patterns, capture characters, and string stuff in general, see here, and here.

Last edited by Lokasenna; 05-18-2016 at 03:33 PM.
Lokasenna is online now   Reply With Quote
Old 05-18-2016, 02:52 PM   #51
Sexan
Human being with feelings
 
Sexan's Avatar
 
Join Date: Jun 2009
Location: Croatia
Posts: 2,452
Default

Learning from start,can someone tell me whats wrong with this picture:
Code:
Template = {}
Button = {}
function Template.new(x,y,h,w) 
local self = {}   
self.x, self.y = x , y
self.w, self.h = w , h
return self
end

function Template.draw()
gfx.rect(x,y,w,h)
end

function DRAW(tbl)
    for key,btn  in pairs(tbl) do Template.draw()  end   
end

function main()
w,h = 20,20 
y,x = 10,10  
    
    for i = 1 , 5 do
    Button[i] = Template.new(x,y,h,w)
    x = x + 10    
    end    
    
    DRAW(Button)
    
    local char = gfx.getchar()
      if char ~= 27 and char ~= -1 then
        reaper.defer(main)
    end     
    
    gfx.update()
end
gfx.init("My Window", 640, 480, 0, 200, 200)
main()
why there are no 5 buttons but only 1 ?

Last edited by Sexan; 05-18-2016 at 03:02 PM.
Sexan is offline   Reply With Quote
Old 05-18-2016, 03:50 PM   #52
cyrilfb
Human being with feelings
 
cyrilfb's Avatar
 
Join Date: Apr 2012
Location: Denver, CO
Posts: 247
Default How to set a text flag

Quote:
Originally Posted by Lokasenna View Post
gfx.setfont(idx[,"fontface", sz, flags])

ReaScript is nice enough to give us a few preset slots we can assign fonts to, determined by giving idx a number from 1 to 16. 0 will give you a default font of some sort, I think. fontface is the font name in text, followed by size and flags like italics, underline or bold.

Note: I can't figure out how to set the flags. If anyone knows, let me know and I'll edit it in here.
Lokasenna, After a bit of research, I found that the flags were actually expected to be a number, not a string. It should be the ascii number of the letter you want to use as a flag.

So in ascii:
Bold = 'b' = 98
Italic = 'i' = 105
Underline = 'u' = 117

gfx.setfont(1, "Arial", 28, 98) sets bold text.
gfx.setfont(1, "Arial", 28, 105) sets italic text.
gfx.setfont(1, "Arial", 28, 117) sets underlined text.

I cannot figure out how to combine flags. It would be nice if someone would tell us.
__________________
Cy Ball
Reaper + inspiration = music.
soundcloud.com/cyball or cyball.bandcamp.com/ or cyrilfb.com
cyrilfb is offline   Reply With Quote
Old 05-18-2016, 04:46 PM   #53
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

(Edited the String lesson with a couple more functions I forgot to include)

cyrilfb, awesome. I'll add that to the lesson.

Sexan, I see a few problems:

- Your Template.draw function doesn't look at the current button's coordinates, it's just reading the x,y,w,h you specify in your main function. As a result, I think it's drawing five buttons in the same spot.

- Your DRAW function doesn't actually pass the current button to Template.draw anyway.

- Because the buttons are being created inside your main function, and then you're deferring main, the buttons are being created every single time it loops. They should be created separately - look at the LS GUI usage demo, and notice that all of the elements are created at the top-level, before any functions are called.

See if this works:
Code:
Template = {}
Button = {}


function Template.new(x,y,h,w) 
local self = {}   
self.x, self.y = x , y
self.w, self.h = w , h
return self
end


function Template.draw(btn)
gfx.rect(btn.x,btn.y,btn.w,btn.h)
end

function DRAW(tbl)
    for key,btn  in pairs(tbl) do Template.draw(btn)  end   
end

w,h = 20,20 
y,x = 10,10

local temp_x = x
for i = 1 , 5 do
	Button[i] = Template.new(temp_x,y,h,w)
	temp_x = temp_x + 30    
end 


function main()
  
    
    DRAW(Button)
    
    local char = gfx.getchar()
      if char ~= 27 and char ~= -1 then
        reaper.defer(main)
    end     
    
    gfx.update()
end

gfx.init("My Window", 640, 480, 0, 200, 200)
main()

Last edited by Lokasenna; 05-19-2016 at 03:22 PM.
Lokasenna is online now   Reply With Quote
Old 05-19-2016, 07:27 AM   #54
Sexan
Human being with feelings
 
Sexan's Avatar
 
Join Date: Jun 2009
Location: Croatia
Posts: 2,452
Default

Can anyone of you translate this script above to ":". I have trouble understanding how and when to use ":" and ".".I know when using Template:new() it uses self reference (so Template:new() is equal to Template.new(Template) but I still dont get it.If I go with ":" am I in metatable domain? because I want to build this without metatable?
I will have better understanding whats going on if the script above is translated.
Sexan is offline   Reply With Quote
Old 05-19-2016, 03:17 PM   #55
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

Code:
Template = {}

function Template:new(x,y,h,w) 

	local temp = {}   
	temp.x, temp.y = x , y
	temp.w, temp.h = w , h

	setmetatable(temp, self)
	self.__index = self
	return temp	

end


function Template:draw()
	
	gfx.rect(self.x, self.y, self.w, self.h)
	
end

function DRAW(tbl)
    for key, btn in pairs(tbl) do btn:draw()  end   
end

w,h = 20,20 
y,x = 10,10

Button = {}
local temp_x = x
for i = 1 , 5 do
	Button[i] = Template:new(temp_x,y,h,w)
	temp_x = temp_x + 30    
end 


function main()
      
    DRAW(Button)
    
    local char = gfx.getchar()
      if char ~= 27 and char ~= -1 then
        reaper.defer(main)
    end     
    
    gfx.update()
	
end

gfx.init("My Window", 640, 480, 0, 200, 200)
main()
I had to increase the value for temp_x to make the individual buttons visible. If your buttons are 20 pixels wide and you only increase x by 10 each time, they just make one big rectangle.

Further reading...
Lua reference manual - Object-oriented programming. There's an explanation of using the : syntax about halfway down
Lua reference manual - Classes
Lokasenna is online now   Reply With Quote
Old 05-19-2016, 08:30 PM   #56
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

10. LS GUI in detail

Note: This lesson refers to the LS GUI library provided in part 6. If you haven't downloaded those files yet, see this post: 6. Introducing the LS GUI library

Next up, I'd like to take a stroll through the demonstration scripts for LS GUI and hopefully provide a better picture of how all these parts work together. Even moreso than the previous lessons, I'd recommend having the demo scripts open somewhere handy so you can follow along.

Note: Numbers with parentheses and written in bold italics are there to denote what line of code we're looking at. i.e. (26)

The usage demo

(1) When working with a library file, the first thing we need to do is tell Lua to load said library file. In programming-speak, this is known as requiring, or including, the library.

- debug.getinfo() grabs a whole bunch of information about the function that called it - in this case, that's Reaper.
- Next we use another string function, string.match(), to extract a folder path to where the current script is located.
- A call to dofile(), as the name suggests, tells Lua to run the file we give it, treating it exactly as if the contents were written in this script.

All of the functions and classes from our library are stored under the variable GUI; as I explained in the Tables lesson, it's a very tidy way to bring a library into your own script without causing the user any trouble.

(15) Now that we have all of the GUI functions available, we can start with a few initial settings - the name of our window, position, and coordinates.

(20) This is just a basic example of a function that you might call when one of your buttons is clicked, as discussed in the Button lesson.

(35) Good god, another table. WHEN WILL THE MADNESS STOP!?! All of our GUI elements are stored here. Putting them all in a table makes life easier for our library's main functions, as we'll see shortly.

(61) The usage demo doesn't make use of it, but the GUI library's Main() function allows the user to specify a function that they'd like included in the script's defer loop. Optionally, you can specify how often to run it so that you don't pillage all of the end-user's processing power.

After that, all we do is call our Init() and Main() functions, and that's it.

The library

(63) Even more tables. I must get it from my dad - he made a spreadsheet for how to get around Disneyland as efficiently as possible once.

These tables are there so we can specify a global set of fonts and colors for our GUI elements. They can be overridden in a user script by including your own GUI.fonts{} and GUI.colors{}, provided the same format for key numbers, names, and values is followed.

They're also accompanied by a couple of helper functions that simplify the process of setting a font or color.

(133) Setting a few mouse variables to their default values, just so nothing returns nil by accident later on. The library functions could definitely be rewritten to check for errors, in which case you wouldn't need this, but I always forget to do that.

(130) The next section contains a number of functions that will make life easier. They can be called from a user script just like everything else in the library, via GUI.function_name(parameters).

(303) GUI.Init() is responsible for creating our window. It also converts the color presets from 0-255 RGB, which I find easiest to work with, into the 0-1 RGB that Reaper prefers.

(322) Alrighty, on to the big boys. GUI.Main() does the following every time our script loops around:

- Updates the mouse and keyboard variables. You could access them directly, but storing them locally makes the process a little faster. I think.

- Sees if the window has been closed or not, and calls our old buddy defer() to keep the party going.

- Asks each of our GUI elements to check in and see if it's been clicked, dragged, etc. I told you that table would help.

- If the user wanted to run their own function during the loop, do so.

(364) Here's where each element is sent for its checkup. If it was clicked on, or double-clicked, or dragged, or typed in, GUI.Update() calls that element's appropriate method. All of the elements are also told to redraw themselves while they're here.

This function is also a good example of why classes are useful - by providing Update() with the name of an object, it can check the status of all of them easily and change their variables. Because all of their methods have the same names, Update can call them with a minimum of fuss.

After that it's just the various classes and their methods, which have either been covered already or are on the list for an upcoming lesson.

Hmm. This post ended up being a lot shorter than I expected, partly because I already tried to leave as many comments as possible in the scripts themselves. If I think of anything else worth discussing, I'll edit it in.
Lokasenna is online now   Reply With Quote
Old 05-21-2016, 09:21 AM   #57
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

11. Example classes - TxtBox, Sldr, and Knb

Note: This lesson refers to the LS GUI library provided in part 6. If you haven't downloaded those files yet, see this post: 6. Introducing the LS GUI library

TxtBox

Typing. Everyone does it. Your script might want to do it too. Here's how.

(Cheers to schwa for his text box example, which this class and methods were largely copied from)

As we saw last time, GUI.Update() has each element check to see if it's been typed in. This requires a) the user to have pressed a key and b) the element to be in focus. focus is set when the element detects a left-click, and here it will activate the TxtBox for typing.

In the :onmousedown() method, we place the typing caret (the little blinking line) using another method that's exclusive to TxtBox - :getcaret():
Code:
self.caret = self:getcaret()
This is done by looping through the textbox's contents, one character at a time, and seeing if the x position of that character matches the position of the mouse cursor.
Code:
	for i = 1, len do
		
		w = gfx.measurestr(string.sub(self.retval, 1, i))
		if GUI.mouse.x < (self.x + self.pad + w) then return i - 1 end
	
	end
As you would expect, :ondrag() lets you select part of the text and :ondoubleclick() selects all of it. The selection is stored as a position relative to the caret.

:ontype() is, surprise surprise, significantly larger than other methods. However, after deleting any selected text, the bulk of the function is just one big "if/then, elseif, elseif, elseif..." statement because we have to tell Lua what to do for each keyboard key. For this example I've programmed in the arrow keys, backspace, and delete.

All that's left, then, is to draw everything. The frame and text are drawn as you would expect, and for selected text we draw a rectangle on top and then redraw that portion of the text in a different color. If the box is focused, we also need to draw the caret; to make it blink, the text box uses a counter to keep track of how many times the Main() loop runs and only draws the caret half of the time.

Code:
self.blink = (self.blink + 1) % 16
For those not familiar, the % symbol is for the mathematical operation "modulo", which gives you the remainder after subtracting as many 16s (in this case) as you can from the original number. Stated another way, this line of code just adds 1 to the current total and loops back to 0 when it hits 16, over and over.

Sldr

Now that we're all experts on clicking and typing, how about dragging?

Because the Sldr class also has a few new parameters, we'll start right at the beginning:
Code:
function Sldr:new(x, y, w, caption, min, max, steps, default)
The first thing you'll notice is that it's missing a parameter for height. Why? Because width as all we care about; we're hardcoding the size of the handle, so it wouldn't make sense to let users adjust the height of the "track". You could rewrite it to include height if you wanted to, you weirdo.

We've also got the slider's minimum and maximum values, how many steps to include between them, and a default step to start the slider on.

Sldr only deals with steps when it's communicating with the user, with all of the internal stuff handled as decimal values between 0 and 1, so we need to convert the :new() parameters from steps to decimals:

Code:
	sldr.curval = sldr.curstep / steps
	sldr.retval = ((max - min) / steps) * sldr.curstep + min
Notice that curval is our 0-1 value, and retval is adjusted for the min, max, and step parameters.

The :onmousedown() method is very similar to TxtBox's - it checks the position of the mouse cursor within the slider and sets the slider to the closest value.

:ondrag(), however, takes a little more effort, as it needs to know a) how far it's been dragged and b) if the Ctrl key is being held down. Ctrl is a fairly common modifier for sliders and knobs that allows for finer control.

To track how far the slider has been dragged, GUI.Update() stores the mouse position from the previous Update loop in GUI.mouse.lx and ly.

Back to Sldr:ondrag(), you can see that we're just comparing the current position of the mouse to where it was in the last loop, and multiplying the difference by a constant value. To allow Ctrl as a modifier, it's just a matter of choosing between two different constants based on the Ctrl state.

Both methods also check to make sure the new slider value is still inside the slider, and then they round the value to the nearest step.

As far as drawing the Sldr there isn't too much going on that we haven't previously covered. A roundrect() provides the track, gfx.circle() for the handle, and then the caption and current value.

Knb

Knobs are pretty much identical to sliders. :ondrag() looks at changes in the y value of the mouse rather than x, and knobs turn instead of moving, so each step is an angle instead of a horizontal offset. In Knb:new() we figure out the size of each step:
Code:
	-- Determine the step angle
	knb.stepangle = (3 / 2) / knb.steps
This part might be really upsetting for anyone who didn't enjoy high school math class. One word for you: radians. Here's a brief introduction for those who need to brush up.

*pause for the screaming and rioting to settle down*

Radians are a tidier way of measuring things on a circle than degrees, and ReaScript has a few functions that require you to use radians as well. In short, 2pi radians is a whole circle - 360 degrees, and pi radians is 180. For a knob, the minimum and maximum positions are often at the southwest and southeast positions, equal to three quarters of the circle or 3/2pi.

Moving on to :draw() we first convert the x,y,w values to things that gfx.circle() can use - coordinates for the center of the circle, and a radius. Next we figure out the current angle (in radians) based on which step the knob is on.

Code:
	local curangle = (-5 / 4) + (curstep * stepangle)
Things worth noting:

- Radian math uses east as its 0. That -5/4 is just an offset so that our minimum is where we want it to be.

- I've specifically written the code to avoid using pi directly. It's not necessary, but I felt that this way was neater and more readable. It might just be me.

- :draw() makes good use of another helper function, polar2cart(), which takes a position in radians, along with your circle's center coordinates, and returns the equivalent x,y coordinates. This is also where pi gets factored in.

Code:
local x, y = polar2cart(angle, r, o.x, o.y)
After the tick marks and values are drawn, it's time for a new function. Oh boy oh boy oh boy oh boy!

gfx.triangle(x1, y1, x2, y2, x3, y3)

I really shouldn't have to explain how that one works.

The built-in triangle function will only draw them filled in, so our library includes yet another wrapper function to provide us with a fill option - GUI.triangle(). It adds an additional parameter at the beginning, a 1 or 0 to determine the fill state.

Code:
	GUI.triangle(0, Ax, Ay, Bx, By, Cx, Cy)
The knob itself is made of a circle and a triangle smushed together. The following block of code determines three points for the triangle - one sticking out of the circle toward the current value, and two more 90 degrees (1/2pi radians) to either side of it, just inside the circle. Put them together, throw in a shadow, and you've got yourself on sexy knob.

As with Sldr, Knb works with the steps as values from 0 to 1 and then converts them to the correct format when drawing text and returning a value.
Lokasenna is online now   Reply With Quote
Old 05-22-2016, 06:01 AM   #58
Sexan
Human being with feelings
 
Sexan's Avatar
 
Join Date: Jun 2009
Location: Croatia
Posts: 2,452
Default

One question since I can't get any response elsewhere,how can you make button do something when alt/ctrl/shift clicking?In my case I want to remove the button when alt clicked
Sexan is offline   Reply With Quote
Old 05-22-2016, 10:06 AM   #59
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

- Add code in the button's :onmousedown() method to check for the state of Alt/Shift/whatever. See Lesson 5.

- For removing an element, you just need to set that element to nil:
Code:
local function remove_elm(elm)

	for key, value in pairs(GUI.elms) do
		if key == elm then GUI.elms[key] = nil end
	end

end
For example, here's a button that just removes itself and nothing else:
Code:
my_btn = GUI.Btn:new(100, 200, 200, 75, "Delete the button", remove_elm, "my_btn")
Things worth noting:
- The name you give has to be a string, i.e. "my_btn" and not my_btn
- If the button is the only element in GUI.elms, it will delete the button but it won't be erased from the window. However, you probably aren't going to just have a button that deletes itself and nothing else.

(Obviously these examples are written for LS GUI, so you'd have to adapt them to whatever you've got set up)

Last edited by Lokasenna; 05-22-2016 at 04:39 PM.
Lokasenna is online now   Reply With Quote
Old 05-22-2016, 11:44 AM   #60
cyrilfb
Human being with feelings
 
cyrilfb's Avatar
 
Join Date: Apr 2012
Location: Denver, CO
Posts: 247
Default Thanks

Lokasenna, thanks for this tutorial.

Just the right level for me. Your teaching techniques are great.

Keep up the good work!
__________________
Cy Ball
Reaper + inspiration = music.
soundcloud.com/cyball or cyball.bandcamp.com/ or cyrilfb.com
cyrilfb is offline   Reply With Quote
Old 05-22-2016, 04:35 PM   #61
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

12. Using images

Today we're going to learn how to work with images:



Dude. It's just like the first time I saw someone playing a Super Nintendo. Pants were crapped that day, let me tell you.

First off, I guess we're going to need some images. If you're like me and couldn't draw a picture of the broad side of a barn even if you had a photo to trace over, allow me to direct you here:

Knobman

This is a really handy little app that makes the process of drawing knobs, sliders, and buttons much easier. There's a bit of a learning curve, though.

More Knobman resources:
https://www.youtube.com/watch?v=AXn5hD1ncyg
https://www.youtube.com/watch?v=NuG72vQ8JCE (ignore the bits about Reaktor)

Many people find Photoshop easier for making buttons, as well.

To help you get started, I recommend grabbing Leslie Sanford's Knobman files. You just download the file you want, open it in Knobman, and export it with the appropriate number of frames. Alternatively, I've already converted a few of his files to .png form for this lesson's example, so you could just use those if you wanted.

Speaking of which, this lesson is NOT going to use the LS GUI library we've been working with previously. Because images are such a different beast, I've rewritten a portion of the library specifically for today's examples. Just download the .zip below, unzip it your scripts folder, and run the "images usage demo" script.

https://www.dropbox.com/s/x2ypv93guq...mages.zip?dl=1

Images

Reaper uses a very common technique to draw images, known as blitting. Blitting is, basically, telling a program to copy/paste a particular section of one image into another.

Images for blitting are typically laid out with all of their frames in one big row or column - in this case, I've opted for columns. If you open knob.png in the lesson files, for instance, you'll see that contains 64 frames of a knob slowly turning around.

Accessing these in our script is relatively easy:

gfx.loadimg(buffer, file)

"I know what a file is, Lokasenna, but what the hell is a buffer?"

Easy. Buffers are a bunch of off-screen places for us to store our images. We can manipulate them too, rotating, scaling, drawing new stuff on them, but for this lesson we're just going to put each image in its own buffer and leave it that way.

In the images library demo script, you'll see a new table with all of our file names in it:
Code:
GUI.images = {
	
	bg = "\\tutorial images\\bg.png",
	btn = "\\tutorial images\\button.png",
	knb = "\\tutorial images\\knob.png",
	sldr = "\\tutorial images\\slider.png",
	opt = "\\tutorial images\\opt.png",
	lbl = "\\tutorial images\\label.png",
	
}
The \\s are necessary because Lua has a few special commands that are written as \n, \s, etc. \\ tells Lua that no, this really is supposed to be a \.

Code:
-- Load all our images into buffers
local function load_images()
	
	local info = debug.getinfo(1,'S')
	script_path = info.source:match[[^@?(.*[\/])[^\/]-$]]
	
	local idx = 0
	
	for key, file in pairs(GUI.images) do
		
		retval = gfx.loadimg(idx, script_path..file)
		GUI.images[key] = idx
		idx = idx + 1
		
	end

end
GUI.load_images = load_images
- First we use the same functions for loading a library from a user script, in this case to find out where the script is being run from.

- Next we loop through each item in the table above. The script path and file name are combined and loaded into sequentially-numbered buffers.

- Since we don't need the file names anymore, each entry in the image table is instead used to save its buffer number.

Once images are loaded, another new function handles drawing them:

gfx.blit(source, scale, rotation[, srcx, srcy, srcw, srch, destx, desty, destw, desth, rotxoffs, rotyoffs])

The parameters:

- A buffer number
- How much to scale the image up or down
- How much to rotate the image
- Coordinates and dimensions in the buffer to copy from
- Coordinates and dimensions in the GUI window to copy to
- A couple of optional parameters that let you do fancier transformations with the image. We can ignore them.

The source coordinates are of particular importance here, because that's how we specify what frame to grab. If a knob is 64 pixels tall, the first frame would be at y = 0, the second at y = 64, and so on.

I think we can agree that gfx.blit() is pretty scary, and it would be nice if we could avoid ever having to call it directly. Good thing some enterprising young (fine, I'm 32) gentleman wrote a helper function for all of this:

Code:
-- Draw the specified frame from a given image buffer
local function img(x, y, w, h, step, buffer)
	
	gfx.mode = 0
	local img_y = h * GUI.round(step)
	gfx.blit(buffer, 1, 0, 0, img_y, w, h, x, y, w, h)

end
GUI.img = img
- gfx.mode gives you a few options for how to combine the copied image with whatever's already been drawn in the GUI window. We're making sure it stays set to 0, for normal drawing.

- Using the element height h and frame number step, we figure out a y-coordinate in the buffer to get our current frame from. Note that all of the classes in this example have been rewritten so that h specifies the image height, not the element as a whole. For instance, specifying h = 50 for the OptLst class tells it that each individual button in the list is 50 pixels tall, not the entire group.

- Finally we blit from the source buffer to the window.

Drawing our elements with images

Before we do anything else, I suppose we need to have a background. This gets done in the Main() loop, prior to drawing the individual elements:

Code:
	GUI.img(0, 0, GUI.w, GUI.h, 0, GUI.images.bg)
Well that was thrilling.

As you can see in the images library demo, each class's :draw() method has been rewritten. Let's start with the Knob and Slider, since they're drawn the same way:

Code:
	local frame = (curstep / steps) * 63	
	
	GUI.img(x, y, w, w, frame, GUI.images.knb)
Rather than having to muck around with tick marks, shadows, circles, or polar coordinates, life here has gotten WAY easier. Some basic math tells us what frame we want, and then we phone up GUI.img to get things done. Simple as that.

Btn is pretty much the same; its image file only has two frames, not-pushed and pushed, so we just draw whichever one matches the button's current state.

The Lbl up top uses frames to do a nice visual trick. The image is, again, two frames - the background, and a sort of frosted-over cover. I was trying to make it look like a little LED readout, but it didn't quite work. Oh well.

To create this effect, we just draw the first frame, print our text on top of it, and draw the second on top of that. Because the second frame's pixels are mostly transparent, you can still read the text.

Lastly we've got OptLst, which coincidentally requires the most work to draw. Almost like I planned it this way, right?

The first thing you might notice if you compare the source image to the functioning script is that, in the source, there's a lot of empty space around the button. This wasn't my choice, it's just how the button came when I grabbed it from the link above. So why isn't there any empty space around the options in our GUI? Simple - :new() takes an extra parameter, img_pad, that lets us control the spacing between the options. For this image, setting the image pad to -12 sticks them all together.

:new() also keeps track of the individual option height separately from their collective height, since we'll need to know both.

Moving on to :draw(), we just have a loop drawing each button and then hopping down by one button height for the next one. It also checks if the current button is pushed and grabs the second frame when needed.

The button text also moves over when it's being pushed, both here and in the original LS GUI library. To do so, we just add 2 to the x and y coordinates before drawing the text. I think it looks nice.

And with that, we've come to the end of my lesson plan. Hopefully you learned something.

Last edited by Lokasenna; 04-16-2018 at 07:16 AM.
Lokasenna is online now   Reply With Quote
Old 05-22-2016, 04:43 PM   #62
X-Raym
Human being with feelings
 
X-Raym's Avatar
 
Join Date: Apr 2013
Location: France
Posts: 6,061
Default

I never understood loading image in GFX before this lesson. ^^
X-Raym is offline   Reply With Quote
Old 06-04-2016, 06:43 AM   #63
Sumalc
Human being with feelings
 
Join Date: Oct 2009
Location: France
Posts: 561
Default

I think this topic should remain in the beginning and have "sticky" status to prevent its valuable content disappears in these pages, isn't it?
Sumalc is offline   Reply With Quote
Old 06-10-2016, 09:50 PM   #64
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

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:
Code:
local function do_stuff_on_each_loop()

	...blah blah blah...

end


GUI.func = do_stuff_on_each_loop
GUI.freq = 0

GUI.Init()
GUI.Main()
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.

Code:
	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)
		
	end
	
	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.

Code:
GUI.elms.note_len.output = {
	[0] = "1/64",
	"1/32",
	"1/16",
	"1/8",
	"1/4",
	"1/2"
}
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:
Code:
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.

Code:
	local vol = GUI.Val("vol_sldr")
	
	if vol ~= last_vol then
		
		GUI.elms.vol_sldr.output = vol.."db"
	
	end
	
	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
Old 07-05-2016, 01:03 PM   #65
me2beats
Human being with feelings
 
me2beats's Avatar
 
Join Date: Jul 2015
Location: Yekaterinburg, Russia
Posts: 400
Default

wow. thank you very much!
me2beats is offline   Reply With Quote
Old 08-02-2016, 09:21 PM   #66
woodslanding
Human being with feelings
 
woodslanding's Avatar
 
Join Date: Mar 2007
Location: Denver, CO
Posts: 402
Default

I'm trying part 2 of the tutorial, but the IDE is complaining. Here's my code:

Code:
local function Main()

  local char = gfx.getchar()
  if char ~= 27 and char ~= -1 then
    reaper.defer(Main)
  end
  gfx.update()

end

local function rgb2num(red, green, blue)
  
  green = green * 256
  blue = blue * 256 * 256
  
  return red + green + blue

end

-- A nice, respectable shade of grey.
local r, g, b = 64, 64, 64
gfx.clear(rgb2num(r, g, b))

gfx.init("My Window", 640, 480, 0, 200, 200)
Main()
When I run it, it gives the error: attempt to call a number value (field 'clear')

Can't see what I might be doing wrong....
__________________
eric moon
Very Stable Genius
https://gogolab.com/
woodslanding is offline   Reply With Quote
Old 08-03-2016, 04:11 AM   #67
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

That's a whoops on my part. gfx.clear is a variable, not a function, so the line should look like:
Code:
gfx.clear = rgb2num(r, g, b)
I've corrected the original example.
Lokasenna is online now   Reply With Quote
Old 08-03-2016, 01:26 PM   #68
mpl
Human being with feelings
 
mpl's Avatar
 
Join Date: Oct 2013
Location: Moscow, Russia
Posts: 2,542
Default

What I noticed from this thread is even if I try to draw nice GUI, there is no way to run from old noobie coding habbits to use functions and tables instead any elegant solutions explained here.

Any synthax with : or :: or lua metatables looks very complex and so hard to understand for me.
Hope I`ll come back to this amazing tutorial later with more knowledges.
__________________
SoundCloud | MPL Scripts discussion | ReaPack | Donate
mpl is offline   Reply With Quote
Old 08-03-2016, 04:49 PM   #69
Nz0
Human being with feelings
 
Nz0's Avatar
 
Join Date: May 2016
Location: Switzerland
Posts: 19
Default require ("Lokasenna_GUI Library")

Hello Lokasenna

Thanks a lot for putting this helpful library together.

Do you know that you can create a lua folder in the Reaper folder (i.e: C:\Program Files\REAPER (x64)\lua), save your library there and call it from any script using the following?

Code:
 require ("Lokasenna_GUI Library")
No need to copy your code over and over for each GUI you develop.

HTH
Nz0
Nz0 is offline   Reply With Quote
Old 08-03-2016, 06:19 PM   #70
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

That's good to know - I got really frustrated trying to come up with a more reasonable way to do that.
Lokasenna is online now   Reply With Quote
Old 08-04-2016, 04:57 PM   #71
woodslanding
Human being with feelings
 
woodslanding's Avatar
 
Join Date: Mar 2007
Location: Denver, CO
Posts: 402
Default

Quote:
Originally Posted by Lokasenna View Post
That's a whoops on my part. gfx.clear is a variable, not a function, so the line should look like:
Code:
gfx.clear = rgb2num(r, g, b)
I've corrected the original example.
Ahh, so much more satisfying!

I remember reading it was a variable, and thinking (with my beginner LUA brain) how can you tell??
__________________
eric moon
Very Stable Genius
https://gogolab.com/
woodslanding is offline   Reply With Quote
Old 08-04-2016, 08:51 PM   #72
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

You can't, really, except that the section of the API that gives you gfx.clear starts with this:

Quote:
The following global variables are special and will be used by the graphics system
It took me a while at first to notice it.
Lokasenna is online now   Reply With Quote
Old 08-04-2016, 09:18 PM   #73
cfillion
Human being with feelings
 
cfillion's Avatar
 
Join Date: May 2015
Location: Québec, Canada
Posts: 2,831
Default

Quote:
Originally Posted by woodslanding View Post
how can you tell??
Lua often give useful hints in the error messages:

Quote:
somefile:10: attempt to call a number value (field 'clear')
With this you can discover that:
  1. the "field" clear used at line 10 is a number
  2. the script attempted to use it like a function

Another way to check:
Code:
reaper.ShowConsoleMsg(type(gfx.clear) .. "\n") --> number
reaper.ShowConsoleMsg(type(gfx.line) .. "\n") --> function

Last edited by cfillion; 08-04-2016 at 09:25 PM.
cfillion is offline   Reply With Quote
Old 08-05-2016, 12:15 PM   #74
woodslanding
Human being with feelings
 
woodslanding's Avatar
 
Join Date: Mar 2007
Location: Denver, CO
Posts: 402
Default

Rereading....

Woah, so there is actually no necessary syntactical difference between a function and a variable??

I'll get used to it

THANKS!
__________________
eric moon
Very Stable Genius
https://gogolab.com/
woodslanding is offline   Reply With Quote
Old 08-06-2016, 06:52 PM   #75
Nz0
Human being with feelings
 
Nz0's Avatar
 
Join Date: May 2016
Location: Switzerland
Posts: 19
Default

Quote:
Originally Posted by Lokasenna View Post
That's good to know - I got really frustrated trying to come up with a more reasonable way to do that.
I think that I found a way to reduce your frustration even more

I came across a lua library called "lupy", a simple Python-like OO implementation for lua. It does a lot of things that I don't even understand, nevertheless I made a toy example with encapsulation.

You can define virtual functions, not sure if the term is correct. I mean you define the function in the object file and then override it in your main script.

Extract the Zip-File under "C:\Program Files\REAPER (x64)". For simplicity I also included the lupy code. Load AKAI_MPK25_Track_Control.lua in Reaper as usual. Try left and right clicking on the pads.

lupy: https://github.com/uleelx/lupy
other OO libs: http://lua-users.org/wiki/ObjectOrientedProgramming

HTH
Nz0
Attached Files
File Type: lua AKAI_MPK25_Track_Control.lua (965 Bytes, 131 views)
File Type: zip Pad.zip (11.8 KB, 40 views)
Nz0 is offline   Reply With Quote
Old 12-12-2016, 10:13 AM   #76
Spacemen Tree
Human being with feelings
 
Spacemen Tree's Avatar
 
Join Date: Mar 2013
Posts: 501
Default

Just dropped by to say thanks again for this. This tutorial is great and has helped me quite a bit. Plus I'm having fun mangling your LS GUI script. I wish I could code like that. Very well thought out and it's easy to follow.

Could I interest you in updating this with a how to make a scroll bar?
Would be super.

Thanks
__________________
"After silence, that which comes nearest to expressing the inexpressible is music", Aldous Huxley
Spacemen Tree is offline   Reply With Quote
Old 12-12-2016, 12:45 PM   #77
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 10,316
Default

Lokasenna, this is a terrific tutorial! I don't know how I haven't seen it before.

Quote:
Originally Posted by Lokasenna View Post
It should be the ascii number of the letter you want to use as a flag.

So in ascii:
Bold = 'b' = 98
Italic = 'i' = 105
Underline = 'u' = 117

gfx.setfont(1, "Arial", 28, 98) sets bold text.
gfx.setfont(1, "Arial", 28, 105) sets italic text.
gfx.setfont(1, "Arial", 28, 117) sets underlined text.

I cannot figure out how to combine flags. It would be nice if someone would tell us.
This may already be answered up-thread, but if not:

Code:
BOLD=98
ITALIC=105
UNDERLINE=117

gfx.setfont(1,"arial",20,(ITALIC<<8)|UNDERLINE)
-- equivalent:
gfx.setfont(1,"arial",20,tonumber(string.format("0x%02X%02X",ITALIC,UNDERLINE)))
schwa is offline   Reply With Quote
Old 12-12-2016, 12:47 PM   #78
Lokasenna
Human being with feelings
 
Lokasenna's Avatar
 
Join Date: Sep 2008
Location: Calgary, AB, Canada
Posts: 6,356
Default

We'll see, but I doubt it. My spare time is extremely limited right now, so what I do get ends up being spent on guitar.

As for the scrollbar, you mean for working with more stuff than the window can fit? I'm not sure LS GUI can do that as is - you'd have to add an offset value for where the "current" top-left corner of the window is, and use that in the other elements' drawing routines to determine where they should show up.

I'll add it to my to-do list anyway, though.
Lokasenna is online now   Reply With Quote
Old 12-12-2016, 12:51 PM   #79
schwa
Administrator
 
schwa's Avatar
 
Join Date: Mar 2007
Location: NY
Posts: 10,316
Default

FWIW the lyrics editor script (lyrics.lua) that will be included with REAPER 5.30 includes a text entry box and scrollbar, which may be useful as coding examples (and could certainly be improved further!).
schwa is offline   Reply With Quote
Old 12-12-2016, 01:38 PM   #80
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 12,485
Default

Quote:
Originally Posted by schwa View Post
Code:
BOLD=98
ITALIC=105
UNDERLINE=117

gfx.setfont(1,"arial",20,(ITALIC<<8)|UNDERLINE)
-- equivalent:
gfx.setfont(1,"arial",20,tonumber(string.format("0x%02X%02X",ITALIC,UNDERLINE)))

Or:
Code:
gfx.setfont(1,"Arial",20, (string.byte('i')<<8)|string.byte('u'));
The ordering of the various bytes is not important. This is a carryover from EEL, which has a convenient multibyte character syntax, e.g. gfx_setfont(1,"Arial",20,'iu');

Another bit of helper code:
Code:
function fontflag(str) local v=0; for a=1,str:len() do v=v*256+string.byte(str,a) end return v end

gfx.setfont(1,"Arial",20, fontflag('iu'));

Last edited by Justin; 12-12-2016 at 01:47 PM.
Justin is online now   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 02:32 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.