# Getting Started This guide will walk you through creating your first TUI application with Clojure TUI. ## Installation ### Using deps.edn Add the library to your `deps.edn`: ```clojure {:deps {io.github.yourname/clojure-tui {:git/tag "v0.1.0" :git/sha "..."}}} ``` ### Requirements - **Clojure 1.11+** for full async runtime (`tui.core`) - **Babashka** for simple sync runtime (`tui.simple`) - A terminal that supports ANSI escape codes (most modern terminals) ## Your First Application Let's build a simple "Hello World" TUI application. ### Step 1: Create the Project ```bash mkdir my-tui-app cd my-tui-app ``` Create a `deps.edn`: ```clojure {:deps {io.github.yourname/clojure-tui {:git/tag "v0.1.0" :git/sha "..."}}} ``` ### Step 2: Write the Application Create `src/myapp/core.clj`: ```clojure (ns myapp.core (:require [tui.simple :as tui])) ;; 1. Model - the application state (def initial-model {:message "Hello, TUI!"}) ;; 2. Update - handle messages and return [new-model command] (defn update-fn [model msg] (cond ;; Quit on 'q' key (tui/key= msg "q") [model tui/quit] ;; Default: no change :else [model nil])) ;; 3. View - render the model as hiccup (defn view [{:keys [message]}] [:col [:text {:fg :cyan :bold true} message] [:space {:height 1}] [:text {:fg :gray} "Press q to quit"]]) ;; Run the application (defn -main [& args] (tui/run {:init initial-model :update update-fn :view view})) ``` ### Step 3: Run It ```bash clojure -M -m myapp.core ``` You'll see: ``` Hello, TUI! Press q to quit ``` Press `q` to exit. ## Understanding the Elm Architecture Clojure TUI uses the [Elm Architecture](https://guide.elm-lang.org/architecture/), a pattern for building interactive applications. ### The Three Parts ``` ┌─────────────┐ │ Model │ │ (state) │ └──────┬──────┘ │ ┌────────────┴────────────┐ │ │ v │ ┌─────────────┐ ┌──────┴──────┐ │ View │ │ Update │ │ (render UI) │ │(handle msg) │ └──────┬──────┘ └──────┬──────┘ │ ^ │ │ v │ ┌─────────────┐ ┌──────┴──────┐ │ Screen │ │ Message │ │ (output) │ │ (input) │ └─────────────┘ └─────────────┘ ``` 1. **Model**: Your application state. Can be any Clojure data structure. 2. **Update**: A pure function that takes the current model and a message, returning a vector of `[new-model command]`. 3. **View**: A pure function that takes the model and returns a hiccup data structure representing the UI. ### The Flow 1. The runtime renders the initial view 2. User presses a key 3. The key is parsed into a message like `[:key {:char \a}]` 4. The `update` function receives the model and message 5. `update` returns a new model and optional command 6. The view is re-rendered with the new model 7. If a command was returned, it's executed (may produce more messages) 8. Repeat until `tui/quit` is returned ## Handling Input Keys are represented as messages in the format `[:key ...]`. ### Key Message Examples ```clojure [:key {:char \a}] ;; Regular character [:key {:char \Z}] ;; Uppercase character [:key :enter] ;; Enter key [:key :up] ;; Arrow up [:key {:ctrl true :char \c}] ;; Ctrl+C [:key {:alt true :char \x}] ;; Alt+X ``` ### Matching Keys Use `tui/key=` to match keys in your update function: ```clojure (defn update-fn [model msg] (cond ;; Match character 'q' (tui/key= msg "q") [model tui/quit] ;; Match Enter key (tui/key= msg :enter) [(handle-enter model) nil] ;; Match arrow keys (tui/key= msg :up) [(move-up model) nil] (tui/key= msg :down) [(move-down model) nil] ;; Match Ctrl+C (tui/key= msg [:ctrl \c]) [model tui/quit] ;; Default :else [model nil])) ``` ### Special Keys | Key | Pattern | |-----|---------| | Enter | `:enter` | | Escape | `:escape` | | Tab | `:tab` | | Backspace | `:backspace` | | Arrow Up | `:up` | | Arrow Down | `:down` | | Arrow Left | `:left` | | Arrow Right | `:right` | | Home | `:home` | | End | `:end` | | Page Up | `:page-up` | | Page Down | `:page-down` | | Delete | `:delete` | | Insert | `:insert` | | F1-F12 | `:f1` through `:f12` | ## Building a Counter Let's build something more interactive: a counter. ```clojure (ns myapp.counter (:require [tui.simple :as tui])) (defn update-fn [model msg] (cond ;; Quit (tui/key= msg "q") [model tui/quit] ;; Increment with 'k' or up arrow (or (tui/key= msg "k") (tui/key= msg :up)) [(inc model) nil] ;; Decrement with 'j' or down arrow (or (tui/key= msg "j") (tui/key= msg :down)) [(dec model) nil] ;; Reset with 'r' (tui/key= msg "r") [0 nil] :else [model nil])) (defn view [count] [:col [:box {:border :rounded :padding [0 2]} [:row [:text "Count: "] [:text {:fg (cond (pos? count) :green (neg? count) :red :else :yellow) :bold true} (str count)]]] [:space {:height 1}] [:text {:fg :gray} "j/k or arrows: change value"] [:text {:fg :gray} "r: reset q: quit"]]) (defn -main [& args] (tui/run {:init 0 :update update-fn :view view})) ``` Output: ``` ╭────────────────╮ │ Count: 0 │ ╰────────────────╯ j/k or arrows: change value r: reset q: quit ``` After pressing `k` a few times: ``` ╭────────────────╮ │ Count: 3 │ ╰────────────────╯ j/k or arrows: change value r: reset q: quit ``` ## Commands Commands are how your application performs side effects. ### Available Commands | Command | Description | |---------|-------------| | `tui/quit` | Exit the application | | `(tui/tick ms)` | Send `:tick` message after delay | | `(tui/batch cmd1 cmd2 ...)` | Run commands in parallel | | `(tui/sequentially cmd1 cmd2 ...)` | Run commands in sequence | ### Returning Commands Commands are returned as the second element of the update function's return vector: ```clojure (defn update-fn [model msg] (cond ;; Return quit command (tui/key= msg "q") [model tui/quit] ;; Return tick command (async runtime only) (tui/key= msg "s") [{:started true} (tui/tick 1000)] ;; No command :else [model nil])) ``` ### Timer Example (Async Runtime) ```clojure (ns myapp.timer (:require [tui.core :as tui])) ;; Note: tui.core for async (defn update-fn [model msg] (cond (tui/key= msg "q") [model tui/quit] ;; Handle tick message (= msg :tick) (let [new-count (dec (:count model))] (if (pos? new-count) [{:count new-count} (tui/tick 1000)] [{:count 0 :done true} nil])) :else [model nil])) (defn view [{:keys [count done]}] [:col (if done [:text {:fg :green :bold true} "Time's up!"] [:text {:bold true} (str "Countdown: " count)]) [:text {:fg :gray} "q: quit"]]) (defn -main [& args] (tui/run {:init {:count 10 :done false} :update update-fn :view view :init-cmd (tui/tick 1000)})) ;; Start first tick ``` ## Choosing a Runtime ### `tui.simple` - For Babashka and Simple Apps - Works with Babashka - Synchronous (blocking) input - No async commands (tick, batch, etc.) - Lower resource usage ### `tui.core` - For Complex Applications - Requires full Clojure with core.async - Async input handling - Full command support (tick, batch, sequentially) - Better for animations and background tasks ## Configuration Options The `run` function accepts these options: | Option | Description | Default | |--------|-------------|---------| | `:init` | Initial model (required) | - | | `:update` | Update function (required) | - | | `:view` | View function (required) | - | | `:init-cmd` | Initial command to run | `nil` | | `:fps` | Frames per second (core only) | `60` | | `:alt-screen` | Use alternate screen buffer | `true` | ### Alternate Screen By default, applications use the alternate screen buffer. This means: - Your application gets a clean screen - When you quit, the original terminal content is restored To disable this: ```clojure (tui/run {:init model :update update-fn :view view :alt-screen false}) ``` ## Next Steps - [Hiccup Views](hiccup-views.md) - Learn about all view elements and styling - [API Reference](api-reference.md) - Complete API documentation - [Examples](examples.md) - Study annotated example applications