Table of Contents
Lua Scripting Introduction
Just like their physical counterparts on non-smartwatches, Watch faces can be very complex, with intricate parts all with their own simple functions, and they need to interact with other parts to fulfill more complex functions. They're not all just pretty pictures with hands.
Luckily, WatchMaker allows us to customize not only the visual aspects of these parts, but also what they'll do, and when, and for how long, etc. It does this by the inclusion of Lua, a full programming language for embedded systems. The full documentation for Lua is available at http://www.lua.org/docs.html. It may seem daunting, but don't worry, most of that stuff is irrelevant to WatchMaker, and so, isn't included.
What is included, are the following three aspects: Conditionals, Math Functions, and String Functions.
- Conditionals – This is your basic “IF this THEN that ELSE the other thing” statement. It allows you to do things like: If it is morning, then show AM, otherwise show PM.
- Math Functions – This can be as basic as 1+1, or as complex as SIN/COS, and can be used for anything from adding/subtracting/multiplying/Dividing numbers to advanced object placement or rotation.
- String Functions – Sometimes you're not dealing with numbers, but with words or other characters. This aspect lets you manipulate such items. For instance, replace AM/PM with Morning, Afternoon, Evening, and Night.
In addition, WatchMaker does a lot of the tedious work for you of gathering information, and makes this information available to you as variables. Want to know what the current Hour, Minute, or Second is? How about the Weather, or Time Zone, or the time of your next Appointment? Don't worry, just select and use the appropriate variable.
Lua makes all of this possible, and this section's goal is to answer questions you may have about how something is done in WatchMaker. It will show you the code required to perform a function, and explain that code in detail, so that you will know how to custimize it to your needs. All of this to allow you to create (or modify) your perfect watch face (or, at least, today's perfect watch face) – and who knows, maybe we'll have a little fun along the way.
(Note: We're just starting to add Lua code examples and explanations, so it may look sparse now, but keep checking back! Some exciting stuff is on its way!)
Conditional Statements
Conditional statements (IF-THEN-ELSE) work in much the same as most languages and also supports “elseif” (there are some examples below), for more information see http://www.lua.org/pil/4.3.1.html
Conditional Value Assignment
These allow you to conditionally set a value to a property based on some conditions, the syntax uses (IF-AND-OR).
It's easy once you know this:
IF <condition> THEN <x> ELSE <y> | becomes: | (<condition>) and <x> or <y> |
---|
A simple Example:
({dm} % 2 == 0) and 'even' or 'odd'
A more complex example:
({dh} < 11) and 'Morning' or ({dh} < 15) and 'Noon' or 'Evening'
Note the parentheses. The AND and OR statements can mean multiple things to Lua. Sometimes Lua needs help determining whether you're using one of them as a conjunction (and/or) or as an operation (then/else). If it gets confused, you may get undesired results. We help out Lua by encapsulating the entire “IF” part of the logic statement in parentheses.
Here's another example of why it's a good idea to add parentheses:
({dh} > 23 or {dh} < 1) and 'Midnight' or ({dh} > 11 and {dh} < 13) and 'Noon' or ({dh} < 12) and 'Morning' or 'Afternoon'
Math
The math library helps you calculate.
Functions
For example, to rotate something depending on the current time on a radius of 100px around the center by setting the x and y position:
x: math.sin(math.rad({drh})) * 100 y: math.cos(math.rad({drh})) * -100
Strings
Text strings such as “some text” can be manipulated through the string library (http://www.lua.org/manual/5.3/manual.html\#6.4).
You can quote strings with double or single quotes so 'some text' is also a string.
Methods
Upper/Lowercase a string:
string.upper('{ddww}') string.lower('{ddww}')
Reverse a string:
string.reverse('{ddww}')
Shorten a string (use only first 20 chars):
string.sub('{c1t}', 1, 20)
Concatenation
Say you want to add strings together. The “..” operator concatenates - adds- two strings together:
"{c1b}" .. "{c1l}"
We can also include strings that have been manipulated by methods:
"{c1b}" .. string.sub('{c1t}', 0, 10) .. "{c1l}"
If you're going to start a text field with a conditional formula, then plan to concatenate a string to it, you must encapsulate the entire formula part in a single set of parentheses. For example:
({wt}<={wth} and {wth} or {wt}) .. " is (or will be) today's high temp."
In that example, the concatenation will occur regardless of the conditional outcome. So, “ is (or will be) today's high temp.” will be shown after either {wth} or {wt} is displayed. If, instead, you're looking to have the text only display if a condition has (or hasn't) been met, then the concatenation is simply moved inside the parentheses. In the following example, the concatenation will only occur if the conditional is false:
({wt}<={wth} and {wt} or {wt} .. ", exceeding today's forcast high!")
Lua Script Files
From WatchMaker 3.4.0 onwards, you can now create Lua script files within your watch!
The script file is saved in your BeautifulWatches/scripts folder and will have the same Id as your watch, e.g. watch “watches/u3.xml” would create “scripts/u3.txt”
You can edit the script file with any code editor on your phone, PC or Mac. You can see Lua errors and use print() statements for debugging using a Lua text editor like this.
Or edit direct in WatchMaker: click on watch properties (icon opposite Undo), then click Script. This will also show Lua errors.
Your script file is executed each time your watch is initialized. You can also restart your script by hitting the back button when you are in edit mode.
The script files can include any of the following :
- Custom variables - e.g. below for number, string or color examples :
var_mynumber = 123 var_mystring = 'hello world' var_mycolor = 'ff00ff'
Whilst there is no strict naming convention for variables, please prefix all variables that you wish to expose to WatchMaker layers with var_ so that WatchMaker knows to interpret these using Lua, e.g. var_mynumber
- Custom Functions - e.g. below for a calculator watch :
function click_button (digit) if digit == '=' then formula_temp = calc_formula formula_temp = string.gsub(formula_temp, 'x', '*') formula_temp = string.gsub(formula_temp, '÷', '/') calc_formula = load('return '..formula_temp)() elseif digit == 'C' then calc_formula = '' else calc_formula=calc_formula..digit end var_calc_display = string.len(calc_formula) >= 1 and calc_formula or '0' end
- Code to run at startup - e.g. below for a calculator watch :
click_button (0)
Run Lua from Tap Actions
From WatchMaker 4.4 onwards, you can execute Lua scripts by tapping any watch layer! Just click a layer, hit Tap Action → Run Script and enter the script required.
You are recommended to keep Tap Action scripts shorter e.g. by running functions in the main script file, as Tap Action scripts are stored in the XML file and not the separate script file.
Run Lua Function Every Hour, Minute, Second or Millisecond
function on_hour(h) - every hour (h = hour) function on_minute(h, m) - every minute (h = hour, m = minute) function on_second(h, m, s) - every second (h = hour, m = minute, s = second) function on_millisecond(dt) - every millisecond (dt = delta time)
If you have some custom variables that update every millisecond, you can create an on_millisecond(..) function in your script file. This function will be called every millisecond. In the example below this will move points around a circle (co-ordinates stored in var_ms_posx and var_ms_posy) :
angle = 0.0 radius = 175.0 var_ms_posx = 0.0 var_ms_posy = 0.0 function on_millisecond(dt) angle = angle + 20.0 * dt var_ms_posx = radius * math.sin(math.rad(angle)) var_ms_posy = radius * math.cos(math.rad(angle)) end
So that WatchMaker updates the layers that use these custom variables each millisecond, you need to prefix all variables that need to update WatchMaker layers with var_ms_
If you only need second updates instead of millisecond updates, please use on_second(..) and prefix variables with var_s_
Run Lua Function When Watch Turns Bright / Dim
The following example will animate a tweens.radius property to change when the watch turns bright / dim - this code goes in your script file :
function on_display_bright() wm_schedule { action='tween', tween='radius', from=115, to=100, duration=0.9, easing=outQuad } end
function on_display_not_bright() wm_schedule { action='tween', tween='radius', to=115, duration=0.9, easing=outQuad } end
Scheduling Animations / Functions
You can schedule animations or functions from WatchMaker events, e.g. watch turning bright/dim or tap actions. An example animation is below :
wm_schedule { action='tween', tween='rotate_sec', from=0, to=90, duration=1, easing=outQuad }
This will start an animation on the tweens.rotate_sec property from 0 to 90. The animation will take 1s and will have an easing function of outQuad.
Any properties that you animate should be used by a watch layer using the tweens table, e.g. create a second hand in WatchMaker and set the rotation formula to tweens.rotate_sec
If you are animating scale, you are strongly recommended to use the Anim Scale X and Anim Scale Y properties instead of text size or radius. This will ensure much smoother animations.
The full options for WatchMaker scheduling is shown below :
tween -- name of tween variable, e.g. 'rotate_sec' would be accessed by a watch layer with tweens.rotate_sec from -- starting property value (numeric) to -- ending property value (numeric) duration -- duration (s) easing -- name of easing function - full list below (don't use quotes) start_offset -- start offset time (s)
Schedule a sleep delay of e.g. 1s, which is useful for chaining animations :
wm_schedule { action='sleep', sleep=1 }
Schedule a Lua function to run (don't use quotes on your function name) :
wm_schedule { action='run_function', run_function=myfunc }
Chain animations together like this :
wm_schedule { { action='tween', tween='rotate_sec', from=0, to=90, duration=1, easing=outQuad }, { action='sleep', sleep=1 }, { action='run_function', run_function=myfunc }, { action='tween', tween='rotate_sec', from=90, to=0, duration=1, easing=outQuad }, }
WatchMaker Lua API
WatchMaker extends the Lua commandset. Just use the wm_action() or other functions anywhere in your script or tap actions:
wm_action('sw_start_stop') -- start or stop the stopwatch wm_action('sw_reset') -- reset stopwatch wm_action('media_next') -- next track wm_action('media_prev') -- previous track wm_action('media_play') -- play media wm_action('media_pause') -- pause media wm_action('media_play_pause') -- play paused media or pause playing media wm_action('vol_up') -- increase the volume on the connected phone wm_action('vol_down') -- decrease the volume on the connected phone wm_action('m_update_weather') -- update weather wm_action('m_task:MyTask') -- run Tasker task 'MyTask' wm_action('color_switch_next') -- Color Switch: Next Color wm_action('color_switch_prev') -- Color Switch: Previous Color wm_action('color_switch_select') -- Color Switch: Select Color wm_action('tap_launcher') -- Launch Tap Launcher wm_action('widget_weather') -- Launch Weather Widget wm_action('widget_health') -- Launch Health Widget wm_action('widget_calendar') -- Launch Calendar Widget wm_action('w_app:App Name`com.app.package.name') -- Launch Wear OS app* wm_action('w_app:App Name`com.app.package.name`com.app.package.name.activityName') -- Launch specific activity wm_schedule('...') -- schedule an animation or event (see Scheduling Animations / Functions) wm_unschedule_all() -- unschedule all animations or events wm_vibrate(d, r) -- vibrate for duration d (milliseconds) and repeat r times wm_sfx('sfx_file') -- play MP3 file with name sfx_file.mp3 (needs to be in /BeautifulWatches/sfx) - supported watches with speaker only wm_transition('...') -- run a transition (see Transitions) wm_anim_set('layerName', 'anim_in', 'Typewriter") -- set animation in for 'layerName' to Typewriter wm_anim_set('layerName', 'dur_in', 1.0) -- set duration in for 'layerName' to 1.0 (or use -1 for default) wm_anim_start('layerName') -- start animation on 'layerName' wm_tag('...') -- ONLY use for dynamic variables otherwise will break return a value of a WatchMaker tag, e.g. wm_tag("{c"..var_myvar.."bp}") is_bright -- return true if watch is bright, else false
*About wm_action w_app
The delimiter for the app link is a backtick `
and it consists of two or three parts:
- App name (may be empty)
- Package name
- Activity name (optional)
The app name is optional. If it is empty, the app main activity is launched. Example: wm_action('w_app:`com.samsung.android.watch.weather')
The easiest way to find the package name (example com.samsung.android.calendar
) is to search for the app on https://www.apkmirror.com/ or https://apkpure.net/. The package names are listed on the download pages. A more precise approach is to create a tap action first, select the app via “Watch: Select Application”, export the watch, unzip the .watch file and search for tap_action=“w_app:
in watch.xml. It uses the same app link format.
Available activities of apps can be obtained by downloading the APK file and uploading it to https://www.virustotal.com/. The “details” tab will list all activity names. Though at the time of writing, no activity could be launched successfully, so this may be broken (Wear OS 4).
Tweening Functions
Bring your animations to life using one of 45 tweening functions or even write your own function if needed. Remember not to use quotes in the {… easing=outQuad }
The full list is as follows :
linear inQuad, outQuad, inOutQuad, outInQuad, inCubic, outCubic, inOutCubic, outInCubic, inQuart, outQuart, inOutQuart, outInQuart, inQuint, outQuint, inOutQuint, outInQuint, inSine, outSine, inOutSine, outInSine, inExpo, outExpo, inOutExpo, outInExpo, inCirc, outCirc, inOutCirc, outInCirc, inElastic, outElastic, inOutElastic, outInElastic, inBack, outBack, inOutBack, outInBack, inBounce, outBounce, inOutBounce, outInBounce
You can write your own tweening function like this :
function linear_myversion(d, b, c, t) return c * t / d + b end function inQuad_myversion(d, b, c, t) t = t / d return c * math.pow(t, 2) + b end -- t = time == running time. How much time has passed *right now* -- b = begin == starting property value -- c = change == ending - beginning -- d = duration == how much time has to pass for the tweening to complete
Transitions
From WatchMaker 3.6, you can run over 45 transitions when switching between screens on a watchface.
wm_transition('FoldFromRight')
This will start a transition to the next screen.
Free Transitions Watch: http://goo.gl/LBDr9j
See video of all transitions here: http://goo.gl/OO60I2
You should not use wm_transition for bright / dim animations - instead set the transition directly in WatchMaker watch editor.
The full list of transitions is shown below (you can add spaces if needed or use lower-case) :
None, Random, FlipFromLeft, FlipFromRight, ScrollFromLeft, ScrollFromRight, ScrollFromTop, ScrollFromBottom, SlideFromLeft, SlideFromRight, SlideFromTop, SlideFromBottom, CrossHatch, CrossZoom, CubeFromLeft, CubeFromRight, Dreamy, DreamyZoom, Fade, FoldFromLeft, FoldFromRight, FoldFromTop, FoldFromBottom, GlitchDisplace, GlitchMemories, Kaleidoscope, Morph, Mosaic, Pinwheel, SquareSwipe, Swirl, DefocusBlur, ColorDistance, Dissolve, HSVFade, LinearBlur, RandomSquares, PolkaDotsCurtain, PageCurl, Radial, PowerDisformation, Swap, Flash, Doorway, FadeBlack, FadeWhite, CircleOpen,
Learn More
You can learn more about how to use Lua from the many use cases listed on our Tutorials and Recipes page.