diff --git a/README.md b/README.md index 2cab027..1e9a0aa 100644 --- a/README.md +++ b/README.md @@ -55,22 +55,25 @@ or via clojars maven repository ```clojure (ns myapp.core - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style])) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") [state charm/quit-cmd] - (charm/key-match? msg "k") [(update state :count inc) nil] - (charm/key-match? msg "j") [(update state :count dec) nil] + (msg/key-match? msg "q") [state program/quit-cmd] + (msg/key-match? msg "k") [(update state :count inc) nil] + (msg/key-match? msg "j") [(update state :count dec) nil] :else [state nil])) (defn view [state] (str "Count: " (:count state) "\n\n" "j/k to change, q to quit")) -(charm/run {:init {:count 0} - :update update-fn - :view view}) +(program/run {:init {:count 0} + :update update-fn + :view view}) ``` ## API Overview @@ -78,15 +81,15 @@ or via clojars maven repository ### Running a Program ```clojure -(charm/run {:init initial-state-or-fn - :update (fn [state msg] [new-state cmd]) - :view (fn [state] "string to render") - - ;; Options - :alt-screen false ; Use [alternate screen buffer](#alternate-screen-buffer) - :mouse :cell ; Mouse mode: nil, :normal, :cell, :all - :focus-reporting false ; Report focus in/out events - :fps 60}) ; Frames per second +(program/run {:init initial-state-or-fn + :update (fn [state msg] [new-state cmd]) + :view (fn [state] "string to render") + + ;; Options + :alt-screen false ; Use [alternate screen buffer](#alternate-screen-buffer) + :mouse :cell ; Mouse mode: nil, :normal, :cell, :all + :focus-reporting false ; Report focus in/out events + :fps 60}) ; Frames per second ``` #### Alternate Screen Buffer @@ -104,21 +107,21 @@ Messages are maps with a `:type` key. Built-in message types: ```clojure ;; Check message types -(charm/key-press? msg) ; Keyboard input -(charm/mouse? msg) ; Mouse event -(charm/window-size? msg) ; Terminal resized -(charm/quit? msg) ; Quit signal +(msg/key-press? msg) ; Keyboard input +(msg/mouse? msg) ; Mouse event +(msg/window-size? msg) ; Terminal resized +(msg/quit? msg) ; Quit signal ;; Match specific keys -(charm/key-match? msg "q") ; Letter q -(charm/key-match? msg "ctrl+c") ; Ctrl+C -(charm/key-match? msg "enter") ; Enter key -(charm/key-match? msg :up) ; Arrow up +(msg/key-match? msg "q") ; Letter q +(msg/key-match? msg "ctrl+c") ; Ctrl+C +(msg/key-match? msg "enter") ; Enter key +(msg/key-match? msg :up) ; Arrow up ;; Check modifiers -(charm/ctrl? msg) -(charm/alt? msg) -(charm/shift? msg) +(msg/ctrl? msg) +(msg/alt? msg) +(msg/shift? msg) ``` ### Commands @@ -127,47 +130,47 @@ Commands are async functions that produce messages: ```clojure ;; Quit the program -charm/quit-cmd +program/quit-cmd ;; Create a custom command -(charm/cmd (fn [] (charm/key-press :custom))) +(program/cmd (fn [] (msg/key-press :custom))) ;; Run multiple commands in parallel -(charm/batch cmd1 cmd2 cmd3) +(program/batch cmd1 cmd2 cmd3) ;; Run commands in sequence -(charm/sequence-cmds cmd1 cmd2 cmd3) +(program/sequence-cmds cmd1 cmd2 cmd3) ``` ### Styling ```clojure -(require '[charm.core :as charm]) +(require '[charm.style.core :as style]) ;; Create a style (def my-style - (charm/style :fg charm/red + (style/style :fg style/red :bold true :padding [1 2])) ;; Apply style to text -(charm/render my-style "Hello!") +(style/render my-style "Hello!") ;; Shorthand -(charm/styled "Hello!" :fg charm/green :italic true) +(style/styled "Hello!" :fg style/green :italic true) ;; Colors -(charm/rgb 255 100 50) ; True color -(charm/hex "#ff6432") ; Hex color -(charm/ansi :red) ; ANSI 16 colors -(charm/ansi256 196) ; 256 palette +(style/rgb 255 100 50) ; True color +(style/hex "#ff6432") ; Hex color +(style/ansi :red) ; ANSI 16 colors +(style/ansi256 196) ; 256 palette -;; Borders -(charm/render (charm/style :border charm/rounded-border) "boxed") +;; Borders (require '[charm.style.border :as border]) +(style/render (style/style :border border/rounded) "boxed") ;; Layout -(charm/join-horizontal :top block1 block2) -(charm/join-vertical :center block1 block2) +(style/join-horizontal :top block1 block2) +(style/join-vertical :center block1 block2) ``` ## Examples @@ -179,8 +182,7 @@ Please take a look at the [examples](docs/examples/README.md) in the docs folder ``` charm.clj/ ├── src/charm/ -│ ├── core.clj ; Public API -│ ├── program.clj ; Event loop +│ ├── program.clj ; Event loop & program runner │ ├── terminal.clj ; JLine wrapper │ ├── message.clj ; Message types │ ├── ansi/ @@ -194,7 +196,18 @@ charm.clj/ │ │ ├── core.clj ; Style API │ │ ├── color.clj ; Color definitions │ │ ├── border.clj ; Border styles +│ │ ├── overlay.clj ; Overlay placement │ │ └── layout.clj ; Padding, margin, alignment +│ ├── components/ +│ │ ├── spinner.clj ; Spinner animations +│ │ ├── text_input.clj; Text input field +│ │ ├── list.clj ; Scrollable list +│ │ ├── paginator.clj ; Page navigation +│ │ ├── timer.clj ; Countdown timer +│ │ ├── progress.clj ; Progress bar +│ │ ├── viewport.clj ; Scrollable content +│ │ ├── table.clj ; Table display +│ │ └── help.clj ; Help key bindings │ └── render/ │ ├── core.clj ; Renderer │ └── screen.clj ; ANSI sequences diff --git a/docs/adr/006-remove-charm-core-facade.md b/docs/adr/006-remove-charm-core-facade.md new file mode 100644 index 0000000..65aab52 --- /dev/null +++ b/docs/adr/006-remove-charm-core-facade.md @@ -0,0 +1,49 @@ +# ADR 006: Remove charm.core Facade Namespace + +## Status + +Accepted + +## Context + +charm.clj originally exposed its entire public API through a single `charm.core` namespace that re-exported vars from `charm.message`, `charm.program`, `charm.style.core`, `charm.style.overlay`, and all component namespaces (`charm.components.*`). This facade contained ~130 `def` bindings with no logic of its own. + +Problems with this approach: + +1. **Naming gymnastics** — To avoid clashes in a single namespace, component functions needed prefixes: `text-input-value`, `list-selected-item`, `timer-timeout`, `progress-complete?`, etc. With direct namespace requires, these become the more natural `text-input/value`, `item-list/selected-item`, `timer/timeout`, `progress/complete?`. + +2. **Maintenance burden** — Every new or renamed function in any component required a corresponding update in `charm.core`. This was a frequent source of drift. + +3. **Discoverability** — Users couldn't easily tell which namespace a function actually lived in, making source navigation harder. + +4. **Unnecessary coupling** — Requiring `charm.core` pulled in every component even when only a few were needed. + +## Decision + +Remove `charm.core` entirely. Users require the namespaces they need directly: + +```clojure +(ns my-app + (:require + [charm.components.list :as item-list] + [charm.components.text-input :as text-input] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style])) +``` + +The public API namespaces are: + +- `charm.program` — `run`, `run-async`, `cmd`, `batch`, `quit-cmd` +- `charm.message` — `key-match?`, `key-press?`, `window-size?`, `ctrl?`, etc. +- `charm.style.core` — `style`, `render`, colors, border aliases, layout joins +- `charm.style.border` — border definitions (`rounded`, `normal`, `thick`, etc.) +- `charm.style.overlay` — `place-overlay`, `center-overlay` +- `charm.components.*` — each component in its own namespace + +## Consequences + +- Simple examples need 3-4 requires instead of 1. This is standard Clojure practice and improves clarity. +- Function names become shorter and more natural (e.g. `timer/timeout` instead of `charm/timer-timeout`). +- Adding new component functions no longer requires updating a central file. +- This is a breaking change for all existing users of `charm.core`. diff --git a/docs/api/layout.md b/docs/api/layout.md index 5913318..879929d 100644 --- a/docs/api/layout.md +++ b/docs/api/layout.md @@ -7,7 +7,7 @@ The layout API provides functions for combining and aligning text blocks. ### join-horizontal ```clojure -(charm/join-horizontal position & texts) +(style/join-horizontal position & texts) ``` Join multiple text blocks side by side. @@ -15,10 +15,10 @@ Join multiple text blocks side by side. **Position** specifies vertical alignment: `:top`, `:center`, or `:bottom`. ```clojure -(charm/join-horizontal :top "Left" "Right") +(style/join-horizontal :top "Left" "Right") ; LeftRight -(charm/join-horizontal :top +(style/join-horizontal :top "Line 1\nLine 2\nLine 3" " | " "A\nB") @@ -31,16 +31,16 @@ Join multiple text blocks side by side. ```clojure (def box1 - (charm/render - (charm/style :border charm/rounded-border :padding [0 1]) + (style/render + (style/style :border border/rounded :padding [0 1]) "Box 1")) (def box2 - (charm/render - (charm/style :border charm/rounded-border :padding [0 1]) + (style/render + (style/style :border border/rounded :padding [0 1]) "Box 2")) -(charm/join-horizontal :top box1 " " box2) +(style/join-horizontal :top box1 " " box2) ; ╭───────╮ ╭───────╮ ; │ Box 1 │ │ Box 2 │ ; ╰───────╯ ╰───────╯ @@ -50,7 +50,7 @@ Join multiple text blocks side by side. ```clojure ;; Top aligned (default) -(charm/join-horizontal :top +(style/join-horizontal :top "Short" "Tall\ntext\nhere") ; ShortTall @@ -58,7 +58,7 @@ Join multiple text blocks side by side. ; here ;; Center aligned -(charm/join-horizontal :center +(style/join-horizontal :center "Short" "Tall\ntext\nhere") ; Tall @@ -66,7 +66,7 @@ Join multiple text blocks side by side. ; here ;; Bottom aligned -(charm/join-horizontal :bottom +(style/join-horizontal :bottom "Short" "Tall\ntext\nhere") ; Tall @@ -77,7 +77,7 @@ Join multiple text blocks side by side. ### join-vertical ```clojure -(charm/join-vertical position & texts) +(style/join-vertical position & texts) ``` Join multiple text blocks vertically (stacked). @@ -85,11 +85,11 @@ Join multiple text blocks vertically (stacked). **Position** specifies horizontal alignment: `:left`, `:center`, or `:right`. ```clojure -(charm/join-vertical :left "Top" "Bottom") +(style/join-vertical :left "Top" "Bottom") ; Top ; Bottom -(charm/join-vertical :center +(style/join-vertical :center "Short" "Longer text" "Medium") @@ -97,7 +97,7 @@ Join multiple text blocks vertically (stacked). ; Longer text ; Medium -(charm/join-vertical :right +(style/join-vertical :right "Short" "Longer text" "Medium") @@ -110,16 +110,16 @@ Join multiple text blocks vertically (stacked). ```clojure (def header - (charm/render - (charm/style :border charm/rounded-border :width 30 :align :center) + (style/render + (style/style :border border/rounded :width 30 :align :center) "Header")) (def content - (charm/render - (charm/style :border charm/normal-border :width 30) + (style/render + (style/style :border border/normal :width 30) "Content goes here")) -(charm/join-vertical :left header content) +(style/join-vertical :left header content) ; ╭──────────────────────────────╮ ; │ Header │ ; ╰──────────────────────────────╯ @@ -128,33 +128,19 @@ Join multiple text blocks vertically (stacked). ; └──────────────────────────────┘ ``` -## Alignment Constants - -```clojure -;; Horizontal alignment -charm/left -charm/center -charm/right - -;; Vertical alignment -charm/top -charm/bottom -; charm/center (same constant) -``` - ## Building Layouts ### Two-Column Layout ```clojure (defn two-column [left-content right-content] - (charm/join-horizontal :top - (charm/render - (charm/style :width 30 :border charm/normal-border) + (style/join-horizontal :top + (style/render + (style/style :width 30 :border border/normal) left-content) " " - (charm/render - (charm/style :width 30 :border charm/normal-border) + (style/render + (style/style :width 30 :border border/normal) right-content))) ``` @@ -162,15 +148,15 @@ charm/bottom ```clojure (defn page-layout [header content footer] - (charm/join-vertical :left - (charm/render - (charm/style :width 60 :align :center :border charm/rounded-border) + (style/join-vertical :left + (style/render + (style/style :width 60 :align :center :border border/rounded) header) - (charm/render - (charm/style :width 60 :border charm/normal-border :padding [1 2]) + (style/render + (style/style :width 60 :border border/normal :padding [1 2]) content) - (charm/render - (charm/style :width 60 :align :center :fg 240) + (style/render + (style/style :width 60 :align :center :fg 240) footer))) ``` @@ -178,12 +164,12 @@ charm/bottom ```clojure (defn sidebar-layout [sidebar main-content] - (charm/join-horizontal :top - (charm/render - (charm/style :width 20 :border charm/normal-border) + (style/join-horizontal :top + (style/render + (style/style :width 20 :border border/normal) sidebar) - (charm/render - (charm/style :width 50 :border charm/normal-border :padding [0 1]) + (style/render + (style/style :width 50 :border border/normal :padding [0 1]) main-content))) ``` @@ -191,47 +177,49 @@ charm/bottom ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.style.border :as border] + [charm.style.core :as style])) (def title-style - (charm/style :fg charm/cyan :bold true :align :center)) + (style/style :fg style/cyan :bold true :align :center)) (def box-style - (charm/style :border charm/rounded-border :padding [0 1])) + (style/style :border border/rounded :padding [0 1])) (def dim-style - (charm/style :fg 240)) + (style/style :fg 240)) (defn render-sidebar [items selected] - (charm/render - (charm/style :border charm/normal-border :width 20) + (style/render + (style/style :border border/normal :width 20) (clojure.string/join "\n" (map-indexed (fn [i item] (if (= i selected) - (charm/render (charm/style :fg charm/cyan :bold true) + (style/render (style/style :fg style/cyan :bold true) (str "> " item)) (str " " item))) items)))) (defn render-content [text] - (charm/render - (charm/style :border charm/rounded-border + (style/render + (style/style :border border/rounded :padding [1 2] :width 40) text)) (defn render-help [] - (charm/render dim-style "j/k: navigate q: quit")) + (style/render dim-style "j/k: navigate q: quit")) (defn view [state] (let [sidebar (render-sidebar (:items state) (:selected state)) content (render-content (nth (:items state) (:selected state))) help (render-help)] - (charm/join-vertical :left - (charm/render title-style "My Application") + (style/join-vertical :left + (style/render title-style "My Application") "" - (charm/join-horizontal :top sidebar " " content) + (style/join-horizontal :top sidebar " " content) "" help))) ``` @@ -244,10 +232,10 @@ Use empty strings for spacing: ```clojure ;; Horizontal spacing -(charm/join-horizontal :top box1 " " box2) ; 3 spaces +(style/join-horizontal :top box1 " " box2) ; 3 spaces ;; Vertical spacing -(charm/join-vertical :left header "" "" content) ; 2 blank lines +(style/join-vertical :left header "" "" content) ; 2 blank lines ``` ### Consistent Widths @@ -255,10 +243,10 @@ Use empty strings for spacing: Set explicit widths for alignment: ```clojure -(charm/join-vertical :left - (charm/render (charm/style :width 40) "Row 1") - (charm/render (charm/style :width 40) "Row 2") - (charm/render (charm/style :width 40) "Row 3")) +(style/join-vertical :left + (style/render (style/style :width 40) "Row 1") + (style/render (style/style :width 40) "Row 2") + (style/render (style/style :width 40) "Row 3")) ``` ### Nesting Layouts @@ -266,11 +254,11 @@ Set explicit widths for alignment: Layouts can be nested: ```clojure -(charm/join-vertical :center +(style/join-vertical :center header - (charm/join-horizontal :top + (style/join-horizontal :top left-panel - (charm/join-vertical :left + (style/join-vertical :left top-right bottom-right)) footer) diff --git a/docs/api/messages.md b/docs/api/messages.md index 3553188..d4f2737 100644 --- a/docs/api/messages.md +++ b/docs/api/messages.md @@ -7,7 +7,7 @@ Messages are plain maps with a `:type` key that flow through the update function ### key-press ```clojure -(charm/key-press key & options) +(msg/key-press key & options) ``` Create a key press message (mainly for testing). @@ -22,15 +22,15 @@ Create a key press message (mainly for testing). | `:shift` | boolean | `false` | Shift modifier | ```clojure -(charm/key-press "a") -(charm/key-press :enter) -(charm/key-press "c" :ctrl true) +(msg/key-press "a") +(msg/key-press :enter) +(msg/key-press "c" :ctrl true) ``` ### key-press? ```clojure -(charm/key-press? msg) ; => boolean +(msg/key-press? msg) ; => boolean ``` Check if a message is a key press. @@ -38,7 +38,7 @@ Check if a message is a key press. ### key-match? ```clojure -(charm/key-match? msg key) ; => boolean +(msg/key-match? msg key) ; => boolean ``` Check if a key press matches a specific key pattern. @@ -75,19 +75,19 @@ Check if a key press matches a specific key pattern. ```clojure (defn update-fn [state msg] (cond - (charm/key-match? msg "q") [state charm/quit-cmd] - (charm/key-match? msg :enter) [(submit state) nil] - (charm/key-match? msg "ctrl+c") [state charm/quit-cmd] - (charm/key-match? msg :up) [(move-up state) nil] + (msg/key-match? msg "q") [state program/quit-cmd] + (msg/key-match? msg :enter) [(submit state) nil] + (msg/key-match? msg "ctrl+c") [state program/quit-cmd] + (msg/key-match? msg :up) [(move-up state) nil] :else [state nil])) ``` ### Modifier Predicates ```clojure -(charm/ctrl? msg) ; => boolean - Ctrl modifier set? -(charm/alt? msg) ; => boolean - Alt modifier set? -(charm/shift? msg) ; => boolean - Shift modifier set? +(msg/ctrl? msg) ; => boolean - Ctrl modifier set? +(msg/alt? msg) ; => boolean - Alt modifier set? +(msg/shift? msg) ; => boolean - Shift modifier set? ``` ## Mouse Messages @@ -95,7 +95,7 @@ Check if a key press matches a specific key pattern. ### mouse ```clojure -(charm/mouse action button x y & options) +(msg/mouse action button x y & options) ``` Create a mouse event message (mainly for testing). @@ -112,14 +112,14 @@ Create a mouse event message (mainly for testing). **Options:** `:ctrl`, `:alt`, `:shift` (boolean modifiers) ```clojure -(charm/mouse :press :left 10 5) -(charm/mouse :wheel-up :none 10 5) +(msg/mouse :press :left 10 5) +(msg/mouse :wheel-up :none 10 5) ``` ### mouse? ```clojure -(charm/mouse? msg) ; => boolean +(msg/mouse? msg) ; => boolean ``` Check if a message is a mouse event. @@ -142,11 +142,11 @@ Check if a message is a mouse event. ```clojure (defn update-fn [state msg] (cond - (and (charm/mouse? msg) (= :press (:action msg))) + (and (msg/mouse? msg) (= :press (:action msg))) (let [{:keys [x y button]} msg] [(handle-click state x y button) nil]) - (and (charm/mouse? msg) (= :wheel-up (:action msg))) + (and (msg/mouse? msg) (= :wheel-up (:action msg))) [(scroll-up state) nil] :else @@ -158,7 +158,7 @@ Check if a message is a mouse event. ### window-size ```clojure -(charm/window-size width height) +(msg/window-size width height) ``` Create a window size message (sent automatically on resize). @@ -166,7 +166,7 @@ Create a window size message (sent automatically on resize). ### window-size? ```clojure -(charm/window-size? msg) ; => boolean +(msg/window-size? msg) ; => boolean ``` **Window size message structure:** @@ -181,7 +181,7 @@ Create a window size message (sent automatically on resize). ```clojure (defn update-fn [state msg] - (if (charm/window-size? msg) + (if (msg/window-size? msg) [(assoc state :width (:width msg) :height (:height msg)) @@ -194,15 +194,15 @@ Create a window size message (sent automatically on resize). ### focus / blur ```clojure -(charm/focus) ; Create focus gained message -(charm/blur) ; Create focus lost message +(msg/focus) ; Create focus gained message +(msg/blur) ; Create focus lost message ``` ### focus? / blur? ```clojure -(charm/focus? msg) ; => boolean -(charm/blur? msg) ; => boolean +(msg/focus? msg) ; => boolean +(msg/blur? msg) ; => boolean ``` Requires `:focus-reporting true` in run options. @@ -210,10 +210,10 @@ Requires `:focus-reporting true` in run options. ```clojure (defn update-fn [state msg] (cond - (charm/focus? msg) + (msg/focus? msg) [(assoc state :active true) nil] - (charm/blur? msg) + (msg/blur? msg) [(assoc state :active false) nil] :else @@ -225,25 +225,25 @@ Requires `:focus-reporting true` in run options. ### quit ```clojure -(charm/quit) ; Create quit message +(msg/quit) ; Create quit message ``` ### quit? ```clojure -(charm/quit? msg) ; => boolean +(msg/quit? msg) ; => boolean ``` ### error ```clojure -(charm/error throwable) ; Create error message +(msg/error throwable) ; Create error message ``` ### error? ```clojure -(charm/error? msg) ; => boolean +(msg/error? msg) ; => boolean ``` ## Message Type Helper @@ -251,13 +251,13 @@ Requires `:focus-reporting true` in run options. ### msg-type ```clojure -(charm/msg-type msg) ; => keyword +(msg/msg-type msg) ; => keyword ``` Get the type of any message. ```clojure -(charm/msg-type {:type :key-press :key "a"}) ; => :key-press +(msg/msg-type {:type :key-press :key "a"}) ; => :key-press ``` ## Custom Messages @@ -287,35 +287,37 @@ Create custom messages as plain maps with a `:type` key: ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program])) (defn update-fn [state msg] (cond ;; Quit on q or Ctrl+C - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c")) + [state program/quit-cmd] ;; Navigation - (charm/key-match? msg :up) + (msg/key-match? msg :up) [(update state :cursor dec) nil] - (charm/key-match? msg :down) + (msg/key-match? msg :down) [(update state :cursor inc) nil] ;; Window resize - (charm/window-size? msg) + (msg/window-size? msg) [(assoc state :size [(:width msg) (:height msg)]) nil] ;; Mouse click - (and (charm/mouse? msg) (= :press (:action msg))) + (and (msg/mouse? msg) (= :press (:action msg))) [(assoc state :clicked [(:x msg) (:y msg)]) nil] ;; Focus changes - (charm/focus? msg) + (msg/focus? msg) [(assoc state :focused true) nil] - (charm/blur? msg) + (msg/blur? msg) [(assoc state :focused false) nil] :else diff --git a/docs/api/program.md b/docs/api/program.md index 22c9b7f..7b31350 100644 --- a/docs/api/program.md +++ b/docs/api/program.md @@ -7,7 +7,7 @@ The program module provides the main event loop and command system for TUI appli ### run ```clojure -(charm/run options) +(program/run options) ``` Run a TUI program with the Elm Architecture pattern. @@ -28,12 +28,12 @@ Run a TUI program with the Elm Architecture pattern. **Example:** ```clojure -(charm/run +(program/run {:init {:count 0} :update (fn [state msg] (cond - (charm/key-match? msg "q") [state charm/quit-cmd] - (charm/key-match? msg "up") [(update state :count inc) nil] + (msg/key-match? msg "q") [state program/quit-cmd] + (msg/key-match? msg "up") [(update state :count inc) nil] :else [state nil])) :view (fn [state] (str "Count: " (:count state) "\nPress q to quit")) @@ -43,7 +43,7 @@ Run a TUI program with the Elm Architecture pattern. ### run-async ```clojure -(charm/run-async options) +(program/run-async options) ``` Run a TUI program in the background. Accepts the same options as `run` but returns immediately with a handle instead of blocking. Mainly for testing in the REPL. @@ -59,10 +59,10 @@ Run a TUI program in the background. Accepts the same options as `run` but retur ```clojure ;; Start the app in the background -(def app (charm/run-async {:init init - :update update-fn - :view view - :alt-screen true})) +(def app (program/run-async {:init init + :update update-fn + :view view + :alt-screen true})) ;; Stop it from another thread / REPL ((:quit! app)) @@ -78,60 +78,60 @@ Commands are asynchronous operations that produce messages. They're returned fro ### cmd ```clojure -(charm/cmd f) +(program/cmd f) ``` Create a command from a function that returns a message. ```clojure ;; Command that sends a message after 1 second -(charm/cmd (fn [] - (Thread/sleep 1000) - {:type :timer-done})) +(program/cmd (fn [] + (Thread/sleep 1000) + {:type :timer-done})) ``` ### batch ```clojure -(charm/batch & cmds) +(program/batch & cmds) ``` Combine multiple commands into one. All commands run in parallel. ```clojure -(charm/batch - (charm/cmd #(do-thing-1)) - (charm/cmd #(do-thing-2)) - (charm/cmd #(do-thing-3))) +(program/batch + (program/cmd #(do-thing-1)) + (program/cmd #(do-thing-2)) + (program/cmd #(do-thing-3))) ``` ### sequence-cmds ```clojure -(charm/sequence-cmds & cmds) +(program/sequence-cmds & cmds) ``` Run commands in sequence (each waits for the previous to complete). ```clojure -(charm/sequence-cmds - (charm/cmd #(step-1)) - (charm/cmd #(step-2)) - (charm/cmd #(step-3))) +(program/sequence-cmds + (program/cmd #(step-1)) + (program/cmd #(step-2)) + (program/cmd #(step-3))) ``` ### quit-cmd ```clojure -charm/quit-cmd +program/quit-cmd ``` A pre-built command that exits the program. ```clojure (defn update-fn [state msg] - (if (charm/key-match? msg "q") - [state charm/quit-cmd] + (if (msg/key-match? msg "q") + [state program/quit-cmd] [state nil])) ``` @@ -149,8 +149,8 @@ The `init` option can be: ```clojure {:init (fn [] - (let [[timer cmd] (charm/timer-init (charm/timer :timeout 5000))] - [{:timer timer} cmd]))} + (let [[t cmd] (timer/timer-init (timer/timer :timeout 5000))] + [{:timer t} cmd]))} ``` 3. **A function** returning just `state` @@ -167,11 +167,11 @@ The update function receives the current state and a message, returning `[new-st (defn update-fn [state msg] (cond ;; Handle quit - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; Handle key press - (charm/key-match? msg "up") + (msg/key-match? msg "up") [(update state :count inc) nil] ;; Handle custom message @@ -203,10 +203,10 @@ The view function receives state and returns a string to display. | `:all` | All mouse events including motion | ```clojure -(charm/run {:init init - :update update-fn - :view view - :mouse :normal}) +(program/run {:init init + :update update-fn + :view view + :mouse :normal}) ``` ## Focus Reporting @@ -214,31 +214,38 @@ The view function receives state and returns a string to display. When enabled, focus events are sent when the terminal gains/loses focus. ```clojure -(charm/run {:init init - :update update-fn - :view view - :focus-reporting true}) +(program/run {:init init + :update update-fn + :view view + :focus-reporting true}) ;; In update function (defn update-fn [state msg] (cond - (charm/focus? msg) [(assoc state :focused true) nil] - (charm/blur? msg) [(assoc state :focused false) nil] - :else [state nil])) + (msg/focus? msg) + [(assoc state :focused true) nil] + + (msg/blur? msg) + [(assoc state :focused false) nil] + + :else + [state nil])) ``` ## Complete Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program])) (defn fetch-data-cmd [] - (charm/cmd (fn [] - ;; Simulate async data fetch - (Thread/sleep 1000) - {:type :data-loaded - :data ["Item 1" "Item 2" "Item 3"]}))) + (program/cmd (fn [] + ;; Simulate async data fetch + (Thread/sleep 1000) + {:type :data-loaded + :data ["Item 1" "Item 2" "Item 3"]}))) (defn init [] [{:loading true :data nil} @@ -246,8 +253,8 @@ When enabled, focus events are sent when the terminal gains/loses focus. (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] (= :data-loaded (:type msg)) [(assoc state :loading false :data (:data msg)) nil] @@ -262,8 +269,8 @@ When enabled, focus events are sent when the terminal gains/loses focus. "\n\nPress q to quit"))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) ``` diff --git a/docs/components/help.md b/docs/components/help.md index 633acf8..3940ed2 100644 --- a/docs/components/help.md +++ b/docs/components/help.md @@ -5,21 +5,21 @@ Keyboard shortcut display component with short and expanded modes. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.help :as help]) (def bindings [{:key "j/k" :desc "up/down"} {:key "q" :desc "quit"}]) -(def my-help (charm/help bindings)) +(def my-help (help/help bindings)) ;; In view function -(charm/help-view my-help) ; => "j/k up/down • q quit" +(help/short-help-view my-help) ; => "j/k up/down • q quit" ``` ## Creation Options ```clojure -(charm/help bindings & options) +(help/help bindings & options) ``` | Option | Type | Default | Description | @@ -38,22 +38,22 @@ Keyboard shortcut display component with short and expanded modes. ```clojure ;; Maps with :key and :desc -(charm/help [{:key "j/k" :desc "up/down"} - {:key "q" :desc "quit"}]) +(help/help [{:key "j/k" :desc "up/down"} + {:key "q" :desc "quit"}]) ;; Pairs (vectors) -(charm/help [["j/k" "up/down"] - ["q" "quit"]]) +(help/help [["j/k" "up/down"] + ["q" "quit"]]) ;; Using from-pairs helper -(charm/help (charm/help-from-pairs - "j/k" "up/down" - "q" "quit")) +(help/help (help/from-pairs + "j/k" "up/down" + "q" "quit")) ;; Or with vectors -(charm/help (charm/help-from-pairs - ["j/k" "up/down"] - ["q" "quit"])) +(help/help (help/from-pairs + ["j/k" "up/down"] + ["q" "quit"])) ``` ## Display Modes @@ -63,14 +63,14 @@ Keyboard shortcut display component with short and expanded modes. Single line with separator: ```clojure -(charm/help bindings) +(help/help bindings) ;; => "j/k up/down • q quit • ? help" ``` With width constraint: ```clojure -(charm/help bindings :width 30) +(help/help bindings :width 30) ;; => "j/k up/down • q quit • …" ``` @@ -79,7 +79,7 @@ With width constraint: Multi-line with aligned columns: ```clojure -(charm/help bindings :show-all true) +(help/help bindings :show-all true) ;; j/k up/down ;; q quit ;; ? help @@ -87,76 +87,81 @@ Multi-line with aligned columns: ## Functions -### help-view +### short-help-view / full-help-view ```clojure -(charm/help-view help) ; => "j/k up/down • q quit" +(help/short-help-view h) ; => "j/k up/down • q quit" +(help/full-help-view h) ; => multi-line aligned view ``` Render help as a string. -### help-toggle-show-all +### toggle-show-all ```clojure -(charm/help-toggle-show-all help) +(help/toggle-show-all h) ``` Toggle between short and full display modes. -### help-set-show-all +### set-show-all ```clojure -(charm/help-set-show-all help true) ; Show full -(charm/help-set-show-all help false) ; Show short +(help/set-show-all h true) ; Show full +(help/set-show-all h false) ; Show short ``` -### help-set-bindings +### set-bindings ```clojure -(charm/help-set-bindings help new-bindings) +(help/set-bindings h new-bindings) ``` -### help-add-binding +### add-binding ```clojure -(charm/help-add-binding help "n" "new item") +(help/add-binding h "n" "new item") ``` -### help-from-pairs +### from-pairs ```clojure ;; Interleaved arguments -(charm/help-from-pairs "j" "down" "k" "up" "q" "quit") +(help/from-pairs "j" "down" "k" "up" "q" "quit") ;; Vector pairs -(charm/help-from-pairs ["j" "down"] ["k" "up"] ["q" "quit"]) +(help/from-pairs ["j" "down"] ["k" "up"] ["q" "quit"]) ``` ## Full Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.help :as help] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style])) (def bindings - (charm/help-from-pairs + (help/from-pairs "j/k" "navigate" "Enter" "select" "?" "toggle help" "q" "quit")) (defn init [] - [{:help (charm/help bindings :width 60) + [{:help (help/help bindings :width 60) :items ["Item 1" "Item 2" "Item 3"]} nil]) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] - (charm/key-match? msg "?") - [(update state :help charm/help-toggle-show-all) nil] + (msg/key-match? msg "?") + [(update state :help help/toggle-show-all) nil] :else [state nil])) @@ -169,19 +174,19 @@ Toggle between short and full display modes. (if show-full? (str "Keyboard Shortcuts\n" "──────────────────\n" - (charm/help-view (:help state))) - (charm/help-view (:help state)))))) + (help/full-help-view (:help state))) + (help/short-help-view (:help state)))))) -(charm/run {:init init :update update-fn :view view}) +(program/run {:init init :update update-fn :view view}) ``` ## Styled Help ```clojure -(charm/help bindings - :key-style (charm/style :fg charm/cyan :bold true) - :desc-style (charm/style :fg 250) - :separator-style (charm/style :fg 240)) +(help/help bindings + :key-style (style/style :fg style/cyan :bold true) + :desc-style (style/style :fg 250) + :separator-style (style/style :fg 240)) ``` ## Dynamic Bindings @@ -191,14 +196,14 @@ Update bindings based on application state: ```clojure (defn get-bindings [state] (if (:editing state) - (charm/help-from-pairs + (help/from-pairs "Esc" "cancel" "Enter" "save") - (charm/help-from-pairs + (help/from-pairs "e" "edit" "d" "delete" "q" "quit"))) (defn view [state] - (charm/help-view (charm/help (get-bindings state)))) + (help/short-help-view (help/help (get-bindings state)))) ``` diff --git a/docs/components/list.md b/docs/components/list.md index 19986a8..db2b129 100644 --- a/docs/components/list.md +++ b/docs/components/list.md @@ -5,17 +5,17 @@ Scrollable list component with keyboard navigation and item selection. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.list :as item-list]) -(def my-list (charm/item-list ["Apple" "Banana" "Cherry"])) +(def my-list (item-list/item-list ["Apple" "Banana" "Cherry"])) ;; In update function -(let [[list cmd] (charm/list-update my-list msg)] +(let [[list cmd] (item-list/list-update my-list msg)] ;; Handle navigation ) ;; In view function -(charm/list-view my-list) +(item-list/list-view my-list) ;; > Apple ;; Banana ;; Cherry @@ -24,7 +24,7 @@ Scrollable list component with keyboard navigation and item selection. ## Creation Options ```clojure -(charm/item-list items & options) +(item-list/item-list items & options) ``` | Option | Type | Default | Description | @@ -50,16 +50,16 @@ Items can be strings or maps: ```clojure ;; Simple strings -(charm/item-list ["Apple" "Banana" "Cherry"]) +(item-list/item-list ["Apple" "Banana" "Cherry"]) ;; Maps with title and description -(charm/item-list [{:title "Apple" :description "A red fruit"} - {:title "Banana" :description "A yellow fruit"}] - :show-descriptions true) +(item-list/item-list [{:title "Apple" :description "A red fruit"} + {:title "Banana" :description "A yellow fruit"}] + :show-descriptions true) ;; Maps with custom keys -(charm/item-list [{:name "Alice" :role "Admin"} - {:name "Bob" :role "User"}]) +(item-list/item-list [{:name "Alice" :role "Admin"} + {:name "Bob" :role "User"}]) ;; Uses :name as title, :role as description ``` @@ -79,7 +79,7 @@ Items can be strings or maps: ### list-update ```clojure -(charm/list-update list msg) ; => [list cmd] +(item-list/list-update list msg) ; => [list cmd] ``` Handle navigation messages. @@ -87,57 +87,61 @@ Handle navigation messages. ### list-view ```clojure -(charm/list-view list) ; => "> Apple\n Banana\n Cherry" +(item-list/list-view list) ; => "> Apple\n Banana\n Cherry" ``` Render the list as a string. -### list-selected-item +### selected-item ```clojure -(charm/list-selected-item list) ; => "Apple" or {:title "Apple" ...} +(item-list/selected-item list) ; => "Apple" or {:title "Apple" ...} ``` Get the currently selected item. -### list-selected-index +### selected-index ```clojure -(charm/list-selected-index list) ; => 0 +(item-list/selected-index list) ; => 0 ``` Get the index of the selected item. -### list-set-items +### set-items ```clojure -(charm/list-set-items list new-items) +(item-list/set-items list new-items) ``` Replace all items, adjusting cursor if needed. -### list-select +### select ```clojure -(charm/list-select list 2) ; Select item at index 2 +(item-list/select list 2) ; Select item at index 2 ``` ### Navigation Functions ```clojure -(charm/list-cursor-up list) -(charm/list-cursor-down list) -(charm/list-page-up list) -(charm/list-page-down list) -(charm/list-go-to-start list) -(charm/list-go-to-end list) +(item-list/cursor-up list) +(item-list/cursor-down list) +(item-list/page-up list) +(item-list/page-down list) +(item-list/go-to-start list) +(item-list/go-to-end list) ``` ## Full Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.list :as item-list] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style])) (def items [{:title "New File" :description "Create a new file"} @@ -146,34 +150,34 @@ Replace all items, adjusting cursor if needed. {:title "Exit" :description "Quit the application"}]) (defn init [] - [{:menu (charm/item-list items - :height 10 - :show-descriptions true - :cursor-style (charm/style :fg charm/yellow :bold true))} + [{:menu (item-list/item-list items + :height 10 + :show-descriptions true + :cursor-style (style/style :fg style/yellow :bold true))} nil]) (defn update-fn [state msg] (cond - (charm/key-match? msg "enter") - (let [selected (charm/list-selected-item (:menu state))] + (msg/key-match? msg "enter") + (let [selected (item-list/selected-item (:menu state))] (println "Selected:" (:title selected)) (if (= "Exit" (:title selected)) - [state charm/quit-cmd] + [state program/quit-cmd] [state nil])) - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] :else - (let [[menu cmd] (charm/list-update (:menu state) msg)] + (let [[menu cmd] (item-list/list-update (:menu state) msg)] [(assoc state :menu menu) cmd]))) (defn view [state] (str "Select an option:\n\n" - (charm/list-view (:menu state)) + (item-list/list-view (:menu state)) "\n\nEnter to select, q to quit")) -(charm/run {:init init :update update-fn :view view}) +(program/run {:init init :update update-fn :view view}) ``` ## Scrolling @@ -181,16 +185,16 @@ Replace all items, adjusting cursor if needed. When `:height` is set, the list scrolls to keep the cursor visible: ```clojure -(charm/item-list (range 100) - :height 10) ; Shows 10 items, scrolls with cursor +(item-list/item-list (range 100) + :height 10) ; Shows 10 items, scrolls with cursor ``` ## Filtering ```clojure ;; Filter items by predicate -(charm/list-filter-items list #(str/includes? (:title %) "search")) +(item-list/filter-items list #(str/includes? (:title %) "search")) ;; Find and select first match -(charm/list-select-first-match list #(= "target" (:title %))) +(item-list/select-first-match list #(= "target" (:title %))) ``` diff --git a/docs/components/overview.md b/docs/components/overview.md index 69bec54..c2d2978 100644 --- a/docs/components/overview.md +++ b/docs/components/overview.md @@ -34,22 +34,23 @@ Components integrate with charm's program loop which handles: - **Rendering**: The view function renders state to strings ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.spinner :as spinner] + '[charm.program :as program]) (defn init [] - [{:spinner (charm/spinner :dots)} + [{:spinner (spinner/spinner :dots)} nil]) (defn update-fn [state msg] - (let [[new-spinner cmd] (charm/spinner-update (:spinner state) msg)] + (let [[new-spinner cmd] (spinner/spinner-update (:spinner state) msg)] [(assoc state :spinner new-spinner) cmd])) (defn view [state] - (str "Loading " (charm/spinner-view (:spinner state)))) + (str "Loading " (spinner/spinner-view (:spinner state)))) -(charm/run {:init init - :update update-fn - :view view}) +(program/run {:init init + :update update-fn + :view view}) ``` ## Available Components @@ -74,7 +75,7 @@ Components like `spinner` and `timer` use asynchronous tick commands to animate. ```clojure ;; Spinner sends tick messages to itself -(let [[spinner cmd] (charm/spinner-init my-spinner)] +(let [[s cmd] (spinner/spinner-init my-spinner)] ;; cmd will trigger a :spinner-tick message after the interval ) ``` @@ -85,27 +86,27 @@ Multiple components can be combined in a single application: ```clojure (defn init [] - [{:input (charm/text-input :prompt "Search: ") - :list (charm/item-list items) - :help (charm/help bindings)} + [{:input (text-input/text-input :prompt "Search: ") + :list (item-list/item-list items) + :help (help/help bindings)} nil]) (defn update-fn [state msg] (cond ;; Route to text-input when focused (:focused (:input state)) - (let [[input cmd] (charm/text-input-update (:input state) msg)] + (let [[input cmd] (text-input/text-input-update (:input state) msg)] [(assoc state :input input) cmd]) ;; Otherwise route to list :else - (let [[list cmd] (charm/list-update (:list state) msg)] + (let [[list cmd] (item-list/list-update (:list state) msg)] [(assoc state :list list) cmd]))) (defn view [state] - (str (charm/text-input-view (:input state)) "\n" - (charm/list-view (:list state)) "\n" - (charm/short-help-view (:help state)))) + (str (text-input/text-input-view (:input state)) "\n" + (item-list/list-view (:list state)) "\n" + (help/short-help-view (:help state)))) ``` ## Styling Components @@ -113,17 +114,17 @@ Multiple components can be combined in a single application: Most components accept style options: ```clojure -(charm/spinner :dots - :style (charm/style :fg charm/cyan)) +(spinner/spinner :dots + :style (style/style :fg style/cyan)) -(charm/text-input :prompt "Name: " - :prompt-style (charm/style :fg charm/green :bold true) - :text-style (charm/style :fg charm/white) - :cursor-style (charm/style :reverse true)) +(text-input/text-input :prompt "Name: " + :prompt-style (style/style :fg style/green :bold true) + :text-style (style/style :fg style/white) + :cursor-style (style/style :reverse true)) -(charm/item-list items - :cursor-style (charm/style :fg charm/yellow :bold true) - :item-style (charm/style :fg 240)) +(item-list/item-list items + :cursor-style (style/style :fg style/yellow :bold true) + :item-style (style/style :fg 240)) ``` See [styling](../api/styling.md) for full styling documentation. diff --git a/docs/components/paginator.md b/docs/components/paginator.md index 3c45655..2ea023a 100644 --- a/docs/components/paginator.md +++ b/docs/components/paginator.md @@ -5,23 +5,23 @@ Page navigation indicators with dots or numeric display. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.paginator :as paginator]) -(def pager (charm/paginator :total-pages 5)) +(def pager (paginator/paginator :total-pages 5)) ;; In update function -(let [[pager cmd] (charm/paginator-update pager msg)] +(let [[pager cmd] (paginator/paginator-update pager msg)] ;; Handle navigation ) ;; In view function -(charm/paginator-view pager) ; => "•○○○○" +(paginator/paginator-view pager) ; => "•○○○○" ``` ## Creation Options ```clojure -(charm/paginator & options) +(paginator/paginator & options) ``` | Option | Type | Default | Description | @@ -42,7 +42,7 @@ Page navigation indicators with dots or numeric display. ### Dots (default) ```clojure -(charm/paginator :total-pages 5 :type :dots) +(paginator/paginator :total-pages 5 :type :dots) ;; Page 0: •○○○○ ;; Page 2: ○○•○○ ``` @@ -50,14 +50,14 @@ Page navigation indicators with dots or numeric display. ### Arabic ```clojure -(charm/paginator :total-pages 5 :type :arabic) +(paginator/paginator :total-pages 5 :type :arabic) ;; Page 0: 1/5 ;; Page 2: 3/5 ;; Custom format -(charm/paginator :total-pages 5 - :type :arabic - :arabic-format "Page %d of %d") +(paginator/paginator :total-pages 5 + :type :arabic + :arabic-format "Page %d of %d") ;; => "Page 1 of 5" ``` @@ -73,7 +73,7 @@ Page navigation indicators with dots or numeric display. ### paginator-update ```clojure -(charm/paginator-update pager msg) ; => [pager cmd] +(paginator/paginator-update pager msg) ; => [pager cmd] ``` Handle navigation messages. @@ -81,7 +81,7 @@ Handle navigation messages. ### paginator-view ```clojure -(charm/paginator-view pager) ; => "•○○○○" +(paginator/paginator-view pager) ; => "•○○○○" ``` Render the paginator as a string. @@ -89,79 +89,54 @@ Render the paginator as a string. ### Navigation ```clojure -(charm/paginator-page pager) ; Get current page (0-indexed) -(charm/paginator-total-pages pager) ; Get total pages -(charm/paginator-set-page pager 2) ; Set current page - -(charm/paginator-next-page pager) ; Go to next page -(charm/paginator-prev-page pager) ; Go to previous page -(charm/paginator-go-to-first pager) ; Go to first page -(charm/paginator-go-to-last pager) ; Go to last page -``` - -### Bounds Checking +(paginator/page pager) ; Get current page (0-indexed) +(paginator/total-pages pager) ; Get total pages +(paginator/set-page pager 2) ; Set current page -```clojure -(charm/paginator-on-first-page? pager) ; => true/false -(charm/paginator-on-last-page? pager) ; => true/false -``` - -### Slice Bounds - -For paginating data: - -```clojure -(def pager (charm/paginator :total-pages 10 :per-page 5)) - -;; Get [start end] for slicing -(charm/paginator-slice-bounds pager 47) ; => [0 5] for page 0 - -;; Calculate from item count -(charm/paginator-set-total-items pager 47) ; Sets total-pages to 10 +(paginator/next-page pager) ; Go to next page +(paginator/prev-page pager) ; Go to previous page ``` ## Full Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.paginator :as paginator] + [charm.message :as msg] + [charm.program :as program])) (def all-items (vec (range 1 51))) ; 50 items (def per-page 10) (defn init [] - [{:pager (charm/paginator :per-page per-page - :total-pages (int (Math/ceil (/ (count all-items) per-page)))) + [{:pager (paginator/paginator :per-page per-page + :total-pages (int (Math/ceil (/ (count all-items) per-page)))) :items all-items} nil]) -(defn get-page-items [state] - (let [[start end] (charm/paginator-slice-bounds (:pager state) (count all-items))] - (subvec all-items start end))) - (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] :else - (let [[pager cmd] (charm/paginator-update (:pager state) msg)] + (let [[pager cmd] (paginator/paginator-update (:pager state) msg)] [(assoc state :pager pager) cmd]))) (defn view [state] - (let [items (get-page-items state)] - (str "Items: " (clojure.string/join ", " items) "\n\n" - (charm/paginator-view (:pager state)) "\n\n" - "Left/Right to navigate, q to quit"))) + (str "Items: " (clojure.string/join ", " (take per-page all-items)) "\n\n" + (paginator/paginator-view (:pager state)) "\n\n" + "Left/Right to navigate, q to quit")) -(charm/run {:init init :update update-fn :view view}) +(program/run {:init init :update update-fn :view view}) ``` ## Custom Dots ```clojure -(charm/paginator :total-pages 5 - :active-dot "●" - :inactive-dot "○" - :active-style (charm/style :fg charm/cyan)) +(paginator/paginator :total-pages 5 + :active-dot "●" + :inactive-dot "○" + :active-style (style/style :fg style/cyan)) ``` diff --git a/docs/components/progress.md b/docs/components/progress.md index 9e7ece9..f203eb5 100644 --- a/docs/components/progress.md +++ b/docs/components/progress.md @@ -5,21 +5,21 @@ Progress indicator with 7 built-in bar styles. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.progress :as progress]) -(def my-bar (charm/progress-bar :width 40)) +(def my-bar (progress/progress-bar :width 40)) ;; Update progress (0.0 to 1.0) -(def updated-bar (charm/progress-set-progress my-bar 0.5)) +(def updated-bar (progress/set-progress my-bar 0.5)) ;; In view function -(charm/progress-view updated-bar) ; => "████████████████████░░░░░░░░░░░░░░░░░░░░" +(progress/progress-view updated-bar) ; => "████████████████████░░░░░░░░░░░░░░░░░░░░" ``` ## Creation Options ```clojure -(charm/progress-bar & options) +(progress/progress-bar & options) ``` | Option | Type | Default | Description | @@ -49,14 +49,14 @@ Progress indicator with 7 built-in bar styles. ## Custom Bar Style ```clojure -(charm/progress-bar :bar-style {:full "▰" - :empty "▱"}) +(progress/progress-bar :bar-style {:full "▰" + :empty "▱"}) ;; With brackets -(charm/progress-bar :bar-style {:full "=" - :empty " " - :left "[" - :right "]"}) +(progress/progress-bar :bar-style {:full "=" + :empty " " + :left "[" + :right "]"}) ``` ## Functions @@ -64,59 +64,62 @@ Progress indicator with 7 built-in bar styles. ### progress-view ```clojure -(charm/progress-view bar) ; => "████████░░░░░░░░" +(progress/progress-view bar) ; => "████████░░░░░░░░" ``` Render the progress bar as a string. -### progress-set-progress +### set-progress ```clojure -(charm/progress-set-progress bar 0.75) ; Set to 75% +(progress/set-progress bar 0.75) ; Set to 75% ``` Set progress as a float from 0.0 to 1.0. -### progress-set-progress-int +### set-progress-int ```clojure -(charm/progress-set-progress-int bar 75) ; Set to 75% +(progress/set-progress-int bar 75) ; Set to 75% ``` Set progress as an integer from 0 to 100. -### progress-increment / progress-decrement +### increment / decrement ```clojure -(charm/progress-increment bar) ; +1% -(charm/progress-increment bar 0.05) ; +5% -(charm/progress-decrement bar 0.1) ; -10% +(progress/increment bar) ; +1% +(progress/increment bar 0.05) ; +5% +(progress/decrement bar 0.1) ; -10% ``` -### progress-percent +### percent ```clojure -(charm/progress-percent bar) ; => 0.75 (float) -(charm/progress-percent-int bar) ; => 75 (int) +(progress/percent bar) ; => 0.75 (float) +(progress/percent-int bar) ; => 75 (int) ``` -### progress-complete? +### complete? ```clojure -(charm/progress-complete? bar) ; => true if >= 100% +(progress/complete? bar) ; => true if >= 100% ``` -### progress-reset +### reset ```clojure -(charm/progress-reset bar) ; Reset to 0% +(progress/reset bar) ; Reset to 0% ``` ## Full Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.progress :as progress] + [charm.message :as msg] + [charm.program :as program])) (defn tick-cmd [] {:type :cmd @@ -125,24 +128,24 @@ Set progress as an integer from 0 to 100. {:type :tick})}) (defn init [] - [{:bar (charm/progress-bar :width 50 - :bar-style :default - :show-percent true) + [{:bar (progress/progress-bar :width 50 + :bar-style :default + :show-percent true) :running false} nil]) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] - (charm/key-match? msg " ") + (msg/key-match? msg " ") [(assoc state :running (not (:running state))) (when-not (:running state) (tick-cmd))] (= :tick (:type msg)) - (let [bar (charm/progress-increment (:bar state) 0.02)] - (if (charm/progress-complete? bar) + (let [bar (progress/increment (:bar state) 0.02)] + (if (progress/complete? bar) [(assoc state :bar bar :running false) nil] [(assoc state :bar bar) (when (:running state) (tick-cmd))])) @@ -152,32 +155,32 @@ Set progress as an integer from 0 to 100. (defn view [state] (str "Download Progress\n\n" - (charm/progress-view (:bar state)) "\n\n" - (if (charm/progress-complete? (:bar state)) + (progress/progress-view (:bar state)) "\n\n" + (if (progress/complete? (:bar state)) "Complete!" (if (:running state) "Downloading... (Space to pause)" "Press Space to start, Q to quit")))) -(charm/run {:init init :update update-fn :view view}) +(program/run {:init init :update update-fn :view view}) ``` ## Styled Progress Bar ```clojure -(charm/progress-bar :width 40 - :bar-style :default - :show-percent true - :full-style (charm/style :fg charm/green) - :empty-style (charm/style :fg 240) - :percent-style (charm/style :fg charm/yellow :bold true)) +(progress/progress-bar :width 40 + :bar-style :default + :show-percent true + :full-style (style/style :fg style/green) + :empty-style (style/style :fg 240) + :percent-style (style/style :fg style/yellow :bold true)) ``` ## Multiple Progress Bars ```clojure (defn view [state] - (str "File 1: " (charm/progress-view (:bar1 state)) "\n" - "File 2: " (charm/progress-view (:bar2 state)) "\n" - "File 3: " (charm/progress-view (:bar3 state)))) + (str "File 1: " (progress/progress-view (:bar1 state)) "\n" + "File 2: " (progress/progress-view (:bar2 state)) "\n" + "File 3: " (progress/progress-view (:bar3 state)))) ``` diff --git a/docs/components/spinner.md b/docs/components/spinner.md index b5ad1dc..5924738 100644 --- a/docs/components/spinner.md +++ b/docs/components/spinner.md @@ -5,28 +5,28 @@ Animated loading indicators with 15 built-in animation styles. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.spinner :as spinner]) -(def my-spinner (charm/spinner :dots)) +(def my-spinner (spinner/spinner :dots)) ;; Initialize to start animation -(let [[spinner cmd] (charm/spinner-init my-spinner)] - ;; spinner is ready, cmd starts the tick loop +(let [[s cmd] (spinner/spinner-init my-spinner)] + ;; s is ready, cmd starts the tick loop ) ;; In update function -(let [[spinner cmd] (charm/spinner-update spinner msg)] +(let [[s cmd] (spinner/spinner-update s msg)] ;; Handle spinner tick messages ) ;; In view function -(charm/spinner-view spinner) ; => "⠋" (animates) +(spinner/spinner-view s) ; => "⠋" (animates) ``` ## Creation Options ```clojure -(charm/spinner type & options) +(spinner/spinner type & options) ``` | Option | Type | Default | Description | @@ -58,8 +58,8 @@ Animated loading indicators with 15 built-in animation styles. ## Custom Spinner ```clojure -(charm/spinner {:frames ["◐" "◓" "◑" "◒"] - :interval 150}) +(spinner/spinner {:frames ["◐" "◓" "◑" "◒"] + :interval 150}) ``` ## Functions @@ -67,7 +67,7 @@ Animated loading indicators with 15 built-in animation styles. ### spinner-init ```clojure -(charm/spinner-init spinner) ; => [spinner cmd] +(spinner/spinner-init s) ; => [spinner cmd] ``` Initialize the spinner and start the tick loop. Returns `[spinner cmd]` where `cmd` triggers the first tick. @@ -75,7 +75,7 @@ Initialize the spinner and start the tick loop. Returns `[spinner cmd]` where `c ### spinner-update ```clojure -(charm/spinner-update spinner msg) ; => [spinner cmd] +(spinner/spinner-update s msg) ; => [spinner cmd] ``` Handle tick messages. Returns `[spinner cmd]` to continue animation, or `[spinner nil]` if message not handled. @@ -83,7 +83,7 @@ Handle tick messages. Returns `[spinner cmd]` to continue animation, or `[spinne ### spinner-view ```clojure -(charm/spinner-view spinner) ; => "⠋" +(spinner/spinner-view s) ; => "⠋" ``` Render current frame as a string. @@ -91,7 +91,7 @@ Render current frame as a string. ### spinning? ```clojure -(charm/spinning? spinner msg) ; => boolean +(spinner/spinning? s msg) ; => boolean ``` Check if a message is a tick for this spinner. @@ -100,35 +100,38 @@ Check if a message is a tick for this spinner. ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.spinner :as spinner] + [charm.message :as msg] + [charm.program :as program])) (defn init [] - (let [[spinner cmd] (charm/spinner-init (charm/spinner :dots))] - [{:spinner spinner + (let [[s cmd] (spinner/spinner-init (spinner/spinner :dots))] + [{:spinner s :loading true} cmd])) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] :else - (let [[spinner cmd] (charm/spinner-update (:spinner state) msg)] - [(assoc state :spinner spinner) cmd]))) + (let [[s cmd] (spinner/spinner-update (:spinner state) msg)] + [(assoc state :spinner s) cmd]))) (defn view [state] - (str (charm/spinner-view (:spinner state)) + (str (spinner/spinner-view (:spinner state)) " Loading data...")) -(charm/run {:init init - :update update-fn - :view view}) +(program/run {:init init + :update update-fn + :view view}) ``` ## Styled Spinner ```clojure -(charm/spinner :dots - :style (charm/style :fg charm/cyan :bold true)) +(spinner/spinner :dots + :style (style/style :fg style/cyan :bold true)) ``` diff --git a/docs/components/text-input.md b/docs/components/text-input.md index 09b14d7..7907493 100644 --- a/docs/components/text-input.md +++ b/docs/components/text-input.md @@ -5,24 +5,24 @@ Text entry component with cursor movement, editing, and multiple echo modes. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.text-input :as text-input]) -(def my-input (charm/text-input :prompt "Name: " - :placeholder "Enter your name")) +(def my-input (text-input/text-input :prompt "Name: " + :placeholder "Enter your name")) ;; In update function -(let [[input cmd] (charm/text-input-update my-input msg)] +(let [[input cmd] (text-input/text-input-update my-input msg)] ;; Handle key presses ) ;; In view function -(charm/text-input-view my-input) ; => "Name: |Enter your name" +(text-input/text-input-view my-input) ; => "Name: |Enter your name" ``` ## Creation Options ```clojure -(charm/text-input & options) +(text-input/text-input & options) ``` | Option | Type | Default | Description | @@ -45,14 +45,14 @@ Text entry component with cursor movement, editing, and multiple echo modes. ```clojure ;; Normal - shows typed text -(charm/text-input :echo-mode charm/echo-normal) +(text-input/text-input :echo-mode text-input/echo-normal) ;; Password - shows asterisks -(charm/text-input :echo-mode charm/echo-password) -(charm/text-input :echo-mode charm/echo-password :echo-char \●) +(text-input/text-input :echo-mode text-input/echo-password) +(text-input/text-input :echo-mode text-input/echo-password :echo-char \●) ;; None - hides all input -(charm/text-input :echo-mode charm/echo-none) +(text-input/text-input :echo-mode text-input/echo-none) ``` ## Key Bindings @@ -77,7 +77,7 @@ Text entry component with cursor movement, editing, and multiple echo modes. ### text-input-update ```clojure -(charm/text-input-update input msg) ; => [input cmd] +(text-input/text-input-update input msg) ; => [input cmd] ``` Handle key messages. Only processes input when focused. @@ -85,36 +85,36 @@ Handle key messages. Only processes input when focused. ### text-input-view ```clojure -(charm/text-input-view input) ; => "Name: John|" +(text-input/text-input-view input) ; => "Name: John|" ``` Render the input with prompt and cursor. -### text-input-value +### value ```clojure -(charm/text-input-value input) ; => "John" +(text-input/value input) ; => "John" ``` Get current value as a string. -### text-input-focus / text-input-blur +### focus / blur ```clojure -(charm/text-input-focus input) ; Focus the input -(charm/text-input-blur input) ; Unfocus the input +(text-input/focus input) ; Focus the input +(text-input/blur input) ; Unfocus the input ``` -### text-input-reset +### reset ```clojure -(charm/text-input-reset input) ; Clear the value +(text-input/reset input) ; Clear the value ``` -### text-input-set-value +### set-value ```clojure -(charm/text-input-set-value input "new value") +(text-input/set-value input "new value") ``` Set the value and move cursor to end. @@ -123,43 +123,46 @@ Set the value and move cursor to end. ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.text-input :as text-input] + [charm.message :as msg] + [charm.program :as program])) (defn init [] - [{:username (charm/text-input :prompt "Username: " :focused true) - :password (charm/text-input :prompt "Password: " - :echo-mode charm/echo-password - :focused false) + [{:username (text-input/text-input :prompt "Username: " :focused true) + :password (text-input/text-input :prompt "Password: " + :echo-mode text-input/echo-password + :focused false) :current :username} nil]) (defn update-fn [state msg] (cond - (charm/key-match? msg "tab") + (msg/key-match? msg "tab") (let [next-field (if (= :username (:current state)) :password :username)] [(-> state (update :username (if (= next-field :username) - charm/text-input-focus - charm/text-input-blur)) + text-input/focus + text-input/blur)) (update :password (if (= next-field :password) - charm/text-input-focus - charm/text-input-blur)) + text-input/focus + text-input/blur)) (assoc :current next-field)) nil]) - (charm/key-match? msg "enter") + (msg/key-match? msg "enter") ;; Submit form - [state charm/quit-cmd] + [state program/quit-cmd] :else (let [field (:current state) - [input cmd] (charm/text-input-update (get state field) msg)] + [input cmd] (text-input/text-input-update (get state field) msg)] [(assoc state field input) cmd]))) (defn view [state] - (str (charm/text-input-view (:username state)) "\n" - (charm/text-input-view (:password state)) "\n\n" + (str (text-input/text-input-view (:username state)) "\n" + (text-input/text-input-view (:password state)) "\n\n" "Tab to switch fields, Enter to submit")) -(charm/run {:init init :update update-fn :view view}) +(program/run {:init init :update update-fn :view view}) ``` diff --git a/docs/components/timer.md b/docs/components/timer.md index ca8e35b..98beea7 100644 --- a/docs/components/timer.md +++ b/docs/components/timer.md @@ -5,28 +5,28 @@ Countdown timer component with start/stop controls and formatted display. ## Quick Example ```clojure -(require '[charm.core :as charm]) +(require '[charm.components.timer :as timer]) -(def my-timer (charm/timer :timeout 60000)) ; 60 seconds +(def my-timer (timer/timer :timeout 60000)) ; 60 seconds ;; Initialize to start countdown -(let [[timer cmd] (charm/timer-init my-timer)] - ;; timer is ready, cmd starts the tick loop +(let [[t cmd] (timer/timer-init my-timer)] + ;; t is ready, cmd starts the tick loop ) ;; In update function -(let [[timer cmd] (charm/timer-update timer msg)] +(let [[t cmd] (timer/timer-update t msg)] ;; Handle timer ticks ) ;; In view function -(charm/timer-view timer) ; => "1:00" +(timer/timer-view t) ; => "1:00" ``` ## Creation Options ```clojure -(charm/timer & options) +(timer/timer & options) ``` | Option | Type | Default | Description | @@ -42,7 +42,7 @@ Countdown timer component with start/stop controls and formatted display. ### timer-init ```clojure -(charm/timer-init timer) ; => [timer cmd] +(timer/timer-init t) ; => [timer cmd] ``` Initialize the timer. Returns command to start ticking if `:running` is true. @@ -50,7 +50,7 @@ Initialize the timer. Returns command to start ticking if `:running` is true. ### timer-update ```clojure -(charm/timer-update timer msg) ; => [timer cmd] +(timer/timer-update t msg) ; => [timer cmd] ``` Handle tick messages. Decrements timeout and continues ticking until timeout reaches 0. @@ -58,7 +58,7 @@ Handle tick messages. Decrements timeout and continues ticking until timeout rea ### timer-view ```clojure -(charm/timer-view timer) ; => "1:00" +(timer/timer-view t) ; => "1:00" ``` Render the timer as formatted duration: @@ -70,85 +70,88 @@ Render the timer as formatted duration: ```clojure ;; Start the timer, returns [timer cmd] -(charm/timer-start timer) +(timer/start t) ;; Stop the timer, returns [timer nil] -(charm/timer-stop timer) +(timer/stop t) ;; Toggle running state, returns [timer cmd-or-nil] -(charm/timer-toggle timer) +(timer/toggle t) ;; Reset to a timeout value, returns [timer cmd] -(charm/timer-reset timer 60000) +(timer/reset t 60000) ``` ### Accessors ```clojure -(charm/timer-timeout timer) ; Get remaining ms -(charm/timer-interval timer) ; Get tick interval -(charm/timer-running? timer) ; Check if running -(charm/timer-timed-out? timer) ; Check if <= 0 +(timer/timeout t) ; Get remaining ms +(timer/interval t) ; Get tick interval +(timer/running? t) ; Check if running +(timer/timed-out? t) ; Check if <= 0 ``` ## Full Example ```clojure (ns my-app - (:require [charm.core :as charm])) + (:require + [charm.components.timer :as timer] + [charm.message :as msg] + [charm.program :as program])) (def initial-time 30000) ; 30 seconds (defn init [] - (let [[timer cmd] (charm/timer-init - (charm/timer :timeout initial-time - :interval 100 - :running false))] - [{:timer timer + (let [[t cmd] (timer/timer-init + (timer/timer :timeout initial-time + :interval 100 + :running false))] + [{:timer t :initial-time initial-time} cmd])) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; Space to toggle - (charm/key-match? msg " ") - (let [[timer cmd] (charm/timer-toggle (:timer state))] - [(assoc state :timer timer) cmd]) + (msg/key-match? msg " ") + (let [[t cmd] (timer/toggle (:timer state))] + [(assoc state :timer t) cmd]) ;; R to reset - (charm/key-match? msg "r") - (let [[timer cmd] (charm/timer-reset (:timer state) (:initial-time state))] - [(assoc state :timer timer) cmd]) + (msg/key-match? msg "r") + (let [[t cmd] (timer/reset (:timer state) (:initial-time state))] + [(assoc state :timer t) cmd]) ;; Handle timer ticks :else - (let [[timer cmd] (charm/timer-update (:timer state) msg)] - [(assoc state :timer timer) cmd]))) + (let [[t cmd] (timer/timer-update (:timer state) msg)] + [(assoc state :timer t) cmd]))) (defn view [state] - (let [timer (:timer state) + (let [t (:timer state) status (cond - (charm/timer-timed-out? timer) "Time's up!" - (charm/timer-running? timer) "Running" + (timer/timed-out? t) "Time's up!" + (timer/running? t) "Running" :else "Paused")] - (str "Timer: " (charm/timer-view timer) "\n" + (str "Timer: " (timer/timer-view t) "\n" "Status: " status "\n\n" "Space: start/stop R: reset Q: quit"))) -(charm/run {:init init :update update-fn :view view :alt-screen true}) +(program/run {:init init :update update-fn :view view :alt-screen true}) ``` ## Styled Timer ```clojure -(charm/timer :timeout 60000 - :style (charm/style :fg charm/cyan +(timer/timer :timeout 60000 + :style (style/style :fg style/cyan :bold true :padding [1 2] - :border charm/rounded-border)) + :border border/rounded)) ``` ## Timer Events @@ -169,5 +172,5 @@ The timer sends these message types: Check if a message is for a specific timer: ```clojure -(charm/for-timer? timer msg) ; => boolean +(timer/for-timer? t msg) ; => boolean ``` diff --git a/docs/examples/src/examples/cheatsheet.clj b/docs/examples/src/examples/cheatsheet.clj index 09f034f..324eed4 100644 --- a/docs/examples/src/examples/cheatsheet.clj +++ b/docs/examples/src/examples/cheatsheet.clj @@ -8,8 +8,10 @@ (:require [charm.ansi.width :as ansi-width] [charm.components.help :as help] + [charm.components.text-input :as text-input] [charm.components.viewport :as viewport] - [charm.core :as charm] + [charm.message :as msg] + [charm.program :as program] [charm.style.border :as border] [charm.style.core :as style] [charm.style.overlay :as overlay] @@ -30,34 +32,34 @@ ;; --------------------------------------------------------------------------- (def title-style - (charm/style :fg clj-green :bold true)) + (style/style :fg clj-green :bold true)) (def section-rule-style - (charm/style :fg 240)) + (style/style :fg 240)) (def subsection-title-style - (charm/style :fg clj-blue :bold true)) + (style/style :fg clj-blue :bold true)) (def group-label-style - (charm/style :fg 240)) + (style/style :fg 240)) (def fn-selected-style - (charm/style :fg :black :bg clj-light-green :bold true)) + (style/style :fg :black :bg clj-light-green :bold true)) (def overlay-title-style - (charm/style :fg clj-blue :bold true)) + (style/style :fg clj-blue :bold true)) (def overlay-arglists-style - (charm/style :fg clj-green)) + (style/style :fg clj-green)) (def overlay-section-rule-style - (charm/style :fg 240)) + (style/style :fg 240)) (def overlay-seealso-style - (charm/style :fg clj-blue)) + (style/style :fg clj-blue)) (def dim-style - (charm/style :fg 240)) + (style/style :fg 240)) ;; --------------------------------------------------------------------------- ;; Helpers @@ -89,7 +91,7 @@ prefix-width (ansi-width/string-width prefix) fill-width (max 0 (- width prefix-width)) fill (apply str (repeat fill-width "─"))] - (charm/render section-rule-style (str prefix fill)))) + (style/render section-rule-style (str prefix fill)))) (defn- overlay-section-rule "Render an overlay section divider ── Title ─────" @@ -98,7 +100,7 @@ prefix-width (count prefix) fill-width (max 0 (- width prefix-width)) fill (apply str (repeat fill-width "─"))] - (charm/render overlay-section-rule-style (str prefix fill)))) + (style/render overlay-section-rule-style (str prefix fill)))) ;; --------------------------------------------------------------------------- ;; State Initialization @@ -119,27 +121,27 @@ (defn init [] (let [state {:mode :browse - :filter-input (charm/text-input :prompt "" - :placeholder "filter..." - :focused false) + :filter-input (text-input/text-input :prompt "" + :placeholder "filter..." + :focused false) :filter-focused false :cursor {:row 0 :col 0} :scroll-offset 0 :overlay {:fn-sym nil - :viewport (charm/viewport "" :height 20) + :viewport (viewport/viewport "" :height 20) :see-alsos [] :see-also-idx 0} :help (make-help :browse false) :width 80 :height 24}] - [state (charm/cmd data/load-docs!)])) + [state (program/cmd data/load-docs!)])) ;; --------------------------------------------------------------------------- ;; Computed Values ;; --------------------------------------------------------------------------- (defn- current-query [state] - (charm/text-input-value (:filter-input state))) + (text-input/value (:filter-input state))) (defn- filtered-sections [state] (data/filter-sections data/sections (current-query state))) @@ -183,7 +185,7 @@ (when-let [arglists (:arglists doc-data)] (concat (for [args arglists] - (charm/render overlay-arglists-style + (style/render overlay-arglists-style (str " (" n " " args ")"))) [""])) ;; Docstring @@ -208,7 +210,7 @@ "" (str/join " " (map (fn [kw] - (charm/render overlay-seealso-style (name kw))) + (style/render overlay-seealso-style (name kw))) (take 12 see-alsos)))]) ;; Fallback (when (nil? doc-data) @@ -230,9 +232,9 @@ (-> state (assoc :mode :overlay) (assoc :overlay {:fn-sym fn-sym - :viewport (charm/viewport content - :height overlay-height - :width content-width) + :viewport (viewport/viewport content + :height overlay-height + :width content-width) :see-alsos see-alsos :see-also-idx 0}) (assoc :help (make-help :overlay false))))) @@ -297,18 +299,18 @@ (defn- focus-filter [state] (-> state (assoc :filter-focused true) - (update :filter-input charm/text-input-focus) + (update :filter-input text-input/focus) (assoc :help (make-help :browse true)))) (defn- blur-filter [state] (-> state (assoc :filter-focused false) - (update :filter-input charm/text-input-blur) + (update :filter-input text-input/blur) (assoc :help (make-help :browse false)))) (defn- clear-and-blur-filter [state] (-> state - (update :filter-input charm/text-input-reset) + (update :filter-input text-input/reset) (blur-filter) (assoc :cursor {:row 0 :col 0}) (assoc :scroll-offset 0))) @@ -337,7 +339,7 @@ (tap> {:update-state state :update-msg msg}) (cond ;; Window resize - (charm/window-size? msg) + (msg/window-size? msg) [(-> state (assoc :width (:width msg)) (assoc :height (:height msg)) @@ -345,8 +347,8 @@ nil] ;; Global quit - (charm/key-match? msg "ctrl+c") - [state charm/quit-cmd] + (msg/key-match? msg "ctrl+c") + [state program/quit-cmd] :else (case (:mode state) @@ -354,42 +356,42 @@ :browse (cond ;; Vertical navigation: up/down moves between groups - (or (charm/key-match? msg "down") (and (not (:filter-focused state)) (charm/key-match? msg "j"))) + (or (msg/key-match? msg "down") (and (not (:filter-focused state)) (msg/key-match? msg "j"))) [(adjust-scroll (move-row state 1)) nil] - (or (charm/key-match? msg "up") (and (not (:filter-focused state)) (charm/key-match? msg "k"))) + (or (msg/key-match? msg "up") (and (not (:filter-focused state)) (msg/key-match? msg "k"))) [(adjust-scroll (move-row state -1)) nil] ;; Horizontal navigation: left/right moves between fns within a group - (or (charm/key-match? msg "left") (and (not (:filter-focused state)) (charm/key-match? msg "h"))) + (or (msg/key-match? msg "left") (and (not (:filter-focused state)) (msg/key-match? msg "h"))) [(adjust-scroll (move-col state -1)) nil] - (or (charm/key-match? msg "right") (and (not (:filter-focused state)) (charm/key-match? msg "l"))) + (or (msg/key-match? msg "right") (and (not (:filter-focused state)) (msg/key-match? msg "l"))) [(adjust-scroll (move-col state 1)) nil] ;; Enter opens overlay - (charm/key-match? msg "enter") + (msg/key-match? msg "enter") (if-let [sym (cursor-sym state)] [(open-overlay state sym) nil] [state nil]) ;; Escape clears filter and blurs - (charm/key-match? msg "escape") + (msg/key-match? msg "escape") (if (:filter-focused state) [(clear-and-blur-filter state) nil] [state nil]) ;; / focuses filter (when blurred) - (and (not (:filter-focused state)) (charm/key-match? msg "/")) + (and (not (:filter-focused state)) (msg/key-match? msg "/")) [(focus-filter state) nil] ;; q quits (when blurred) - (and (not (:filter-focused state)) (charm/key-match? msg "q")) - [state charm/quit-cmd] + (and (not (:filter-focused state)) (msg/key-match? msg "q")) + [state program/quit-cmd] ;; When focused, pass typing to text-input (:filter-focused state) - (let [[new-input cmd] (charm/text-input-update (:filter-input state) msg) + (let [[new-input cmd] (text-input/text-input-update (:filter-input state) msg) state (assoc state :filter-input new-input) ;; Reset cursor when filter changes state (-> state (assoc :cursor {:row 0 :col 0}) (assoc :scroll-offset 0)) @@ -402,19 +404,19 @@ ;; ----- Overlay mode ----- :overlay (cond - (charm/key-match? msg "escape") + (msg/key-match? msg "escape") [(close-overlay state) nil] - (or (charm/key-match? msg "down") (charm/key-match? msg "j") - (charm/key-match? msg "up") (charm/key-match? msg "k") - (charm/key-match? msg "ctrl+d") (charm/key-match? msg "ctrl+u")) + (or (msg/key-match? msg "down") (msg/key-match? msg "j") + (msg/key-match? msg "up") (msg/key-match? msg "k") + (msg/key-match? msg "ctrl+d") (msg/key-match? msg "ctrl+u")) (let [[vp _] (viewport/viewport-update (get-in state [:overlay :viewport]) msg)] [(assoc-in state [:overlay :viewport] vp) nil]) - ; (charm/key-match? msg "n") + ; (msg/key-match? msg "n") ; [(next-see-also state) nil] ; - ; (charm/key-match? msg "p") + ; (msg/key-match? msg "p") ; [(prev-see-also state) nil] :else @@ -428,7 +430,7 @@ "Word-wrap a group's fns into lines. Returns {:lines [str ...] :cursor-line }." [{:keys [label fns]} label-width selected? col width line-offset] - (let [label-str (charm/render group-label-style (ansi-width/pad-right label label-width)) + (let [label-str (style/render group-label-style (ansi-width/pad-right label label-width)) prefix (str " " label-str " ") prefix-w (ansi-width/string-width prefix) indent (apply str (repeat prefix-w " "))] @@ -438,7 +440,7 @@ (if-let [[fi sym] (first remaining)] (let [sel? (and selected? (= fi col)) text (if sel? - (charm/render fn-selected-style (name sym)) + (style/render fn-selected-style (name sym)) (name sym)) tw (ansi-width/string-width text) start? (= w prefix-w) @@ -481,7 +483,7 @@ :gap (update acc :lines conj "") :rule (update acc :lines conj (section-rule (first args) width)) :heading (update acc :lines conj - (str " " (charm/render subsection-title-style (first args)))) + (str " " (style/render subsection-title-style (first args)))) :group (let [[group lw] args result (render-group-fns group lw (= (:gi acc) row) col width (count (:lines acc)))] @@ -492,15 +494,15 @@ {:lines [] :cursor-line 0 :gi 0} items)] (if (empty? lines) - {:text (charm/render dim-style " No matching functions") :cursor-line 0} + {:text (style/render dim-style " No matching functions") :cursor-line 0} {:text (str/join "\n" lines) :cursor-line cursor-line}))) (def filter-field-width 30) (defn- render-filter-bar [state] - (let [input-view (charm/text-input-view (:filter-input state)) + (let [input-view (text-input/text-input-view (:filter-input state)) field-content (str "🔍 " input-view)] - (charm/render (charm/style :bg clj-light-blue + (style/render (style/style :bg clj-light-blue :width filter-field-width :border border/inner-half-block :border-fg clj-light-blue @@ -508,7 +510,7 @@ field-content))) (defn- render-title-bar [state] - (let [title (charm/render title-style "Clojure Cheatsheet") + (let [title (style/render title-style "Clojure Cheatsheet") filter-bar (render-filter-bar state) filter-lines (str/split-lines filter-bar) ;; Filter field width including padding @@ -546,7 +548,7 @@ (let [{:keys [fn-sym viewport]} (:overlay state) title (str " " (namespace fn-sym) "/" (name fn-sym) " ") content (viewport/viewport-view viewport) - bordered (charm/render (charm/style :border border/rounded + bordered (style/render (style/style :border border/rounded :border-fg clj-blue :padding [0 1]) content) @@ -555,14 +557,14 @@ top-line (first bordered-lines) top-width (ansi-width/string-width top-line) ;; Build new top line with title embedded - title-styled (charm/render overlay-title-style title) + title-styled (style/render overlay-title-style title) corner-l "╭─" corner-r (let [title-area-width (ansi-width/string-width (str corner-l title)) remaining (max 0 (- top-width title-area-width 1))] (str (apply str (repeat remaining "─")) "╮")) - new-top (str (charm/render (charm/style :fg clj-blue) corner-l) + new-top (str (style/render (style/style :fg clj-blue) corner-l) title-styled - (charm/render (charm/style :fg clj-blue) corner-r)) + (style/render (style/style :fg clj-blue) corner-r)) final-lines (assoc (vec bordered-lines) 0 new-top)] (str/join "\n" final-lines))) @@ -605,7 +607,7 @@ content-str "\n\n" help-bar) ;; Wrap in thick border, filling the terminal width - base-view (charm/render (charm/style :border border/thick + base-view (style/render (style/style :border border/thick :border-fg clj-blue :width (- width 2)) inner-view)] @@ -630,14 +632,14 @@ ; {:quit! (fn [] ...) - stop the app ; :result (promise) - deref to get the final state}" ; [] - (def app (charm/run-async {:init init - :update update-fn - :view view - :alt-screen true})) + (def app (program/run-async {:init init + :update update-fn + :view view + :alt-screen true})) ((:quit! app))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/countdown.clj b/docs/examples/src/examples/countdown.clj index 48af138..a37550b 100644 --- a/docs/examples/src/examples/countdown.clj +++ b/docs/examples/src/examples/countdown.clj @@ -1,88 +1,93 @@ (ns examples.countdown "Countdown timer demonstrating timer component with start/stop controls." - (:require [charm.core :as charm])) + (:require + [charm.components.timer :as timer] + [charm.message :as msg] + [charm.program :as program] + [charm.style.border :as border] + [charm.style.core :as style])) (def initial-time (* 60 1000)) ; 60 seconds (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def timer-style - (charm/style :fg charm/cyan + (style/style :fg style/cyan :bold true :padding [1 3] - :border charm/rounded-border)) + :border border/rounded)) (def timer-stopped-style - (charm/style :fg charm/yellow + (style/style :fg style/yellow :bold true :padding [1 3] - :border charm/rounded-border)) + :border border/rounded)) (def timer-done-style - (charm/style :fg charm/red + (style/style :fg style/red :bold true :padding [1 3] - :border charm/rounded-border)) + :border border/rounded)) (def button-style - (charm/style :fg charm/green :bold true)) + (style/style :fg style/green :bold true)) (def button-inactive-style - (charm/style :fg 240)) + (style/style :fg 240)) (def hint-style - (charm/style :fg 240)) + (style/style :fg 240)) (defn init [] - (let [timer (charm/timer :timeout initial-time - :interval 100 - :running false) - [timer cmd] (charm/timer-init timer)] - [{:timer timer + (let [t (timer/timer :timeout initial-time + :interval 100 + :running false) + [t cmd] (timer/timer-init t)] + [{:timer t :initial-time initial-time} cmd])) (defn update-fn [state msg] (cond ;; Quit - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Space to toggle start/stop - (charm/key-match? msg " ") - (let [[new-timer cmd] (charm/timer-toggle (:timer state))] + (msg/key-match? msg " ") + (let [[new-timer cmd] (timer/toggle (:timer state))] [(assoc state :timer new-timer) cmd]) ;; R to reset - (charm/key-match? msg "r") - (let [[new-timer cmd] (charm/timer-start - (charm/timer :timeout (:initial-time state) + (msg/key-match? msg "r") + (let [[new-timer cmd] (timer/start + (timer/timer :timeout (:initial-time state) :interval 100 :running true))] [(assoc state :timer new-timer) cmd]) ;; Up/k to add 10 seconds - (or (charm/key-match? msg :up) - (charm/key-match? msg "k")) - (let [timer (:timer state) - new-timeout (+ (charm/timer-timeout timer) 10000) - new-timer (assoc timer :timeout new-timeout)] + (or (msg/key-match? msg :up) + (msg/key-match? msg "k")) + (let [t (:timer state) + new-timeout (+ (timer/timeout t) 10000) + new-timer (assoc t :timeout new-timeout)] [(assoc state :timer new-timer :initial-time new-timeout) nil]) ;; Down/j to subtract 10 seconds (min 10 seconds) - (or (charm/key-match? msg :down) - (charm/key-match? msg "j")) - (let [timer (:timer state) - new-timeout (max 10000 (- (charm/timer-timeout timer) 10000)) - new-timer (assoc timer :timeout new-timeout)] + (or (msg/key-match? msg :down) + (msg/key-match? msg "j")) + (let [t (:timer state) + new-timeout (max 10000 (- (timer/timeout t) 10000)) + new-timer (assoc t :timeout new-timeout)] [(assoc state :timer new-timer :initial-time new-timeout) nil]) ;; Timer tick :else - (let [[new-timer cmd] (charm/timer-update (:timer state) msg)] + (let [[new-timer cmd] (timer/timer-update (:timer state) msg)] [(assoc state :timer new-timer) cmd]))) (defn format-time @@ -94,34 +99,34 @@ (format "%02d:%04.1f" minutes seconds))) (defn view [state] - (let [timer (:timer state) - timeout (charm/timer-timeout timer) - running? (charm/timer-running? timer) - done? (charm/timer-timed-out? timer) + (let [t (:timer state) + timeout (timer/timeout t) + running? (timer/running? t) + done? (timer/timed-out? t) time-str (format-time timeout) - style (cond - done? timer-done-style - running? timer-style - :else timer-stopped-style) + s (cond + done? timer-done-style + running? timer-style + :else timer-stopped-style) status (cond done? "Time's up!" running? "Running" :else "Paused")] - (str (charm/render title-style "Countdown Timer") "\n\n" - (charm/render style time-str) "\n\n" - (charm/render (if running? button-style hint-style) status) "\n\n" - (charm/render hint-style "Controls:") "\n" - (charm/render (if done? button-inactive-style button-style) " [Space]") + (str (style/render title-style "Countdown Timer") "\n\n" + (style/render s time-str) "\n\n" + (style/render (if running? button-style hint-style) status) "\n\n" + (style/render hint-style "Controls:") "\n" + (style/render (if done? button-inactive-style button-style) " [Space]") " " (if running? "Pause" "Start") "\n" - (charm/render button-style " [R]") + (style/render button-style " [R]") " Reset\n" - (charm/render (if running? button-inactive-style button-style) " [j/k]") + (style/render (if running? button-inactive-style button-style) " [j/k]") " -/+ 10 seconds\n" - (charm/render button-style " [Q]") + (style/render button-style " [Q]") " Quit"))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/counter.clj b/docs/examples/src/examples/counter.clj index de91e1f..422073e 100644 --- a/docs/examples/src/examples/counter.clj +++ b/docs/examples/src/examples/counter.clj @@ -1,31 +1,35 @@ (ns examples.counter - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program] + [charm.style.border :as border] + [charm.style.core :as style])) (def state (atom 0)) (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def count-style - (charm/style :fg charm/cyan + (style/style :fg style/cyan :padding [0 1] - :border charm/rounded-border)) + :border border/rounded)) (defn update-fn [state msg] (cond ;; Quit on q or Ctrl+C - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c")) + [state program/quit-cmd] ;; Increment on k or up arrow - (or (charm/key-match? msg "k") - (charm/key-match? msg :up)) + (or (msg/key-match? msg "k") + (msg/key-match? msg :up)) [(update state :count inc) nil] ;; Decrement on j or down arrow - (or (charm/key-match? msg "j") - (charm/key-match? msg :down)) + (or (msg/key-match? msg "j") + (msg/key-match? msg :down)) [(update state :count dec) nil] ;; Ignore other messages @@ -33,13 +37,13 @@ [state nil])) (defn view [state] - (str (charm/render title-style "Counter App") "\n\n" - (charm/render count-style (str (:count state))) "\n\n" + (str (style/render title-style "Counter App") "\n\n" + (style/render count-style (str (:count state))) "\n\n" "j/k or arrows to change\n" "q to quit")) (defn -main [& args] - (charm/run {:init {:count 0} - :update update-fn - :view view - :alt-screen true})) + (program/run {:init {:count 0} + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/download.clj b/docs/examples/src/examples/download.clj index c64783a..d875508 100644 --- a/docs/examples/src/examples/download.clj +++ b/docs/examples/src/examples/download.clj @@ -1,7 +1,10 @@ (ns examples.download "Simulated download demonstrating all progress bar styles." (:require - [charm.core :as charm] + [charm.components.progress :as progress] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] [clojure.string :as str])) (def bar-style-names @@ -9,16 +12,16 @@ [:default :ascii :thin :thick :blocks :arrows :dots :brackets]) (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def label-style - (charm/style :fg charm/cyan)) + (style/style :fg style/cyan)) (def complete-style - (charm/style :fg charm/green :bold true)) + (style/style :fg style/green :bold true)) (def hint-style - (charm/style :fg 240)) + (style/style :fg 240)) (defn tick-msg "Create a progress tick message." @@ -43,9 +46,9 @@ [] (into {} (map (fn [style-name] - [style-name (charm/progress-bar :width 30 - :bar-style style-name - :show-percent true)]) + [style-name (progress/progress-bar :width 30 + :bar-style style-name + :show-percent true)]) bar-style-names))) (defn init [] @@ -76,23 +79,23 @@ (defn all-complete? "Check if all bars are complete." [bars] - (every? charm/progress-complete? (vals bars))) + (every? progress/complete? (vals bars))) (defn update-fn [state msg] (cond ;; Quit - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Space to start (and (not (:running state)) - (charm/key-match? msg " ")) + (msg/key-match? msg " ")) (start-download state) ;; R to reset - (charm/key-match? msg "r") + (msg/key-match? msg "r") (reset-download state) ;; Progress tick - increment all bars with random variation @@ -104,7 +107,7 @@ new-bars (reduce (fn [bars style-name] (let [bar (get bars style-name) increment (+ 0.005 (* 0.015 (rand)))] - (assoc bars style-name (charm/progress-increment bar increment)))) + (assoc bars style-name (progress/increment bar increment)))) bars bar-style-names) all-done? (all-complete? new-bars) @@ -122,34 +125,33 @@ "Render a single progress bar with label." [bars style-name] (let [bar (get bars style-name) - label (name style-name) - complete? (charm/progress-complete? bar)] - (str (charm/render label-style (format "%-10s" label)) + complete? (progress/complete? bar)] + (str (style/render label-style (format "%-10s" (name style-name))) " " - (charm/progress-view bar) + (progress/progress-view bar) (when complete? - (str " " (charm/render complete-style "Done!")))))) + (str " " (style/render complete-style "Done!")))))) (defn view [state] (let [{:keys [bars running]} state all-done? (all-complete? bars)] - (str (charm/render title-style "Download Demo") "\n" - (charm/render hint-style "Demonstrating all progress bar styles") "\n\n" + (str (style/render title-style "Download Demo") "\n" + (style/render hint-style "Demonstrating all progress bar styles") "\n\n" (str/join "\n" (map #(render-bar bars %) bar-style-names)) "\n\n" (cond all-done? - (str (charm/render complete-style "All downloads complete!") "\n\n" - (charm/render hint-style "Press R to restart, Q to quit")) + (str (style/render complete-style "All downloads complete!") "\n\n" + (style/render hint-style "Press R to restart, Q to quit")) running - (charm/render hint-style "Downloading... Press R to reset, Q to quit") + (style/render hint-style "Downloading... Press R to reset, Q to quit") :else - (charm/render hint-style "Press Space to start download, Q to quit"))))) + (style/render hint-style "Press Space to start download, Q to quit"))))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/file_browser.clj b/docs/examples/src/examples/file_browser.clj index 826ea98..92d6870 100644 --- a/docs/examples/src/examples/file_browser.clj +++ b/docs/examples/src/examples/file_browser.clj @@ -1,20 +1,22 @@ (ns examples.file-browser "File browser demonstrating list component with a details pane." (:require - [charm.ansi.width :as w] - [charm.components.help :as help] - [charm.core :as charm] - [charm.style.border :as border] - [charm.style.core :as style] - [clojure.java.io :as io] - [clojure.string :as str]) + [charm.ansi.width :as w] + [charm.components.help :as help] + [charm.components.list :as item-list] + [charm.message :as msg] + [charm.program :as program] + [charm.style.border :as border] + [charm.style.core :as style] + [clojure.java.io :as io] + [clojure.string :as str]) (:import - [java.io File] - [java.text SimpleDateFormat] - [java.util Date])) + [java.io File] + [java.text SimpleDateFormat] + [java.util Date])) (def title-style - (style/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def path-style (style/style :fg 240)) @@ -23,10 +25,10 @@ (style/style :fg 240)) (def detail-value-style - (style/style :fg charm/cyan)) + (style/style :fg style/cyan)) (def help-bindings - (charm/help-from-pairs + (help/from-pairs "j/k" "navigate" "Enter/l" "open" "Backspace/h" "back" @@ -93,11 +95,11 @@ (max 3 (quot (- term-height chrome-height) 2))) (defn- make-file-list [items term-width term-height] - (charm/item-list items - :height (list-height term-height) - :width (list-width term-width) - :show-descriptions true - :cursor-style (charm/style :fg charm/cyan :bold true))) + (item-list/item-list items + :height (list-height term-height) + :width (list-width term-width) + :show-descriptions true + :cursor-style (style/style :fg style/cyan :bold true))) (defn init [] (let [start-path (System/getProperty "user.dir") @@ -109,7 +111,7 @@ :term-width 80 :term-height 24 :file-list (make-file-list items 80 24) - :help (charm/help help-bindings :width 60)} + :help (help/help help-bindings :width 60)} nil])) (defn navigate-to @@ -136,7 +138,7 @@ (defn enter-selected "Enter selected directory or do nothing for files." [state] - (let [selected (charm/list-selected-item (:file-list state))] + (let [selected (item-list/selected-item (:file-list state))] (when-let [info (:data selected)] (if (:directory? info) (navigate-to state (:path info)) @@ -145,13 +147,13 @@ (defn update-fn [state msg] (cond ;; Quit - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Window resize - (charm/window-size? msg) + (msg/window-size? msg) (let [w (:width msg) h (:height msg)] [(assoc state @@ -161,26 +163,26 @@ nil]) ;; Go up directory - (or (charm/key-match? msg "backspace") - (charm/key-match? msg "h") - (charm/key-match? msg :left)) + (or (msg/key-match? msg "backspace") + (msg/key-match? msg "h") + (msg/key-match? msg :left)) [(go-up state) nil] ;; Enter directory - (or (charm/key-match? msg "enter") - (charm/key-match? msg "l") - (charm/key-match? msg :right)) + (or (msg/key-match? msg "enter") + (msg/key-match? msg "l") + (msg/key-match? msg :right)) [(or (enter-selected state) state) nil] ;; Pass to list for navigation :else - (let [[new-list cmd] (charm/list-update (:file-list state) msg)] + (let [[new-list cmd] (item-list/list-update (:file-list state) msg)] [(assoc state :file-list new-list) cmd]))) (defn render-details "Render the details pane for selected file." [state] - (if-let [selected (charm/list-selected-item (:file-list state))] + (if-let [selected (item-list/selected-item (:file-list state))] (let [info (:data selected)] (str (style/render detail-label-style "Name ") (style/render detail-value-style (:name info)) "\n" @@ -209,7 +211,7 @@ (str/join "\n" (map render-row (range height))))) (defn view [state] - (let [file-list-view (charm/list-view (:file-list state)) + (let [file-list-view (item-list/list-view (:file-list state)) details-view (render-details state) details-style (style/style :border border/rounded :border-fg 240 @@ -227,7 +229,7 @@ (help/short-help-view (:help state))))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/form.clj b/docs/examples/src/examples/form.clj index f633886..e2deba2 100644 --- a/docs/examples/src/examples/form.clj +++ b/docs/examples/src/examples/form.clj @@ -1,31 +1,35 @@ (ns examples.form "Login form demonstrating text-input with multiple fields and echo modes." - (:require [charm.core :as charm] - [clojure.string :as str])) + (:require + [charm.components.text-input :as text-input] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] + [clojure.string :as str])) (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def label-style - (charm/style :fg charm/cyan :bold true)) + (style/style :fg style/cyan :bold true)) (def hint-style - (charm/style :fg 240)) + (style/style :fg 240)) (def success-style - (charm/style :fg charm/green :bold true)) + (style/style :fg style/green :bold true)) (def error-style - (charm/style :fg charm/red)) + (style/style :fg style/red)) (defn init [] - [{:username (charm/text-input :prompt "" - :placeholder "Enter username" - :focused true) - :password (charm/text-input :prompt "" - :placeholder "Enter password" - :echo-mode charm/echo-password - :focused false) + [{:username (text-input/text-input :prompt "" + :placeholder "Enter username" + :focused true) + :password (text-input/text-input :prompt "" + :placeholder "Enter password" + :echo-mode text-input/echo-password + :focused false) :focused-field :username :submitted false :message nil} @@ -35,8 +39,8 @@ "Focus a specific field and blur others." [state field] (-> state - (update :username (if (= field :username) charm/text-input-focus charm/text-input-blur)) - (update :password (if (= field :password) charm/text-input-focus charm/text-input-blur)) + (update :username (if (= field :username) text-input/focus text-input/blur)) + (update :password (if (= field :password) text-input/focus text-input/blur)) (assoc :focused-field field))) (defn next-field @@ -49,8 +53,8 @@ (defn validate-form "Validate the form and return error message or nil." [state] - (let [username (charm/text-input-value (:username state)) - password (charm/text-input-value (:password state))] + (let [username (text-input/value (:username state)) + password (text-input/value (:password state))] (cond (str/blank? username) "Username is required" (< (count username) 3) "Username must be at least 3 characters" @@ -66,42 +70,42 @@ (assoc state :submitted true :message {:type :success - :text (str "Welcome, " (charm/text-input-value (:username state)) "!")}))) + :text (str "Welcome, " (text-input/value (:username state)) "!")}))) (defn update-fn [state msg] (cond ;; Quit on Ctrl+C or Esc - (or (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Quit on q only when submitted - (and (:submitted state) (charm/key-match? msg "q")) - [state charm/quit-cmd] + (and (:submitted state) (msg/key-match? msg "q")) + [state program/quit-cmd] ;; Already submitted, ignore other input (:submitted state) [state nil] ;; Tab or Down to next field - (or (charm/key-match? msg "tab") - (charm/key-match? msg :down)) + (or (msg/key-match? msg "tab") + (msg/key-match? msg :down)) [(next-field state) nil] ;; Shift+Tab or Up to previous field - (or (charm/key-match? msg "shift+tab") - (charm/key-match? msg :up)) + (or (msg/key-match? msg "shift+tab") + (msg/key-match? msg :up)) [(next-field state) nil] ;; Enter to submit - (charm/key-match? msg "enter") + (msg/key-match? msg "enter") [(submit-form state) nil] ;; Pass input to focused field :else (let [field (:focused-field state) input (get state field) - [new-input cmd] (charm/text-input-update input msg)] + [new-input cmd] (text-input/text-input-update input msg)] [(-> state (assoc field new-input) (assoc :message nil)) @@ -111,27 +115,27 @@ "Render a form field with label and focus indicator." [label input focused?] (let [indicator (if focused? "> " " ") - label-str (charm/render label-style (format "%-10s" label))] - (str indicator label-str (charm/text-input-view input)))) + label-str (style/render label-style (format "%-10s" label))] + (str indicator label-str (text-input/text-input-view input)))) (defn view [state] (let [{:keys [username password focused-field submitted message]} state] - (str (charm/render title-style "Login Form") "\n\n" + (str (style/render title-style "Login Form") "\n\n" (render-field "Username:" username (= focused-field :username)) "\n" (render-field "Password:" password (= focused-field :password)) "\n\n" (when message - (str (charm/render (if (= (:type message) :error) error-style success-style) + (str (style/render (if (= (:type message) :error) error-style success-style) (:text message)) "\n\n")) (if submitted - (charm/render hint-style "Press q to quit") - (charm/render hint-style "Tab: next field Enter: submit Esc: quit"))))) + (style/render hint-style "Press q to quit") + (style/render hint-style "Tab: next field Enter: submit Esc: quit"))))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/pomodoro.clj b/docs/examples/src/examples/pomodoro.clj index e29dbaa..abf842e 100644 --- a/docs/examples/src/examples/pomodoro.clj +++ b/docs/examples/src/examples/pomodoro.clj @@ -9,8 +9,14 @@ - 50/10: 50 minutes work, 10 minutes break Press q or Ctrl+C to quit." - (:require [charm.core :as charm] - [clojure.string :as str]) + (:require + [charm.components.list :as item-list] + [charm.components.progress :as progress] + [charm.components.timer :as timer] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] + [clojure.string :as str]) (:gen-class)) ;; --------------------------------------------------------------------------- @@ -44,25 +50,25 @@ ;; --------------------------------------------------------------------------- (def title-style - (charm/style :bold true)) + (style/style :bold true)) -(def work-color (charm/rgb 255 140 100)) -(def break-color (charm/rgb 100 220 180)) +(def work-color (style/rgb 255 140 100)) +(def break-color (style/rgb 100 220 180)) (defn phase-style [phase] - (charm/style :fg (if (= phase :work) work-color break-color) :bold true)) + (style/style :fg (if (= phase :work) work-color break-color) :bold true)) (def time-style - (charm/style :fg (charm/rgb 140 140 140))) + (style/style :fg (style/rgb 140 140 140))) (def hint-style - (charm/style :fg (charm/rgb 100 100 100))) + (style/style :fg (style/rgb 100 100 100))) (defn gradient-color "Interpolate color based on progress and phase." - ([progress phase] (gradient-color progress phase false)) - ([progress phase dim?] - (let [p (double progress) + ([p phase] (gradient-color p phase false)) + ([p phase dim?] + (let [p (double p) [r1 g1 b1 r2 g2 b2] (if (= phase :work) ;; Work: orange to red [255 180 100 255 80 80] @@ -72,7 +78,7 @@ g (+ g1 (* p (- g2 g1))) b (+ b1 (* p (- b2 b1))) factor (if dim? 0.4 1.0)] - (charm/rgb (int (* r factor)) + (style/rgb (int (* r factor)) (int (* g factor)) (int (* b factor)))))) @@ -82,12 +88,12 @@ (defn view-selecting [state] (let [{:keys [menu]} state] - (str (charm/render title-style "Pomodoro Timer") + (str (style/render title-style "Pomodoro Timer") "\n\n" "Select your focus mode:\n\n" - (charm/list-view menu) + (item-list/list-view menu) "\n\n" - (charm/render hint-style "↑/↓ to select, Enter to start, q to quit")))) + (style/render hint-style "↑/↓ to select, Enter to start, q to quit")))) ;; --------------------------------------------------------------------------- ;; View - Timer Running @@ -95,23 +101,23 @@ (defn view-running [state] (let [{:keys [phase timer total-ms cycle-count]} state - remaining (max 0 (charm/timer-timeout timer)) + remaining (max 0 (timer/timeout timer)) elapsed (- total-ms remaining) - progress (if (pos? total-ms) (/ elapsed total-ms) 0.0) + p (if (pos? total-ms) (/ elapsed total-ms) 0.0) phase-label (if (= phase :work) "WORK" "BREAK") - bar (charm/progress-bar :width 30 - :percent progress - :bar-style :thick - :full-style (charm/style :fg (gradient-color progress phase)) - :empty-style (charm/style :fg (gradient-color progress phase true)))] - (str (charm/render (phase-style phase) phase-label) - (charm/render hint-style (str " (cycle " cycle-count ")")) + bar (progress/progress-bar :width 30 + :percent p + :bar-style :thick + :full-style (style/style :fg (gradient-color p phase)) + :empty-style (style/style :fg (gradient-color p phase true)))] + (str (style/render (phase-style phase) phase-label) + (style/render hint-style (str " (cycle " cycle-count ")")) "\n\n" - (charm/progress-view bar) + (progress/progress-view bar) " " - (charm/render time-style (format-remaining remaining)) + (style/render time-style (format-remaining remaining)) "\n\n" - (charm/render hint-style "p to pause/resume, q to quit")))) + (style/render hint-style "p to pause/resume, q to quit")))) ;; --------------------------------------------------------------------------- ;; View - Paused @@ -119,25 +125,25 @@ (defn view-paused [state] (let [{:keys [phase timer total-ms cycle-count]} state - remaining (max 0 (charm/timer-timeout timer)) + remaining (max 0 (timer/timeout timer)) elapsed (- total-ms remaining) - progress (if (pos? total-ms) (/ elapsed total-ms) 0.0) + p (if (pos? total-ms) (/ elapsed total-ms) 0.0) phase-label (if (= phase :work) "WORK" "BREAK") - bar (charm/progress-bar :width 30 - :percent progress - :bar-style :thick - :full-style (charm/style :fg (gradient-color progress phase)) - :empty-style (charm/style :fg (gradient-color progress phase true)))] - (str (charm/render (phase-style phase) phase-label) - (charm/render hint-style (str " (cycle " cycle-count ")")) + bar (progress/progress-bar :width 30 + :percent p + :bar-style :thick + :full-style (style/style :fg (gradient-color p phase)) + :empty-style (style/style :fg (gradient-color p phase true)))] + (str (style/render (phase-style phase) phase-label) + (style/render hint-style (str " (cycle " cycle-count ")")) " " - (charm/render (charm/style :fg (charm/rgb 255 200 100) :bold true) "PAUSED") + (style/render (style/style :fg (style/rgb 255 200 100) :bold true) "PAUSED") "\n\n" - (charm/progress-view bar) + (progress/progress-view bar) " " - (charm/render time-style (format-remaining remaining)) + (style/render time-style (format-remaining remaining)) "\n\n" - (charm/render hint-style "p to resume, q to quit")))) + (style/render hint-style "p to resume, q to quit")))) ;; --------------------------------------------------------------------------- ;; Main View @@ -158,14 +164,14 @@ duration (if (= phase :work) (:work-ms mode) (:break-ms mode)) - timer (charm/timer :timeout duration - :interval 100 - :running true) - [timer cmd] (charm/timer-init timer)] + t (timer/timer :timeout duration + :interval 100 + :running true) + [t cmd] (timer/timer-init t)] [(assoc state :screen :running :phase phase - :timer timer + :timer t :total-ms duration) cmd])) @@ -177,38 +183,38 @@ (let [{:keys [screen]} state] (cond ;; Global quit - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Mode selection screen (= screen :selecting) (cond - (charm/key-match? msg "enter") - (let [selected (charm/list-selected-item (:menu state)) + (msg/key-match? msg "enter") + (let [selected (item-list/selected-item (:menu state)) new-state (assoc state :mode selected :cycle-count 1)] (start-phase-timer new-state :work)) :else - (let [[new-menu _] (charm/list-update (:menu state) msg)] + (let [[new-menu _] (item-list/list-update (:menu state) msg)] [(assoc state :menu new-menu) nil])) ;; Running screen - pause toggle - (and (= screen :running) (charm/key-match? msg "p")) - (let [[new-timer _] (charm/timer-stop (:timer state))] + (and (= screen :running) (msg/key-match? msg "p")) + (let [[new-timer _] (timer/stop (:timer state))] [(assoc state :screen :paused :timer new-timer) nil]) ;; Paused screen - resume - (and (= screen :paused) (charm/key-match? msg "p")) - (let [[new-timer cmd] (charm/timer-start (:timer state))] + (and (= screen :paused) (msg/key-match? msg "p")) + (let [[new-timer cmd] (timer/start (:timer state))] [(assoc state :screen :running :timer new-timer) cmd]) ;; Running screen - timer tick (= screen :running) - (let [[new-timer cmd] (charm/timer-update (:timer state) msg) + (let [[new-timer cmd] (timer/timer-update (:timer state) msg) new-state (assoc state :timer new-timer)] - (if (charm/timer-timed-out? new-timer) + (if (timer/timed-out? new-timer) ;; Phase complete - switch to next phase (let [{:keys [phase cycle-count]} state next-phase (if (= phase :work) :break :work) @@ -224,9 +230,9 @@ ;; --------------------------------------------------------------------------- (defn init [] - (let [menu (charm/item-list modes - :show-descriptions false - :cursor-style (charm/style :fg (charm/rgb 255 180 100) :bold true))] + (let [menu (item-list/item-list modes + :show-descriptions false + :cursor-style (style/style :fg (style/rgb 255 180 100) :bold true))] [{:screen :selecting :menu menu :mode nil @@ -257,9 +263,9 @@ (println " p Pause/resume timer") (println " q, Ctrl+C Quit")) (do - (charm/run {:init init - :update update-fn - :view view - :alt-screen false - :hide-cursor true}) + (program/run {:init init + :update update-fn + :view view + :alt-screen false + :hide-cursor true}) (println)))) diff --git a/docs/examples/src/examples/spinner_demo.clj b/docs/examples/src/examples/spinner_demo.clj index 58a2f65..41b2993 100644 --- a/docs/examples/src/examples/spinner_demo.clj +++ b/docs/examples/src/examples/spinner_demo.clj @@ -1,7 +1,10 @@ (ns examples.spinner-demo "Demonstrates all 14 spinner animation types side by side." (:require - [charm.core :as charm] + [charm.components.spinner :as spinner] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] [clojure.string :as str])) (def spinner-names @@ -13,14 +16,14 @@ "Create a map of spinner-type -> spinner for all types." [] (into {} - (map (fn [t] [t (charm/spinner t :id t)]) + (map (fn [t] [t (spinner/spinner t :id t)]) spinner-names))) (defn init-spinners "Initialize all spinners and collect their commands." [spinners] (reduce (fn [[spinners cmds] [k spinner]] - (let [[s cmd] (charm/spinner-init spinner)] + (let [[s cmd] (spinner/spinner-init spinner)] [(assoc spinners k s) (if cmd (conj cmds cmd) cmds)])) [spinners []] @@ -33,22 +36,22 @@ [spinners cmds] (init-spinners spinners)] [{:spinners spinners} (when (seq cmds) - (apply charm/batch cmds))])) + (apply program/batch cmds))])) (defn update-fn [state msg] (cond ;; Quit on q or Ctrl+C - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Handle spinner ticks - update the matching spinner (= :spinner-tick (:type msg)) (let [spinner-id (:spinner-id msg) spinners (:spinners state)] - (if-let [spinner (get spinners spinner-id)] - (let [[new-spinner cmd] (charm/spinner-update spinner msg)] + (if-let [s (get spinners spinner-id)] + (let [[new-spinner cmd] (spinner/spinner-update s msg)] [(assoc-in state [:spinners spinner-id] new-spinner) cmd]) [state nil])) @@ -56,18 +59,18 @@ [state nil])) (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def label-style - (charm/style :fg charm/cyan)) + (style/style :fg style/cyan)) (defn format-spinner-row "Format a single spinner with its label." [spinners spinner-type] - (let [spinner (get spinners spinner-type) + (let [s (get spinners spinner-type) label (name spinner-type) - frame (charm/spinner-view spinner)] - (str (charm/render label-style (format "%-12s" label)) " " frame))) + frame (spinner/spinner-view s)] + (str (style/render label-style (format "%-12s" label)) " " frame))) (defn view [state] (let [spinners (:spinners state) @@ -82,14 +85,14 @@ ""))) col1 (concat col2 (repeat nil)))] - (str (charm/render title-style "Spinner Demo") "\n" - (charm/render (charm/style :fg 240) "All 14 spinner animation types") "\n\n" + (str (style/render title-style "Spinner Demo") "\n" + (style/render (style/style :fg 240) "All 14 spinner animation types") "\n\n" (str/join "\n" rows) "\n\n" - (charm/render (charm/style :fg 240) "Press q to quit")))) + (style/render (style/style :fg 240) "Press q to quit")))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/examples/src/examples/timer.clj b/docs/examples/src/examples/timer.clj index f77fa08..54eadc0 100644 --- a/docs/examples/src/examples/timer.clj +++ b/docs/examples/src/examples/timer.clj @@ -7,8 +7,13 @@ clj -M:timer 1h30m # 1 hour 30 minutes clj -M:timer 90 # 90 seconds (default unit) clj -M:timer -n \"Tea\" 3m # Named timer" - (:require [charm.core :as charm] - [clojure.string :as str]) + (:require + [charm.components.progress :as progress] + [charm.components.timer :as timer] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] + [clojure.string :as str]) (:gen-class)) ;; --------------------------------------------------------------------------- @@ -87,42 +92,42 @@ ;; --------------------------------------------------------------------------- (def name-style - (charm/style :fg (charm/rgb 180 140 255) :bold true)) + (style/style :fg (style/rgb 180 140 255) :bold true)) (def time-style - (charm/style :fg (charm/rgb 140 140 140))) + (style/style :fg (style/rgb 140 140 140))) (defn gradient-color "Interpolate from cyan to magenta based on progress. When dim? is true, returns a darker version." - ([progress] (gradient-color progress false)) - ([progress dim?] - (let [p (double progress) + ([p] (gradient-color p false)) + ([p dim?] + (let [p (double p) ;; Start: cyan (0, 220, 255) -> End: magenta (255, 100, 255) r (+ 0 (* p 255)) g (- 220 (* p 120)) b 255 ;; Apply dimming factor (0.3 = 30% brightness) factor (if dim? 0.8 1.0)] - (charm/rgb (int (* r factor)) + (style/rgb (int (* r factor)) (int (* g factor)) (int (* b factor)))))) (defn view [state] (let [{:keys [name timer total-ms]} state - remaining (max 0 (charm/timer-timeout timer)) + remaining (max 0 (timer/timeout timer)) elapsed (- total-ms remaining) - progress (if (pos? total-ms) (/ elapsed total-ms) 0.0) - bar (charm/progress-bar :width 30 - :percent progress - :bar-style :thick - :full-style (charm/style :fg (gradient-color progress)) - :empty-style (charm/style :fg (gradient-color progress true)))] + p (if (pos? total-ms) (/ elapsed total-ms) 0.0) + bar (progress/progress-bar :width 30 + :percent p + :bar-style :thick + :full-style (style/style :fg (gradient-color p)) + :empty-style (style/style :fg (gradient-color p true)))] (str (when name - (str (charm/render name-style name) "\n")) - (charm/progress-view bar) + (str (style/render name-style name) "\n")) + (progress/progress-view bar) " " - (charm/render time-style (format-remaining remaining))))) + (style/render time-style (format-remaining remaining))))) ;; --------------------------------------------------------------------------- ;; Update @@ -131,18 +136,18 @@ (defn update-fn [state msg] (cond ;; Quit on q or Ctrl+C - (or (charm/key-match? msg "q") - (charm/key-match? msg "ctrl+c") - (charm/key-match? msg "esc")) - [state charm/quit-cmd] + (or (msg/key-match? msg "q") + (msg/key-match? msg "ctrl+c") + (msg/key-match? msg "esc")) + [state program/quit-cmd] ;; Timer tick :else - (let [[new-timer cmd] (charm/timer-update (:timer state) msg) + (let [[new-timer cmd] (timer/timer-update (:timer state) msg) new-state (assoc state :timer new-timer)] ;; Auto-quit when timer completes - (if (charm/timer-timed-out? new-timer) - [new-state charm/quit-cmd] + (if (timer/timed-out? new-timer) + [new-state program/quit-cmd] [new-state cmd])))) ;; --------------------------------------------------------------------------- @@ -151,12 +156,12 @@ (defn init [opts] (fn [] - (let [timer (charm/timer :timeout (:duration-ms opts) - :interval 100 - :running true) - [timer cmd] (charm/timer-init timer)] + (let [t (timer/timer :timeout (:duration-ms opts) + :interval 100 + :running true) + [t cmd] (timer/timer-init t)] [{:name (:name opts) - :timer timer + :timer t :total-ms (:duration-ms opts)} cmd]))) @@ -192,9 +197,9 @@ (usage) (System/exit 1)) (do - (charm/run {:init (init opts) - :update update-fn - :view view - :alt-screen false - :hide-cursor true}) + (program/run {:init (init opts) + :update update-fn + :view view + :alt-screen false + :hide-cursor true}) (println)))))) ; Newline after progress bar diff --git a/docs/examples/src/examples/todos.clj b/docs/examples/src/examples/todos.clj index d6eb228..8f4b92b 100644 --- a/docs/examples/src/examples/todos.clj +++ b/docs/examples/src/examples/todos.clj @@ -1,31 +1,36 @@ (ns examples.todos "Full todo application demonstrating component composition: list + text-input + help working together." - (:require [charm.core :as charm] - [charm.components.help :as help] - [clojure.string :as str])) + (:require + [charm.components.help :as help] + [charm.components.list :as item-list] + [charm.components.text-input :as text-input] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] + [clojure.string :as str])) (def title-style - (charm/style :fg charm/magenta :bold true)) + (style/style :fg style/magenta :bold true)) (def input-style - (charm/style :fg charm/cyan)) + (style/style :fg style/cyan)) (def done-style - (charm/style :fg 240 :strikethrough true)) + (style/style :fg 240 :strikethrough true)) (def pending-style - (charm/style :fg charm/white)) + (style/style :fg style/white)) (def count-style - (charm/style :fg charm/yellow)) + (style/style :fg style/yellow)) (def hint-style - (charm/style :fg 240)) + (style/style :fg 240)) ;; Help bindings (def help-bindings - (charm/help-from-pairs + (help/from-pairs "j/k" "up/down" "a" "add todo" "x" "toggle done" @@ -53,7 +58,7 @@ [state] (let [todos (:todos state) items (mapv todo->list-item todos)] - (assoc state :todo-list (charm/list-set-items (:todo-list state) items)))) + (assoc state :todo-list (item-list/set-items (:todo-list state) items)))) (defn init [] (let [initial-todos [(make-todo "Learn charm.clj") @@ -61,15 +66,15 @@ (make-todo "Have fun!")] items (mapv todo->list-item initial-todos)] [{:todos initial-todos - :todo-list (charm/item-list items - :height 10 - :cursor-prefix "> " - :item-prefix " ") - :input (charm/text-input :prompt "New todo: " - :placeholder "What needs to be done?" - :focused false) + :todo-list (item-list/item-list items + :height 10 + :cursor-prefix "> " + :item-prefix " ") + :input (text-input/text-input :prompt "New todo: " + :placeholder "What needs to be done?" + :focused false) :mode :browse ; :browse or :add - :help (charm/help help-bindings :width 60)} + :help (help/help help-bindings :width 60)} nil])) (defn enter-add-mode @@ -77,20 +82,20 @@ [state] (-> state (assoc :mode :add) - (update :input charm/text-input-focus))) + (update :input text-input/focus))) (defn exit-add-mode "Switch back to browse mode." [state] (-> state (assoc :mode :browse) - (update :input charm/text-input-blur) - (update :input charm/text-input-reset))) + (update :input text-input/blur) + (update :input text-input/reset))) (defn add-todo "Add new todo from input." [state] - (let [text (str/trim (charm/text-input-value (:input state)))] + (let [text (str/trim (text-input/value (:input state)))] (if (str/blank? text) (exit-add-mode state) (-> state @@ -101,7 +106,7 @@ (defn toggle-selected "Toggle done state of selected todo." [state] - (let [idx (charm/list-selected-index (:todo-list state))] + (let [idx (item-list/selected-index (:todo-list state))] (if (and idx (< idx (count (:todos state)))) (-> state (update-in [:todos idx :done] not) @@ -111,7 +116,7 @@ (defn delete-selected "Delete the selected todo." [state] - (let [idx (charm/list-selected-index (:todo-list state))] + (let [idx (item-list/selected-index (:todo-list state))] (if (and idx (< idx (count (:todos state)))) (-> state (update :todos (fn [todos] @@ -123,51 +128,51 @@ (defn update-fn [state msg] (cond ;; Quit (only in browse mode, or ctrl+c/esc always) - (or (charm/key-match? msg "ctrl+c") + (or (msg/key-match? msg "ctrl+c") (and (= (:mode state) :browse) - (charm/key-match? msg "q"))) - [state charm/quit-cmd] + (msg/key-match? msg "q"))) + [state program/quit-cmd] ;; Escape always exits add mode or quits in browse mode - (charm/key-match? msg "esc") + (msg/key-match? msg "esc") (if (= (:mode state) :add) [(exit-add-mode state) nil] - [state charm/quit-cmd]) + [state program/quit-cmd]) ;; In add mode (= (:mode state) :add) (cond ;; Enter to submit - (charm/key-match? msg "enter") + (msg/key-match? msg "enter") [(add-todo state) nil] ;; Pass to text input :else - (let [[new-input cmd] (charm/text-input-update (:input state) msg)] + (let [[new-input cmd] (text-input/text-input-update (:input state) msg)] [(assoc state :input new-input) cmd])) ;; In browse mode :else (cond ;; A to add new todo - (charm/key-match? msg "a") + (msg/key-match? msg "a") [(enter-add-mode state) nil] ;; X to toggle done - (charm/key-match? msg "x") + (msg/key-match? msg "x") [(toggle-selected state) nil] ;; D to delete - (charm/key-match? msg "d") + (msg/key-match? msg "d") [(delete-selected state) nil] ;; ? to toggle help - (charm/key-match? msg "?") - [(update state :help charm/help-toggle-show-all) nil] + (msg/key-match? msg "?") + [(update state :help help/toggle-show-all) nil] ;; Pass navigation to list :else - (let [[new-list cmd] (charm/list-update (:todo-list state) msg)] + (let [[new-list cmd] (item-list/list-update (:todo-list state) msg)] [(assoc state :todo-list new-list) cmd])))) (defn count-todos @@ -181,8 +186,8 @@ (let [{:keys [todos todo-list input mode help]} state counts (count-todos todos) show-full-help? (:show-all help)] - (str (charm/render title-style "Todo List") "\n" - (charm/render count-style + (str (style/render title-style "Todo List") "\n" + (style/render count-style (format "%d pending, %d done" (:pending counts) (:done counts))) @@ -190,23 +195,23 @@ ;; Todo list (if (empty? todos) - (charm/render hint-style "No todos yet. Press 'a' to add one!") - (charm/list-view todo-list)) + (style/render hint-style "No todos yet. Press 'a' to add one!") + (item-list/list-view todo-list)) "\n\n" ;; Input (visible in add mode) (when (= mode :add) - (str (charm/text-input-view input) "\n\n")) + (str (text-input/text-input-view input) "\n\n")) ;; Help (if show-full-help? - (str (charm/render (charm/style :bold true) "Keyboard Shortcuts") "\n" + (str (style/render (style/style :bold true) "Keyboard Shortcuts") "\n" (help/full-help-view help) "\n\n" - (charm/render hint-style "Press ? to hide help")) + (style/render hint-style "Press ? to hide help")) (help/short-help-view help))))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) diff --git a/docs/guides/component-composition.md b/docs/guides/component-composition.md index 2a37c1d..379b088 100644 --- a/docs/guides/component-composition.md +++ b/docs/guides/component-composition.md @@ -17,9 +17,9 @@ Store each component's state as a key in your app state: ```clojure (defn init [] - [{:input (charm/text-input :prompt "Search: ") - :list (charm/item-list ["Apple" "Banana" "Cherry"]) - :help (charm/help [["/" "search"] ["Enter" "select"] ["q" "quit"]])} + [{:input (text-input/text-input :prompt "Search: ") + :list (item-list/item-list ["Apple" "Banana" "Cherry"]) + :help (help/help [["/" "search"] ["Enter" "select"] ["q" "quit"]])} nil]) ``` @@ -30,8 +30,8 @@ Use a mode or focus field to route messages: ```clojure (defn init [] [{:mode :browse ; :browse or :search - :input (charm/text-input :prompt "Search: " :focused false) - :list (charm/item-list items)} + :input (text-input/text-input :prompt "Search: " :focused false) + :list (item-list/item-list items)} nil]) (defn update-fn [state msg] @@ -42,40 +42,40 @@ Use a mode or focus field to route messages: (defn handle-search-mode [state msg] (cond ;; Escape exits search mode - (charm/key-match? msg "esc") + (msg/key-match? msg "esc") [(-> state (assoc :mode :browse) - (update :input charm/text-input-blur)) + (update :input text-input/blur)) nil] ;; Enter confirms search - (charm/key-match? msg "enter") + (msg/key-match? msg "enter") [(-> state (assoc :mode :browse) - (update :input charm/text-input-blur) + (update :input text-input/blur) (filter-list-by-search)) nil] ;; Pass to text input :else - (let [[input cmd] (charm/text-input-update (:input state) msg)] + (let [[input cmd] (text-input/text-input-update (:input state) msg)] [(assoc state :input input) cmd]))) (defn handle-browse-mode [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; / enters search mode - (charm/key-match? msg "/") + (msg/key-match? msg "/") [(-> state (assoc :mode :search) - (update :input charm/text-input-focus)) + (update :input text-input/focus)) nil] ;; Pass to list :else - (let [[list cmd] (charm/list-update (:list state) msg)] + (let [[list cmd] (item-list/list-update (:list state) msg)] [(assoc state :list list) cmd]))) ``` @@ -85,7 +85,7 @@ Components often need to affect each other: ```clojure (defn filter-list-by-search [state] - (let [query (charm/text-input-value (:input state)) + (let [query (text-input/value (:input state)) all-items (:all-items state) filtered (if (empty? query) all-items @@ -93,7 +93,7 @@ Components often need to affect each other: (clojure.string/lower-case (:title %)) (clojure.string/lower-case query)) all-items))] - (update state :list charm/list-set-items filtered))) + (update state :list item-list/set-items filtered))) ``` ## Example: File Browser @@ -102,9 +102,15 @@ A complete example combining list, text-input for filtering, and help: ```clojure (ns file-browser.core - (:require [charm.core :as charm] - [clojure.java.io :as io] - [clojure.string :as str])) + (:require + [charm.components.help :as help] + [charm.components.list :as item-list] + [charm.components.text-input :as text-input] + [charm.message :as msg] + [charm.program :as program] + [charm.style.core :as style] + [clojure.java.io :as io] + [clojure.string :as str])) ;; State structure ;; {:mode :browse | :filter @@ -136,68 +142,68 @@ A complete example combining list, text-input for filtering, and help: [{:mode :browse :path path :all-files files - :list (charm/item-list (files->list-items files) :height 15) - :filter-input (charm/text-input :prompt "Filter: " :focused false) - :help (charm/help [["j/k" "navigate"] - ["Enter" "open"] - ["/" "filter"] - ["q" "quit"]])} + :list (item-list/item-list (files->list-items files) :height 15) + :filter-input (text-input/text-input :prompt "Filter: " :focused false) + :help (help/help [["j/k" "navigate"] + ["Enter" "open"] + ["/" "filter"] + ["q" "quit"]])} nil])) (defn apply-filter [state] - (let [query (str/lower-case (charm/text-input-value (:filter-input state))) + (let [query (str/lower-case (text-input/value (:filter-input state))) files (if (empty? query) (:all-files state) (filter #(str/includes? (str/lower-case (:title %)) query) (:all-files state)))] - (update state :list charm/list-set-items (files->list-items files)))) + (update state :list item-list/set-items (files->list-items files)))) (defn navigate-to [state path] (let [files (list-files path)] (-> state (assoc :path path) (assoc :all-files files) - (assoc :list (charm/item-list (files->list-items files) :height 15)) - (update :filter-input charm/text-input-reset)))) + (assoc :list (item-list/item-list (files->list-items files) :height 15)) + (update :filter-input text-input/reset)))) (defn handle-browse [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] - (charm/key-match? msg "/") + (msg/key-match? msg "/") [(-> state (assoc :mode :filter) - (update :filter-input charm/text-input-focus)) + (update :filter-input text-input/focus)) nil] - (charm/key-match? msg "enter") - (let [selected (:data (charm/list-selected-item (:list state)))] + (msg/key-match? msg "enter") + (let [selected (:data (item-list/selected-item (:list state)))] (if (:directory? selected) [(navigate-to state (:path selected)) nil] [state nil])) - (charm/key-match? msg "backspace") + (msg/key-match? msg "backspace") (let [parent (.getParent (io/file (:path state)))] (if parent [(navigate-to state parent) nil] [state nil])) :else - (let [[list cmd] (charm/list-update (:list state) msg)] + (let [[list cmd] (item-list/list-update (:list state) msg)] [(assoc state :list list) cmd]))) (defn handle-filter [state msg] (cond - (or (charm/key-match? msg "esc") - (charm/key-match? msg "enter")) + (or (msg/key-match? msg "esc") + (msg/key-match? msg "enter")) [(-> state (assoc :mode :browse) - (update :filter-input charm/text-input-blur)) + (update :filter-input text-input/blur)) nil] :else - (let [[input cmd] (charm/text-input-update (:filter-input state) msg)] + (let [[input cmd] (text-input/text-input-update (:filter-input state) msg)] [(-> state (assoc :filter-input input) apply-filter) @@ -209,21 +215,21 @@ A complete example combining list, text-input for filtering, and help: :browse (handle-browse state msg))) (defn view [state] - (str (charm/render (charm/style :fg charm/cyan :bold true) "File Browser") + (str (style/render (style/style :fg style/cyan :bold true) "File Browser") "\n" - (charm/render (charm/style :fg 240) (:path state)) + (style/render (style/style :fg 240) (:path state)) "\n\n" - (charm/list-view (:list state)) + (item-list/list-view (:list state)) "\n\n" (when (= :filter (:mode state)) - (str (charm/text-input-view (:filter-input state)) "\n\n")) - (charm/help-view (:help state)))) + (str (text-input/text-input-view (:filter-input state)) "\n\n")) + (help/short-help-view (:help state)))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) ``` ## Pattern: Tick-Based Components @@ -232,26 +238,26 @@ When using spinner or timer, handle their ticks: ```clojure (defn init [] - (let [[spinner cmd] (charm/spinner-init (charm/spinner :dots)) - [timer timer-cmd] (charm/timer-init (charm/timer :timeout 30000))] - [{:spinner spinner - :timer timer} - (charm/batch cmd timer-cmd)])) + (let [[s cmd] (spinner/spinner-init (spinner/spinner :dots)) + [t timer-cmd] (timer/timer-init (timer/timer :timeout 30000))] + [{:spinner s + :timer t} + (program/batch cmd timer-cmd)])) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; Route spinner ticks (= :spinner-tick (:type msg)) - (let [[spinner cmd] (charm/spinner-update (:spinner state) msg)] - [(assoc state :spinner spinner) cmd]) + (let [[s cmd] (spinner/spinner-update (:spinner state) msg)] + [(assoc state :spinner s) cmd]) ;; Route timer ticks (= :timer-tick (:type msg)) - (let [[timer cmd] (charm/timer-update (:timer state) msg)] - [(assoc state :timer timer) cmd]) + (let [[t cmd] (timer/timer-update (:timer state) msg)] + [(assoc state :timer t) cmd]) :else [state nil])) @@ -269,10 +275,10 @@ Update help bindings based on mode: :edit [["Ctrl+S" "save"] ["Esc" "cancel"]])) (defn view [state] - (let [help (charm/help (get-help-bindings (:mode state)))] + (let [h (help/help (get-help-bindings (:mode state)))] (str (main-content-view state) "\n\n" - (charm/help-view help)))) + (help/short-help-view h)))) ``` ## Tips @@ -310,9 +316,9 @@ Split update logic by mode or component: When initializing multiple tick-based components: ```clojure -(let [[spinner1 cmd1] (charm/spinner-init s1) - [spinner2 cmd2] (charm/spinner-init s2) - [timer cmd3] (charm/timer-init timer)] - [{:s1 spinner1 :s2 spinner2 :timer timer} - (charm/batch cmd1 cmd2 cmd3)]) +(let [[spinner1 cmd1] (spinner/spinner-init s1) + [spinner2 cmd2] (spinner/spinner-init s2) + [t cmd3] (timer/timer-init t)] + [{:s1 spinner1 :s2 spinner2 :timer t} + (program/batch cmd1 cmd2 cmd3)]) ``` diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 00b8ba0..1c33503 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -27,7 +27,9 @@ Create `src/counter/core.clj`: ```clojure (ns counter.core - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program])) (defn init [] [{:count 0} nil]) @@ -39,9 +41,9 @@ Create `src/counter/core.clj`: (str "Count: " (:count state))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view})) + (program/run {:init init + :update update-fn + :view view})) ``` Run it: @@ -60,17 +62,17 @@ Add key handling to the update function: (defn update-fn [state msg] (cond ;; Quit on 'q' - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; Increment on up arrow or 'k' - (or (charm/key-match? msg :up) - (charm/key-match? msg "k")) + (or (msg/key-match? msg :up) + (msg/key-match? msg "k")) [(update state :count inc) nil] ;; Decrement on down arrow or 'j' - (or (charm/key-match? msg :down) - (charm/key-match? msg "j")) + (or (msg/key-match? msg :down) + (msg/key-match? msg "j")) [(update state :count dec) nil] ;; Ignore other input @@ -96,22 +98,29 @@ Add instructions and styling: Make it visually appealing: ```clojure +(ns counter.core + (:require + [charm.message :as msg] + [charm.program :as program] + [charm.style.border :as border] + [charm.style.core :as style])) + (def title-style - (charm/style :fg charm/cyan :bold true)) + (style/style :fg style/cyan :bold true)) (def count-style - (charm/style :fg charm/yellow + (style/style :fg style/yellow :bold true :padding [1 3] - :border charm/rounded-border)) + :border border/rounded)) (def help-style - (charm/style :fg 240)) ; Gray + (style/style :fg 240)) ; Gray (defn view [state] - (str (charm/render title-style "Counter App") "\n\n" - (charm/render count-style (str (:count state))) "\n\n" - (charm/render help-style "Up/k: +1 Down/j: -1 q: quit"))) + (str (style/render title-style "Counter App") "\n\n" + (style/render count-style (str (:count state))) "\n\n" + (style/render help-style "Up/k: +1 Down/j: -1 q: quit"))) ``` ### Step 5: Use Alternate Screen @@ -120,57 +129,61 @@ For a cleaner experience, use the alternate screen buffer: ```clojure (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) ``` ## Complete Counter App ```clojure (ns counter.core - (:require [charm.core :as charm])) + (:require + [charm.message :as msg] + [charm.program :as program] + [charm.style.border :as border] + [charm.style.core :as style])) (def title-style - (charm/style :fg charm/cyan :bold true)) + (style/style :fg style/cyan :bold true)) (def count-style - (charm/style :fg charm/yellow + (style/style :fg style/yellow :bold true :padding [1 3] - :border charm/rounded-border)) + :border border/rounded)) (def help-style - (charm/style :fg 240)) + (style/style :fg 240)) (defn init [] [{:count 0} nil]) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] - (or (charm/key-match? msg :up) (charm/key-match? msg "k")) + (or (msg/key-match? msg :up) (msg/key-match? msg "k")) [(update state :count inc) nil] - (or (charm/key-match? msg :down) (charm/key-match? msg "j")) + (or (msg/key-match? msg :down) (msg/key-match? msg "j")) [(update state :count dec) nil] :else [state nil])) (defn view [state] - (str (charm/render title-style "Counter App") "\n\n" - (charm/render count-style (str (:count state))) "\n\n" - (charm/render help-style "Up/k: +1 Down/j: -1 q: quit"))) + (str (style/render title-style "Counter App") "\n\n" + (style/render count-style (str (:count state))) "\n\n" + (style/render help-style "Up/k: +1 Down/j: -1 q: quit"))) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) ``` ## Understanding the Elm Architecture @@ -206,33 +219,36 @@ Let's add a spinner to show loading state. ```clojure (ns loader.core - (:require [charm.core :as charm])) + (:require + [charm.components.spinner :as spinner] + [charm.message :as msg] + [charm.program :as program])) (defn init [] - (let [[spinner cmd] (charm/spinner-init (charm/spinner :dots))] - [{:spinner spinner + (let [[s cmd] (spinner/spinner-init (spinner/spinner :dots))] + [{:spinner s :loading true} cmd])) (defn update-fn [state msg] (cond - (charm/key-match? msg "q") - [state charm/quit-cmd] + (msg/key-match? msg "q") + [state program/quit-cmd] ;; Pass spinner ticks to the spinner :else - (let [[spinner cmd] (charm/spinner-update (:spinner state) msg)] - [(assoc state :spinner spinner) cmd]))) + (let [[s cmd] (spinner/spinner-update (:spinner state) msg)] + [(assoc state :spinner s) cmd]))) (defn view [state] - (str (charm/spinner-view (:spinner state)) + (str (spinner/spinner-view (:spinner state)) " Loading...")) (defn -main [& _args] - (charm/run {:init init - :update update-fn - :view view - :alt-screen true})) + (program/run {:init init + :update update-fn + :view view + :alt-screen true})) ``` ## Next Steps diff --git a/docs/guides/styling-patterns.md b/docs/guides/styling-patterns.md index 2428496..4cca378 100644 --- a/docs/guides/styling-patterns.md +++ b/docs/guides/styling-patterns.md @@ -8,32 +8,34 @@ Create reusable styles at the top of your namespace: ```clojure (ns my-app.core - (:require [charm.core :as charm])) + (:require + [charm.style.border :as border] + [charm.style.core :as style])) ;; Text styles (def title-style - (charm/style :fg charm/cyan :bold true)) + (style/style :fg style/cyan :bold true)) (def subtitle-style - (charm/style :fg charm/white :italic true)) + (style/style :fg style/white :italic true)) (def error-style - (charm/style :fg charm/red :bold true)) + (style/style :fg style/red :bold true)) (def success-style - (charm/style :fg charm/green)) + (style/style :fg style/green)) (def muted-style - (charm/style :fg 240)) ; Gray + (style/style :fg 240)) ; Gray ;; Box styles (def panel-style - (charm/style :border charm/rounded-border + (style/style :border border/rounded :padding [1 2])) (def highlight-box - (charm/style :border charm/double-border - :border-fg charm/yellow + (style/style :border border/double-border + :border-fg style/yellow :padding [0 1])) ``` @@ -44,12 +46,12 @@ Create reusable styles at the top of your namespace: Works in any terminal: ```clojure -(def primary charm/cyan) -(def secondary charm/white) -(def accent charm/yellow) -(def danger charm/red) -(def success charm/green) -(def muted (charm/ansi 8)) ; bright-black/gray +(def primary style/cyan) +(def secondary style/white) +(def accent style/yellow) +(def danger style/red) +(def success style/green) +(def muted (style/ansi 8)) ; bright-black/gray ``` ### Extended (ANSI 256) @@ -58,15 +60,15 @@ Grayscale and more colors: ```clojure ;; Grayscale -(def gray-dark (charm/ansi256 236)) -(def gray (charm/ansi256 240)) -(def gray-light (charm/ansi256 245)) -(def gray-lighter (charm/ansi256 250)) +(def gray-dark (style/ansi256 236)) +(def gray (style/ansi256 240)) +(def gray-light (style/ansi256 245)) +(def gray-lighter (style/ansi256 250)) ;; Extended colors -(def orange (charm/ansi256 208)) -(def pink (charm/ansi256 205)) -(def teal (charm/ansi256 43)) +(def orange (style/ansi256 208)) +(def pink (style/ansi256 205)) +(def teal (style/ansi256 43)) ``` ### True Color (24-bit) @@ -74,12 +76,12 @@ Grayscale and more colors: Full color control: ```clojure -(def brand-primary (charm/hex "#6366f1")) ; Indigo -(def brand-secondary (charm/hex "#8b5cf6")) ; Purple -(def brand-accent (charm/hex "#f59e0b")) ; Amber +(def brand-primary (style/hex "#6366f1")) ; Indigo +(def brand-secondary (style/hex "#8b5cf6")) ; Purple +(def brand-accent (style/hex "#f59e0b")) ; Amber -(def bg-dark (charm/rgb 24 24 27)) -(def bg-light (charm/rgb 250 250 250)) +(def bg-dark (style/rgb 24 24 27)) +(def bg-light (style/rgb 250 250 250)) ``` ## Common UI Patterns @@ -88,16 +90,16 @@ Full color control: ```clojure (defn header [title] - (charm/render - (charm/style :fg charm/cyan + (style/render + (style/style :fg style/cyan :bold true :padding [0 0 1 0]) ; bottom padding title)) (defn header-with-border [title] - (charm/render - (charm/style :fg charm/white - :bg charm/blue + (style/render + (style/style :fg style/white + :bg style/blue :bold true :width 60 :align :center @@ -109,16 +111,16 @@ Full color control: ```clojure (defn status-badge [status] - (let [[text style] (case status - :success ["✓ Success" (charm/style :fg charm/green)] - :warning ["⚠ Warning" (charm/style :fg charm/yellow)] - :error ["✗ Error" (charm/style :fg charm/red)] - :info ["ℹ Info" (charm/style :fg charm/cyan)] - ["?" (charm/style :fg 240)])] - (charm/render style text))) + (let [[text s] (case status + :success ["✓ Success" (style/style :fg style/green)] + :warning ["⚠ Warning" (style/style :fg style/yellow)] + :error ["✗ Error" (style/style :fg style/red)] + :info ["ℹ Info" (style/style :fg style/cyan)] + ["?" (style/style :fg 240)])] + (style/render s text))) (defn loading-indicator [spinner text] - (str (charm/spinner-view spinner) " " text)) + (str (spinner/spinner-view spinner) " " text)) ``` ### Lists with Selection @@ -126,10 +128,10 @@ Full color control: ```clojure (defn render-list-item [item selected?] (let [prefix (if selected? "▸ " " ") - style (if selected? - (charm/style :fg charm/cyan :bold true) - (charm/style :fg charm/white))] - (str prefix (charm/render style (:title item))))) + s (if selected? + (style/style :fg style/cyan :bold true) + (style/style :fg style/white))] + (str prefix (style/render s (:title item))))) (defn render-list [items selected-idx] (str/join "\n" @@ -144,34 +146,34 @@ Full color control: ```clojure (defn labeled-input [label input focused?] (let [label-style (if focused? - (charm/style :fg charm/cyan :bold true) - (charm/style :fg 240))] - (str (charm/render label-style (str label ": ")) - (charm/text-input-view input)))) + (style/style :fg style/cyan :bold true) + (style/style :fg 240))] + (str (style/render label-style (str label ": ")) + (text-input/text-input-view input)))) (defn form-field [label input error] (str (labeled-input label input true) (when error - (str "\n" (charm/render (charm/style :fg charm/red) error))))) + (str "\n" (style/render (style/style :fg style/red) error))))) ``` ### Progress Display ```clojure -(defn task-progress [label progress] - (let [bar (charm/progress-bar :width 30 :percent progress :show-percent true)] - (str (charm/render (charm/style :fg charm/white) label) "\n" - (charm/progress-view bar)))) +(defn task-progress [label p] + (let [bar (progress/progress-bar :width 30 :percent p :show-percent true)] + (str (style/render (style/style :fg style/white) label) "\n" + (progress/progress-view bar)))) (defn multi-progress [tasks] (str/join "\n\n" (for [{:keys [name progress status]} tasks] - (str (charm/render - (charm/style :fg (if (= status :done) charm/green charm/white)) + (str (style/render + (style/style :fg (if (= status :done) style/green style/white)) name) "\n" - (charm/progress-view - (charm/progress-bar :width 40 :percent progress)))))) + (progress/progress-view + (progress/progress-bar :width 40 :percent progress)))))) ``` ## Layout Patterns @@ -180,25 +182,25 @@ Full color control: ```clojure (defn two-columns [left right] - (charm/join-horizontal :top - (charm/render (charm/style :width 30) left) + (style/join-horizontal :top + (style/render (style/style :width 30) left) " " - (charm/render (charm/style :width 40) right))) + (style/render (style/style :width 40) right))) ``` ### Sidebar Layout ```clojure (defn sidebar-layout [sidebar main] - (charm/join-horizontal :top - (charm/render - (charm/style :border charm/normal-border + (style/join-horizontal :top + (style/render + (style/style :border border/normal :width 25 :height 20) sidebar) " " - (charm/render - (charm/style :border charm/rounded-border + (style/render + (style/style :border border/rounded :width 50 :height 20 :padding [0 1]) @@ -209,16 +211,16 @@ Full color control: ```clojure (defn card [title content] - (charm/render - (charm/style :border charm/rounded-border + (style/render + (style/style :border border/rounded :padding [0 1]) - (str (charm/render (charm/style :bold true) title) "\n" + (str (style/render (style/style :bold true) title) "\n" content))) (defn card-grid [cards] - (charm/join-vertical :left + (style/join-vertical :left (for [row (partition-all 2 cards)] - (apply charm/join-horizontal :top + (apply style/join-horizontal :top (interpose " " row))))) ``` @@ -226,21 +228,21 @@ Full color control: ```clojure (defn centered-box [content width] - (charm/render - (charm/style :width width + (style/render + (style/style :width width :align :center - :border charm/rounded-border + :border border/rounded :padding [1 2]) content)) (defn modal [title content] - (charm/render - (charm/style :width 50 + (style/render + (style/style :width 50 :align :center - :border charm/double-border - :border-fg charm/cyan + :border border/double-border + :border-fg style/cyan :padding [1 2]) - (str (charm/render (charm/style :bold true :align :center) title) + (str (style/render (style/style :bold true :align :center) title) "\n\n" content))) ``` @@ -251,8 +253,8 @@ Full color control: ```clojure (defn help-line [bindings] - (let [help (charm/help bindings :separator " ")] - (charm/help-view help))) + (let [h (help/help bindings :separator " ")] + (help/short-help-view h))) ;; Usage (help-line [["j/k" "move"] ["Enter" "select"] ["q" "quit"]]) @@ -262,12 +264,12 @@ Full color control: ```clojure (defn hint [text] - (charm/render (charm/style :fg 240 :italic true) text)) + (style/render (style/style :fg 240 :italic true) text)) (defn keyboard-hint [key description] - (str (charm/render (charm/style :fg charm/cyan :bold true) key) + (str (style/render (style/style :fg style/cyan :bold true) key) " " - (charm/render (charm/style :fg 240) description))) + (style/render (style/style :fg 240) description))) ``` ## Conditional Styling @@ -275,22 +277,22 @@ Full color control: ```clojure (defn value-color [value] (cond - (pos? value) charm/green - (neg? value) charm/red - :else charm/white)) + (pos? value) style/green + (neg? value) style/red + :else style/white)) (defn render-value [value] - (charm/render - (charm/style :fg (value-color value) :bold (not (zero? value))) + (style/render + (style/style :fg (value-color value) :bold (not (zero? value))) (str value))) (defn render-status [status] - (charm/render - (charm/style :fg (case status - :active charm/green - :pending charm/yellow - :error charm/red - charm/white)) + (style/render + (style/style :fg (case status + :active style/green + :pending style/yellow + :error style/red + style/white)) (name status))) ``` @@ -301,9 +303,9 @@ Adapt to terminal width: ```clojure (defn responsive-box [content {:keys [width]}] (let [box-width (min 60 (- width 4))] - (charm/render - (charm/style :width box-width - :border charm/rounded-border) + (style/render + (style/style :width box-width + :border border/rounded) content))) (defn view [state] @@ -312,7 +314,7 @@ Adapt to terminal width: ;; Handle window resize in update (defn update-fn [state msg] - (if (charm/window-size? msg) + (if (msg/window-size? msg) [(assoc state :terminal-width (:width msg)) nil] [state nil])) ``` @@ -321,40 +323,42 @@ Adapt to terminal width: ```clojure (ns my-app.theme - (:require [charm.core :as charm])) + (:require + [charm.style.border :as border] + [charm.style.core :as style])) ;; Colors (def colors - {:primary charm/cyan - :secondary charm/white - :accent charm/yellow - :success charm/green - :warning charm/yellow - :danger charm/red - :muted (charm/ansi256 240) - :bg (charm/ansi256 235)}) + {:primary style/cyan + :secondary style/white + :accent style/yellow + :success style/green + :warning style/yellow + :danger style/red + :muted (style/ansi256 240) + :bg (style/ansi256 235)}) ;; Typography (def typography - {:title (charm/style :fg (:primary colors) :bold true) - :subtitle (charm/style :fg (:secondary colors) :italic true) - :body (charm/style :fg (:secondary colors)) - :caption (charm/style :fg (:muted colors)) - :code (charm/style :fg (:accent colors))}) + {:title (style/style :fg (:primary colors) :bold true) + :subtitle (style/style :fg (:secondary colors) :italic true) + :body (style/style :fg (:secondary colors)) + :caption (style/style :fg (:muted colors)) + :code (style/style :fg (:accent colors))}) ;; Components (def components - {:panel (charm/style :border charm/rounded-border :padding [0 1]) - :card (charm/style :border charm/normal-border :padding [1 2]) - :modal (charm/style :border charm/double-border + {:panel (style/style :border border/rounded :padding [0 1]) + :card (style/style :border border/normal :padding [1 2]) + :modal (style/style :border border/double-border :border-fg (:primary colors) :padding [1 2]) - :button (charm/style :bold true :padding [0 1])}) + :button (style/style :bold true :padding [0 1])}) ;; Usage (defn render-title [text] - (charm/render (:title typography) text)) + (style/render (:title typography) text)) (defn render-panel [content] - (charm/render (:panel components) content)) + (style/render (:panel components) content)) ``` diff --git a/src/charm/core.clj b/src/charm/core.clj deleted file mode 100644 index 1b46c30..0000000 --- a/src/charm/core.clj +++ /dev/null @@ -1,253 +0,0 @@ -(ns charm.core - "charm.clj - A Clojure TUI library inspired by Bubble Tea. - - This is the main entry point for charm.clj applications. - - Example usage: - ```clojure - (require '[charm.core :as charm]) - - (defn update-fn [state msg] - (cond - (charm/key-match? msg \"k\") [(update state :count inc) nil] - (charm/key-match? msg \"j\") [(update state :count dec) nil] - (charm/key-match? msg \"q\") [state charm/quit-cmd] - :else [state nil])) - - (defn view [state] - (str \"Count: \" (:count state) \"\\n\\n(j/k to change, q to quit)\")) - - (charm/run {:init {:count 0} - :update update-fn - :view view}) - ```" - (:require - [charm.components.help :as help] - [charm.components.list :as list-comp] - [charm.components.paginator :as paginator] - [charm.components.progress :as progress] - [charm.components.spinner :as spinner] - [charm.components.table :as table] - [charm.components.text-input :as text-input] - [charm.components.timer :as timer] - [charm.components.viewport :as viewport] - [charm.message :as msg] - [charm.program :as prog] - [charm.style.core :as style] - [charm.style.overlay :as overlay] - [charm.terminal :as term])) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.message -;; --------------------------------------------------------------------------- - -(def key-press msg/key-press) -(def window-size msg/window-size) -(def quit msg/quit) -(def error msg/error) -(def mouse msg/mouse) -(def focus msg/focus) -(def blur msg/blur) - -(def key-press? msg/key-press?) -(def window-size? msg/window-size?) -(def quit? msg/quit?) -(def error? msg/error?) -(def mouse? msg/mouse?) - -(def key-match? msg/key-match?) -(def ctrl? msg/ctrl?) -(def alt? msg/alt?) -(def shift? msg/shift?) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.terminal -;; --------------------------------------------------------------------------- - -(def create-terminal term/create-terminal) -(def get-size term/get-size) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.program -;; --------------------------------------------------------------------------- - -(def cmd prog/cmd) -(def batch prog/batch) -(def sequence-cmds prog/sequence-cmds) -(def quit-cmd prog/quit-cmd) -(def run prog/run) -(def run-async prog/run-async) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.style -;; --------------------------------------------------------------------------- - -(def style style/style) -(def render style/render) -(def styled style/styled) - -;; Colors -(def rgb style/rgb) -(def hex style/hex) -(def ansi style/ansi) -(def ansi256 style/ansi256) - -(def black style/black) -(def red style/red) -(def green style/green) -(def yellow style/yellow) -(def blue style/blue) -(def magenta style/magenta) -(def cyan style/cyan) -(def white style/white) - -;; Borders -(def normal-border style/normal-border) -(def rounded-border style/rounded-border) -(def thick-border style/thick-border) -(def double-border style/double-border) - -;; Layout -(def join-horizontal style/join-horizontal) -(def join-vertical style/join-vertical) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.spinner -;; --------------------------------------------------------------------------- - -(def spinner spinner/spinner) -(def spinner-init spinner/spinner-init) -(def spinner-update spinner/spinner-update) -(def spinner-view spinner/spinner-view) -(def spinner-types spinner/spinner-types) -(def spinning? spinner/spinning?) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.text-input -;; --------------------------------------------------------------------------- - -(def text-input text-input/text-input) -(def text-input-init text-input/text-input-init) -(def text-input-update text-input/text-input-update) -(def text-input-view text-input/text-input-view) -(def text-input-value text-input/value) -(def text-input-set-value text-input/set-value) -(def text-input-focus text-input/focus) -(def text-input-blur text-input/blur) -(def text-input-reset text-input/reset) - -;; Echo modes -(def echo-normal text-input/echo-normal) -(def echo-password text-input/echo-password) -(def echo-none text-input/echo-none) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.list -;; --------------------------------------------------------------------------- - -(def item-list list-comp/item-list) -(def list-init list-comp/list-init) -(def list-update list-comp/list-update) -(def list-view list-comp/list-view) -(def list-items list-comp/items) -(def list-selected-item list-comp/selected-item) -(def list-selected-index list-comp/selected-index) -(def list-set-items list-comp/set-items) -(def list-select list-comp/select) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.paginator -;; --------------------------------------------------------------------------- - -(def paginator paginator/paginator) -(def paginator-init paginator/paginator-init) -(def paginator-update paginator/paginator-update) -(def paginator-view paginator/paginator-view) -(def paginator-page paginator/page) -(def paginator-total-pages paginator/total-pages) -(def paginator-set-page paginator/set-page) -(def paginator-set-total-pages paginator/set-total-pages) -(def paginator-next-page paginator/next-page) -(def paginator-prev-page paginator/prev-page) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.timer -;; --------------------------------------------------------------------------- - -(def timer timer/timer) -(def timer-init timer/timer-init) -(def timer-update timer/timer-update) -(def timer-view timer/timer-view) -(def timer-timeout timer/timeout) -(def timer-running? timer/running?) -(def timer-timed-out? timer/timed-out?) -(def timer-start timer/start) -(def timer-stop timer/stop) -(def timer-toggle timer/toggle) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.progress -;; --------------------------------------------------------------------------- - -(def progress-bar progress/progress-bar) -(def progress-init progress/progress-init) -(def progress-update progress/progress-update) -(def progress-view progress/progress-view) -(def progress-percent progress/percent) -(def progress-set progress/set-progress) -(def progress-increment progress/increment) -(def progress-decrement progress/decrement) -(def progress-complete? progress/complete?) -(def progress-bar-styles progress/bar-styles) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.help -;; --------------------------------------------------------------------------- - -(def help help/help) -(def help-init help/help-init) -(def help-update help/help-update) -(def help-view-short help/short-help-view) -(def help-view-full help/full-help-view) -(def help-bindings help/bindings) -(def help-set-bindings help/set-bindings) -(def help-toggle-show-all help/toggle-show-all) -(def help-from-pairs help/from-pairs) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.viewport -;; --------------------------------------------------------------------------- - -(def viewport viewport/viewport) -(def viewport-init viewport/viewport-init) -(def viewport-update viewport/viewport-update) -(def viewport-view viewport/viewport-view) -(def viewport-content viewport/viewport-content) -(def viewport-set-content viewport/viewport-set-content) -(def viewport-set-dimensions viewport/viewport-set-dimensions) -(def viewport-scroll-to viewport/viewport-scroll-to) -(def viewport-scroll-percent viewport/viewport-scroll-percent) -(def viewport-at-top? viewport/viewport-at-top?) -(def viewport-at-bottom? viewport/viewport-at-bottom?) -(def viewport-line-count viewport/viewport-line-count) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.components.table -;; --------------------------------------------------------------------------- - -(def table table/table) -(def table-init table/table-init) -(def table-update table/table-update) -(def table-view table/table-view) -(def table-rows table/table-rows) -(def table-set-rows table/table-set-rows) -(def table-cursor table/table-cursor) -(def table-selected-row table/table-selected-row) -(def table-set-cursor table/table-set-cursor) - -;; --------------------------------------------------------------------------- -;; Re-exported from charm.style.overlay -;; --------------------------------------------------------------------------- - -(def place-overlay overlay/place-overlay) -(def center-overlay overlay/center-overlay)