AtlatestRepositorysigil-tui
1
;;; Chat Demo — exercises all sigil-tui components2
;;;3
;;; Run with:4
;;; ./build/dev/bin/sigil run packages/sigil-tui/examples/chat-demo.sgl6
(import (sigil core)7
(sigil struct)8
(sigil math)9
(sigil time)10
(sigil tui))12
;; ============================================================13
;; Data model14
;; ============================================================16
(define-struct contact17
(name default: "")18
(online? default: #f)19
(group? default: #f))21
(define-struct message22
(sender default: "")23
(text default: "")24
(timestamp default: 0))26
;; Application state — all mutable for easy updates27
(define-struct app-state28
(contacts default: '() mutable: #t)29
(selected default: 0 mutable: #t)30
(conversations default: '() mutable: #t) ;; alist: name -> list of messages31
(input default: #f mutable: #t)32
(mode default: 'roster mutable: #t) ;; 'roster or 'chat33
(scroll-offset default: 0 mutable: #t)34
(last-tick default: 0 mutable: #t)35
(sim-counter default: 0 mutable: #t))37
;; ============================================================38
;; Helpers39
;; ============================================================41
(define (format-time ts)42
(let* ((total (exact (floor ts)))43
(hours (modulo (quotient total 3600) 24))44
(mins (modulo (quotient total 60) 60))45
(h-str (if (< hours 10)46
(string-append "0" (number->string hours))47
(number->string hours)))48
(m-str (if (< mins 10)49
(string-append "0" (number->string mins))50
(number->string mins))))51
(string-append h-str ":" m-str)))53
(define (get-conversation state name)54
(let ((entry (assoc name (app-state-conversations state))))55
(if entry (cdr entry) '())))57
(define (add-message! state contact-name sender text)58
(let* ((msg (message sender: sender59
text: text60
timestamp: (current-second)))61
(convs (app-state-conversations state))62
(entry (assoc contact-name convs))63
(msgs (if entry (cdr entry) '()))64
(new-msgs (append msgs (list msg))))65
(if entry66
(set-cdr! entry new-msgs)67
(set-app-state-conversations! state68
(cons (cons contact-name new-msgs) convs)))))70
(define (selected-contact state)71
(let ((contacts (app-state-contacts state))72
(idx (app-state-selected state)))73
(if (< idx (length contacts))74
(list-ref contacts idx)75
#f)))77
;; ============================================================78
;; Simulated incoming messages79
;; ============================================================81
(define *sim-messages*82
'(("alice" "Hey, how's it going?")83
("alice" "Working on anything fun?")84
("bob" "Did you see the game last night?")85
("alice" "I just finished the new TUI library!")86
("#general" "Welcome to #general!")87
("bob" "Let me know when you're free")88
("#general" "Anyone working on something cool?")89
("alice" "The grid diffing is so fast")90
("bob" "Pizza for lunch?")91
("#general" "Reminder: standup at 10am")))93
(define (maybe-simulate-message! state)94
(let ((now (current-second))95
(last (app-state-last-tick state)))96
(when (> (- now last) 3.0)97
(set-app-state-last-tick! state now)98
(let* ((idx (modulo (app-state-sim-counter state)99
(length *sim-messages*)))100
(entry (list-ref *sim-messages* idx))101
(contact-name (car entry))102
(text (cadr entry))103
(sender (if (and (> (string-length contact-name) 0)104
(char=? (string-ref contact-name 0) #\#))105
"someone"106
contact-name)))107
(add-message! state contact-name sender text)108
(set-app-state-sim-counter! state109
(+ 1 (app-state-sim-counter state)))))))111
;; ============================================================112
;; UI rendering113
;; ============================================================115
(define (render-roster state)116
(let* ((contacts (app-state-contacts state))117
(items (map (lambda (c)118
(let* ((dot (if (contact-online? c) "● " "○ "))119
(name (contact-name c))120
(label (string-append dot name))121
(fg (if (contact-online? c)122
color-green123
color-bright-black))124
(bg color-default))125
(list label fg bg attr-none)))126
contacts)))127
(selectable-list items (app-state-selected state))))129
(define (render-chat-messages state contact-name)130
(let* ((msgs (get-conversation state contact-name))131
(lines (map (lambda (m)132
(let* ((time-str (format-time (message-timestamp m)))133
(sender (message-sender m))134
(text (message-text m))135
(line (string-append136
"[" time-str "] "137
sender ": " text)))138
(if (string=? sender "you")139
(list line color-cyan color-default attr-none)140
(list line color-default color-default attr-none))))141
msgs)))142
(if (null? lines)143
(text-block (list (list "No messages yet."144
color-bright-black color-default attr-italic)))145
(text-block lines))))147
(define (render-app state)148
(let* ((contact (selected-contact state))149
(contact-name (if contact (contact-name contact) ""))150
(chat-title (string-append "Chat: " contact-name))151
(input-st (app-state-input state))152
(prompt (string-append contact-name "> "))153
(status-contact (if contact154
(string-append contact-name155
(if (contact-online? contact)156
" (online)"157
" (offline)"))158
""))159
(mode-str (if (eq? (app-state-mode state) 'roster)160
"[roster]"161
"[chat]"))162
(status-right (string-append mode-str " Ctrl-Q quit")))163
(vertical-split 0.92164
(horizontal-split 0.25165
(panel "Roster" (render-roster state))166
(vertical-split 0.88167
(panel chat-title168
(render-chat-messages state contact-name))169
(panel ""170
(text-input-widget prompt171
(input-value input-st)172
(input-cursor input-st)))))173
(status-bar "chat-demo" status-contact status-right))))175
;; ============================================================176
;; Event handling177
;; ============================================================179
(define (handle-event state event)180
(maybe-simulate-message! state)182
(cond183
;; Quit184
((and (key-event? event)185
(equal? event '(key #\q ctrl)))186
'quit)188
;; Tab to switch focus mode189
((and (key-event? event)190
(equal? event '(key tab)))191
(set-app-state-mode! state192
(if (eq? (app-state-mode state) 'roster) 'chat 'roster))193
state)195
;; Roster mode keys196
((eq? (app-state-mode state) 'roster)197
(handle-roster-event state event))199
;; Chat mode keys200
((eq? (app-state-mode state) 'chat)201
(handle-chat-event state event))203
(else state)))205
(define (handle-roster-event state event)206
(cond207
((and (key-event? event)208
(equal? event '(key up)))209
(let ((sel (app-state-selected state)))210
(when (> sel 0)211
(set-app-state-selected! state (- sel 1))))212
state)214
((and (key-event? event)215
(equal? event '(key down)))216
(let ((sel (app-state-selected state))217
(max-idx (- (length (app-state-contacts state)) 1)))218
(when (< sel max-idx)219
(set-app-state-selected! state (+ sel 1))))220
state)222
;; Enter or right arrow switches to chat223
((and (key-event? event)224
(or (equal? event '(key enter))225
(equal? event '(key right))))226
(set-app-state-mode! state 'chat)227
state)229
(else state)))231
(define (handle-chat-event state event)232
(cond233
;; Left arrow goes back to roster234
((and (key-event? event)235
(equal? event '(key left)))236
(set-app-state-mode! state 'roster)237
state)239
;; Pass key events to input240
((key-event? event)241
(let ((result (input-handle-key (app-state-input state) event)))242
(when (eq? result 'submit)243
(let* ((contact (selected-contact state))244
(text (input-value (app-state-input state))))245
(when (and contact (> (string-length text) 0))246
(add-message! state (contact-name contact) "you" text)247
(input-clear! (app-state-input state))))))248
state)250
(else state)))252
;; ============================================================253
;; Main254
;; ============================================================256
(define (make-initial-state)257
(let ((state (app-state258
contacts: (list259
(contact name: "alice" online?: #t)260
(contact name: "bob" online?: #f)261
(contact name: "#general" online?: #t group?: #t)262
(contact name: "#random" online?: #f group?: #t))263
input: (make-input-state)264
last-tick: (current-second))))265
;; Seed some initial messages266
(add-message! state "alice" "alice" "Hey, welcome to the chat!")267
(add-message! state "alice" "you" "Thanks! Testing the TUI.")268
(add-message! state "#general" "admin" "Welcome to #general")269
state))271
(tui-run272
render-app273
initial-state: (make-initial-state)274
on-event: handle-event275
fps: 30)