reflex-vty 1.0.0.0 → 1.0.0.1
raw patch · 16 files changed
+579/−2 lines, 16 filesdep ~basedep ~containersdep ~reflexnew-component:exe:examplesPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, containers, reflex, text, time, vty, vty-crossplatform
API changes (from Hackage documentation)
Files
- ChangeLog.md +4/−0
- README.md +17/−1
- reflex-vty.cabal +35/−1
- src-bin/Example/CellBuffer.hs +48/−0
- src-bin/Example/Common.hs +44/−0
- src-bin/Example/DoomFire.hs +63/−0
- src-bin/Example/Pager.hs +28/−0
- src-bin/Example/Progress.hs +36/−0
- src-bin/Example/Space.hs +50/−0
- src-bin/Example/Spinner.hs +29/−0
- src-bin/Example/Splash.hs +51/−0
- src-bin/Example/Stopwatch.hs +30/−0
- src-bin/Example/TextInput.hs +27/−0
- src-bin/Example/Timer.hs +31/−0
- src-bin/Example/Views.hs +36/−0
- src-bin/examples.hs +50/−0
ChangeLog.md view
@@ -1,5 +1,9 @@ # Revision history for reflex-vty +## 1.0.0.1++* Add some more examples, especially of the new canvas features+ ## 1.0.0.0 * *Breaking change*: Add `HasColorProfile` class (with `colorProfile`/`localColorProfile` and a `ColorProfileReader` transformer) to `Reflex.Vty.Widget`. Widgets can call `colorProfile` to make decisions based on terminal capability.
README.md view
@@ -49,7 +49,23 @@ Run the bundled demo with `cabal run example` to see many of these in action: a text editor, a to-do list, scrollable text, clickable buttons, a live CPU-usage display with a true-color gradient bar, a scrollbar-modes demo, a terminal-cursor demo, and a little styling showcase featuring gradients, color operations, a canvas overlay, border presets, theming, and color-profile downsampling. -Feature requests, pull requests, and other feedback are welcome and appreciated (see the [contribution guide](CONTRIBUTING.md)). This library is still experimental, so big changes are possible.+### Example gallery++Each of these examples is a module in [`src-bin/Example/`](src-bin/Example/). You can run any of them with `cabal run examples -- <name>`.++| | |+| :-- | :-- |+| **`spinner`** — an animated braille spinner | <img src="https://vhs.charm.sh/vhs-2ZldM9gFD9ovea54MAwio3.gif" alt="spinner demo" width="420"> |+| **`progress`** — a gradient-filled progress bar | <img src="https://vhs.charm.sh/vhs-3oJNQQcQH6vM4VyH7Yww56.gif" alt="progress bar demo" width="420"> |+| **`stopwatch`** — a tick-driven count-up timer | <img src="https://vhs.charm.sh/vhs-5C8hkG3W9P4tATBBtLhxIm.gif" alt="stopwatch demo" width="420"> |+| **`timer`** — a count-down timer | <img src="https://vhs.charm.sh/vhs-3gpq3fy1zWP3IsXysnT7WT.gif" alt="timer demo" width="420"> |+| **`textinput`** — a focused text-input field | <img src="https://vhs.charm.sh/vhs-4FzSVaJAnGq4V1NNrk1lal.gif" alt="text input demo" width="420"> |+| **`pager`** — a scrollable text viewport | <img src="https://vhs.charm.sh/vhs-7a2bCmFHleF9MG3KxY4yeh.gif" alt="pager demo" width="420"> |+| **`views`** — toggle between two views | <img src="https://vhs.charm.sh/vhs-7rflGBcgFrmIQgk0JmZNKe.gif" alt="views demo" width="420"> |++**More demos**: full-screen canvas animations (the classic Doom fire, a plasma field, a starfield, an animated splash) live on the [examples page](EXAMPLES.md).++Feature requests, pull requests, and other feedback are welcome and appreciated (see the [contribution guide](CONTRIBUTING.md)). ### How to Build
reflex-vty.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: reflex-vty-version: 1.0.0.0+version: 1.0.0.1 synopsis: Reflex FRP host and widgets for VTY applications description: Build terminal applications using functional reactive programming (FRP) with Reflex FRP (<https://reflex-frp.org>).@@ -151,6 +151,40 @@ TupleSections TypeApplications TypeFamilies++executable examples+ hs-source-dirs: src-bin+ main-is: examples.hs+ ghc-options: -threaded -Wall+ build-depends:+ base,+ containers,+ reflex,+ reflex-vty,+ text,+ time,+ vty+ default-language: Haskell2010+ other-modules:+ Example.CellBuffer+ Example.Common+ Example.DoomFire+ Example.Pager+ Example.Progress+ Example.Space+ Example.Spinner+ Example.Splash+ Example.Stopwatch+ Example.TextInput+ Example.Timer+ Example.Views+ default-extensions:+ ConstraintKinds+ FlexibleContexts+ LambdaCase+ OverloadedStrings+ RankNTypes+ ScopedTypeVariables executable leaktest if !flag(leaktest)
+ src-bin/Example/CellBuffer.hs view
@@ -0,0 +1,48 @@+-- |+-- Description: An animated plasma field rendered cell-by-cell, à la the+-- cellbuffer example.+module Example.CellBuffer (cellBuffer) where++import Control.Monad.IO.Class (liftIO)+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A classic plasma effect: each cell's character and color are a pure+-- function of its position and the frame, summed over a few sine waves.+cellBuffer :: Demo t m => m (Event t ())+cellBuffer = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.06 t0+ n <- foldDyn (\_ i -> i + 1) (0 :: Int) tick+ dw <- displayWidth+ dh <- displayHeight+ tellImages $ (\w h i -> [plasma w h i]) <$> current dw <*> current dh <*> current n+ quit+ where+ ramp = " .:-=+*#%@"+ grad =+ gradient1D+ [ (0, RGB 25 20 70)+ , (0.4, RGB 190 40 140)+ , (0.7, RGB 255 150 40)+ , (1, RGB 255 240 150)+ ]+ plasma w h i =+ let t = fromIntegral i * 0.12 :: Double+ cell x y =+ let fx = fromIntegral x+ fy = fromIntegral y+ v =+ sin (fx * 0.18 + t)+ + sin (fy * 0.27 + t * 1.2)+ + sin ((fx + fy) * 0.12 + t * 0.8)+ + sin (sqrt (fx * fx + fy * fy) * 0.2 - t)+ u = (v + 4) / 8 -- normalize roughly to [0,1]+ ci = max 0 $ min (length ramp - 1) $ floor (u * fromIntegral (length ramp))+ clr = fromRGB $ sampleGradient1D grad u+ in V.char (V.withForeColor V.defAttr clr) (ramp !! ci)+ in V.vertCat [V.horizCat [cell x y | x <- [0 .. w - 1]] | y <- [0 .. h - 1]]
+ src-bin/Example/Common.hs view
@@ -0,0 +1,44 @@+-- |+-- Description: Shared scaffolding for the small standalone example demos.+module Example.Common where++import Control.Monad.Fix (MonadFix)+import Control.Monad.IO.Class (MonadIO)+import qualified Graphics.Vty as V+import Reflex++import Reflex.Vty++-- | The set of capabilities the little single-screen demos rely on. They draw+-- with 'tellImages'\/'text', read the clock via 'tickLossy', and quit on a key.+type Demo t m =+ ( Reflex t+ , MonadFix m+ , MonadHold t m+ , MonadIO m+ , MonadIO (Performable m)+ , PerformEvent t m+ , PostBuild t m+ , TriggerEvent t m+ , HasInput t m+ , HasImageWriter t m+ , HasDisplayRegion t m+ , HasFocusReader t m+ , HasTheme t m+ )++-- | The brand accent color (cyan), matching the rest of the demos.+accent :: V.Color+accent = rgbColor 0 200 230++-- | A dim grey for hints.+dim :: V.Color+dim = rgbColor 120 120 140++-- | An 'Event' that fires when the user asks to quit (q or Ctrl+C). Demos+-- return this from their widget so 'mainWidget' shuts down.+quit :: (Reflex t, Monad m, HasInput t m) => m (Event t ())+quit = do+ q <- key (V.KChar 'q')+ c <- ctrlc+ pure $ leftmost [() <$ q, c]
+ src-bin/Example/DoomFire.hs view
@@ -0,0 +1,63 @@+-- |+-- Description: The classic Doom fire effect, à la the doom-fire example.+module Example.DoomFire (doomFire) where++import Control.Monad.IO.Class (liftIO)+import Data.Bits (shiftR, xor)+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++maxHeat :: Int+maxHeat = 36++-- | A heat grid seeded from a hot bottom row; each frame every cell cools and+-- drifts upward from the cell below it, producing rising flames. The grid is+-- sized to the terminal when the demo starts.+doomFire :: Demo t m => m (Event t ())+doomFire = do+ w <- sample . current =<< displayWidth+ h <- sample . current =<< displayHeight+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.05 t0+ st <- foldDyn (\_ (g, f) -> (step w h f g, f + 1)) (initGrid w h, 0 :: Int) tick+ tellImages $ current $ ffor st $ \(g, _) -> [renderFire g]+ quit+ where+ initGrid w h =+ [[if y == h - 1 then maxHeat else 0 | _ <- [0 .. w - 1]] | y <- [0 .. h - 1]]+ step w h f grid =+ let stepRow y below =+ [ let r = rnd x y f+ srcX = max 0 $ min (w - 1) $ x + ((r `div` 5) `mod` 3 - 1)+ decay = r `mod` 5+ in max 0 (below !! srcX - decay)+ | x <- [0 .. w - 1]+ ]+ in [stepRow y (grid !! (y + 1)) | y <- [0 .. h - 2]] ++ [replicate w maxHeat]+ renderFire grid = V.vertCat [V.horizCat (map cell rowHeats) | rowHeats <- grid]+ cell heat+ | heat <= 0 = V.char V.defAttr ' '+ | otherwise = V.char (V.withForeColor V.defAttr (heatColor heat)) (heatChar heat)+ heatChar heat+ | heat < 10 = '░'+ | heat < 19 = '▒'+ | heat < 28 = '▓'+ | otherwise = '█'+ heatColor heat = fromRGB $ sampleGradient1D fireGrad (fromIntegral heat / fromIntegral maxHeat)+ fireGrad =+ gradient1D+ [ (0.0, RGB 12 8 8)+ , (0.18, RGB 90 0 0)+ , (0.4, RGB 210 35 0)+ , (0.6, RGB 255 110 0)+ , (0.8, RGB 255 200 40)+ , (1.0, RGB 255 255 200)+ ]+ rnd x y f =+ let a = x * 374761393 + y * 668265263 + f * 2246822519 + 12345+ b = (a `xor` (a `shiftR` 13)) * 1274126177+ in abs (b `xor` (b `shiftR` 16))
+ src-bin/Example/Pager.hs view
@@ -0,0 +1,28 @@+-- |+-- Description: A scrollable pager, à la bubbles\/viewport (pager example).+module Example.Pager (pager) where++import qualified Data.Text as T+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A 'scrollableText' viewport over a block of numbered lines. Scroll it with+-- the arrow keys or the mouse wheel.+pager :: Demo t m => m (Event t ())+pager = do+ tellImages $+ pure [V.text' (V.withForeColor V.defAttr accent) " ↑/↓ or mouse wheel to scroll · q to quit"]+ dw <- displayWidth+ dh <- displayHeight+ let viewport w h = Region 0 2 w (max 1 (h - 2))+ _ <- pane (viewport <$> dw <*> dh) (pure True) $ scrollableText def (pure body)+ quit+ where+ body =+ T.unlines+ [ "Line " <> T.pack (show n) <> " — scrollable viewport content"+ | n <- [1 .. 60 :: Int]+ ]
+ src-bin/Example/Progress.hs view
@@ -0,0 +1,36 @@+-- |+-- Description: An animated, gradient-filled progress bar, à la bubbles\/progress.+module Example.Progress (progressBar) where++import Control.Monad.IO.Class (liftIO)+import qualified Data.Text as T+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex+import Text.Printf (printf)++import Example.Common+import Reflex.Vty++-- | A progress bar that fills 0→100% (then loops), tinted with a red→yellow→+-- green 'Gradient1D' so each cell carries its own color.+progressBar :: Demo t m => m (Event t ())+progressBar = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.04 t0+ pct <- foldDyn (\_ p -> if p >= 1 then 0 else min 1 (p + 0.008)) (0 :: Double) tick+ dw <- displayWidth+ tellImages $ (\w p -> [barImage w p]) <$> current dw <*> current pct+ quit+ where+ grad = gradient1D [(0, RGB 255 70 90), (0.5, RGB 250 200 60), (1, RGB 80 220 120)]+ barImage w p =+ let barW = max 1 (w - 7)+ filled = round (p * fromIntegral barW) :: Int+ cellColor i = fromRGB $ sampleGradient1D grad (fromIntegral i / fromIntegral (max 1 (barW - 1)))+ cell i+ | i < filled = V.char (V.withForeColor V.defAttr (cellColor i)) '█'+ | otherwise = V.char (V.withForeColor V.defAttr (rgbColor 64 64 78)) '░'+ bar = V.horizCat $ map cell [0 .. barW - 1]+ label = V.text' (V.withForeColor V.defAttr accent) $ T.pack $ printf " %3d%%" (round (p * 100) :: Int)+ in V.horizCat [bar, label]
+ src-bin/Example/Space.hs view
@@ -0,0 +1,50 @@+-- |+-- Description: A drifting starfield, à la the space example.+module Example.Space (space) where++import Control.Monad.IO.Class (liftIO)+import Data.Bits (shiftR, xor)+import qualified Data.Map as Map+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A starfield warping leftward. Each star has a pseudo-random row and speed+-- (its "depth"); faster stars are brighter and drawn with a bolder glyph.+space :: Demo t m => m (Event t ())+space = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.06 t0+ n <- foldDyn (\_ i -> i + 1) (0 :: Int) tick+ dw <- displayWidth+ dh <- displayHeight+ tellImages $ (\w h i -> [starfield w h i]) <$> current dw <*> current dh <*> current n+ quit+ where+ starfield w h i+ | w <= 0 || h <= 0 = V.emptyImage+ | otherwise =+ let nStars = max 1 (w * h `div` 14)+ stars = Map.fromList [star w h i k | k <- [0 .. nStars - 1]]+ mkRow y = V.horizCat [Map.findWithDefault gap (x, y) stars | x <- [0 .. w - 1]]+ in V.vertCat [mkRow y | y <- [0 .. h - 1]]+ gap = V.char V.defAttr ' '+ star w h i k =+ let y = hash (k * 2 + 1) `mod` h+ speed = 1 + hash (k * 3 + 2) `mod` 3+ x = (hash k - i * speed) `mod` w+ (ch, clr) = case speed of+ 1 -> ('·', rgbColor 90 90 120)+ 2 -> ('*', rgbColor 170 170 205)+ _ -> ('✦', rgbColor 255 255 255)+ in ((x, y), V.char (V.withForeColor V.defAttr clr) ch)+ -- A cheap integer hash, always non-negative.+ hash :: Int -> Int+ hash k0 =+ let a = k0 * 374761393 + 668265263+ b = (a `xor` (a `shiftR` 13)) * 1274126177+ c = b `xor` (b `shiftR` 16)+ in abs c
+ src-bin/Example/Spinner.hs view
@@ -0,0 +1,29 @@+-- |+-- Description: An animated spinner, à la bubbles\/spinner.+module Example.Spinner (spinner) where++import Control.Monad.IO.Class (liftIO)+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A braille spinner that cycles a frame every 80ms.+spinner :: Demo t m => m (Event t ())+spinner = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.08 t0+ idx <- foldDyn (\_ i -> (i + 1) `mod` n) 0 tick+ tellImages $ current $ ffor idx frame+ quit+ where+ frames = "⣾⣽⣻⢿⡿⣟⣯⣷"+ n = length frames+ frame i =+ [ V.horizCat+ [ V.char (V.withForeColor V.defAttr accent) (frames !! i)+ , V.text' (V.withForeColor V.defAttr dim) " Spinning forever… (q to quit)"+ ]+ ]
+ src-bin/Example/Splash.hs view
@@ -0,0 +1,51 @@+-- |+-- Description: An animated splash screen, à la the splash example.+module Example.Splash (splash) where++import Control.Monad.IO.Class (liftIO)+import qualified Data.Text as T+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A centered title that types out in a pink→violet→cyan gradient, with a+-- subtitle that fades in beneath it; the whole thing loops.+splash :: Demo t m => m (Event t ())+splash = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.06 t0+ n <- foldDyn (\_ i -> i + 1) (0 :: Int) tick+ dw <- displayWidth+ dh <- displayHeight+ tellImages $ (\w h i -> [frame w h i]) <$> current dw <*> current dh <*> current n+ quit+ where+ title = "reflex-vty"+ subtitle = "reactive terminal UIs"+ grad = gradient1D [(0, RGB 255 64 160), (0.5, RGB 150 90 255), (1, RGB 0 200 230)]+ frame w h i =+ let p = i `mod` 95+ revealed = min (length title) (max 0 (p `div` 2))+ subAlpha = max 0 (min 1 (fromIntegral (p - 26) / 18 :: Double))+ pad k = V.text' V.defAttr (T.replicate (max 0 k) " ")+ center len = (w - len) `div` 2+ titleImg =+ V.horizCat+ [ V.char (V.withStyle (V.withForeColor V.defAttr (charColor k)) V.bold) c+ | (k, c) <- zip [0 :: Int ..] (take revealed title)+ ]+ charColor k = fromRGB $ sampleGradient1D grad (fromIntegral k / fromIntegral (max 1 (length title - 1)))+ subImg+ | subAlpha <= 0 = V.emptyImage+ | otherwise = V.text' (V.withForeColor V.defAttr (fromRGB (mix subAlpha (RGB 35 35 45) (RGB 150 150 170)))) (T.pack subtitle)+ gap = V.char V.defAttr ' '+ top = max 0 (h `div` 2 - 1)+ in V.vertCat $+ replicate top gap+ ++ [ V.horizCat [pad (center (length title)), titleImg]+ , gap+ , V.horizCat [pad (center (length subtitle)), subImg]+ ]
+ src-bin/Example/Stopwatch.hs view
@@ -0,0 +1,30 @@+-- |+-- Description: A count-up stopwatch, à la bubbles\/stopwatch.+module Example.Stopwatch (stopwatch) where++import Control.Monad.IO.Class (liftIO)+import qualified Data.Text as T+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex+import Text.Printf (printf)++import Example.Common+import Reflex.Vty++-- | Counts up from zero, ticking every 100ms.+stopwatch :: Demo t m => m (Event t ())+stopwatch = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.1 t0+ elapsed <- foldDyn (\_ d -> d + 0.1) (0 :: Double) tick+ tellImages $ current $ ffor elapsed line+ quit+ where+ line d =+ [ V.horizCat+ [ V.text' (V.withForeColor V.defAttr accent) " ⏱ "+ , V.text' V.defAttr $ T.pack $ printf "%.1fs" d+ , V.text' (V.withForeColor V.defAttr dim) " (q to quit)"+ ]+ ]
+ src-bin/Example/TextInput.hs view
@@ -0,0 +1,27 @@+-- |+-- Description: A single text input field, à la bubbles\/textinput.+module Example.TextInput (textInputExample) where++import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | A prompt and a 'textInput' below it. The input lives in an always-focused+-- 'pane', so it accepts typing immediately. Quit with Ctrl+C (q is a character).+textInputExample :: Demo t m => m (Event t ())+textInputExample = do+ dw <- displayWidth+ tellImages $+ pure+ [ V.vertCat+ [ V.text' (V.withForeColor V.defAttr accent) " What's your name?"+ , V.text' V.defAttr ""+ ]+ ]+ let inputRegion w = Region 2 2 (max 1 (w - 4)) 1+ _ <- pane (inputRegion <$> dw) (pure True) $ textInput def+ tellImages $+ pure [V.translate 2 4 $ V.text' (V.withForeColor V.defAttr dim) "(type away — Ctrl+C to quit)"]+ ctrlc
+ src-bin/Example/Timer.hs view
@@ -0,0 +1,31 @@+-- |+-- Description: A countdown timer, à la bubbles\/timer.+module Example.Timer (timer) where++import Control.Monad.IO.Class (liftIO)+import qualified Data.Text as T+import Data.Time (getCurrentTime)+import qualified Graphics.Vty as V+import Reflex+import Text.Printf (printf)++import Example.Common+import Reflex.Vty++-- | Counts down from ten seconds, ticking every 100ms.+timer :: Demo t m => m (Event t ())+timer = do+ t0 <- liftIO getCurrentTime+ tick <- tickLossy 0.1 t0+ remaining <- foldDyn (\_ r -> max 0 (r - 0.1)) (10 :: Double) tick+ tellImages $ current $ ffor remaining line+ quit+ where+ line r =+ [ V.horizCat+ [ V.text' (V.withForeColor V.defAttr accent) " ⏳ "+ , V.text' V.defAttr $ T.pack $ printf "%.1fs" r+ , V.text' (V.withForeColor V.defAttr dim) $+ if r <= 0 then " time's up! (q to quit)" else " remaining (q to quit)"+ ]+ ]
+ src-bin/Example/Views.hs view
@@ -0,0 +1,36 @@+-- |+-- Description: Switching between views, à la the composable-views example.+module Example.Views (views) where++import qualified Graphics.Vty as V+import Reflex++import Example.Common+import Reflex.Vty++-- | Two views toggled with Tab. Each is just a different rendering chosen from+-- a 'Dynamic' index — the FRP way to swap "views".+views :: Demo t m => m (Event t ())+views = do+ tab <- key (V.KChar '\t')+ n <- foldDyn (\_ i -> (i + 1) `mod` 2) (0 :: Int) tab+ tellImages $ current $ ffor n viewImage+ quit+ where+ hint = V.text' (V.withForeColor V.defAttr dim) " Tab to switch views · q to quit"+ viewImage 0 =+ [ V.vertCat+ [ V.text' (V.withStyle (V.withForeColor V.defAttr accent) V.bold) " ◖ View one ◗"+ , V.text' V.defAttr " reactive, composable terminal UIs"+ , V.char V.defAttr ' '+ , hint+ ]+ ]+ viewImage _ =+ [ V.vertCat+ [ V.text' (V.withStyle (V.withForeColor V.defAttr (rgbColor 200 120 255)) V.bold) " ◆ View two ◆"+ , V.text' V.defAttr " swapped with a single keystroke"+ , V.char V.defAttr ' '+ , hint+ ]+ ]
+ src-bin/examples.hs view
@@ -0,0 +1,50 @@+-- | A gallery of small, standalone reflex-vty demos — ports of the Bubble Tea+-- examples (https://github.com/charmbracelet/bubbletea/tree/main/examples).+--+-- Run one with: @cabal run examples -- <name>@ (defaults to @spinner@).+module Main where++import qualified Data.Text as T+import Reflex+import System.Environment (getArgs)++import Example.CellBuffer+import Example.Common+import Example.DoomFire+import Example.Pager+import Example.Progress+import Example.Space+import Example.Spinner+import Example.Splash+import Example.Stopwatch+import Example.TextInput+import Example.Timer+import Example.Views+import Reflex.Vty++main :: IO ()+main = do+ args <- getArgs+ let name = case args of+ (a : _) -> a+ _ -> "spinner"+ mainWidget def $ runExample name++-- | Dispatch to a demo by name.+runExample :: Demo t m => String -> m (Event t ())+runExample = \case+ "spinner" -> spinner+ "progress" -> progressBar+ "progress-bar" -> progressBar+ "stopwatch" -> stopwatch+ "timer" -> timer+ "textinput" -> textInputExample+ "pager" -> pager+ "views" -> views+ "cellbuffer" -> cellBuffer+ "doom-fire" -> doomFire+ "space" -> space+ "splash" -> splash+ other -> do+ text $ pure $ "Unknown example: " <> T.pack other <> " (q to quit)"+ quit