Check out my first novel, midnight's simulacra!
Outcurses: Difference between revisions
Line 7: | Line 7: | ||
==Metric prefixes== | ==Metric prefixes== | ||
Not ncurses-related, but it's UI, so I stuck it here. Fed a <tt>uintmax_t</tt> (large enough to represent any unsigned integer type), a buffer, and a base (almost always 1000 or 1024), <tt>enmetric</tt> renders the number into the buffer such that the characteristic (material to the left of the decimal point) is less than the base. For numbers greater than the base, this means some metric suffix will be employed, and that the displayed number will have a mantissa (e.g. 1234 with a base of 1000 becomes 1.234K). Numbers through 2^89 are properly handled, using the suffixes Y, Z, E, P, T, G, M, and K. Use of base 1024 is necessary to properly employ the [https://en.wikipedia.org/wiki/Kibibyte metric units of digital information]. For instance, a "10 terabyte" hard drive typically has 10,000,000,000,000 (ten trillion) bytes, but 10 * 1,024 * 1,024 * 1,024 is 10,995,116,277,760 bytes aka 10 tibibytes. A kilobyte is 97.7% of a kibibyte, but a terabyte is only 91% of a tibibyte. | Not ncurses-related, but it's UI, so I stuck it here. Fed a <tt>uintmax_t</tt> (large enough to represent any unsigned integer type), a buffer, and a base (almost always 1000 or 1024), <tt>enmetric</tt> renders the number into the buffer such that the characteristic (material to the left of the decimal point) is less than the base. For numbers greater than the base, this means some metric suffix will be employed, and that the displayed number will have a mantissa (e.g. 1234 with a base of 1000 becomes 1.234K). Numbers through 2^89 are properly handled, using the suffixes Y, Z, E, P, T, G, M, and K. Use of base 1024 is necessary to properly employ the [https://en.wikipedia.org/wiki/Kibibyte metric units of digital information]. For instance, a "10 terabyte" hard drive typically has 10^13 == 10,000,000,000,000 (ten trillion) bytes, but 10 * 2^40 == 10 * 1,024 * 1,024 * 1,024 is 10,995,116,277,760 bytes aka 10 tibibytes. A kilobyte is 97.7% of a kibibyte, but a terabyte is only 91% of a tibibyte. | ||
The necessary output buffer is a fixed size for a given base. For 1000, at most 7 <tt>chars</tt> are necessary: | The necessary output buffer is a fixed size for a given base. For 1000, at most 7 <tt>chars</tt> are necessary: |
Revision as of 01:06, 18 October 2019
During the development of Growlight and Omphalos, I found myself implementing significant UI code atop ncurses. It's my goal to extract most of this, unify it, and make it available as liboutcurses. Code lives on github.
Why is it called 'outcurses'? Because there's more cursing, duh.
Palette fades
Back when I was a young software witch, the first cool thing you did on a PC was switch to mode 13h and fade the 6 bits of its 3 color channels using some x86 assembly. The effect still fills me with joy. Now it's available to your text-mode programs! Specify a number of milliseconds through which you wish to fade, and a balanced, delay-adaptive fade will be executed across the palette. The default text color (colorpair -1 in ncurses semantics) is *not* faded.
Metric prefixes
Not ncurses-related, but it's UI, so I stuck it here. Fed a uintmax_t (large enough to represent any unsigned integer type), a buffer, and a base (almost always 1000 or 1024), enmetric renders the number into the buffer such that the characteristic (material to the left of the decimal point) is less than the base. For numbers greater than the base, this means some metric suffix will be employed, and that the displayed number will have a mantissa (e.g. 1234 with a base of 1000 becomes 1.234K). Numbers through 2^89 are properly handled, using the suffixes Y, Z, E, P, T, G, M, and K. Use of base 1024 is necessary to properly employ the metric units of digital information. For instance, a "10 terabyte" hard drive typically has 10^13 == 10,000,000,000,000 (ten trillion) bytes, but 10 * 2^40 == 10 * 1,024 * 1,024 * 1,024 is 10,995,116,277,760 bytes aka 10 tibibytes. A kilobyte is 97.7% of a kibibyte, but a terabyte is only 91% of a tibibyte.
The necessary output buffer is a fixed size for a given base. For 1000, at most 7 chars are necessary:
- 3 for the characteristic (or 1 with 3 chars of mantissa, etc.)
- 0 <= characteristic < base
- 1 for the decimal point
- 1 for the mantissa (or 2 with 2 chars of characteristic, etc.)
- 0 <= mantissa <= 0.999 (mantissa range is independent of base)
- 1 for the metric suffix
- 1 for the NUL terminator
An optional universal suffix can be supplied to enmetric; this is printed following the metric suffix. This is intended to support 'i' when the base is 1024. In this maximal case, 9 chars are necessary. Less space than this can always be used; the output will be left-padded with spaces. The minimal output is two bytes (a single digit and a NUL).
If the omitdec boolean is true, no decimal point or mantissa will be printed when said mantissa would be all zeroes.
Panelreels
The Ncurses panels extension (originating in AT&T System V) facilitates management of a deck of possibly-overlapping window objects sharing a screen (there's little point in using panels if windows are strictly tiled among the screen, but not much reason not to, either). The various panels of a screen have a z-ordering, and higher panels obscure panels underneath. Using the panels extension requires linking in the extra library libpanel (or libpanelw for wide character support).
The panelreel is a UI abstraction supported by outcurses in which dynamically-created and -destroyed toplevel entities (referred to as tablets) are arranged in a torus (circular loop), allowing for infinite scrolling (infinite scrolling can be disabled, resulting in a line segment rather than a torus). This works naturally with keyboard navigation, mouse scrolling wheels, and touchpads (including the capacitive touchscreens of modern cell phones). The "panel" comes from the underlying ncurses objects (each entity corresponds to a single panel) and the "reel" from slot machines. A panelreel initially has no tablets; at any given time thereafter, it has zero or more tablets, and if there is at least one tablet, one tablet is focused (and on-screen). If the last tablet is removed, no tablet is focused. A tablet can support navigation within the tablet, in which case there is an in-tablet focus for the focused tablet, which can also move among elements within the tablet.
The panelreel object tracks the size of the screen, the size, number, information depth, and order of tablets, and the focuses. It also draws the optional borders around tablets and the optional border of the reel itself. It knows nothing about the actual content of a tablet, save the number of lines it occupies at each information depth. The typical control flow is that an application receives events (from the UI or other event sources), and calls into outcurses saying e.g. "Tablet 2 now has 40 valid lines of information". Outcurses might then call back into the application, asking it to draw some line(s) from some tablet(s) at some particular coordinate of that tablet's panel. Finally, control returns to the application, and the cycle starts anew.
Each tablet might be wholly, partially, or not on-screen. Outcurses always places as much of the focused tablet as is possible on-screen (if the focused tablet has more lines than the actual reel does, it cannot be wholly on-screen. In this case, the focused subelements of the tablet are always on-screen). The placement of the focused tablet depends on how it was reached (when moving to the next tablet, offscreen tablets are brought onscreen at the bottom. When moving to the previous tablet, offscreen tablets are brought onscreen at the top. When moving to an arbitrary tablet which is neither the next nor previous tablet, it will be placed in the center).
The controlling application can, at any time,
- Insert a new tablet somewhere in the reel (possibly off-screen)
- Delete a (possibly off-screen) tablet from the reel
- Change focus to the next or previous tablet, bringing it on-screen if it is off
- Change focus to some arbitrary other tablet, bringing it on-screen if it is off
- Expand or collapse the information depth of a tablet
- Change the content of a tablet, updating it if it is on-screen
- Remove content from a tablet, possibly resizing it, and possibly changing focus within the tablet
- Add content to the tablet, possibly resizing it, and possibly creating focus within the tablet
- Navigate within the focused tablet
- Create or destroy new panels atop the panelreel
- Indicate that the screen has been resized or needs be redrawn
A special case arises when moving among the tablets of a reel having multiple tablets, all of which fit entirely on-screen, and infinite scrolling is in use. Normally, upon moving to the next tablet from the bottommost tablet, the (offscreen) next tablet is pulled up into the bottom of the reel (the reverse is true when moving to the previous tablet from the topmost). When all tablets are onscreen with infinite scrolling, there are two possibilities: either the focus scrolls (moving from the bottom tablet to the top tablet, for instance), or the reel scrolls (preserving order among the tablets, but changing their order on-screen). In this latter case, moving to the next tablet from the bottommost tablet results in the tablet which is gaining focus being brought to the bottom of the screen from the top, and all other tablets moving up on the screen. Moving to the previous tablet from the topmost tablet results in the bottommost tablet moving to the top of the screen, and all other tablets moving down. This behavior matches the typical behavior precisely, and avoids a rude UI discontinuity when the tablets grow to fill the entire screen (or shrink to not fill it). If it is not desired, however, scrolling of focus can be configured instead.
See also
- My Xcurses project, which went exactly nowhere