Skip to content
Ooidget

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) };
  },
};