github yappologistic/CUI v1.2.0
CUI 1.2

one hour ago

Added

  • Right-to-left layout (pull request #2, by Omid Saadat): cui_direction with CUI_DIRECTION_INHERIT, LTR, RTL and AUTO (from the first strong character) on cui_ctx_desc, cui_box and cui_text_opts, inherited by children. Under RTL, rows, alignment, padding, grids, stacks and horizontal scrolling mirror, and so do directional icons, switches, progress fills, placeholders and popovers; clicks land on the mirrored rectangles. With CUI_TEXT_SHAPING=ON a container's direction is the base direction of its paragraphs. cui_text_align gains CUI_TEXT_START and CUI_TEXT_END. Sliders fill from the right and follow the pointer and arrow keys that way; radio cards swap the arrow keys. Code, numbers, times, key combinations, the week calendar and the gantt keep left to right inside an RTL screen. See Layout.
  • cui_state_on_free(ui, id, fn): a function that frees what a piece of state holds (textures, buffers) when the state is dropped or the context is destroyed.
  • cui_request_wakeup(ui, seconds): a frame within that many seconds with no input, for content that changes on a timer (a clock, polling, messages arriving). An app that used to redraw continuously for that can now sleep in between.
  • Soak test (tests/soak.c): runs an app the way a person would, clicking what it drew, typing, scrolling, dragging, resizing and leaving it idle, for as long as asked. It records library memory, allocations per frame, frame times, textures and process memory, and fails if anything is left allocated after cui_destroy. It runs headless at many times real speed, or in a real window driven by real input events.
  • Emoji picker and color emoji: the 1,914 emoji of Unicode 17 with names, groups, search, recent emoji, skin tones and a preview. It has keys and touch scrolling. Emoji come from the system's color emoji font (sbix, CBDT or COLR version 0), read from the file as needed, with sequences found through the font's ligatures. cui_emoji and cui_draw_emoji draw an emoji anywhere. The built-in list adds about 16 KB.
  • Terminal: output over an input line. It reads ANSI colors (16, 256 and 24-bit), bold, dim, underline and inverse, \r progress bars, tabs and clear sequences, and drops the escapes it does not draw. Lines wrap at the panel width, only the rows in view are drawn, and it follows new output. It has drag-to-copy without the colors, command history, Ctrl+C to interrupt and Ctrl+L to clear. The app owns a fixed ring buffer, so writing never allocates.
  • File browser and file picker: a folder view with sorting, natural name order, a filter, back, forward and up, a typeable path bar, places, type-ahead and keys. The open, save and folder dialog is drawn by CUI. Listing goes through a callback (the local disk by default), and big folders are read once and built only where they show.
  • Image viewer and cropper: zoom around the pointer, pan, double-click for actual pixels, keys, eased motion and a zoom pill. The cropper has corner and edge handles, aspect lock, a round mask for avatars, thirds lines and the size while dragging.
  • Guided tour: a spotlight that glides from control to control with a card saying what each one does. It has Back, Next and Done, progress dots and keys, and can show a centered card for steps without a target. The spotlighted control can still be tried, and the button text can be translated.
  • Month calendar: a month grid with events. Multi-day and all-day events are bars that share lanes across a week; timed events show in time order with a dot and the time, and a full day says "+N more" and opens a list. It supports keyboard navigation across months, double-click to add, localized month and day names, any first weekday, and a phone layout with colored dots and an agenda of the selected day.
  • Radial gauge: a value on a dial with colored bands, labelled ticks, a target marker and a needle that springs to new values, or a filled arc without the needle.
  • Candlestick chart: open, high, low and close with volume, overlay lines and a last price tag. It zooms around the pointer, pans by dragging, follows new candles, and shows a crosshair with each candle's values. Zoomed out, candles merge per pixel column, so 100,000 of them build in about a millisecond.
  • Knob, fader and level meter: the controls of audio, synth and mixer panels. Knobs turn by dragging up and down with a fine mode on Shift, can be bipolar or logarithmic, and reset on double-click. Faders have a labelled scale and an optional meter beside the track. Level meters have peak hold, a falling bar, a clip light and LED segments. The wheel only turns a control that has focus.
  • cui_set_strings(ui, &table): month names, short month names, weekday names and "Today" in another language, switched at runtime with one call (issue #11, by pdenapo). A partial table falls back to English for what it leaves out. The date picker, date range picker, calendar, month and week calendars, gantt, calendar heatmap and file browser use it. The names used to be copied in seven files; they now live in one small file that only these components link. See Integration.
  • web/tools/build.sh app my_app.c: your own app compiled to WebAssembly and opened in the browser at #/app/my_app, beside its source (issue #14). Any app whose main calls cui_app_run builds unchanged. See Getting started.
  • Single-file distribution (issue #10, by r-lyeh): tools/amalgamate.py writes cui.h + cui.c (add one file to any build) and cui_single.h (stb style, with CUI_IMPLEMENTATION), with the window layer picked by the preprocessor and macros for what CMake options used to choose. Releases attach the files, and CI builds an app from both forms with gcc and clang, warnings as errors. See Getting started.

Changed

  • CUI_TEXT_LEFT is now 3 (CUI_TEXT_START is 0 and means left in a left-to-right box, so the default look is the same). Code that uses the names only needs a rebuild; code that stores alignments as numbers changes meaning. cui_ctx_desc, cui_box and cui_text_opts gained a direction field.
  • Animation runs at the display's rate. An animating window used to draw as fast as it could whenever the swap did not wait for the display: under software OpenGL or with vsync forced off by a driver, an always-animating app drew 150 frames a second on three cores, and the software renderer was capped at 120. Frames now come at the display's refresh rate (asked from the system, 60 when it cannot tell). OpenGL notices a swap that does not wait and paces by the clock, and pointer events no longer start frames early. A mixer panel on the software renderer went from 68 frames a second at 10.6% of a core to a steady 60 at 6.8%. On Windows, timed waits use a high-resolution timer, and a raw-input flag that Wine leaves set no longer wakes every wait.
  • Dialogs, sheets and soft shadows paint about twice as fast in software. A dialog or sheet animating over a dimmed window repaints the whole window each frame, and most of that went into its shadow and the dim layer. The inside of a shadow, where it is a flat color, is now drawn as one run; long runs of one translucent color use a per-channel table that gives the same bytes; the shadow falloff comes from a table instead of erf for every pixel; and the blend is inlined. The Mail example's worst frame went from 318 ms to 60 ms (one thread at 2560 × 1600), and the Assistant's sheet from 93 ms to 61 ms. Screenshots are unchanged except for 123 shadow pixels that moved by one level out of 255.
  • Smaller builds. MinGW's linker keeps whole files (it cannot drop a function with an unwind record), so release builds of CUI now also carry link-time optimization data and the examples link with -flto=auto: assistant.exe went from 740 KB to 420 KB and hello.exe from 418 KB to 300 KB. Apps that do not use LTO link as before. Release builds on Linux and BSD leave out unwind tables, 6 to 7% of an app (hello 254 KB to 242 KB); -DCUI_KEEP_UNWIND_TABLES=ON keeps them.
  • Smaller Windows builds. The core called into the emoji code to release its texture, and Windows linkers keep a whole file once anything in it is used, so every Windows app carried the emoji picker and the PNG decoder. The core now reaches emoji through a hook set when they are first used: hello.exe went from 478 KB to 407 KB.
  • No GPU, no OpenGL. When the OpenGL driver draws on the CPU (Mesa llvmpipe, SwiftShader and the like, common in virtual machines and remote sessions), X11 and Win32 apps on the Auto renderer use CUI's software renderer instead, which redraws only what changed. Notes under llvmpipe went from 27% of a core to 4%. cui_gl_is_software tells, and CUI_RENDERER=opengl keeps OpenGL.
  • Full software repaints are about twice as fast. An opaque solid, or the inside of an opaque rounded rectangle, is filled as one run of a color instead of pixel by pixel, which was most of the work on a typical screen of panels and cards. The output is identical, byte for byte, on every screenshot.
  • Per-frame buffers shrink again. Vertices, indices, draw commands, nodes and the frame arena shrink once they have been more than four times bigger than needed for 600 frames, so a heavy page no longer leaves its buffers behind.
  • Long lists keep less state. Scrolling through thousands of rows used to leave three state entries per row for five seconds, and the table never shrank: paging through a 5,000 row grid held 15,000 entries. Now an entry that holds only last frame's rectangle expires after 30 frames, hover fades take state only once a row is hovered, buttons and links keep none until touched, and the table packs itself when mostly empty. The same paging holds about 1,700 live entries, and 150 at rest.
  • Emoji cost less. The emoji texture is released once no emoji has been drawn for 600 frames, which frees 1 to 4 MB after the picker closes. Small emoji use a quarter of the texture. Bitmap emoji are scaled with precomputed weights, which makes drawing new ones about three times faster, and at most 12 are drawn per frame, so scrolling the picker stays near 2 ms a frame.
  • The software renderer draws only what changed. cui_sw_set_incremental keeps the last frame in the buffer and repaints only the 64 pixel tiles whose triangles changed, so a hover or a caret blink costs a few tiles rather than the window. New glyphs and emoji arriving in a texture redraw only what samples them. A frame where nothing changed draws nothing, and cui_sw_last_damage tells the backend which part to copy to the screen. The app's software path and the browser build use it: with the same input, the Mail example used 3.9% of a core instead of 12.9%. Output is identical to a full repaint (tested frame by frame).
  • The glyph atlas shrinks again. It still grows when it fills up, but glyphs not drawn for five seconds (sizes from before a zoom or a change of display scale) are dropped, and the rest move into the smallest atlas they fit. A long session no longer keeps a 2048 or 4096 pixel atlas it no longer needs.
  • Truncated right-to-left text puts its ellipsis on the left, where the text ends (with text shaping).
  • Text field placeholders are shaped like other labels, so Arabic and other joined scripts read correctly in them (with text shaping). The text being edited stays unshaped, as before.
  • Every component page fits a phone. Layouts that used to need more than a phone's width now rearrange instead of shrinking the preview. The calendar, countdown, radar chart, activity rings and donut legend scale down. The waffle legend moves under its grid, and the banner puts its action under the message. The player drops its previous button, and the stepper keeps only the current label. Pagination collapses to the first, current and last pages, and the status bar keeps icons and drops labels. Toolbars, path bars and inspectors fold, and the dock shrinks its icons and stops magnifying.
  • Docking shows every panel as a tab of one stack when the dockspace is too narrow for panes side by side. The saved layout is untouched, and the panes come back when there is room.
  • Segmented controls and button groups tighten their padding before a label truncates.
  • Sankey, funnel and heatmaps keep their labels readable on narrow charts. Sankey labels stack over their values, funnel step rates shorten, heatmaps label every other row, and the calendar heatmap drops its Less and More words.
  • Gantt charts use a slimmer task list on phones, so the timeline keeps most of the width. Tables fade at the edge that has more columns.
  • Prompt suggestions and radio cards drop to fewer columns before their text gets cramped. The command palette's key hints wrap to a second line.
  • Signatures scale down with a narrower pad instead of being cut off.
  • The docs site lays out each preview at the width it has before measuring it, so components that adapt are shown at full size.

Fixed

  • In radio cards, one press of an arrow key moved two cards: the card that took focus acted on the same press.
  • The gallery's image compare page made new textures each time it was visited and never freed the old ones, about 2 MB per visit. Found by the soak test.
  • Joined wheel pickers in the demo sit flush, so the selection band runs across all three.
  • A demo sankey read past the end of its link array.
  • The X11 backend declared one typedef twice, which C99 does not allow (clang warned about it).

Documentation

  • How CUI is built: the frame lifecycle, where things live, the outline every component follows and how to change code without changing behavior. Contributing gains rules for keeping the code readable, and the largest components were split into named phases with no change in what they draw.
  • Integration explains what a window costs in memory with each renderer, measured, and how much of it is the graphics driver rather than CUI: a bare OpenGL window takes 76 MB on its own on the test machine, CUI's own memory is 2 MB or so (issue #9).
  • Getting started lists what to install on Windows, macOS and three Linux families, builds and runs an example from a fresh checkout, explains running in the browser and where Android and iOS stand, and has a troubleshooting table for the common build and startup problems (issue #3).

Don't miss a new CUI release

NewReleases is sending notifications on new releases.