|
50 | 50 | :tip "🟡"} |
51 | 51 | :emoji-boxes {:empty "⬛️" |
52 | 52 | :full "⬜️" |
53 | | - :tip "🟨"} |
54 | | - }) |
| 53 | + :tip "🟨"}}) |
55 | 54 |
|
56 | 55 | (defn- clamp |
57 | 56 | "Clamps a value within a range." |
58 | 57 | [mn mx x] |
59 | 58 | (max mn (min mx x))) |
60 | 59 |
|
| 60 | +(defn- round |
| 61 | + "Rounds `n` to the nearest integer. Unlike `Math/round` returns an integer." |
| 62 | + [n] |
| 63 | + (int (Math/round (double n)))) |
| 64 | + |
| 65 | +(defn- round-up |
| 66 | + "Rounds `n` up to the nearest integer. Unlike `Math/ceil` returns an integer." |
| 67 | + [n] |
| 68 | + (int (Math/ceil (double n)))) |
| 69 | + |
| 70 | +(defn- round-down |
| 71 | + "Rounds `n` down to the nearest integer. Unlike `Math/floor` returns an integer." |
| 72 | + [n] |
| 73 | + (int (Math/floor (double n)))) |
| 74 | + |
| 75 | +(defn- pad-to-width |
| 76 | + "Pads `s` to width `w`, if needed." |
| 77 | + [s w] |
| 78 | + (if (< (w/display-width s) w) |
| 79 | + (str s s) |
| 80 | + s)) |
| 81 | + |
61 | 82 | (defn- redraw-progress-indicator! |
62 | 83 | "Redraws the progress indicator." |
63 | | - [style style-widths label line width counter? total digits-in-total units new-value] |
| 84 | + [style unit-width-cols body-width-chars label line counter? total digits-in-total units new-value] |
64 | 85 | ; Make sure this code is non re-entrant |
65 | 86 | (locking lock |
66 | | - (let [percent-complete (/ (double new-value) total) |
67 | | - body-cols (- width |
68 | | - (if-not (s/blank? label) (:label style-widths) 0) |
69 | | - (if (:left style) (:left style-widths) 0) |
70 | | - (if (:right style) (:right style-widths) 0) |
71 | | - (if counter? (inc (* 2 (count (str total)))) 0) |
72 | | - (if (and counter? (not (s/blank? units))) (:units style-widths) 0)) |
73 | | - tip-cols (if (:tip style) 1 0) |
74 | | - tip-chars (* tip-cols (get style-widths :tip 0)) |
75 | | - fill-cols (clamp 0 body-cols (Math/ceil (* percent-complete body-cols))) |
76 | | - fill-chars (- (Math/ceil (/ fill-cols (:full style-widths))) tip-chars) |
77 | | - empty-cols (- body-cols (* fill-chars (:full style-widths))) ; We do it this way due to rounding |
78 | | - empty-chars (Math/floor (/ empty-cols (:empty style-widths))) |
| 87 | + (let [; How complete are we? |
| 88 | + percent-complete (/ (double new-value) total) |
| 89 | + |
| 90 | + ; How many characters of each type? |
| 91 | + tip-chars (if (:tip style) 1 0) |
| 92 | + complete-chars (clamp 0 body-width-chars (round-up (* percent-complete body-width-chars))) |
| 93 | + fill-chars (clamp 0 body-width-chars (- complete-chars tip-chars)) |
| 94 | + empty-chars (clamp 0 body-width-chars (- body-width-chars complete-chars)) |
| 95 | + |
| 96 | + ; Separately, calculate how much to pad the current value |
79 | 97 | counter-pad (- digits-in-total (count (str new-value)))] |
| 98 | + (ansi/hide-cursor!) |
80 | 99 | (when line |
81 | 100 | (ansi/save-cursor!) |
82 | | - (ansi/hide-cursor!) |
83 | 101 | (jansi/cursor! 1 line)) |
84 | 102 | (print (str ; Go to the start of the line |
85 | 103 | "\r" |
|
98 | 116 | (:left-attrs style) |
99 | 117 | (:left style))) |
100 | 118 | ; Full |
101 | | - (ansi/apply-colours-and-attrs (:full-fg-colour style) |
102 | | - (:full-bg-colour style) |
103 | | - (:full-attrs style) |
104 | | - (s/join (repeat fill-chars (:full style)))) |
| 119 | + (when (pos? fill-chars) |
| 120 | + (ansi/apply-colours-and-attrs (:full-fg-colour style) |
| 121 | + (:full-bg-colour style) |
| 122 | + (:full-attrs style) |
| 123 | + (s/join (repeat fill-chars (pad-to-width (:full style) unit-width-cols))))) |
105 | 124 | ; Tip (optional) |
106 | | - (when (:tip style) |
| 125 | + (when (and (:tip style) (pos? complete-chars)) |
107 | 126 | (ansi/apply-colours-and-attrs (:tip-fg-colour style) |
108 | 127 | (:tip-bg-colour style) |
109 | 128 | (:tip-attrs style) |
110 | | - (:tip style))) |
| 129 | + (pad-to-width (:tip style) unit-width-cols))) |
111 | 130 | ; Empty |
112 | | - (ansi/apply-colours-and-attrs (:empty-fg-colour style) |
113 | | - (:empty-bg-colour style) |
114 | | - (:empty-attrs style) |
115 | | - (s/join (repeat empty-chars (:empty style)))) |
| 131 | + (when (pos? empty-chars) |
| 132 | + (ansi/apply-colours-and-attrs (:empty-fg-colour style) |
| 133 | + (:empty-bg-colour style) |
| 134 | + (:empty-attrs style) |
| 135 | + (s/join (repeat empty-chars (pad-to-width (:empty style) unit-width-cols))))) |
116 | 136 | ; Right (optional) |
117 | 137 | (when (:right style) |
118 | 138 | (ansi/apply-colours-and-attrs (:right-fg-colour style) |
|
205 | 225 | redraw-rate 10}} |
206 | 226 | f] |
207 | 227 | (when (and a f) |
208 | | - (let [style-widths (merge {:empty (valid-width (:empty style)) |
209 | | - :full (valid-width (:full style))} |
210 | | - (when-not (s/blank? label) {:label (inc (valid-width label))}) ; Include space delimiter |
211 | | - (when (:left style) {:left (valid-width (:left style))}) |
212 | | - (when (:right style) {:right (valid-width (:right style))}) |
213 | | - (when (:tip style) {:tip (valid-width (:tip style))}) |
214 | | - (when-not (s/blank? units) {:units (inc (valid-width units))})) ; Include space delimiter |
215 | | - render-fn! (partial redraw-progress-indicator! style style-widths label line width counter? total (count (str total)) units) |
| 228 | + (let [; We pre-compute a lot of stuff here, so that we're not doing it every pass through the rendering loop |
| 229 | + label-width (if-not (s/blank? label) (inc (valid-width label)) 0) ; Include space delimiter |
| 230 | + left-width (if-not (s/blank? (:left style)) (valid-width (:left style)) 0) |
| 231 | + full-width (valid-width (:full style)) |
| 232 | + tip-width (if-not (s/blank? (:tip style)) (valid-width (:tip style)) 0) |
| 233 | + empty-width (valid-width (:empty style)) |
| 234 | + right-width (if-not (s/blank? (:right style)) (valid-width (:right style)) 0) |
| 235 | + digits-in-total (count (str total)) |
| 236 | + counter-width (if counter? (+ 2 (* 2 digits-in-total)) 0) ; Include space delimier, / delimiter, current value and total |
| 237 | + units-width (if (and counter? (not (s/blank? (:units style)))) (inc (valid-width (:units style))) 0) ; Include space delimiter |
| 238 | + body-width-cols (- width label-width left-width right-width counter-width units-width) |
| 239 | + unit-width-cols (max empty-width full-width tip-width) |
| 240 | + body-width-chars (round-down (/ body-width-cols unit-width-cols)) |
| 241 | + |
| 242 | + ; Now setup the rendering function with all that pre-computed stuff baked in, and fire it off in a future that polls the atom |
| 243 | + render-fn! (partial redraw-progress-indicator! style unit-width-cols body-width-chars label line counter? total digits-in-total units) |
216 | 244 | running-promise? (promise) |
217 | | - poll-interval-ms (Math/round (double (/ 1000 redraw-rate))) |
| 245 | + poll-interval-ms (round (/ 1000 redraw-rate)) |
218 | 246 | fut (e/future* (poll-atom a running-promise? poll-interval-ms render-fn!))] |
219 | 247 | (try |
220 | 248 | (f) |
221 | 249 | (finally |
222 | 250 | ; Teardown logic |
223 | 251 | (deliver running-promise? false) |
224 | | - (future-cancel fut) ; Just in case... |
| 252 | + @fut ; Ensures any exceptions are rethrown |
225 | 253 | (locking lock ; Make sure this isn't re-entrant with the future, since the TTY can only save a single cursor position at a time |
226 | 254 | (if preserve? |
227 | 255 | ; Make sure we draw the indicator with the final value of the atom |
|
0 commit comments