Skip to content

Introduction to Squirrel Programming

liquid8d edited this page Jan 28, 2017 · 15 revisions

#Table of Contents

Getting familiar with Squirrel

Squirrel is the scripting language built into Attract-Mode which allows users to extend the functionality of the frontend.

You can view some of the scripts used in the following folders on your Attract-Mode installation:

modules/
plugins/
layouts/

Some are contained in folders, but each of them use text files with the .nut extension which uses the Squirrel language format.

It's helpful to have some basic programming understanding, but isn't required. Since Squirrel is a programming language it has its own syntax in regards to variables, functions, objects and classes. We'll try to keep things simple here, but you can learn more about Squirrel at these links:

Squirrel 3.0 Reference Manual
Squirrel 3.0 Standard Library Manual
ElecticImp Documentation

A variable is a way to store a value or a reference to something in your script.

    local myInteger = 100
    ::myString = "This is global"

A variable can be an integer (negative or positive number), a float (decimal number) a string (text), a boolean (true/false), arrays and tables (multiple values), functions (mini-script) or an instance to a class (a reference to an object).

Variable names should be short but understandable - usually with a lowercase first letter and uppercase for additional words or use all lowercase with an underscore (_) to separate words. There can be no spaces or most special characters.

Variables can be stored local to the script, or stored in the Squirrel root table using :: in front of the variable name to be access globally from any script.

Each line may end with a semi-colon as with most programming languages, but this is not required unless you are combining multiple statements together on a single line.

A function is a way to separate a block of code that performs a certain function. You might make a 'debug' function that prints some text:

function debug( text )
{
    print( "Debug: " + text + "\n" )
    return true
}

Functions can then be called anywhere else in the script, meaning you don't have to keep re-writing the code.

Functions can include variables that are passed to it allowing it to perform calculations. If desired, you can return a value from your function which can then be assigned to a variable.

A function can be on a single line, but is sometimes better to span across multiple lines for readability. A function does not have to have the semi-colon at the end of it.

Squirrel provides many useful built-in functions, for example the print( str ) function used above.

To store multiple values within a single variable, you can use what Squirrel calls a table.

local myTable = {
  firstValue: "This is the first value",
  secondValue: 100
}

A table begins with the opening curly brace and ends with the closing curly brace. Each item you include in the table will be a variable name, followed by a colon, then the variables value. If you include more than one variable, each one should be separated with a comma.

A table can be on a single line, but is sometimes better to span across multiple lines for readability. A table does not have to have the semi-colon at the end of it.

Code without comments is bad, mmkkaay? When you write a program, or script - you want to make sure you describe what you are doing. All programming languages provide a way to add comments to your code. For squirrel, there is a single line comment and multi-line comment:

//this is a single line comment

Just put double forward slash in front of your comment for a single line comment. This can even be after your semi-colon. As soon as Squirrel see the // it ignores the rest of the line as a comment.

/*
This is a multiple line comment.
We can go into more detail with this.
*/

Multi-line comments are started with /* and continue multiple lines until it reaches */ which ends your comment.

If you want to learn to code, or just make life easier when creating your layout - get in the habit of commenting! It helps in case you forget something, and even more if someone else is trying to figure out what you did!

To interact with Attract-Mode from a Squirrel script, an fe table is made accessible to you in any .nut file you create. A table contains variables and functions that will be useful to you. For example, if you want to know the directory your script or layout is running from:

print( fe.script_dir + "\n" )

A full list of the functions and variables that can be found using fe can be seen at: Layout and Plugin Programming Reference

We will use the fe table as we go on, so you can reference that if you want to know more about a certain variable or function we use.

Now that we understand a bit about Squirrel, we can start creating layouts. In a nutshell, a layout controls what objects are displayed on the screen and where, but they can do much more.

##First Steps Create a folder
Layouts are stored in your Attract-Mode folder in the layouts/ folder. All layouts should be in their own folder - the only exception is the screensaver.nut which is the layout file for the frontend screensaver.

Create a layout.nut file
For a layout to work, you must create a file named layout.nut in your layout folder. This is where we will write a script to describe our layout. Open your layout.nut file in any text editor. Any will do, but a list of recommended editors can be found here.

First, we want to make sure that our layout will work correctly regardless of someones resolution, so the first thing we will do is tell fe we want the layout to be a specific resolution.

In your layout.nut file add the following lines:

fe.layout.width = 640
fe.layout.height = 480

While you don't have to specify the layout width/height - it would default to your screen resolution which may be different for each user. This ensures that Attract-Mode will scale your layout for other users to fit the resolution that you designed the layout for.

The next thing we want to do is add some objects to our layout. At least one object must be added for a layout to be shown.

Using the fe table described above, Attract-Mode provides function to do exactly that:

fe.add_text( str, x, y, w, h )
fe.add_artwork( art, x, y, w, h )
fe.add_listbox( x, y, w, h)
fe.add_image( img, x, y, w, h )

We'll walk through adding each of these to your layout one by one.

We'll start by adding a single text object that contains the game title. Add the following line to your layout.nut file:

local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )

Here we running the fe.add_text function telling it the text we want, the x, y coordinates on the screen and the width, height of the text object. We assigned the result to the title variable. We used a special fe variable to set the width to the full width of our layout.

While we don't have to assign the functions result to a variable, this will allow us to access the text object further in our code.

Since we have the text object (title), we can now use the Attract-Mode functions and variables for a Text object. Add this line to your layout.nut:

title.set_rgb( 0, 255, 0 )

This will set the title text color to bright green ( 0 Red, 255 Green (max), 0 Blue)

Your layout.nut should now look like this:

fe.layout.width = 640
fe.layout.height = 480
local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )
title.set_rgb( 0, 255, 0 )

Save your layout.nut file. Open Attract-Mode, hit TAB (for Settings), select your current Display and change the layout to the one you created (as named by the folder you created). Hit ESC to go back to your layout and you should see a single text line at the top-left of your screen. Using up and down this will change the current game.

A full list of what you can alter with a Text object can be found here.

That's it! You made your first layout. Just a text with the title isn't very fancy. Let's spruce it up.

Attract-Mode makes displaying artwork easy by having the user specify their artwork folders. By default for Mame: the flyer, marquee, snap and wheel folders are supplied asking the user to tell it where the artwork for each is. More can be added, though!

Let's add the snap artwork to our layout. Add the following to your layout.nut:

local snap = fe.add_artwork( "snap", 338, 70, 287, 214 )

Notice we specified the "snap" artwork. We set it to some pretty specific sizes here - you'll see why later. Basically though, the snap x (338) is set a little past the center of the 640 width layout, and snap y (70) down a bit from the top (so it won't overlap with the title).

Your layout.nut should now look like this:

fe.layout.width = 640
fe.layout.height = 480
local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )
title.set_rgb( 0, 255, 0 )
local snap = fe.add_artwork( "snap", 338, 70, 287, 214 )

Save your layout.nut and open Attract-Mode again. You should now see your snap or video of the selected game. If you don't, make sure you have the correct location for the snap artwork path in your Emulator configuration.

A full list of what you can alter with the Artwork object can be found here.

A Listbox creates a simple list of games that can be navigated in our layout. . Add the following lines to your layout.nut:

local list = fe.add_listbox( 9, 75, 311, 385 )
list.rows = 20
list.charsize = 16

Again we have some specific sizes here. x is a little off the left side, y is a bit down from the top (not to overlap with the title), and the width and height makes it fit in the lower left side of the layout.

We are also setting some listbox variables - rows is how many games will be displayed in the list, and charsize is the font size of each text item.

Save your layout.nut file. It should now look like this:

fe.layout.width = 640
fe.layout.height = 480
local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )
title.set_rgb( 0, 255, 0 )
local snap = fe.add_artwork( "snap", 338, 70, 287, 214 )
local list = fe.add_listbox( 0, 50, 300, 590 )
list.rows = 20
list.charsize = 16

A full list of what you can alter with the ListBox object can be found here.

Artwork images are automatically found from the path provided by the user - but you can add any image to your layout.

We'll add a background image to the layout. BUT - we have to be careful. Objects in Attract-Mode are drawn in the order that you add them in your layout. This means if we add a background image AFTER our other objects, it will be drawn ON TOP. Since we don't want that - at least for now :) - we will add this line ABOVE all the other objects. Add these lines above your title object in your layout.nut:

local bg = fe.add_image( "bg.png", 0, 0, fe.layout.width, fe.layout.height )
bg.set_rgb( 0, 100, 230 )

This will now look for a 'bg.png' file in your layout folder. If it doesn't exist, the image won't show up. You can use this bg.png file I created just for this layout or you can add your own. You can change the filename if you want. But make sure to copy it into your layout folder.

We also set the color on the image. Yep! Any grey-scale colors will be altered based on the color you put.

Your layout.nut file should now look like:

fe.layout.width = 640
fe.layout.height = 480
local bg = fe.add_image( "bg.png", 0, 0, fe.layout.width, fe.layout.height )
bg.set_rgb( 0, 100, 230 )
local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )
title.set_rgb( 0, 255, 0 )
local snap = fe.add_artwork( "snap", 338, 70, 287, 214 )
local list = fe.add_listbox( 9, 75, 311, 385 )
list.rows = 20
list.charsize = 14

Save your layout.nut file and run Attract-Mode again. Now you should see all your objects, with a background!

##Wrapup Now we have a pretty functional layout. Let's add a couple finishing touches:

List selected background color
The default background color for our list doesn't match the green that we used for our background, so we'll change it:

list.set_selbg_rgb(30,180,30)

Add a Marquee
If you used the bg.png provided here, you'll notice there is an open space for an image or text. Let's add another artwork at the end of your layout.nut file:

local marquee = fe.add_artwork( "marquee", 338, 303, 287, 75 )
marquee.preserve_aspect_ratio = true

We'll also use a special variable called 'preserve_aspect_ratio' which makes the image fit in our specified size, but doesn't stretch it.

Add Information Text
Finally, we'll add some text information right below the marquee:

fe.add_text("[DisplayName]", 338, 385, 300, 20)
fe.add_text("[FilterName]", 338, 405, 300, 20)
fe.add_text("[ListEntry] of [ListSize]", 338, 425, 300, 30)

I didn't set these as variables, because I'm not changing any of their properties. Notice all of our text uses special strings enclosed in brackets. This means Attract-Mode will fill in the correct information when you go through your games. You can put your own text in there if you want, or combine multiple Magic Tokens with regular text.

Intro Layout

Hey! Looks pretty good. Play around with some of the objects properties until you have everything the way you like. You can remove any of the objects we put in, or add more if you want.

Next we'll get to some more advanced stuff.

##Additional Squirrel TODO:

  • arrays
  • if/elseif/else
  • for/foreach
  • switch/case

Layouts can have configuration options that the user can change under 'Layout Options' when using your layout. These can be added in your layout.nut in a special format:

class UserConfig </ help="Intro Layout Options" />
{
	</ label="Color", help="The theme color", options="Green,Red,Blue" order=1 />
	color="Green"
}

The config creates a class that will contain user settings - with specific syntax to describe the help text and each configuration option. Notice each description starts with </ and ends with />. The help description immediately following UserConfig is the help text that shows at the bottom of the LayoutOptions screen when no option is currently selected. Each option is described within the curly braces.

When adding options there is certain information you can provide:

  • label: The name of the option, displayed in Layout Options
  • help: The help text shown at the bottom of the Layout Options screen
  • options: User selectable options. Each option should be followed by a comma, no spaces. This is optional. If it is not provided, the option will be a text input for the user
  • order: The order this option will shown in the Layout Options screen

Immediately following the description of our option - we specify a variable with it's default value. We will be able to find out later what the user has set this to, and make changes in our layout based on that.

Above, we have added an option for theme color to our layout. If you remember before, we set the background image color - and we're going to make that an option for the user.

Copy the above code at the top of your layout.nut file.

Save your layout.nut and start Attract-Mode. Hit TAB, select your Display and go to Layout Options. You should now see the option we added. Changing it won't do anything yet, we have to add some code for that.

Back in your layout.nut, remove the lines with bg.set_rgb(), title.set_rgb, and list.set_selbg_rgb().

At the end of your layout.nut, add these lines:

local config = fe.get_config()
if ( config["color"] == "Red" )
{
   bg.set_rgb( 170, 10, 50 )
   title.set_rgb( 255, 0, 0 )
   list.set_selbg_rgb( 180, 30, 30 )
} else if ( config["color"] == "Blue" )
{
  bg.set_rgb( 50, 10, 170 )
  title.set_rgb( 0, 0, 255 )
  list.set_selbg_rgb( 30, 30, 180 )
} else {
  //default to Green
  bg.set_rgb( 10, 170, 50 )
  title.set_rgb( 0, 255, 0 )
  list.set_selbg_rgb( 30, 180, 30 )
}

To access the color variable we put in the user config, we have to get the current settings with fe.get_config(). The get_config returns table with multiple values, which we will store in the config variable. To get get the color value, we use config["color"].

Next, we add an if statement. An if statement can check if something is true or something equals a certain value. Let's break it down:

  • if our color config value equals (==) "Red", then we will set the color variables on our bg, title and list to red colors

  • else if that is not the case, but it IS "Blue", set color variables to blue

  • else (not "Red" or "Blue"), use the default "Green" and set it to green colors.

Now, if a user has set their color Layout option to Blue, it will go through the if statement and set the colors correctly.

Here's what your layout should look like at this point:

class UserConfig </ help="Intro Layout Options" />
{
	</ label="Color", help="The theme color", options="Green,Red,Blue" order=1 />
	color="Green"
}

fe.layout.width = 640
fe.layout.height = 480

local bg = fe.add_image( "bg.png", 0, 0, fe.layout.width, fe.layout.height )

local title = fe.add_text( "[Title]", 0, 10, fe.layout.width, 35 )
title.set_rgb( 0, 255, 0 )

local snap = fe.add_artwork( "snap", 338, 70, 287, 214 )

local list = fe.add_listbox( 9, 75, 311, 385 )
list.rows = 20
list.charsize = 14
list.set_selbg_rgb(30,180,30)

local marquee = fe.add_artwork( "marquee", 338, 303, 287, 75 )
marquee.preserve_aspect_ratio = true

fe.add_text("[DisplayName]", 338, 385, 300, 20)
fe.add_text("[FilterName]", 338, 405, 300, 20)
fe.add_text("[ListEntry] of [ListSize]", 338, 425, 300, 30)

local config = fe.get_config()
if ( config["color"] == "Red" )
{
   bg.set_rgb( 170, 10, 50 )
   title.set_rgb( 255, 0, 0 )
   list.set_selbg_rgb( 180, 30, 30 )
} else if ( config["color"] == "Blue" )
{
  bg.set_rgb( 50, 10, 170 )
  title.set_rgb( 0, 0, 255 )
  list.set_selbg_rgb( 30, 30, 180 )
} else {
  //default to Green
  bg.set_rgb( 10, 170, 50 )
  title.set_rgb( 0, 255, 0 )
  list.set_selbg_rgb( 30, 180, 30 )
}

That's it! We made a pretty neat Layout Option. You can make other options that the user can setup - and then change your layout based on user preferences creating a flexible layout.

##Sound

##Orientation

Attract Mode also provides the ability to hook your own functions into events that occur, aka callbacks. This means when certain events happen, you can specify a function that will run, allowing you to act on those events - making your layouts more dynamic.

###Transition Callback Transitions occur when the layout transitions to a new state in the layout. This may be when the user moves up or down the game list, switches to a new list, along with a few other events. ttype here is the event, or transition, that is occurring.

This is handy if you need to show/hide objects ( set an objects alpha to 0 or 255 ) or retrieve updated game information to update your objects ( such as updating a text objects msg property) based on what is selected or just when the user switches to a new game or list.

function transition( ttype, var, transition_time )
{
   if ( ttype == Transition.StartLayout ) {
      //do something when the layout is started
   }
   if ( ttype == Transition.FromOldSelection ) {
      //do something when moving to a new game
      //here, the var variable is the index offset of the game we are moving *from*
   }
   return false
}

//now hook your function as a transition callback
fe.add_transition_callback(this, "on_transition")

Notice that we have to return true or false here.. return false if your changes are immediate and no further action is required. Return true for the transition function to keep being called until no further action is required. When that is done, the transition_time will be updated for each call. Be careful that you don't create an endless loop here, as it will crash the application.

Read more about transition callbacks here

###Ticks Callback The ticks callback runs constantly while your layout is running. This function will allow you to continually update something in your layout, or at least continually check if something needs to be updated.

local txt_time = fe.add_text("", 0, 0, fe.layout.width, 20)
function on_tick( tick_time )
{
   //here we can retrieve the system date and time, and update our text object to show it
   local ttime = date()
   txt_time.msg = format("%02d:%02d", ttime.hour, ttime.min)
}
//now hook your function as a ticks callback
fe.add_ticks_callback(this, "on_tick")

There is no return here... your tick function is called as often as possible, as to not affect the rest of Attract Modes operation. Since the ticks callback is constantly running, you want to avoid doing too much heavy lifting here, as it can dramatically slow down your layout.

Read more about ticks callbacks here

###Signal Handler Signal handlers allow you to catch when a signal is received by Attract Mode. A signal could be an application key such as back, up, down, left and select - or various other events that occur, such as when a menu is shown. The str variable passed to your function is the signal that was received:

function on_signal(str) {
   if ( str == "custom1" ) {
      //do something when the custom1 button is pressed
   }
   return false
}
//now hook our signal handler
fe.add_signal_handler(this, "on_signal")

Just like the transition callback, you need to specify a return value. Return false if your actions do not require any further processing. Return true to continually run your function until no more processing is needed, then return false. Again, make sure your code does not cause an endless loop here, as it will crash the frontend.

Signal handlers can also be removed when they are no longer needed, to preserve resources.

Read more about signal handlers here A complete list of sigals that can be received is here

###Sending Signals You can also send signals to Attract Mode to trigger an action. At anytime in your code, you can do this with the fe.signal call:

   //send a signal to select a random game
   fe.signal("random_game")

Read more about sending signals and see a list of available signals here

Advanced Squirrel

Classes

Like other object oriented languages, Squirrel supports using classes to create object instances. In Attract Mode, you can use classes to create your own custom objects. Ideally, you'll give these objects their own surface, giving you control of where the object is positioned.

Here is an example class that will create both an artwork object and a text object on its own surface:

class ArtAndText {
    surface = null  //the surface we create that will hold our objects
    art_obj = null  //an art object we will add to the surface
    text_obj = null //a text object we will add to the surface
    constructor( x, y, width, height, parent = ::fe ) {
        //create the surface - parent could be another surface - if the parent isn't given, it creates its own surface using fe.add_surface
        surface = ( parent == ::fe ) ? parent.add_surface(::fe.layout.width, ::fe.layout.height) : parent.add_surface(parent.width, parent.height)
        create( x, y, width, height)
    }
    
    function create( x, y, width, height) {
        //create the art and text objects
        art_obj = surface.add_artwork("snap", 0, 0, surface.width, surface.height)
        text_obj = surface.add_text("[Title]", 0, 0, surface.width, 50)
        //set the surface position
        set_pos( x, y, width, height)
    }
    
    function set_pos(x, y, width, height) {
        surface.x = x
        surface.y = y
        surface.width = width
        surface.height = height
    }
}

This new class can now be instantiated as a new object in your layout:

local myObject = ArtAndText(50, 50, 320, 240)

Here we only provided a function to set the position of the surface, but you could add additional functions to the class to make it more customizable for the user.

External Scripts, Modules and Plugins

External Scripts

Attract Mode scripts can include other scripts code in their own by using the do_nut function:

fe.do_nut("my-script.nut")

By default, Attract Mode looks for the script in the fe.script_dir (your current layout folder). External scripts can be useful for a more organized layout, keeping related code in its own file but still being accessible from your main layout file.

Modules

Attract Mode allows developers to create reusable modules, which are just external scripts that are available globally to all layouts. These modules are located in the modules/ folder in the Attract Mode directory.

Using Modules

Layout developers can use existing modules in their own layout using the load_module function.

Here is an example using the pan-and-scan module:

fe.load_module("pan-and-scan")

local my_art = PanAndScanArt("flyer", 0, 0, 640,480)
my_art.set_fit_or_fill("fill")
my_art.set_anchor(::Anchor.Center)
my_art.set_zoom(4.5, 0.00008)
my_art.set_animate(::AnimateType.Bounce, 0.07, 0.07)
my_art.set_randomize_on_transition(true)
my_art.set_start_scale(1.15)

You can now use any additional functionality that the module provides. If you are unsure how to use the module, open the module .nut file, or look for additional documentation on how to use it.

Some modules may make use of other modules, so you need to may need to make sure that those are accessible as well.

Developing Modules

To create a module, you can add a named .nut file in the modules/ folder. However, it is recommended to create a folder especially if more than just the module .nut file is needed. If you create a folder, the main module file in the folder should be named module.nut.

Ideal use cases for modules are:

  • Classes that can expand the functionality of AttractMode, such as custom objects
  • Global "helper" functions or tables

Be sure to provide some documentation or commented code in your module so that users know how to implement it.

Certain modules are bundled with AttractMode. Keep this in mind before modifying modules - you should typically not modify module code unless you are the developer or you plan to contribute to that modules development.

If you do develop a module, consider doing a request to have it added to the Attract Mode repository.

If you are creating layouts that make use of a custom module and it is not bundled with Attract Mode, you will need to bundle it with your layout and inform users of this when distributing it to them.

Try to ensure that your module won't create conflicts with other modules. If you store global functions or variables, they may overlap if layouts make use of other modules that try to use similarly named functions or variables.

Plugins

Similarly to modules, Attract Mode allows developers to create plugins which enhance Attract Mode functionality.

Using Plugins

Functional plugins are already listed in the AttractMode menu. Press your configure key and go to the Plugins options in the menu. Select the plugin you are interested in using and press enter. Each plugins can be enabled or disabled individually, and may contain config options that you can adjust.

Developing Plugins

Plugins are similar to modules, but will contain a class that is registered with AttractMode as a plugin, and an optional UserConfig:

class UserConfig </ help="A plugin to play background audio" /> {
	</ label="Next Track",
		help="Button to press to jump to the next audio track",
		is_input=true,
		order=1 />
	skip_button="";
}
class AudioMode {
//plugin code
...
}
fe.plugin["AudioMode"] <- AudioMode()