(ns examples.spinner "Spinner example - demonstrates animated loading states. Mirrors bubbletea's spinner example." (:require [tui.core :as tui] [tui.events :as ev])) ;; === Spinner Frames === (def spinner-styles {:dots ["⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏"] :line ["|" "/" "-" "\\"] :circle ["◐" "◓" "◑" "◒"] :square ["◰" "◳" "◲" "◱"] :triangle ["◢" "◣" "◤" "◥"] :bounce ["⠁" "⠂" "⠄" "⠂"] :dots2 ["⣾" "⣽" "⣻" "⢿" "⡿" "⣟" "⣯" "⣷"] :arc ["◜" "◠" "◝" "◞" "◡" "◟"] :moon ["🌑" "🌒" "🌓" "🌔" "🌕" "🌖" "🌗" "🌘"]}) ;; === Model === (def initial-model {:frame 0 :style :dots :loading true :message "Loading..." :styles (vec (keys spinner-styles)) :style-idx 0}) ;; === Update === (defn update-fn [{:keys [model event]}] (let [{:keys [styles style-idx]} model] (cond ;; Quit (or (ev/key= event \q) (ev/key= event \c #{:ctrl})) {:model model :events [(ev/quit)]} ;; Spinner frame - advance animation (= (:type event) :spinner-frame) (if (:loading model) {:model (update model :frame inc) :events [(ev/delayed-event 80 {:type :spinner-frame})]} {:model model}) ;; Space - simulate completion (ev/key= event \space) {:model (assoc model :loading false :message "Done!")} ;; Tab - change spinner style (ev/key= event :tab) (let [new-idx (mod (inc style-idx) (count styles))] {:model (assoc model :style-idx new-idx :style (nth styles new-idx))}) ;; r - restart (ev/key= event \r) {:model (assoc model :loading true :frame 0 :message "Loading...") :events [(ev/delayed-event 80 {:type :spinner-frame})]} :else {:model model}))) ;; === View === (defn spinner-view [{:keys [frame style]}] (let [frames (get spinner-styles style) idx (mod frame (count frames))] (nth frames idx))) (defn view [{:keys [loading message style] :as model}] [:col {:gap 1} [:box {:border :rounded :padding [1 2]} [:col {:gap 1} [:text {:bold true} "Spinner Demo"] [:text ""] [:row {:gap 1} (if loading [:text {:fg :cyan} (spinner-view model)] [:text {:fg :green} "✓"]) [:text message]] [:text ""] [:text {:fg :gray} (str "Style: " (name style))]]] [:col [:text {:fg :gray} "tab: change style space: complete r: restart q: quit"]]]) ;; === Main === (defn -main [& _args] (println "Starting spinner...") (tui/run {:init initial-model :update update-fn :view view :init-events [(ev/delayed-event 80 {:type :spinner-frame})]}) (println "Spinner demo finished."))