#!/usr/bin/env bash # ui-block.sh - TTY-safe status "block" indicator (no special fonts) # # busy : blue block that "breathes" (background colour pulses) # ok : solid green block # fail : solid red block (changed from red X per YuKu's spec) # # Why this works on a bare TTY: the "block" is a BACKGROUND-COLOURED SPACE, # not a U+2588 glyph. No fonts, no UTF-8, no locale dependency. # # Draws on /dev/tty, never stdout: ui_choose/ui_confirm capture stdout with $(), # so a status line on stdout would corrupt every captured answer. _UB_FD="" _UB_PID="" _UB_MSG="" _ub_tty() { if [[ -z "$_UB_FD" ]]; then # probe first: a bare `exec {fd}>/dev/tty` failure still prints its error { : >/dev/tty; } 2>/dev/null || { _UB_FD=""; return 1; } exec {_UB_FD}>/dev/tty 2>/dev/null || { _UB_FD=""; return 1; } fi [[ -n "$_UB_FD" ]] } _ub_depth() { local n n=$(tput colors 2>/dev/null || echo 0) case "$n" in ''|*[!0-9]*) n=0 ;; esac if [[ "$n" -ge 256 ]]; then echo 256 elif [[ "$n" -ge 8 ]]; then echo 16 else echo 0; fi } # paint one background-coloured block (a SPACE) + message _ub_paint() { printf '\r\033[%sm \033[0m %s' "$1" "$2" >&"$_UB_FD" } _ub_busy_loop() { local depth pal=() i depth=$(_ub_depth) case "$depth" in 256) pal=(17 18 19 20 21 27 33 27 21 20 19 18) ;; # blue breathe 16) pal=(44 104 44 104) ;; # blue / bright blue *) pal=("|" "/" "-" "\\") ;; # last-resort spinner esac while :; do for i in "${pal[@]}"; do if [[ "$depth" -eq 256 ]]; then _ub_paint "48;5;$i" "$_UB_MSG" # 256-colour cube background elif [[ "$depth" -eq 16 ]]; then _ub_paint "$i" "$_UB_MSG" # plain ANSI background (44/104) else printf '\r[%s] %s' "$i" "$_UB_MSG" >&"$_UB_FD" fi sleep 0.08 || return 0 done done } _ub_stop() { if [[ -n "$_UB_PID" ]]; then kill "$_UB_PID" 2>/dev/null wait "$_UB_PID" 2>/dev/null _UB_PID="" fi } ui_block_start() { _UB_MSG="${1:-Working}" _ub_tty || return 0 # no tty: silent no-op _ub_stop _ub_busy_loop & _UB_PID=$! } ui_block_ok() { local msg="${1:-Done}" d _ub_tty || return 0 _ub_stop d=$(_ub_depth) case "$d" in 256) _ub_paint "48;5;34" "$msg" ;; 16) _ub_paint "42" "$msg" ;; *) printf '\r[OK] %s' "$msg" >&"$_UB_FD" ;; esac printf '\033[0m\n' >&"$_UB_FD" } ui_block_fail() { local msg="${1:-Failed}" d _ub_tty || return 0 _ub_stop d=$(_ub_depth) case "$d" in 256) printf '\r\033[48;5;196m \033[0m %s' "$msg" >&"$_UB_FD" ;; 16) printf '\r\033[41m \033[0m %s' "$msg" >&"$_UB_FD" ;; *) printf '\r[!] %s' "$msg" >&"$_UB_FD" ;; esac printf '\n' >&"$_UB_FD" } ui_block_close() { _ub_stop if [[ -n "$_UB_FD" ]]; then exec {_UB_FD}>&- 2>/dev/null _UB_FD="" fi return 0 # an EXIT trap's status becomes the script's exit code — never fail here } # ALWAYS install a cleanup trap in the sourcing script: # trap 'ui_block_close' EXIT INT TERM