A transport bar
The example’s project folder, file by file: components as name.json, shared behaviours as name.behaviour.js. Run it.
fl_transport.json
{
"widget": {
"children": [
{
"bind": {
"on": "loop"
},
"use": "fl_loop",
"x": 16,
"y": 16
},
{
"bind": {
"playing": "playing"
},
"use": "fl_play",
"x": 45,
"y": 15
},
{
"bind": {
"playing": "playing",
"position": "position"
},
"use": "fl_stop",
"x": 76,
"y": 15
},
{
"bind": {
"armed": "armed"
},
"use": "fl_record",
"x": 113,
"y": 16
},
{
"bind": {
"tempo": "tempo"
},
"use": "fl_tempo",
"x": 148,
"y": 17.5
}
],
"size": [
221,
59
],
"state": {
"armed": false,
"loop": true,
"playing": false,
"position": 0,
"tempo": 130
}
}
}fl_lcd.json
{
"widget": {
"layers": [
{
"box": [
0,
-0.5,
56,
25
],
"effects": [
{
"linear": {
"angle": 90,
"stops": [
[
0,
"#e4f5fc"
],
[
1,
"#d6e7ef"
]
]
},
"type": "fill"
},
{
"linear": {
"angle": 180,
"stops": [
[
0,
"#ffffff",
0
],
[
0.5,
"#ffffff",
0.55
],
[
1,
"#ffffff",
0
]
]
},
"type": "fill"
},
{
"color": "#97a8b0",
"linear": {
"angle": 90,
"stops": [
[
0,
"#8d9ea6"
],
[
0.5,
"#b9c8cf"
],
[
1,
"#8d9ea6"
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
},
{
"color": "#ffffff",
"linear": {
"angle": 180,
"stops": [
[
0,
"#ffffff",
0
],
[
0.5,
"#ffffff",
0.5
],
[
1,
"#ffffff",
0
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.2,
"y": 0.2
}
],
"radius": 6,
"shape": "rect"
},
{
"d": "M 50.250 3.018 C 49.452 3.696 48.822 4.498 48.105 5.250 C 48.552 5.808 49.185 5.166 49.750 5.063 C 49.872 5.041 51.128 5.041 51.250 5.063 C 51.951 5.191 51.833 5.448 52.750 5.450 L 52.905 5.250 C 52.017 4.293 51.529 3.405 50.250 3.018 Z",
"effects": [
{
"color": "#93a4ab",
"type": "fill"
}
],
"shape": "path"
},
{
"d": "M 48.250 18.564 L 48.105 18.750 C 50.891 21.671 50.007 21.798 52.902 18.750 L 52.750 18.558 C 51.789 18.535 51.959 18.796 51.250 18.937 C 50.321 19.122 49.166 18.738 48.250 18.564 Z",
"effects": [
{
"color": "#93a4ab",
"type": "fill"
}
],
"shape": "path"
}
],
"size": [
56,
24
]
}
}fl_loop.json
{
"widget": {
"layers": [
{
"box": [
0,
0,
60,
27
],
"use": "fl_loop_on",
"when": "s.on"
},
{
"box": [
0,
0,
60,
27
],
"use": "fl_loop_off",
"when": "!s.on"
},
{
"box": [
0.5,
0,
60,
27
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"opacity": 0.12,
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 10.5,
"shape": "rect",
"when": "ui.hover && !ui.down"
},
{
"box": [
0.5,
0,
60,
27
],
"effects": [
{
"blur": 4,
"color": "#6a2400",
"opacity": 0.55,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 2
},
{
"blur": 1.5,
"color": "#000000",
"opacity": 0.25,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 10.5,
"shape": "rect",
"when": "ui.down"
}
],
"size": [
29,
27
],
"state": {
"on": true
}
}
}fl_loop.js
/** fl_loop's code: the functions fl_loop.json refers to by name, and its behaviour. */
export const behaviour = {
click(s) {
return { on: !s.on };
},
};fl_loop_off.json
{
"widget": {
"layers": [
{
"box": [
0.5,
0,
60,
27
],
"effects": [
{
"linear": {
"angle": 180,
"stops": [
[
0,
"#5a636b"
],
[
1,
"#3d454c"
]
]
},
"type": "fill"
},
{
"blur": 0,
"color": "#6b747b",
"opacity": 1,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 1
},
{
"color": "#2a3035",
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 10.5,
"shape": "rect"
},
{
"d": "M 21.750 6.477 C 21.251 7.026 20.774 7.575 20.250 8.102 C 19.281 7.995 18.291 7.314 17.250 7.088 C 14.653 6.524 11.154 7.886 9.844 10.250 L 10.250 10.704 C 10.872 10.026 11.845 9.313 12.750 9.081 C 13.273 8.947 14.736 8.972 15.250 9.134 C 15.868 9.328 17.832 10.405 17.511 11.250 C 17.466 11.369 17.337 11.435 17.250 11.527 C 16.872 11.927 16.510 12.359 16.128 12.750 C 16.431 13.053 22.253 13.374 22.890 12.750 C 22.654 10.713 22.556 8.767 22.342 6.750 L 21.750 6.477 Z",
"effects": [
{
"color": "#aeb6bd",
"type": "fill"
}
],
"shape": "path"
},
{
"d": "M 11.456 16.250 C 11.483 16.810 11.604 19.933 11.818 20.250 C 11.921 20.403 12.106 20.480 12.250 20.595 C 12.346 20.488 13.180 19.555 13.250 19.536 C 13.771 19.394 14.202 19.877 14.750 20.001 C 16.745 20.454 18.556 20.095 20.068 18.750 L 19.750 18.320 C 19.281 18.511 18.748 18.823 18.250 18.902 C 17.442 19.030 15.790 18.459 15.354 17.750 C 15.265 17.604 15.286 17.415 15.242 17.250 C 15.263 17.229 16.033 16.326 16.099 16.250 C 15.983 16.167 15.893 16.011 15.750 16.000 C 15.513 15.982 12.072 15.745 11.456 16.250 Z",
"effects": [
{
"color": "#aeb6bd",
"type": "fill"
}
],
"shape": "path"
}
],
"size": [
60,
27
]
}
}fl_loop_on.json
{
"widget": {
"layers": [
{
"box": [
0.5,
0,
60,
27
],
"effects": [
{
"radial": {
"cx": 0.28,
"cy": 0.49,
"rx": 0.2867,
"ry": 0.48,
"stops": [
[
0,
"#ffffd9"
],
[
0.25,
"#ffffc5"
],
[
0.5,
"#ffffab"
],
[
0.62,
"#fff39d"
],
[
0.75,
"#ffe48d"
],
[
0.88,
"#ffd17b"
],
[
1,
"#ffc26b"
]
]
},
"type": "fill"
},
{
"blur": 2,
"color": "#ff8a2a",
"opacity": 0.6,
"spread": 0,
"type": "innerGlow"
},
{
"color": "#db6710",
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 10.5,
"shape": "rect"
},
{
"d": "M 21.750 6.477 C 21.251 7.026 20.774 7.575 20.250 8.102 C 19.281 7.995 18.291 7.314 17.250 7.088 C 14.653 6.524 11.154 7.886 9.844 10.250 L 10.250 10.704 C 10.872 10.026 11.845 9.313 12.750 9.081 C 13.273 8.947 14.736 8.972 15.250 9.134 C 15.868 9.328 17.832 10.405 17.511 11.250 C 17.466 11.369 17.337 11.435 17.250 11.527 C 16.872 11.927 16.510 12.359 16.128 12.750 C 16.431 13.053 22.253 13.374 22.890 12.750 C 22.654 10.713 22.556 8.767 22.342 6.750 L 21.750 6.477 Z",
"effects": [
{
"color": "#cd4f08",
"type": "fill"
}
],
"shape": "path"
},
{
"d": "M 11.456 16.250 C 11.483 16.810 11.604 19.933 11.818 20.250 C 11.921 20.403 12.106 20.480 12.250 20.595 C 12.346 20.488 13.180 19.555 13.250 19.536 C 13.771 19.394 14.202 19.877 14.750 20.001 C 16.745 20.454 18.556 20.095 20.068 18.750 L 19.750 18.320 C 19.281 18.511 18.748 18.823 18.250 18.902 C 17.442 19.030 15.790 18.459 15.354 17.750 C 15.265 17.604 15.286 17.415 15.242 17.250 C 15.263 17.229 16.033 16.326 16.099 16.250 C 15.983 16.167 15.893 16.011 15.750 16.000 C 15.513 15.982 12.072 15.745 11.456 16.250 Z",
"effects": [
{
"color": "#cd4f08",
"type": "fill"
}
],
"shape": "path"
}
],
"size": [
60,
27
]
}
}fl_pill.json
{
"widget": {
"layers": [
{
"box": [
0.5,
0,
65,
29
],
"effects": [
{
"blur": 0,
"color": "#586268",
"opacity": 1,
"spread": 0,
"type": "dropShadow",
"x": 0,
"y": 1
},
{
"linear": {
"angle": 180,
"stops": [
[
0,
"#4c555a"
],
[
0.5,
"#454e54"
],
[
1,
"#353e44"
]
]
},
"type": "fill"
},
{
"blur": 0,
"color": "#222c31",
"opacity": 1,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": -1.5
},
{
"color": "#3e484d",
"linear": {
"angle": 180,
"stops": [
[
0,
"#3e484d"
],
[
0.5,
"#343d42"
],
[
1,
"#222c31"
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 11,
"shape": "rect"
},
{
"box": [
1.5,
1,
63,
27
],
"effects": [
{
"color": "#5d656b",
"linear": {
"angle": 180,
"stops": [
[
0,
"#5d656b",
1
],
[
0.3,
"#5d656b",
0
],
[
1,
"#5d656b",
0
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 10,
"shape": "rect"
},
{
"box": [
32,
0,
2,
29
],
"effects": [
{
"color": "#30393e",
"type": "fill"
}
],
"radius": 0,
"shape": "rect"
}
],
"size": [
66.5,
30
]
}
}fl_play.json
{
"widget": {
"layers": [
{
"box": [
0,
0,
66.5,
30
],
"use": "fl_pill"
},
{
"box": [
0.5,
0,
65,
29
],
"clip": [
0,
0,
32,
29
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"opacity": 0.06,
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 11,
"shape": "rect",
"when": "ui.hover && !ui.down"
},
{
"box": [
0.5,
0,
65,
29
],
"clip": [
0,
0,
32,
29
],
"effects": [
{
"linear": {
"angle": 180,
"stops": [
[
0,
"#30393e"
],
[
0.55,
"#3b444a"
],
[
1,
"#464f55"
]
]
},
"type": "fill"
},
{
"blur": 3,
"color": "#161d21",
"opacity": 0.85,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 1.5
},
{
"color": "#3e484d",
"linear": {
"angle": 180,
"stops": [
[
0,
"#3e484d"
],
[
0.5,
"#343d42"
],
[
1,
"#222c31"
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 11,
"shape": "rect",
"when": "ui.down"
},
{
"box": [
0,
0,
32,
29
],
"use": "fl_play_icon",
"when": "!s.playing && !ui.down"
},
{
"box": [
0,
0,
32,
29
],
"use": "fl_play_icon_on",
"when": "s.playing && !ui.down"
},
{
"box": [
0,
0.5,
32,
29
],
"use": "fl_play_icon",
"when": "!s.playing && ui.down"
},
{
"box": [
0,
0.5,
32,
29
],
"use": "fl_play_icon_on",
"when": "s.playing && ui.down"
}
],
"size": [
31,
29
],
"state": {
"playing": false
}
}
}fl_play.js
/** fl_play's code: the functions fl_play.json refers to by name, and its behaviour. */
export const behaviour = {
click(s) {
return { playing: !s.playing };
},
};fl_play_icon.json
{
"widget": {
"layers": [
{
"box": [
15,
9.5,
5.5,
10
],
"effects": [
{
"color": "#dfe6eb",
"type": "fill"
}
],
"point": "right",
"shape": "triangle"
}
],
"size": [
32,
29
]
}
}fl_play_icon_on.json
{
"widget": {
"layers": [
{
"box": [
15,
9.5,
5.5,
10
],
"effects": [
{
"blur": 2,
"color": "#7dff4a",
"opacity": 0.5,
"spread": 0,
"type": "outerGlow"
},
{
"color": "#9bf06a",
"type": "fill"
}
],
"point": "right",
"shape": "triangle"
}
],
"size": [
32,
29
]
}
}fl_rec_off.json
{
"widget": {
"layers": [
{
"box": [
-0.15,
-0.15,
27.29,
27.29
],
"effects": [
{
"radial": {
"cx": 0.5,
"cy": 0.5,
"rx": 0.5,
"ry": 0.5,
"stops": [
[
0,
"#70797e"
],
[
0.33,
"#6e777c"
],
[
0.85,
"#5d656a"
],
[
0.851,
"#4c5559"
],
[
1,
"#4b5458"
]
]
},
"type": "fill"
},
{
"color": "#3f484c",
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"shape": "ellipse"
},
{
"box": [
9,
9,
9,
9
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"shape": "ellipse"
}
],
"size": [
27,
27
]
}
}fl_rec_on.json
{
"widget": {
"layers": [
{
"box": [
-0.15,
-0.15,
27.29,
27.29
],
"effects": [
{
"radial": {
"cx": 0.5,
"cy": 0.5,
"rx": 0.5,
"ry": 0.5,
"stops": [
[
0,
"#70797e"
],
[
0.33,
"#6e777c"
],
[
0.85,
"#5d656a"
],
[
0.851,
"#4c5559"
],
[
1,
"#4b5458"
]
]
},
"type": "fill"
},
{
"color": "#3f484c",
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"shape": "ellipse"
},
{
"box": [
9.25,
9.25,
8.5,
8.5
],
"effects": [
{
"blur": 6,
"color": "#ff3b2f",
"opacity": 0.8,
"spread": 1,
"type": "outerGlow"
},
{
"radial": {
"cx": 0.5,
"cy": 0.5,
"rx": 0.5,
"ry": 0.5,
"stops": [
[
0,
"#ff7a6a"
],
[
1,
"#ff3b2f"
]
]
},
"type": "fill"
}
],
"shape": "ellipse"
}
],
"size": [
27,
27
]
}
}fl_record.json
{
"widget": {
"layers": [
{
"use": "fl_rec_off",
"when": "!s.armed"
},
{
"use": "fl_rec_on",
"when": "s.armed"
},
{
"box": [
-0.15,
-0.15,
27.29,
27.29
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"opacity": 0.07,
"shape": "ellipse",
"when": "ui.hover && !ui.down"
},
{
"box": [
-0.15,
-0.15,
27.29,
27.29
],
"effects": [
{
"radial": {
"cx": 0.5,
"cy": 0.5,
"rx": 0.5,
"ry": 0.5,
"stops": [
[
0,
"#000000",
0
],
[
0.7,
"#000000",
0.12
],
[
1,
"#000000",
0.3
]
]
},
"type": "fill"
},
{
"blur": 3,
"color": "#141a1d",
"opacity": 0.8,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 1.5
},
{
"color": "#3f484c",
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"shape": "ellipse",
"when": "ui.down"
},
{
"box": [
9,
9.5,
9,
9
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"shape": "ellipse",
"when": "ui.down && !s.armed"
},
{
"box": [
9.25,
9.75,
8.5,
8.5
],
"effects": [
{
"radial": {
"cx": 0.5,
"cy": 0.5,
"rx": 0.5,
"ry": 0.5,
"stops": [
[
0,
"#ff7a6a"
],
[
1,
"#ff3b2f"
]
]
},
"type": "fill"
}
],
"shape": "ellipse",
"when": "ui.down && s.armed"
}
],
"size": [
27,
27
],
"state": {
"armed": false
}
}
}fl_record.js
/** fl_record's code: the functions fl_record.json refers to by name, and its behaviour. */
export const behaviour = {
click(s) {
return { armed: !s.armed };
},
};fl_stop.json
{
"widget": {
"layers": [
{
"box": [
-30.5,
0,
65,
29
],
"clip": [
3,
0,
32.5,
29
],
"effects": [
{
"color": "#ffffff",
"type": "fill"
}
],
"opacity": 0.06,
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 11,
"shape": "rect",
"when": "ui.hover && !ui.down"
},
{
"box": [
-30.5,
0,
65,
29
],
"clip": [
3,
0,
32.5,
29
],
"effects": [
{
"linear": {
"angle": 180,
"stops": [
[
0,
"#30393e"
],
[
0.55,
"#3b444a"
],
[
1,
"#464f55"
]
]
},
"type": "fill"
},
{
"blur": 3,
"color": "#161d21",
"opacity": 0.85,
"spread": 0,
"type": "innerShadow",
"x": 0,
"y": 1.5
},
{
"color": "#3e484d",
"linear": {
"angle": 180,
"stops": [
[
0,
"#3e484d"
],
[
0.5,
"#343d42"
],
[
1,
"#222c31"
]
]
},
"opacity": 1,
"position": "inside",
"type": "stroke",
"width": 1
}
],
"ops": [
{
"op": "bulge",
"x": 0.5,
"y": 0
}
],
"radius": 11,
"shape": "rect",
"when": "ui.down"
},
{
"box": [
0,
0,
35.5,
29
],
"use": "fl_stop_icon",
"when": "!ui.down"
},
{
"box": [
0,
0.5,
35.5,
29
],
"use": "fl_stop_icon",
"when": "ui.down"
}
],
"size": [
35.5,
29
],
"state": {
"playing": false,
"position": 0
}
}
}fl_stop.js
/** fl_stop's code: the functions fl_stop.json refers to by name, and its behaviour. */
export const behaviour = {
click(s) {
return { playing: false, position: 0 };
},
};fl_stop_icon.json
{
"widget": {
"layers": [
{
"box": [
14,
11,
7,
7
],
"effects": [
{
"color": "#dfe6eb",
"type": "fill"
}
],
"shape": "rect"
}
],
"size": [
35.5,
29
]
}
}fl_tempo.json
{
"widget": {
"layers": [
{
"use": "fl_lcd"
},
{
"d": "M 50.250 3.018 C 49.452 3.696 48.822 4.498 48.105 5.250 C 48.552 5.808 49.185 5.166 49.750 5.063 C 49.872 5.041 51.128 5.041 51.250 5.063 C 51.951 5.191 51.833 5.448 52.750 5.450 L 52.905 5.250 C 52.017 4.293 51.529 3.405 50.250 3.018 Z",
"effects": [
{
"color": "#3d5563",
"type": "fill"
}
],
"shape": "path",
"when": "ui.region == 'up'"
},
{
"d": "M 48.250 18.564 L 48.105 18.750 C 50.891 21.671 50.007 21.798 52.902 18.750 L 52.750 18.558 C 51.789 18.535 51.959 18.796 51.250 18.937 C 50.321 19.122 49.166 18.738 48.250 18.564 Z",
"effects": [
{
"color": "#3d5563",
"type": "fill"
}
],
"shape": "path",
"when": "ui.region == 'down'"
},
{
"align": "center",
"box": [
0,
0,
49,
24
],
"font": "Roboto Flex LCD",
"fontFile": "fonts/RobotoFlexLCD.ttf",
"input": {
"commit": "(t => isNaN(t) ? undefined : { tempo: Math.min(522, Math.max(10, Math.round(t * 1000) / 1000)) })(parseFloat(v))",
"mask": "___.___",
"value": "s.tempo.toFixed(3)"
},
"kern": "optical",
"padding": [
2,
2,
2,
3
],
"runs": [
{
"color": "#151a33",
"size": 17,
"text": "Math.floor(s.tempo)"
},
{
"color": "#1d2440",
"size": 13,
"text": "'.' + (s.tempo % 1).toFixed(3).slice(2)"
}
],
"valign": "center"
}
],
"regions": {
"down": [
45,
12,
11,
12
],
"up": [
45,
0,
11,
12
]
},
"size": [
56,
24
],
"state": {
"tempo": 130
}
}
}fl_tempo.js
/** fl_tempo's code: the functions fl_tempo.json refers to by name, and its behaviour. */
export const behaviour = {
clamp(t) {
return Math.min(522, Math.max(10, Math.round(t * 1000) / 1000));
},
click(s, ui) {
if (ui.region == "up") return { tempo: this.clamp(s.tempo + 1) };
if (ui.region == "down") return { tempo: this.clamp(s.tempo - 1) };
},
drag(s0, dx, dy) {
return { tempo: this.clamp(Math.round(s0.tempo - dy / 2)) };
},
wheel(s, n) {
return { tempo: this.clamp(s.tempo + n) };
},
};