• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

cbeams/H21-Apr-2020-533404

screenshots/H03-May-2022-

.gitignoreH A D21-Apr-202079 108

LICENSEH A D21-Apr-20201.5 KiB2922

MANIFEST.inH A D21-Apr-202017 31

MakefileH A D21-Apr-20202 KiB8046

README.mdH A D21-Apr-20207.9 KiB184133

requirements-dev.inH A D21-Apr-2020106 107

requirements.inH A D21-Apr-202058 42

setup.pyH A D21-Apr-20204.9 KiB14492

README.md

1# cbeams
2
3A command-line program which draws pretty animated colored circles in the
4terminal.
5
6> *I've seen things you people wouldn't believe. Attack ships on fire off the
7> shoulder of Orion. I watched c-beams glitter in the dark, near the
8> Tannhäuser Gate. All those moments will be lost, in time, like tears in
9> rain. Time to die.*
10
11[![click to see animation](screenshots/cbeams.png)](https://asciinema.org/a/141032)
12
13Click the screenshot for an asciicast showing it in motion (Thanks asciinema!)
14It looks even better running in a large terminal locally, where the animation
15is smoother. You should download and run it!
16
17    $ pip install --user cbeams
18    $ cbeams
19
20# Downloading as a binary executable
21
22Older releases contained a Linux binary executable, downloaded from:
23
24    https://github.com/tartley/cbeams/releases
25
26But I'm not building those any more. Get it of PyPI using pip as above.
27
28# Downloading as source
29
30## Dependencies
31
32Developed on on Ubuntu 14.04, likely works on other Linux.
33Does work on OSX.
34Does not work on Windows.
35
36Tested on Python 3.4 & 3.5. Recent releases on 3.8.
37Probably also runs on other 3.x.
38Does not run on 2.x.
39
40Python dependencies are specified in setup.py.
41
42# Usage
43
44    cbeams [-o]
45    cbeams [-h]
46
47    -o  Overwrites one screenful of the existing terminal contents
48    -h  Displays help.
49
50Pressing ctrl-C exits cbeams, flipping back to the regular terminal buffer, so
51the animation doesn't overwrite any of your previous terminal contents.
52
53For fun, there's also a '-o' arg, which overwrites the terminal text without
54flipping buffers. So you can see the expanding circles slowly eat away at your
55existing terminal text, but then when you ctrl-c, it's not possible to restore
56the terminal. So one screenful of your terminal text is overwritten and lost.
57
58# Why did I develop this?
59
60The traditional way to do colors or animation in a terminal is to use the
61venerable UNIX library 'curses', or its open source clone 'ncurses'. There are
62many Python packages that expose ncurses for various uses. Anyone who has used
63these knows that curses is a definite contender for one of the worst APIs in
64existence. It systematically exposes callers to reams of the incidental
65complexity of the underlying implementation, accumulated by supporting decades
66of generations of different terminals and terminal emulators.
67
68Fortunately, nowadays there is a better way. Erik Rose's 'Blessings' package
69layers a sane API on top of ncurses. The documentation page shows how 21 lines
70of incomprehensible code using curses is transformed into four straightforward
71lines of code using blessings.
72
73I wanted an excuse to learn how blessings works, and cbeams is the result.
74
75I tag it onto the end of long-running commands to use as a visual notification
76that the command has finished.
77
78# How it works
79
80Aside from the use of Blessings, the other fun part of this project was in
81representing the circles.
82
83Obviously the model represents circles as a center point, radius, and a color.
84To display these, we convert it into a representation that's useful for
85outputting to the terminal. Namely, each circle is converted into a sequence of
86horizontal slices. Each slice has a vertical position, a leftmost start
87position, and a rightmost end position. To display this to the terminal, we
88just print a line of colored space characters for each slice. Care was taken to
89ensure that the resulting printed shape ended up symmetrical about both the
90vertical and horizontal axes.
91
92Interestingly, this representation as a series of horizontal slices lends
93itself to representing other arbitrary shapes as well as just circles.
94
95With this in mind, obviously the animation doesn't actually display just
96circles. They are annuli or rings. So each ring is modelled as two circles, an
97outer with a color, and an inner with a lesser radius.
98
99To display a ring, we don't just draw the outer circle then overwrite it by
100drawing the inner circle in black. This would display an annoying flicker
101(there's no double-buffer or vsync), and would also prevent us from being able
102to see through the holes in each ring. Instead, we perform the spatial
103subtraction of 'outer_circle - inner_circle' to construct a new shape - the
104ring as a series of horizontal slices.
105
106It was tempting to write an arbitrary spatial operator to do this, capable of
107subtracting any arbitrary shape from another shape, each represented as a
108series of slices, resulting in a new series of slices. As satisfying as this
109might have been it's clearly out of scope for this project, and as such,
110test-driven development instead correctly steered me towards a simpler
111function, that used its knowledge of what the input shapes are to produce the
112required output in fewer lines of code.
113
114Given this, drawing an annulus can use the exact same routine that we
115previously described to draw a circle. Just iterate over the slices, printing
116an appropriate length horizontal strip of colored spaces for each.
117
118In each successive frame then, we asymptotically increase the inner and outer
119radii of each annulus towards the annulus' predetermined 'max radius'. When the
120inner radius gets close enough to that value, we remove that annulus from the
121world.
122
123There's an extra layer of complexity though. What we've described above would
124work great if there was a double-buffering mechanism, whereby each frame erased
125the whole back buffer, draw the next frame in its entirety there, and then
126flipped buffers. This mechanism doesn't exist though. Hence, drawing
127consecutive frames as described above doesn't erase the color from a character
128square once an annulus' inner-radius passes over it.
129
130To achieve that, instead of drawing the desired appearance of the current
131frame, we instead draw the delta between the current frame and the last one.
132Hence, we draw a thin colored annulus outside the outer radius, and a think
133black annulus inside the inner radius.
134
135The width of these thin "delta" annuli are just the difference between the
136current radius value and the last frame. Hence, they are usually less than one
137character wide, taking the form of just a series of disconnected dots speckling
138the outer and inner edges of each visible circle. Over successive frames, the
139colored dots slowly expand the outer radius, while the black ones eat away at
140the inside radius, growing the black hole there.
141
142Drawing the tiny deltas between successive frames like this prevents
143overlapping circles from flickering badly as they would if we continually
144overdrew each whole annulus on every frame.
145
146Also, it ends up making the program run faster, and hence the animation
147look pleasantly smoother, because we have far fewer characters to draw to
148the terminal each frame.
149
150We also have a random probability of adding new annuli into the world at each
151frame. This probability varies sinusoidally over time, so that there are
152quieter and noisier moments in the animation.
153
154New annuli are assigned a randomly chosen color from a set of currently allowed
155colors. We add and remove colors from that set over time, so that sometimes all
156our rings are the same colors, sometimes two colors, and sometimes many colors.
157This helps to keep the animation evolving over time, instead of looking too
158'samey' all the time.
159
160# Hacking
161
162To populate a virtualenv, run tests, etc, see the commands in the Makefile.
163These can often work in Windows too, under Bash shells like Cygwin, Msys.
164
165Populating the virtualenv in the manner shown in the Makefile will also
166add "-e ." to the virtualenv, which adds this project in 'develop mode',
167meaning both that source edits are immediately visible within the virtualenv,
168and that the application entry points listed in setup.py are converted into
169executable scripts on the PATH.
170
171# Thanks
172
173To Erik Rose, for the fabulous Blessings package.
174https://pypi.python.org/pypi/blessings
175
176# Links & Contact
177
178Python package: http://pypi.python.org/pypi/cbeams/
179
180Binaries, source, issues: https://github.com/tartley/cbeams/
181
182Author: Jonathan Hartley, email: tartley at domain tartley.com, Twitter: @tartley.
183
184