diff --git a/python_payload/apps/widgets_demo/__init__.py b/python_payload/apps/widgets_demo/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..beb490a0e2d37574ad20e6a4876194e697b78ca5
--- /dev/null
+++ b/python_payload/apps/widgets_demo/__init__.py
@@ -0,0 +1,246 @@
+import captouch
+import leds
+import math, cmath, time
+from st3m.ui import colours, widgets
+from st3m.application import Application
+
+
+class Screen:
+    def __init__(self):
+        self.captouch_config = captouch.Config.empty()
+        self.widgets = []
+
+    def on_enter(self):
+        for widget in self.widgets:
+            widget.on_enter()
+        self.captouch_config.apply()
+        self.full_redraw = True
+
+    def on_exit(self):
+        for widget in self.widgets:
+            widget.on_exit()
+
+    def think(self, ins, delta_ms):
+        for widget in self.widgets:
+            widget.think(ins, delta_ms)
+
+
+class SensorScreen(Screen):
+    def __init__(self):
+        super().__init__()
+
+        self.inc = widgets.Inclinometer()
+        self.alt = widgets.Altimeter()
+        self.big_slider = widgets.BigSlider(self.captouch_config)
+
+        self.widgets = [self.big_slider, self.inc, self.alt]
+
+        self.coords = [[0, -60], [50, 30], [-50, 30]]
+        self.labels = ["big slider", "tilt", "altitude"]
+
+    def draw(self, ctx):
+        if self.full_redraw:
+            ctx.gray(0.1).rectangle(-120, -120, 240, 240).fill()
+            ctx.gray(1)
+            ctx.text_align = ctx.CENTER
+            ctx.font_size = 16
+            for k in range(3):
+                x, y = self.coords[k]
+                ctx.move_to(x, y + 45)
+                ctx.text(self.labels[k])
+
+        for k in range(3):
+            ctx.save()
+            ctx.translate(*self.coords[k])
+            self.widgets[k].draw(ctx)
+            ctx.restore()
+
+
+# class PoolScreen(Screen):
+#    def __init__(self):
+#        super().__init__()
+#
+#        arena = widgets.constraints.Ellipse(105)
+#        self.pool = widgets.Pool(
+#            self.captouch_config,
+#            petal=8,
+#            gain=40 * captouch.PETAL_ANGLES[8],
+#            constraint=arena,
+#        )
+#        self.scroller = widgets.Scroller(
+#            self.captouch_config,
+#            petal=2,
+#            gain=40 * captouch.PETAL_ANGLES[2],
+#            constraint=arena,
+#        )
+#
+#        self.widgets = [self.pool, self.scroller]
+#
+#    def draw(self, ctx):
+#        ctx.rgb(1, 1, 1).rectangle(-120, -120, 240, 240).fill()
+#        self.pool.draw(ctx)
+#        self.scroller.draw(ctx, draw_background=False)
+
+
+class SliderScreen(Screen):
+    def __init__(self):
+        super().__init__()
+        self.widgets += [
+            widgets.Slider(self.captouch_config, petal=x) for x in range(10)
+        ]
+        self.widget_styles = [widgets.WidgetStyle() for x in range(10)]
+        self.hues = [None] * 40
+        self.sats = [None] * 40
+        self.vals = [None] * 40
+
+    def think(self, delta_ms, ins):
+        super().think(delta_ms, ins)
+        for x in range(0, 10, 2):
+            self.hues[x * 4] = cmath.phase(self.widgets[x].pos)
+            self.sats[x * 4] = abs(self.widgets[x].pos)
+
+        for x in range(1, 10, 2):
+            self.vals[x * 4] = (self.widgets[x].pos.real + 1) / 2
+
+        # get intermediate hue
+        for x in range(0, 40, 8):
+            a = self.hues[x]
+            b = self.hues[(x + 8) % 40]
+            if a - b > math.pi:
+                a -= math.tau
+            elif a - b < -math.pi:
+                a += math.tau
+            for y in range(1, 8):
+                self.hues[x + y] = (a * (8 - y) + b * y) / 8
+
+        # get intermediate sat
+        for x in range(0, 40, 8):
+            a = self.sats[x]
+            b = self.sats[(x + 8) % 40]
+            for y in range(1, 8):
+                self.sats[x + y] = (a * (8 - y) + b * y) / 8
+
+        # get intermediate val
+        for x in range(4, 44, 8):
+            a = self.vals[x % 40]
+            b = self.vals[(x + 8) % 40]
+            for y in range(1, 8):
+                self.vals[(x + y) % 40] = (a * (8 - y) + b * y) / 8
+
+        for x in range(40):
+            leds.set_rgb(
+                x, *colours.hsv_to_rgb(self.hues[x], self.sats[x], self.vals[x])
+            )
+        leds.update()
+
+    def draw(self, ctx):
+        if self.full_redraw:
+            ctx.gray(0.1)
+            ctx.rectangle(-120, -120, 240, 240).fill()
+
+        for x in range(0, 10, 2):
+            if self.hues[x * 4]:
+                col = colours.hsv_to_rgb(self.hues[x * 4], self.sats[x * 4], 1)
+                self.widget_styles[x].marker_col = col
+                self.widget_styles[x].marker_col_inactive = col
+
+        for widget in self.widgets:
+            ctx.save()
+            ctx.rotate(cmath.phase(captouch.PETAL_ANGLES[widget.petal]))
+            ctx.translate(80, 0)
+            widget.draw(
+                ctx, self.widget_styles[widget.petal], full_redraw=self.full_redraw
+            )
+            ctx.restore()
+
+        self.full_redraw = False
+
+
+class XeyesScreen(Screen):
+    def __init__(self):
+        super().__init__()
+        constraint = widgets.constraints.Ellipse(1.4 + 1j)
+        self.widgets += [
+            widgets.Scroller(
+                self.captouch_config, petal=x, constraint=constraint, friction=0.5
+            )
+            for x in range(0, 10, 2)
+        ]
+        self.widget_style = widgets.WidgetStyle(
+            widget_scale=18,
+            outline_width=7,
+            outline_col=(0, 0, 0),
+            marker_padding=0,
+            marker_col=(0, 0, 0),
+            marker_col_inactive=(0, 0, 0),
+            background_col=(1, 1, 1),
+        )
+
+    def draw(self, ctx):
+        if self.full_redraw:
+            ctx.rgb(0.3, 0.2, 0.4)
+            ctx.rectangle(-120, -120, 240, 240).fill()
+
+        for widget in self.widgets:
+            ctx.save()
+            ctx.rotate(cmath.phase(captouch.PETAL_ANGLES[widget.petal]))
+            ctx.translate(67, 0)
+            widget.draw(ctx, self.widget_style)
+            ctx.restore()
+
+        self.full_redraw = False
+
+
+class App(Application):
+    def __init__(self, app_ctx):
+        super().__init__(app_ctx)
+        self._screens = [
+            SliderScreen(),
+            SensorScreen(),
+            XeyesScreen(),
+            # PoolScreen(),
+        ]
+        self._screen_index = 0
+        self.active_screen = self._screens[self._screen_index]
+        self.full_redraw = True
+
+    def think(self, ins, delta_ms):
+        super().think(ins, delta_ms)
+        self.active_screen.think(ins, delta_ms)
+
+        button = self.input.buttons.app
+        lr_dir = button.right.pressed - button.left.pressed
+        if lr_dir:
+            self.active_screen.on_exit()
+            self._screen_index += lr_dir
+            self._screen_index %= len(self._screens)
+            self.active_screen = self._screens[self._screen_index]
+            self.active_screen.on_enter()
+
+    def on_enter(self, vm):
+        super().on_enter(vm)
+        self.active_screen.on_enter()
+        self.always_full_redraw = True
+
+    def on_enter_done(self):
+        self.always_full_redraw = False
+
+    def on_exit(self):
+        self.always_full_redraw = True
+        self.active_screen.on_exit()
+
+    def on_exit_done(self):
+        self.always_full_redraw = False
+
+    def draw(self, ctx):
+        if self.always_full_redraw:
+            self.full_redraw = True
+        self.active_screen.full_redraw |= self.full_redraw
+        self.active_screen.draw(ctx)
+        self.full_redraw = False
+
+
+if __name__ == "__main__":
+    import st3m.run
+
+    st3m.run.run_app(App)
diff --git a/python_payload/apps/widgets_demo/flow3r.toml b/python_payload/apps/widgets_demo/flow3r.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ee8547a2e0cb718f75fd2e2125cbda15efba3068
--- /dev/null
+++ b/python_payload/apps/widgets_demo/flow3r.toml
@@ -0,0 +1,8 @@
+[app]
+name = "widgets demo"
+category = "Apps"
+
+[metadata]
+author = "Flow3r Badge Authors"
+license = "LGPL-3.0-only"
+url = "https://git.flow3r.garden/flow3r/flow3r-firmware"