mirror of
				https://github.com/nicbarker/clay.git
				synced 2025-11-04 08:36:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1777 lines
		
	
	
		
			71 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1777 lines
		
	
	
		
			71 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
    zlib/libpng license
 | 
						|
 | 
						|
    Copyright (c) 2025 Mivirl
 | 
						|
 | 
						|
    This software is provided 'as-is', without any express or implied warranty.
 | 
						|
    In no event will the authors be held liable for any damages arising from the
 | 
						|
    use of this software.
 | 
						|
 | 
						|
    Permission is granted to anyone to use this software for any purpose,
 | 
						|
    including commercial applications, and to alter it and redistribute it
 | 
						|
    freely, subject to the following restrictions:
 | 
						|
 | 
						|
        1. The origin of this software must not be misrepresented; you must not
 | 
						|
        claim that you wrote the original software. If you use this software in a
 | 
						|
        product, an acknowledgment in the product documentation would be
 | 
						|
        appreciated but is not required.
 | 
						|
 | 
						|
        2. Altered source versions must be plainly marked as such, and must not
 | 
						|
        be misrepresented as being the original software.
 | 
						|
 | 
						|
        3. This notice may not be removed or altered from any source
 | 
						|
        distribution.
 | 
						|
*/
 | 
						|
 | 
						|
#include "../../clay.h"
 | 
						|
 | 
						|
#include "image_character_masks.h"
 | 
						|
 | 
						|
#define TB_OPT_ATTR_W 32 // Required for truecolor support
 | 
						|
#include "termbox2.h"
 | 
						|
 | 
						|
#include "stb_image.h"
 | 
						|
#include "stb_image_resize2.h"
 | 
						|
 | 
						|
// -------------------------------------------------------------------------------------------------
 | 
						|
// -- Data structures
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    int width, height;
 | 
						|
} clay_tb_dimensions;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    float width, height;
 | 
						|
} clay_tb_pixel_dimensions;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    int x, y;
 | 
						|
    int width, height;
 | 
						|
} clay_tb_cell_bounding_box;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    Clay_Color clay;
 | 
						|
    uintattr_t termbox;
 | 
						|
} clay_tb_color_pair;
 | 
						|
 | 
						|
enum border_mode {
 | 
						|
    CLAY_TB_BORDER_MODE_DEFAULT,
 | 
						|
    CLAY_TB_BORDER_MODE_ROUND,
 | 
						|
    CLAY_TB_BORDER_MODE_MINIMUM,
 | 
						|
};
 | 
						|
 | 
						|
enum border_chars {
 | 
						|
    CLAY_TB_BORDER_CHARS_DEFAULT,
 | 
						|
    CLAY_TB_BORDER_CHARS_ASCII,
 | 
						|
    CLAY_TB_BORDER_CHARS_UNICODE,
 | 
						|
    CLAY_TB_BORDER_CHARS_BLANK,
 | 
						|
    CLAY_TB_BORDER_CHARS_NONE,
 | 
						|
};
 | 
						|
 | 
						|
enum image_mode {
 | 
						|
    CLAY_TB_IMAGE_MODE_DEFAULT,
 | 
						|
    CLAY_TB_IMAGE_MODE_PLACEHOLDER,
 | 
						|
    CLAY_TB_IMAGE_MODE_BG,
 | 
						|
    CLAY_TB_IMAGE_MODE_ASCII_FG,
 | 
						|
    CLAY_TB_IMAGE_MODE_ASCII_FG_FAST,
 | 
						|
    CLAY_TB_IMAGE_MODE_ASCII,
 | 
						|
    CLAY_TB_IMAGE_MODE_ASCII_FAST,
 | 
						|
    CLAY_TB_IMAGE_MODE_UNICODE,
 | 
						|
    CLAY_TB_IMAGE_MODE_UNICODE_FAST,
 | 
						|
};
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    // Stores information about image loaded from stb
 | 
						|
    int pixel_width, pixel_height;
 | 
						|
    unsigned char *pixel_data;
 | 
						|
 | 
						|
    // Internal cached data from previous renders
 | 
						|
    struct {
 | 
						|
        enum image_mode last_image_mode;
 | 
						|
        int width, height;
 | 
						|
        size_t size_max;
 | 
						|
        uint32_t *characters;
 | 
						|
        Clay_Color *foreground;
 | 
						|
        Clay_Color *background;
 | 
						|
 | 
						|
        // Data storing progress of partially complete image conversions that take multiple renders
 | 
						|
        struct clay_tb_partial_render {
 | 
						|
            bool in_progress;
 | 
						|
            unsigned char *resized_pixel_data;
 | 
						|
            int cursor_x, cursor_y;
 | 
						|
            int cursor_mask;
 | 
						|
            int min_difference_squared_sum;
 | 
						|
            int best_mask;
 | 
						|
            Clay_Color best_foreground, best_background;
 | 
						|
        } partial_render;
 | 
						|
    } internal;
 | 
						|
} clay_tb_image;
 | 
						|
 | 
						|
// Truecolor is only enabled if TB_OPT_ATTR_W is set to 32 or 64. The default is 16, so it must be
 | 
						|
// defined to reference the constant
 | 
						|
#ifndef TB_OUTPUT_TRUECOLOR
 | 
						|
#define TB_OUTPUT_TRUECOLOR (TB_OUTPUT_GRAYSCALE + 1)
 | 
						|
#endif
 | 
						|
 | 
						|
// Constant that doesn't collide with termbox2's existing output modes
 | 
						|
#define CLAY_TB_OUTPUT_NOCOLOR 0
 | 
						|
 | 
						|
#if !(defined NDEBUG || defined CLAY_TB_NDEBUG)
 | 
						|
#define clay_tb_assert(condition, ...)                                                    \
 | 
						|
    if (!(condition)) {                                                                   \
 | 
						|
        Clay_Termbox_Close();                                                             \
 | 
						|
        fprintf(stderr, "%s %d (%s): Assertion failure: ", __FILE__, __LINE__, __func__); \
 | 
						|
        fprintf(stderr, __VA_ARGS__);                                                     \
 | 
						|
        fprintf(stderr, "\n");                                                            \
 | 
						|
        exit(1);                                                                          \
 | 
						|
    }
 | 
						|
#else
 | 
						|
#define clay_tb_assert(condition, ...)
 | 
						|
#endif // NDEBUG || CLAY_TB_NDEBUG
 | 
						|
 | 
						|
 | 
						|
// -------------------------------------------------------------------------------------------------
 | 
						|
// -- Public API
 | 
						|
 | 
						|
/**
 | 
						|
  Set the equivalent size for a terminal cell in pixels.
 | 
						|
 | 
						|
  This size is used to convert Clay's pixel measurements to terminal cells, and
 | 
						|
  affects scaling.
 | 
						|
 | 
						|
  Default dimensions were measured on Debian 12: (9, 21)
 | 
						|
 | 
						|
  \param width Width of a terminal cell in pixels
 | 
						|
  \param height Height of a terminal cell in pixels
 | 
						|
*/
 | 
						|
void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height);
 | 
						|
 | 
						|
/**
 | 
						|
  Sets the color rendering mode for the terminal
 | 
						|
 | 
						|
  \param color_mode Termbox output mode as defined in termbox2.h, excluding truecolor
 | 
						|
                     - TB_OUTPUT_NORMAL - Use default ANSI colors
 | 
						|
                     - TB_OUTPUT_256 - Use 256 terminal colors
 | 
						|
                     - TB_OUTPUT_216 - Use 216 terminal colors from 256 color mode
 | 
						|
                     - TB_OUTPUT_GRAYSCALE - Use 24 gray colors from 256 color mode
 | 
						|
                     - CLAY_TB_OUTPUT_NOCOLOR - Don't use ANSI colors at all
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Color_Mode(int color_mode);
 | 
						|
 | 
						|
/**
 | 
						|
  Sets the method for converting the width of borders to terminal cells
 | 
						|
 | 
						|
  \param border_mode Method for adjusting border sizes to fit terminal cells
 | 
						|
                      - CLAY_TB_BORDER_MODE_DEFAULT       - same as CLAY_TB_BORDER_MODE_MINIMUM
 | 
						|
                      - CLAY_TB_BORDER_MODE_ROUND         - borders will be rounded to nearest cell
 | 
						|
                                                            size
 | 
						|
                      - CLAY_TB_BORDER_MODE_MINIMUM       - borders will have a minimum width of one
 | 
						|
                                                            cell
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode);
 | 
						|
 | 
						|
/**
 | 
						|
  Sets the character style to use for rendering borders
 | 
						|
 | 
						|
  \param border_chars Characters used for rendering borders
 | 
						|
                       - CLAY_TB_BORDER_CHARS_DEFAULT - same as BORDER_UNICODE
 | 
						|
                       - CLAY_TB_BORDER_CHARS_ASCII   - Uses ascii characters: '+', '|', '-'
 | 
						|
                       - CLAY_TB_BORDER_CHARS_UNICODE - Uses unicode box drawing characters
 | 
						|
                       - CLAY_TB_BORDER_CHARS_BLANK   - Draws background colors only
 | 
						|
                       - CLAY_TB_BORDER_CHARS_NONE    - Don't draw borders
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars);
 | 
						|
 | 
						|
/**
 | 
						|
  Sets the method for drawing images
 | 
						|
 | 
						|
  \param image_mode Method for adjusting border sizes to fit terminal cells
 | 
						|
                     - CLAY_TB_IMAGE_MODE_DEFAULT       - same as CLAY_TB_IMAGE_MODE_UNICODE
 | 
						|
                     - CLAY_TB_IMAGE_MODE_PLACEHOLDER   - Draw a placeholder pattern in place of
 | 
						|
                                                          images
 | 
						|
                     - CLAY_TB_IMAGE_MODE_BG            - Draw image by setting the background color
 | 
						|
                                                          for space characters
 | 
						|
                     - CLAY_TB_IMAGE_MODE_ASCII_FG      - Draw image by setting the foreground color
 | 
						|
                                                          for ascii characters
 | 
						|
                     - CLAY_TB_IMAGE_MODE_ASCII         - Draw image by setting the foreground and
 | 
						|
                                                          background colors for ascii characters
 | 
						|
                     - CLAY_TB_IMAGE_MODE_UNICODE       - Draw image by setting the foreground and
 | 
						|
                                                          background colors for unicode characters
 | 
						|
                     - CLAY_TB_IMAGE_MODE_ASCII_FG_FAST - Draw image by setting the foreground color
 | 
						|
                                                          for ascii characters. Checks fewer
 | 
						|
                                                          characters to draw faster
 | 
						|
                     - CLAY_TB_IMAGE_MODE_ASCII_FAST    - Draw image by setting the foreground and
 | 
						|
                                                          background colors for ascii characters.
 | 
						|
                                                          Checks fewer characters to draw faster
 | 
						|
                     - CLAY_TB_IMAGE_MODE_UNICODE_FAST  - Draw image by setting the foreground and
 | 
						|
                                                          background colors for unicode characters.
 | 
						|
                                                          Checks fewer characters to draw faster
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode);
 | 
						|
 | 
						|
/**
 | 
						|
  Fuel corresponds to the amount of time spent per render on drawing images. Increasing this has
 | 
						|
  the image render faster, but the program will be less responsive until it finishes
 | 
						|
 | 
						|
  Cost to draw one cell (lengths of arrays in image_character_masks.h):
 | 
						|
  -  1 : CLAY_TB_IMAGE_MODE_BG
 | 
						|
  - 15 : CLAY_TB_IMAGE_MODE_UNICODE_FAST, CLAY_TB_IMAGE_MODE_ASCII_FAST,
 | 
						|
         CLAY_TB_IMAGE_MODE_ASCII_FG_FAST
 | 
						|
  - 52 : CLAY_TB_IMAGE_MODE_UNICODE
 | 
						|
  - 95 : CLAY_TB_IMAGE_MODE_ASCII, CLAY_TB_IMAGE_MODE_ASCII_FG
 | 
						|
 | 
						|
  \param fuel_max       Maximum amount of fuel used per render (shared between all images)
 | 
						|
  \param fuel_per_image Maximum amount of fuel used per render per image
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image);
 | 
						|
 | 
						|
/**
 | 
						|
  Enables or disables emulated transparency
 | 
						|
 | 
						|
  If the color mode is TB_OUTPUT_NORMAL or CLAY_TB_OUTPUT_NOCOLOR, transparency will not be enabled
 | 
						|
 | 
						|
  \param transparency Transparency value to set
 | 
						|
 */
 | 
						|
void Clay_Termbox_Set_Transparency(bool transparency);
 | 
						|
 | 
						|
/**
 | 
						|
  Current width of the terminal in pixels
 | 
						|
*/
 | 
						|
float Clay_Termbox_Width(void);
 | 
						|
 | 
						|
/**
 | 
						|
  Current height of the terminal in pixels
 | 
						|
*/
 | 
						|
float Clay_Termbox_Height(void);
 | 
						|
 | 
						|
/**
 | 
						|
  Current width of a terminal cell in pixels
 | 
						|
*/
 | 
						|
float Clay_Termbox_Cell_Width(void);
 | 
						|
 | 
						|
/**
 | 
						|
  Current height of a terminal cell in pixels
 | 
						|
*/
 | 
						|
float Clay_Termbox_Cell_Height(void);
 | 
						|
 | 
						|
/**
 | 
						|
  Callback function used to measure the dimensions in pixels of a text string
 | 
						|
 | 
						|
  \param text     Text to measure
 | 
						|
  \param config   Ignored
 | 
						|
  \param userData Ignored
 | 
						|
 */
 | 
						|
static inline Clay_Dimensions Clay_Termbox_MeasureText(
 | 
						|
    Clay_StringSlice text, Clay_TextElementConfig *config, void *userData);
 | 
						|
 | 
						|
/**
 | 
						|
  Load an image from a file into a format usable with this renderer
 | 
						|
 | 
						|
  Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC)
 | 
						|
 | 
						|
  Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is
 | 
						|
  used in multiple places, load it a separate time for each use to reduce unecessary reprocessing
 | 
						|
  every render.
 | 
						|
 | 
						|
  \param filename File to load image from
 | 
						|
 */
 | 
						|
clay_tb_image Clay_Termbox_Image_Load_File(const char *filename);
 | 
						|
 | 
						|
/**
 | 
						|
  Load an image from memory into a format usable with this renderer
 | 
						|
 | 
						|
  Supports image formats from stb_image (JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC)
 | 
						|
 | 
						|
  Note that rendered characters are cached in the returned `clay_tb_image`. If the same image is
 | 
						|
  used in multiple places, load it a separate time for each use to reduce unecessary reprocessing
 | 
						|
  every render.
 | 
						|
 | 
						|
  \param image Image to load. Should be the whole file copied into memory
 | 
						|
  \param size  Size of the image in bytes
 | 
						|
 */
 | 
						|
clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size);
 | 
						|
 | 
						|
/**
 | 
						|
  Free an image
 | 
						|
 | 
						|
  \param image Image to free
 | 
						|
 */
 | 
						|
void Clay_Termbox_Image_Free(clay_tb_image *image);
 | 
						|
 | 
						|
/**
 | 
						|
  Set up configuration, start termbox2, and allocate internal structures.
 | 
						|
 | 
						|
  Configuration can be overriden by environment variables:
 | 
						|
  - CLAY_TB_COLOR_MODE
 | 
						|
    - NORMAL
 | 
						|
    - 256
 | 
						|
    - 216
 | 
						|
    - GRAYSCALE
 | 
						|
    - TRUECOLOR
 | 
						|
    - NOCOLOR
 | 
						|
  - CLAY_TB_BORDER_CHARS
 | 
						|
    - DEFAULT
 | 
						|
    - ASCII
 | 
						|
    - UNICODE
 | 
						|
    - BLANK
 | 
						|
    - NONE
 | 
						|
  - CLAY_TB_IMAGE_MODE
 | 
						|
    - DEFAULT
 | 
						|
    - PLACEHOLDER
 | 
						|
    - BG
 | 
						|
    - ASCII_FG
 | 
						|
    - ASCII
 | 
						|
    - UNICODE
 | 
						|
    - ASCII_FG_FAST
 | 
						|
    - ASCII_FAST
 | 
						|
    - UNICODE_FAST
 | 
						|
  - CLAY_TB_TRANSPARENCY
 | 
						|
    - 1
 | 
						|
    - 0
 | 
						|
  - CLAY_TB_CELL_PIXELS
 | 
						|
    - 10x20
 | 
						|
 | 
						|
  Must be run before using this renderer.
 | 
						|
 | 
						|
  \param color_mode   Termbox output mode as defined in termbox2.h, excluding truecolor
 | 
						|
  \param border_mode  Method for adjusting border sizes to fit terminal cells
 | 
						|
  \param border_chars Characters used for rendering borders
 | 
						|
  \param image_mode   Method for drawing images
 | 
						|
  \param transparency Emulate transparency using background colors
 | 
						|
 */
 | 
						|
void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode,
 | 
						|
    enum border_chars border_chars, enum image_mode image_mode, bool transparency);
 | 
						|
 | 
						|
/**
 | 
						|
 Stop termbox2 and release internal structures
 | 
						|
 */
 | 
						|
void Clay_Termbox_Close(void);
 | 
						|
 | 
						|
/**
 | 
						|
  Render a set of commands to the terminal
 | 
						|
 | 
						|
  \param commands Array of render commands from Clay's CreateLayout() function
 | 
						|
 */
 | 
						|
void Clay_Termbox_Render(Clay_RenderCommandArray commands);
 | 
						|
 | 
						|
/**
 | 
						|
  Convenience function to block until an event is received from termbox. If an image is only
 | 
						|
  partially rendered, this returns immediately.
 | 
						|
 */
 | 
						|
void Clay_Termbox_Waitfor_Event(void);
 | 
						|
 | 
						|
 | 
						|
// -------------------------------------------------------------------------------------------------
 | 
						|
// -- Internal state
 | 
						|
 | 
						|
// Settings/options
 | 
						|
static bool clay_tb_initialized = false;
 | 
						|
static int clay_tb_color_mode = TB_OUTPUT_NORMAL;
 | 
						|
static bool clay_tb_transparency = false;
 | 
						|
static enum border_mode clay_tb_border_mode = CLAY_TB_BORDER_MODE_DEFAULT;
 | 
						|
static enum border_chars clay_tb_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT;
 | 
						|
static enum image_mode clay_tb_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT;
 | 
						|
 | 
						|
// Dimensions of a cell are specified in pixels
 | 
						|
// Default dimensions were measured from the default terminal on Debian 12:
 | 
						|
//  Terminal:  gnome-terminal
 | 
						|
//  Font:      "Monospace Regular"
 | 
						|
//  Font size: 11
 | 
						|
static clay_tb_pixel_dimensions clay_tb_cell_size = { .width = 9, .height = 21 };
 | 
						|
 | 
						|
// Scissor mode prevents drawing outside of the specified bounding box
 | 
						|
static bool clay_tb_scissor_enabled = false;
 | 
						|
clay_tb_cell_bounding_box clay_tb_scissor_box;
 | 
						|
 | 
						|
// Images may be drawn across multiple renders to improve responsiveness. The initial draw will be
 | 
						|
// approximate, then further partial draws will replace characters with more accurate ones
 | 
						|
static bool clay_tb_partial_image_drawn = false;
 | 
						|
 | 
						|
 | 
						|
// Maximum fuel used per render across all images
 | 
						|
static int clay_tb_image_fuel_max = 200 * 1024;
 | 
						|
// Maximum fuel used per render per image
 | 
						|
static int clay_tb_image_fuel_per_image = 100 * 1024;
 | 
						|
// Fuel used this render
 | 
						|
static int clay_tb_image_fuel_used = 0;
 | 
						|
// -----------------------------------------------
 | 
						|
// -- Color buffer
 | 
						|
 | 
						|
// Buffer storing background colors from previously drawn items. Used to emulate transparency and
 | 
						|
// set the background color for text.
 | 
						|
static Clay_Color *clay_tb_color_buffer_clay = NULL;
 | 
						|
// Dimensions are specified in cells
 | 
						|
static clay_tb_dimensions clay_tb_color_buffer_dimensions = { 0, 0 };
 | 
						|
static clay_tb_dimensions clay_tb_color_buffer_max_dimensions = { 0, 0 };
 | 
						|
 | 
						|
 | 
						|
// -------------------------------------------------------------------------------------------------
 | 
						|
// -- Internal utility functions
 | 
						|
 | 
						|
static inline bool clay_tb_valid_color(Clay_Color color)
 | 
						|
{
 | 
						|
    return (
 | 
						|
            0x00 <= color.r && color.r <= 0xff &&
 | 
						|
            0x00 <= color.g && color.g <= 0xff &&
 | 
						|
            0x00 <= color.b && color.b <= 0xff &&
 | 
						|
            0x00 <= color.a && color.a <= 0xff
 | 
						|
    );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 In 256-color mode, there are 216 colors (excluding default terminal colors and gray colors),
 | 
						|
 with 6 different magnitudes for each of r, g, b.
 | 
						|
 | 
						|
 This function clamps to the nearest intensity (represented by 0-5) that can be output in this mode
 | 
						|
 | 
						|
 Possible intensities per component: 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff
 | 
						|
 | 
						|
 Examples:
 | 
						|
 - 0x20  -> 0
 | 
						|
 - 0x2f  -> 1
 | 
						|
 - 0x85  -> 2
 | 
						|
 - 0xff  -> 5
 | 
						|
 | 
						|
 \param color 8-bit intensity of one RGB component
 | 
						|
*/
 | 
						|
static int clay_tb_rgb_intensity_to_index(int color)
 | 
						|
{
 | 
						|
    clay_tb_assert(0x00 <= color && color <= 0xff, "Invalid intensity (allowed range 0x00-0xff)");
 | 
						|
    return (color < 0x2f) ? 0
 | 
						|
        : (color < 0x73)  ? 1
 | 
						|
            : 2 + ((color - 0x73) / 0x28);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Convert an RGB color from Clay's representation to the nearest representable color in the current
 | 
						|
  termbox2 output mode
 | 
						|
 | 
						|
  \param color Color to convert
 | 
						|
 */
 | 
						|
static uintattr_t clay_tb_color_convert(Clay_Color color)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_valid_color(color), "Invalid Clay color: (%f, %f, %f, %f)", color.r,
 | 
						|
        color.g, color.b, color.a);
 | 
						|
 | 
						|
    uintattr_t tb_color = TB_DEFAULT;
 | 
						|
 | 
						|
    switch (clay_tb_color_mode) {
 | 
						|
        default: {
 | 
						|
            clay_tb_assert(false, "Invalid or unimplemented Termbox color output mode (%d)",
 | 
						|
                clay_tb_color_mode);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case TB_OUTPUT_NORMAL: {
 | 
						|
            const int color_lut_count = 16;
 | 
						|
            const uintattr_t color_lut[][4] = {
 | 
						|
                { TB_BLACK,               0x00, 0x00, 0x00 },
 | 
						|
                { TB_RED,                 0xaa, 0x00, 0x00 },
 | 
						|
                { TB_GREEN,               0x00, 0xaa, 0x00 },
 | 
						|
                { TB_YELLOW,              0xaa, 0x55, 0x00 },
 | 
						|
                { TB_BLUE,                0x00, 0x00, 0xaa },
 | 
						|
                { TB_MAGENTA,             0xaa, 0x00, 0xaa },
 | 
						|
                { TB_CYAN,                0x00, 0xaa, 0xaa },
 | 
						|
                { TB_WHITE,               0xaa, 0xaa, 0xaa },
 | 
						|
                { TB_BLACK   | TB_BRIGHT, 0x55, 0x55, 0x55 },
 | 
						|
                { TB_RED     | TB_BRIGHT, 0xff, 0x55, 0x55 },
 | 
						|
                { TB_GREEN   | TB_BRIGHT, 0x55, 0xff, 0x55 },
 | 
						|
                { TB_YELLOW  | TB_BRIGHT, 0xff, 0xff, 0x55 },
 | 
						|
                { TB_BLUE    | TB_BRIGHT, 0x55, 0x55, 0xff },
 | 
						|
                { TB_MAGENTA | TB_BRIGHT, 0xff, 0x55, 0xff },
 | 
						|
                { TB_CYAN    | TB_BRIGHT, 0x55, 0xff, 0xff },
 | 
						|
                { TB_WHITE   | TB_BRIGHT, 0xff, 0xff, 0xff }
 | 
						|
            };
 | 
						|
 | 
						|
            // Find nearest color on the lookup table
 | 
						|
            int color_index = 0;
 | 
						|
            float min_distance_squared = 0xff * 0xff * 3;
 | 
						|
            for (int i = 0; i < color_lut_count; ++i) {
 | 
						|
                float r_distance = color.r - (float)color_lut[i][1];
 | 
						|
                float g_distance = color.g - (float)color_lut[i][2];
 | 
						|
                float b_distance = color.b - (float)color_lut[i][3];
 | 
						|
 | 
						|
                float distance_squared =
 | 
						|
                    (r_distance * r_distance) +
 | 
						|
                    (g_distance * g_distance) +
 | 
						|
                    (b_distance * b_distance);
 | 
						|
 | 
						|
                // Penalize pure black and white to display faded colors more often
 | 
						|
                if (TB_BLACK == color_lut[i][0] || TB_WHITE == color_lut[i][0]
 | 
						|
                    || (TB_BLACK | TB_BRIGHT) == color_lut[i][0]
 | 
						|
                    || (TB_WHITE | TB_BRIGHT) == color_lut[i][0]) {
 | 
						|
                    distance_squared *= 2;
 | 
						|
                }
 | 
						|
 | 
						|
                if (distance_squared < min_distance_squared) {
 | 
						|
                    min_distance_squared = distance_squared;
 | 
						|
                    color_index = i;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            tb_color = color_lut[color_index][0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case TB_OUTPUT_216: {
 | 
						|
            int r_index = clay_tb_rgb_intensity_to_index((int)color.r);
 | 
						|
            int g_index = clay_tb_rgb_intensity_to_index((int)color.g);
 | 
						|
            int b_index = clay_tb_rgb_intensity_to_index((int)color.b);
 | 
						|
 | 
						|
            tb_color = 0x01 + (36 * r_index) + (6 * g_index) + (b_index);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case TB_OUTPUT_256: {
 | 
						|
            const int index_lut_count = 6;
 | 
						|
            const uintattr_t index_lut[] = { 0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff };
 | 
						|
 | 
						|
            int r_index = clay_tb_rgb_intensity_to_index((int)color.r);
 | 
						|
            int g_index = clay_tb_rgb_intensity_to_index((int)color.g);
 | 
						|
            int b_index = clay_tb_rgb_intensity_to_index((int)color.b);
 | 
						|
 | 
						|
            int rgb_color = 0x10 + (36 * r_index) + (6 * g_index) + (b_index);
 | 
						|
 | 
						|
            float rgb_r_distance = color.r - (float)index_lut[r_index];
 | 
						|
            float rgb_g_distance = color.g - (float)index_lut[g_index];
 | 
						|
            float rgb_b_distance = color.b - (float)index_lut[b_index];
 | 
						|
 | 
						|
            float rgb_distance_squared =
 | 
						|
                (rgb_r_distance * rgb_r_distance) +
 | 
						|
                (rgb_g_distance * rgb_g_distance) +
 | 
						|
                (rgb_b_distance * rgb_b_distance);
 | 
						|
 | 
						|
            int avg_color = (int)((color.r + color.g + color.b) / 3);
 | 
						|
            int gray_avg_color = (avg_color * 24 / 0x100);
 | 
						|
            int gray_color = 0xe8 + gray_avg_color;
 | 
						|
 | 
						|
            float gray_r_distance = color.r - (float)gray_avg_color;
 | 
						|
            float gray_g_distance = color.g - (float)gray_avg_color;
 | 
						|
            float gray_b_distance = color.b - (float)gray_avg_color;
 | 
						|
 | 
						|
            float gray_distance_squared =
 | 
						|
                (gray_r_distance * gray_r_distance) +
 | 
						|
                (gray_g_distance * gray_g_distance) +
 | 
						|
                (gray_b_distance * gray_b_distance);
 | 
						|
 | 
						|
            tb_color = (rgb_distance_squared < gray_distance_squared) ? rgb_color : gray_color;
 | 
						|
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case TB_OUTPUT_GRAYSCALE: {
 | 
						|
            // 24 shades of gray
 | 
						|
            float avg_color = ((color.r + color.g + color.b) / 3);
 | 
						|
            tb_color = 0x01 + (int)(avg_color * 24 / 0x100);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case TB_OUTPUT_TRUECOLOR: {
 | 
						|
            clay_tb_assert(32 <= TB_OPT_ATTR_W, "Truecolor requires TB_OPT_ATTR_W to be 32 or 64");
 | 
						|
            tb_color = ((uintattr_t)color.r << 4 * 4) + ((uintattr_t)color.g << 2 * 4)
 | 
						|
                + ((uintattr_t)color.b);
 | 
						|
            if (0x000000 == tb_color) {
 | 
						|
                tb_color = TB_HI_BLACK;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case CLAY_TB_OUTPUT_NOCOLOR: {
 | 
						|
            // Uses default terminal colors
 | 
						|
            tb_color = TB_DEFAULT;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return tb_color;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Round float to nearest integer value
 | 
						|
 | 
						|
  Used instead of roundf() so math.h doesn't need to be linked
 | 
						|
 | 
						|
  \param f Float to round
 | 
						|
 */
 | 
						|
static inline int clay_tb_roundf(float f)
 | 
						|
{
 | 
						|
    int i = f;
 | 
						|
    return (f - i > 0.5f) ? i + 1 : i;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Snap pixel values from Clay to nearest cell values
 | 
						|
 | 
						|
  Width/height accounts for offset from x/y, so a box at x=(1.2 * cell_width) and
 | 
						|
  width=(1.4 * cell_width) is snapped to x=1 and width=2.
 | 
						|
 | 
						|
  \param box Bounding box with pixel measurements to convert
 | 
						|
 */
 | 
						|
static inline clay_tb_cell_bounding_box cell_snap_bounding_box(Clay_BoundingBox box)
 | 
						|
{
 | 
						|
    return (clay_tb_cell_bounding_box) {
 | 
						|
        .x = clay_tb_roundf(box.x / clay_tb_cell_size.width),
 | 
						|
        .y = clay_tb_roundf(box.y / clay_tb_cell_size.height),
 | 
						|
        .width = clay_tb_roundf((box.x + box.width) / clay_tb_cell_size.width)
 | 
						|
            - clay_tb_roundf(box.x / clay_tb_cell_size.width),
 | 
						|
        .height = clay_tb_roundf((box.y + box.height) / clay_tb_cell_size.height)
 | 
						|
            - clay_tb_roundf(box.y / clay_tb_cell_size.height),
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Snap pixel values from Clay to nearest cell values without considering x and y position when
 | 
						|
  calculating width/height.
 | 
						|
 | 
						|
  Width/height ignores offset from x/y, so a box at x=(1.2 * cell_width) and
 | 
						|
  width=(1.4 * cell_width) is snapped to x=1 and width=1.
 | 
						|
 | 
						|
  \param box Bounding box with pixel measurements to convert
 | 
						|
 */
 | 
						|
static inline clay_tb_cell_bounding_box cell_snap_pos_ind_bounding_box(Clay_BoundingBox box)
 | 
						|
{
 | 
						|
    return (clay_tb_cell_bounding_box) {
 | 
						|
        .x = clay_tb_roundf(box.x / clay_tb_cell_size.width),
 | 
						|
        .y = clay_tb_roundf(box.y / clay_tb_cell_size.height),
 | 
						|
        .width = clay_tb_roundf(box.width / clay_tb_cell_size.width),
 | 
						|
        .height = clay_tb_roundf(box.height / clay_tb_cell_size.height),
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Get stored clay color for a position from the internal color buffer
 | 
						|
 | 
						|
  \param x X position of cell
 | 
						|
  \param y Y position of cell
 | 
						|
 */
 | 
						|
static inline Clay_Color clay_tb_color_buffer_clay_get(int x, int y)
 | 
						|
{
 | 
						|
    clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width,
 | 
						|
        "Cell buffer x position (%d) offscreen (range 0-%d)", x,
 | 
						|
        clay_tb_color_buffer_dimensions.width);
 | 
						|
    clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height,
 | 
						|
        "Cell buffer y position (%d) offscreen (range 0-%d)", y,
 | 
						|
        clay_tb_color_buffer_dimensions.height);
 | 
						|
    return clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Set stored clay color for a position in the internal color buffer
 | 
						|
 | 
						|
  \param x     X position of cell
 | 
						|
  \param y     Y position of cell
 | 
						|
  \param color Color to store
 | 
						|
 */
 | 
						|
static inline void clay_tb_color_buffer_clay_set(int x, int y, Clay_Color color)
 | 
						|
{
 | 
						|
    clay_tb_assert(0 <= x && x < clay_tb_color_buffer_dimensions.width,
 | 
						|
        "Cell buffer x position (%d) offscreen (range 0-%d)", x,
 | 
						|
        clay_tb_color_buffer_dimensions.width);
 | 
						|
    clay_tb_assert(0 <= y && y < clay_tb_color_buffer_dimensions.height,
 | 
						|
        "Cell buffer y position (%d) offscreen (range 0-%d)", y,
 | 
						|
        clay_tb_color_buffer_dimensions.height);
 | 
						|
    clay_tb_color_buffer_clay[x + (y * clay_tb_color_buffer_dimensions.width)] = color;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Resize internal color buffer to the current terminal size
 | 
						|
 */
 | 
						|
static void clay_tb_resize_buffer(void)
 | 
						|
{
 | 
						|
    int current_width = tb_width();
 | 
						|
    int current_height = tb_height();
 | 
						|
 | 
						|
    // Reallocate if the new size is larger than the maximum size of the buffer
 | 
						|
    size_t max_size = (size_t)clay_tb_color_buffer_max_dimensions.width
 | 
						|
        * clay_tb_color_buffer_max_dimensions.height;
 | 
						|
    size_t new_size = (size_t)current_width * current_height;
 | 
						|
    if (max_size < new_size) {
 | 
						|
        Clay_Color *tmp_clay = tb_realloc(clay_tb_color_buffer_clay, sizeof(Clay_Color) * new_size);
 | 
						|
        if (NULL == tmp_clay) {
 | 
						|
            clay_tb_assert(false, "Reallocation failure for internal clay color buffer");
 | 
						|
        }
 | 
						|
        clay_tb_color_buffer_clay = tmp_clay;
 | 
						|
        for (size_t i = max_size; i < new_size; ++i) {
 | 
						|
            clay_tb_color_buffer_clay[i] = (Clay_Color) { 0 };
 | 
						|
        }
 | 
						|
 | 
						|
        clay_tb_color_buffer_max_dimensions.width = current_width;
 | 
						|
        clay_tb_color_buffer_max_dimensions.height = current_height;
 | 
						|
    }
 | 
						|
    clay_tb_color_buffer_dimensions.width = current_width;
 | 
						|
    clay_tb_color_buffer_dimensions.height = current_height;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Calculate color at a given position after emulating transparency.
 | 
						|
 | 
						|
  This isn't true transparency, just the background colors changing to emulate it
 | 
						|
 | 
						|
  \param color Pair of termbox and clay color representations to overlay with the background color
 | 
						|
  \param x     X position of cell
 | 
						|
  \param y     Y position of cell
 | 
						|
 */
 | 
						|
static inline clay_tb_color_pair clay_tb_get_transparency_color(
 | 
						|
    int x, int y, clay_tb_color_pair color)
 | 
						|
{
 | 
						|
    if (!clay_tb_transparency) {
 | 
						|
        return color;
 | 
						|
    }
 | 
						|
 | 
						|
    Clay_Color color_bg = clay_tb_color_buffer_clay_get(x, y);
 | 
						|
    Clay_Color new_color = {
 | 
						|
        .r = color_bg.r + (color.clay.a / 255) * (color.clay.r - color_bg.r),
 | 
						|
        .g = color_bg.g + (color.clay.a / 255) * (color.clay.g - color_bg.g),
 | 
						|
        .b = color_bg.b + (color.clay.a / 255) * (color.clay.b - color_bg.b),
 | 
						|
        .a = 255
 | 
						|
    };
 | 
						|
 | 
						|
    return (clay_tb_color_pair) {
 | 
						|
        .clay = new_color,
 | 
						|
        .termbox = clay_tb_color_convert(new_color)
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Draw a character cell at a position on screen.
 | 
						|
 | 
						|
  Accounts for scissor mode and stores the cell to the internal color buffer for transparency and
 | 
						|
  text backgrounds.
 | 
						|
 | 
						|
  \param x     X position of cell
 | 
						|
  \param y     Y position of cell
 | 
						|
  \param ch    Utf32 representation of character to draw
 | 
						|
  \param tb_fg Foreground color in termbox representation
 | 
						|
  \param tb_bg Background color in termbox representation
 | 
						|
  \param fg    Foreground color in clay representation
 | 
						|
  \param bg    Background color in clay representation
 | 
						|
 */
 | 
						|
static int clay_tb_set_cell(
 | 
						|
    int x, int y, uint32_t ch, uintattr_t tb_fg, uintattr_t tb_bg, Clay_Color bg)
 | 
						|
{
 | 
						|
    clay_tb_assert(0 <= x && x < tb_width(), "Cell buffer x position (%d) offscreen (range 0-%d)",
 | 
						|
        x, tb_width());
 | 
						|
    clay_tb_assert(0 <= y && y < tb_height(), "Cell buffer y position (%d) offscreen (range 0-%d)",
 | 
						|
        y, tb_height());
 | 
						|
 | 
						|
    if (!clay_tb_scissor_enabled
 | 
						|
        || (clay_tb_scissor_enabled
 | 
						|
            && (clay_tb_scissor_box.x <= x
 | 
						|
                && x < clay_tb_scissor_box.x + clay_tb_scissor_box.width)
 | 
						|
            && (clay_tb_scissor_box.y <= y
 | 
						|
                && y < clay_tb_scissor_box.y + clay_tb_scissor_box.height))) {
 | 
						|
        int codepoint_width = tb_wcwidth(ch);
 | 
						|
        if (-1 == codepoint_width) {
 | 
						|
            // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD)
 | 
						|
            ch = U'\ufffd';
 | 
						|
            codepoint_width = tb_wcwidth(ch);
 | 
						|
        }
 | 
						|
 | 
						|
        int err;
 | 
						|
        int max_x = CLAY__MIN(x + codepoint_width, tb_width());
 | 
						|
        for (int i = x; i < max_x; ++i) {
 | 
						|
            clay_tb_color_buffer_clay_set(i, y, bg);
 | 
						|
            err = tb_set_cell(i, y, ch, tb_fg, tb_bg);
 | 
						|
            if (TB_OK != err) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return err;
 | 
						|
    }
 | 
						|
    return -1;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Convert a pixel-based image to a cell-based image of the specified width and height. Stores the
 | 
						|
  converted/resized result in the cache of the input image.
 | 
						|
 | 
						|
  If the image has not changed size or image mode since the last convert it is returned unchanged
 | 
						|
 | 
						|
  \param image  Image to convert/resize
 | 
						|
  \param width  Target width in cells for the converted image
 | 
						|
  \param height Target height in cells for the converted image
 | 
						|
 */
 | 
						|
bool clay_tb_image_convert(clay_tb_image *image, int width, int height)
 | 
						|
{
 | 
						|
    clay_tb_assert(NULL != image->pixel_data, "Image must be loaded");
 | 
						|
 | 
						|
    bool image_unchanged = (width == image->internal.width && height == image->internal.height
 | 
						|
        && (clay_tb_image_mode == image->internal.last_image_mode));
 | 
						|
 | 
						|
    if (image_unchanged && !image->internal.partial_render.in_progress) {
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
    if (!image_unchanged) {
 | 
						|
        free(image->internal.partial_render.resized_pixel_data);
 | 
						|
        image->internal.partial_render = (struct clay_tb_partial_render) {
 | 
						|
            .in_progress = false,
 | 
						|
            .resized_pixel_data = NULL,
 | 
						|
            .cursor_x = 0,
 | 
						|
            .cursor_y = 0,
 | 
						|
            .cursor_mask = 0,
 | 
						|
            .min_difference_squared_sum = INT_MAX,
 | 
						|
            .best_mask = 0,
 | 
						|
            .best_foreground = { 0, 0, 0, 0 },
 | 
						|
            .best_background = { 0, 0, 0, 0 }
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    const size_t size = (size_t)width * height;
 | 
						|
 | 
						|
    // Allocate/resize internal cache data
 | 
						|
    if (size > image->internal.size_max) {
 | 
						|
        uint32_t *tmp_characters = realloc(image->internal.characters, size * sizeof(uint32_t));
 | 
						|
        Clay_Color *tmp_foreground = realloc(image->internal.foreground, size * sizeof(Clay_Color));
 | 
						|
        Clay_Color *tmp_background = realloc(image->internal.background, size * sizeof(Clay_Color));
 | 
						|
 | 
						|
        if (NULL == tmp_characters || NULL == tmp_foreground || NULL == tmp_background) {
 | 
						|
            image->internal.size_max = 0;
 | 
						|
            free(tmp_characters);
 | 
						|
            free(tmp_foreground);
 | 
						|
            free(tmp_background);
 | 
						|
            image->internal.characters = NULL;
 | 
						|
            image->internal.foreground = NULL;
 | 
						|
            image->internal.background = NULL;
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        image->internal.characters = tmp_characters;
 | 
						|
        image->internal.foreground = tmp_foreground;
 | 
						|
        image->internal.background = tmp_background;
 | 
						|
        image->internal.size_max = size;
 | 
						|
    }
 | 
						|
 | 
						|
    image->internal.width = width;
 | 
						|
    image->internal.height = height;
 | 
						|
 | 
						|
    // Resize image using the same width/height in cells, but with the pixel sizes of the character
 | 
						|
    // masks instead of the cell size. The pixel data for each character mask will be compared to
 | 
						|
    // the pixel data of a small section of the image under the mask. The closest mask to the image
 | 
						|
    // data is chosen as the character to draw.
 | 
						|
    const int character_mask_pixel_width = 6;
 | 
						|
    const int character_mask_pixel_height = 12;
 | 
						|
    const int pixel_width = width * character_mask_pixel_width;
 | 
						|
    const int pixel_height = height * character_mask_pixel_height;
 | 
						|
 | 
						|
    unsigned char *resized_pixel_data;
 | 
						|
    if (image->internal.partial_render.in_progress) {
 | 
						|
        resized_pixel_data = image->internal.partial_render.resized_pixel_data;
 | 
						|
    } else {
 | 
						|
        resized_pixel_data = stbir_resize_uint8_linear(image->pixel_data, image->pixel_width,
 | 
						|
            image->pixel_height, 0, NULL, pixel_width, pixel_height, 0, STBIR_RGB);
 | 
						|
        image->internal.partial_render.resized_pixel_data = resized_pixel_data;
 | 
						|
    }
 | 
						|
 | 
						|
    int num_character_masks = 1;
 | 
						|
    const clay_tb_character_mask *character_masks = NULL;
 | 
						|
    switch (clay_tb_image_mode) {
 | 
						|
        case CLAY_TB_IMAGE_MODE_BG: {
 | 
						|
            num_character_masks = 1;
 | 
						|
            character_masks = &clay_tb_image_shapes_ascii_fast[0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case CLAY_TB_IMAGE_MODE_ASCII:
 | 
						|
        case CLAY_TB_IMAGE_MODE_ASCII_FG: {
 | 
						|
            num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_BEST_COUNT;
 | 
						|
            character_masks = &clay_tb_image_shapes_ascii_best[0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case CLAY_TB_IMAGE_MODE_UNICODE: {
 | 
						|
            num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_BEST_COUNT;
 | 
						|
            character_masks = &clay_tb_image_shapes_unicode_best[0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case CLAY_TB_IMAGE_MODE_ASCII_FAST:
 | 
						|
        case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: {
 | 
						|
            num_character_masks = CLAY_TB_IMAGE_SHAPES_ASCII_FAST_COUNT;
 | 
						|
            character_masks = &clay_tb_image_shapes_ascii_fast[0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case CLAY_TB_IMAGE_MODE_UNICODE_FAST: {
 | 
						|
            num_character_masks = CLAY_TB_IMAGE_SHAPES_UNICODE_FAST_COUNT;
 | 
						|
            character_masks = &clay_tb_image_shapes_unicode_fast[0];
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    // The number of character masks to check before exiting the render for this step
 | 
						|
    // Used to improve responsiveness by splitting renders across multiple frames
 | 
						|
    const int fuel_amount_initial
 | 
						|
        = CLAY__MIN(clay_tb_image_fuel_per_image, clay_tb_image_fuel_max - clay_tb_image_fuel_used);
 | 
						|
    int fuel_remaining = fuel_amount_initial;
 | 
						|
    bool partial_character_render = false;
 | 
						|
 | 
						|
    // Do a quick initial render to set the background
 | 
						|
    if (!image->internal.partial_render.in_progress) {
 | 
						|
        image->internal.last_image_mode = clay_tb_image_mode;
 | 
						|
        for (int y = image->internal.partial_render.cursor_y; y < height; ++y) {
 | 
						|
            for (int x = image->internal.partial_render.cursor_x; x < width; ++x) {
 | 
						|
                const int cell_top_left_pixel_x = x * character_mask_pixel_width;
 | 
						|
                const int cell_top_left_pixel_y = y * character_mask_pixel_height;
 | 
						|
                const int image_index = 3
 | 
						|
                    * (((cell_top_left_pixel_y + character_mask_pixel_height / 2) * pixel_width)
 | 
						|
                        + (cell_top_left_pixel_x + character_mask_pixel_width / 2));
 | 
						|
                Clay_Color pixel_color = {
 | 
						|
                    (float)resized_pixel_data[image_index],
 | 
						|
                    (float)resized_pixel_data[image_index + 1],
 | 
						|
                    (float)resized_pixel_data[image_index + 2],
 | 
						|
                };
 | 
						|
 | 
						|
                const int cell_index = y * width + x;
 | 
						|
                image->internal.characters[cell_index] = '.';
 | 
						|
                image->internal.foreground[cell_index] = pixel_color;
 | 
						|
                image->internal.background[cell_index] = pixel_color;
 | 
						|
 | 
						|
                fuel_remaining = CLAY__MAX(0, fuel_remaining - 1);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (0 == fuel_remaining) {
 | 
						|
        image->internal.partial_render.in_progress = true;
 | 
						|
        clay_tb_partial_image_drawn = true;
 | 
						|
        goto done;
 | 
						|
    }
 | 
						|
 | 
						|
    for (int y = image->internal.partial_render.cursor_y; y < height; ++y) {
 | 
						|
        for (int x = image->internal.partial_render.cursor_x; x < width; ++x) {
 | 
						|
            const int cell_top_left_pixel_x = x * character_mask_pixel_width;
 | 
						|
            const int cell_top_left_pixel_y = y * character_mask_pixel_height;
 | 
						|
 | 
						|
            // For each possible cell character, use the mask to find the average color for the
 | 
						|
            // foreground ('1's) and background ('0's).
 | 
						|
            int min_difference_squared_sum
 | 
						|
                = image->internal.partial_render.min_difference_squared_sum;
 | 
						|
            int best_mask = image->internal.partial_render.best_mask;
 | 
						|
            Clay_Color best_foreground = image->internal.partial_render.best_foreground;
 | 
						|
            Clay_Color best_background = image->internal.partial_render.best_background;
 | 
						|
 | 
						|
            for (int i = image->internal.partial_render.cursor_mask; i < num_character_masks; ++i) {
 | 
						|
                int color_avg_background_r = 0;
 | 
						|
                int color_avg_background_g = 0;
 | 
						|
                int color_avg_background_b = 0;
 | 
						|
                int color_avg_foreground_r = 0;
 | 
						|
                int color_avg_foreground_g = 0;
 | 
						|
                int color_avg_foreground_b = 0;
 | 
						|
                int foreground_count = 0;
 | 
						|
                int background_count = 0;
 | 
						|
 | 
						|
                for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height;
 | 
						|
                     ++cell_pixel_y) {
 | 
						|
                    for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width;
 | 
						|
                         ++cell_pixel_x) {
 | 
						|
                        const int index = 3
 | 
						|
                            * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width)
 | 
						|
                                + (cell_top_left_pixel_x + cell_pixel_x));
 | 
						|
 | 
						|
                        const int mask_index
 | 
						|
                            = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x;
 | 
						|
                        if (0 == character_masks[i].data[mask_index]) {
 | 
						|
                            if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode
 | 
						|
                                && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) {
 | 
						|
                                color_avg_background_r += resized_pixel_data[index];
 | 
						|
                                color_avg_background_g += resized_pixel_data[index + 1];
 | 
						|
                                color_avg_background_b += resized_pixel_data[index + 2];
 | 
						|
                                background_count += 1;
 | 
						|
                            }
 | 
						|
                        } else {
 | 
						|
                            color_avg_foreground_r += resized_pixel_data[index];
 | 
						|
                            color_avg_foreground_g += resized_pixel_data[index + 1];
 | 
						|
                            color_avg_foreground_b += resized_pixel_data[index + 2];
 | 
						|
                            foreground_count += 1;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (CLAY_TB_IMAGE_MODE_ASCII_FG != clay_tb_image_mode
 | 
						|
                    && CLAY_TB_IMAGE_MODE_ASCII_FG_FAST != clay_tb_image_mode) {
 | 
						|
                    color_avg_background_r /= CLAY__MAX(1, background_count);
 | 
						|
                    color_avg_background_g /= CLAY__MAX(1, background_count);
 | 
						|
                    color_avg_background_b /= CLAY__MAX(1, background_count);
 | 
						|
                } else {
 | 
						|
                    color_avg_background_r = 0;
 | 
						|
                    color_avg_background_g = 0;
 | 
						|
                    color_avg_background_b = 0;
 | 
						|
                }
 | 
						|
 | 
						|
                color_avg_foreground_r /= CLAY__MAX(1, foreground_count);
 | 
						|
                color_avg_foreground_g /= CLAY__MAX(1, foreground_count);
 | 
						|
                color_avg_foreground_b /= CLAY__MAX(1, foreground_count);
 | 
						|
 | 
						|
                // Determine the difference between the mask with colors and the actual pixel data
 | 
						|
                int difference_squared_sum = 0;
 | 
						|
                for (int cell_pixel_y = 0; cell_pixel_y < character_mask_pixel_height;
 | 
						|
                     ++cell_pixel_y) {
 | 
						|
                    for (int cell_pixel_x = 0; cell_pixel_x < character_mask_pixel_width;
 | 
						|
                         ++cell_pixel_x) {
 | 
						|
                        const int index = 3
 | 
						|
                            * (((cell_top_left_pixel_y + cell_pixel_y) * pixel_width)
 | 
						|
                                + (cell_top_left_pixel_x + cell_pixel_x));
 | 
						|
                        int rdiff, gdiff, bdiff, adiff;
 | 
						|
 | 
						|
                        const int mask_index
 | 
						|
                            = (cell_pixel_y * character_mask_pixel_width) + cell_pixel_x;
 | 
						|
                        if (0 == character_masks[i].data[mask_index]) {
 | 
						|
                            rdiff = (color_avg_background_r - resized_pixel_data[index]);
 | 
						|
                            gdiff = (color_avg_background_g - resized_pixel_data[index + 1]);
 | 
						|
                            bdiff = (color_avg_background_b - resized_pixel_data[index + 2]);
 | 
						|
                        } else {
 | 
						|
                            rdiff = (color_avg_foreground_r - resized_pixel_data[index]);
 | 
						|
                            gdiff = (color_avg_foreground_g - resized_pixel_data[index + 1]);
 | 
						|
                            bdiff = (color_avg_foreground_b - resized_pixel_data[index + 2]);
 | 
						|
                        }
 | 
						|
 | 
						|
                        difference_squared_sum += (
 | 
						|
                            (rdiff * rdiff) +
 | 
						|
                            (gdiff * gdiff) +
 | 
						|
                            (bdiff * bdiff));
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                // Choose the closest character mask to the image data
 | 
						|
                if (difference_squared_sum < min_difference_squared_sum) {
 | 
						|
                    min_difference_squared_sum = difference_squared_sum;
 | 
						|
                    best_mask = i;
 | 
						|
                    best_background = (Clay_Color) {
 | 
						|
                        .r = (float)color_avg_background_r,
 | 
						|
                        .g = (float)color_avg_background_g,
 | 
						|
                        .b = (float)color_avg_background_b,
 | 
						|
                        .a = 255
 | 
						|
                    };
 | 
						|
                    best_foreground = (Clay_Color) {
 | 
						|
                        .r = (float)color_avg_foreground_r,
 | 
						|
                        .g = (float)color_avg_foreground_g,
 | 
						|
                        .b = (float)color_avg_foreground_b,
 | 
						|
                        .a = 255
 | 
						|
                    };
 | 
						|
                }
 | 
						|
 | 
						|
                fuel_remaining -= 1;
 | 
						|
                if (0 == fuel_remaining) {
 | 
						|
                    // Set progress for partial render
 | 
						|
                    image->internal.partial_render = (struct clay_tb_partial_render) {
 | 
						|
                        .in_progress = true,
 | 
						|
                        .resized_pixel_data = resized_pixel_data,
 | 
						|
                        .cursor_x = x,
 | 
						|
                        .cursor_y = y,
 | 
						|
                        .cursor_mask = i + 1,
 | 
						|
                        .min_difference_squared_sum = min_difference_squared_sum,
 | 
						|
                        .best_mask = best_mask,
 | 
						|
                        .best_foreground = best_foreground,
 | 
						|
                        .best_background = best_background
 | 
						|
                    };
 | 
						|
                    partial_character_render = true;
 | 
						|
                    clay_tb_partial_image_drawn = true;
 | 
						|
                    goto done;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            image->internal.partial_render.cursor_mask = 0;
 | 
						|
 | 
						|
            // Set data in cache for this character
 | 
						|
            const int index = y * width + x;
 | 
						|
            image->internal.characters[index] = character_masks[best_mask].character;
 | 
						|
            image->internal.foreground[index] = best_foreground;
 | 
						|
            image->internal.background[index] = best_background;
 | 
						|
 | 
						|
            image->internal.partial_render = (struct clay_tb_partial_render) {
 | 
						|
                .in_progress = true,
 | 
						|
                .resized_pixel_data = resized_pixel_data,
 | 
						|
                .cursor_x = x + 1,
 | 
						|
                .cursor_y = y,
 | 
						|
                .cursor_mask = 0,
 | 
						|
                .min_difference_squared_sum = INT_MAX,
 | 
						|
                .best_mask = 0,
 | 
						|
                .best_foreground = { 0, 0, 0, 0 },
 | 
						|
                .best_background = { 0, 0, 0, 0 },
 | 
						|
            };
 | 
						|
            if (0 == fuel_remaining) {
 | 
						|
                clay_tb_partial_image_drawn = true;
 | 
						|
                goto done;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        image->internal.partial_render.cursor_x = 0;
 | 
						|
    }
 | 
						|
    image->internal.partial_render.cursor_y = 0;
 | 
						|
    image->internal.partial_render.in_progress = false;
 | 
						|
    free(resized_pixel_data);
 | 
						|
    image->internal.partial_render.resized_pixel_data = NULL;
 | 
						|
 | 
						|
done:
 | 
						|
    clay_tb_image_fuel_used += fuel_amount_initial - fuel_remaining;
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
// -------------------------------------------------------------------------------------------------
 | 
						|
// -- Public API implementation
 | 
						|
 | 
						|
void Clay_Termbox_Set_Cell_Pixel_Size(float width, float height)
 | 
						|
{
 | 
						|
    clay_tb_assert(0 <= width, "Cell pixel width must be > 0");
 | 
						|
    clay_tb_assert(0 <= height, "Cell pixel height must be > 0");
 | 
						|
    clay_tb_cell_size = (clay_tb_pixel_dimensions) { .width = width, .height = height };
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Color_Mode(int color_mode)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first");
 | 
						|
    clay_tb_assert(CLAY_TB_OUTPUT_NOCOLOR <= color_mode && color_mode <= TB_OUTPUT_TRUECOLOR,
 | 
						|
        "Color mode invalid (%d)", color_mode);
 | 
						|
 | 
						|
    if (CLAY_TB_OUTPUT_NOCOLOR == color_mode) {
 | 
						|
        tb_set_output_mode(TB_OUTPUT_NORMAL);
 | 
						|
    } else {
 | 
						|
        tb_set_output_mode(color_mode);
 | 
						|
    }
 | 
						|
 | 
						|
    // Force complete re-render to ensure all colors are redrawn
 | 
						|
    tb_invalidate();
 | 
						|
 | 
						|
    clay_tb_color_mode = color_mode;
 | 
						|
 | 
						|
    // Re-set transparency value. It will be toggled off if the new output mode doesn't support it
 | 
						|
    Clay_Termbox_Set_Transparency(clay_tb_transparency);
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Border_Mode(enum border_mode border_mode)
 | 
						|
{
 | 
						|
    clay_tb_assert(CLAY_TB_BORDER_MODE_DEFAULT <= border_mode
 | 
						|
            && border_mode <= CLAY_TB_BORDER_MODE_MINIMUM,
 | 
						|
        "Border mode invalid (%d)", border_mode);
 | 
						|
    if (CLAY_TB_BORDER_MODE_DEFAULT == border_mode) {
 | 
						|
        clay_tb_border_mode = CLAY_TB_BORDER_MODE_MINIMUM;
 | 
						|
    } else {
 | 
						|
        clay_tb_border_mode = border_mode;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Border_Chars(enum border_chars border_chars)
 | 
						|
{
 | 
						|
    clay_tb_assert(
 | 
						|
        CLAY_TB_BORDER_CHARS_DEFAULT <= border_chars && border_chars <= CLAY_TB_BORDER_CHARS_NONE,
 | 
						|
        "Border mode invalid (%d)", border_chars);
 | 
						|
    if (CLAY_TB_BORDER_CHARS_DEFAULT == border_chars) {
 | 
						|
        clay_tb_border_chars = CLAY_TB_BORDER_CHARS_UNICODE;
 | 
						|
    } else {
 | 
						|
        clay_tb_border_chars = border_chars;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Image_Mode(enum image_mode image_mode)
 | 
						|
{
 | 
						|
    clay_tb_assert(CLAY_TB_IMAGE_MODE_DEFAULT <= image_mode
 | 
						|
            && image_mode <= CLAY_TB_IMAGE_MODE_UNICODE_FAST,
 | 
						|
        "Image mode invalid (%d)", image_mode);
 | 
						|
    if (CLAY_TB_IMAGE_MODE_DEFAULT == image_mode) {
 | 
						|
        clay_tb_image_mode = CLAY_TB_IMAGE_MODE_UNICODE;
 | 
						|
    } else {
 | 
						|
        clay_tb_image_mode = image_mode;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Image_Fuel(int fuel_max, int fuel_per_image)
 | 
						|
{
 | 
						|
    clay_tb_assert(0 < fuel_max && 0 < fuel_per_image,
 | 
						|
            "Fuel must be positive (%d, %d)", fuel_max, fuel_per_image);
 | 
						|
    clay_tb_image_fuel_max = fuel_max;
 | 
						|
    clay_tb_image_fuel_per_image = fuel_per_image;
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Set_Transparency(bool transparency)
 | 
						|
{
 | 
						|
    clay_tb_transparency = transparency;
 | 
						|
    if (TB_OUTPUT_NORMAL == clay_tb_color_mode || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode) {
 | 
						|
        clay_tb_transparency = false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
float Clay_Termbox_Width(void)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first");
 | 
						|
 | 
						|
    return (float)tb_width() * clay_tb_cell_size.width;
 | 
						|
}
 | 
						|
 | 
						|
float Clay_Termbox_Height(void)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first");
 | 
						|
 | 
						|
    return (float)tb_height() * clay_tb_cell_size.height;
 | 
						|
}
 | 
						|
 | 
						|
float Clay_Termbox_Cell_Width(void)
 | 
						|
{
 | 
						|
    return clay_tb_cell_size.width;
 | 
						|
}
 | 
						|
 | 
						|
float Clay_Termbox_Cell_Height(void)
 | 
						|
{
 | 
						|
    return clay_tb_cell_size.height;
 | 
						|
}
 | 
						|
 | 
						|
static inline Clay_Dimensions Clay_Termbox_MeasureText(
 | 
						|
    Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first");
 | 
						|
 | 
						|
    int width = 0;
 | 
						|
    int height = 1;
 | 
						|
 | 
						|
    // Convert to utf32 so termbox2's internal wcwidth function can get the printed width of each
 | 
						|
    // codepoint
 | 
						|
    for (int32_t i = 0; i < text.length;) {
 | 
						|
        uint32_t ch;
 | 
						|
        int codepoint_bytes = tb_utf8_char_to_unicode(&ch, text.chars + i);
 | 
						|
        if (0 > codepoint_bytes) {
 | 
						|
            clay_tb_assert(false, "Invalid utf8");
 | 
						|
        }
 | 
						|
        i += codepoint_bytes;
 | 
						|
 | 
						|
        int codepoint_width = tb_wcwidth(ch);
 | 
						|
        if (-1 == codepoint_width) {
 | 
						|
            // Nonprintable character, use width of REPLACEMENT CHARACTER (U+FFFD)
 | 
						|
            codepoint_width = tb_wcwidth(0xfffd);
 | 
						|
        }
 | 
						|
        width += codepoint_width;
 | 
						|
    }
 | 
						|
    return (Clay_Dimensions) {
 | 
						|
        (float)width * clay_tb_cell_size.width,
 | 
						|
        (float)height * clay_tb_cell_size.height
 | 
						|
    };
 | 
						|
}
 | 
						|
 | 
						|
clay_tb_image Clay_Termbox_Image_Load_File(const char *filename)
 | 
						|
{
 | 
						|
    clay_tb_assert(NULL != filename, "Filename cannot be null");
 | 
						|
 | 
						|
    clay_tb_image rv = { 0 };
 | 
						|
 | 
						|
    FILE *image_file = NULL;
 | 
						|
 | 
						|
    image_file = fopen(filename, "r");
 | 
						|
    if (NULL == image_file) {
 | 
						|
        fprintf(stderr, "Failed to open image %s: %s\n", filename, strerror(errno));
 | 
						|
        return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    int channels_in_file;
 | 
						|
    const int desired_color_channels = 3;
 | 
						|
    rv.pixel_data = stbi_load_from_file(
 | 
						|
        image_file, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels);
 | 
						|
 | 
						|
    fclose(image_file);
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
clay_tb_image Clay_Termbox_Image_Load_Memory(const void *image, int size)
 | 
						|
{
 | 
						|
    clay_tb_assert(NULL != image, "Image cannot be null");
 | 
						|
    clay_tb_assert(0 < size, "Image size must be > 0");
 | 
						|
 | 
						|
    clay_tb_image rv = { 0 };
 | 
						|
 | 
						|
    int channels_in_file;
 | 
						|
    const int desired_color_channels = 3;
 | 
						|
    rv.pixel_data = stbi_load_from_memory(
 | 
						|
        image, size, &rv.pixel_width, &rv.pixel_height, &channels_in_file, desired_color_channels);
 | 
						|
 | 
						|
    return rv;
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Image_Free(clay_tb_image *image)
 | 
						|
{
 | 
						|
    free(image->pixel_data);
 | 
						|
    free(image->internal.partial_render.resized_pixel_data);
 | 
						|
    free(image->internal.characters);
 | 
						|
    free(image->internal.foreground);
 | 
						|
    free(image->internal.background);
 | 
						|
    *image = (clay_tb_image) { 0 };
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Initialize(int color_mode, enum border_mode border_mode,
 | 
						|
    enum border_chars border_chars, enum image_mode image_mode, bool transparency)
 | 
						|
{
 | 
						|
    int new_color_mode = color_mode;
 | 
						|
    int new_border_mode = border_mode;
 | 
						|
    int new_border_chars = border_chars;
 | 
						|
    int new_image_mode = image_mode;
 | 
						|
    int new_transparency = transparency;
 | 
						|
    clay_tb_pixel_dimensions new_pixel_size = clay_tb_cell_size;
 | 
						|
 | 
						|
    // Check for environment variables that override settings
 | 
						|
 | 
						|
    const char *env_color_mode = getenv("CLAY_TB_COLOR_MODE");
 | 
						|
    if (NULL != env_color_mode) {
 | 
						|
        if (0 == strcmp("NORMAL", env_color_mode)) {
 | 
						|
            new_color_mode = TB_OUTPUT_NORMAL;
 | 
						|
        } else if (0 == strcmp("256", env_color_mode)) {
 | 
						|
            new_color_mode = TB_OUTPUT_256;
 | 
						|
        } else if (0 == strcmp("216", env_color_mode)) {
 | 
						|
            new_color_mode = TB_OUTPUT_216;
 | 
						|
        } else if (0 == strcmp("GRAYSCALE", env_color_mode)) {
 | 
						|
            new_color_mode = TB_OUTPUT_GRAYSCALE;
 | 
						|
        } else if (0 == strcmp("TRUECOLOR", env_color_mode)) {
 | 
						|
            new_color_mode = TB_OUTPUT_TRUECOLOR;
 | 
						|
        } else if (0 == strcmp("NOCOLOR", env_color_mode)) {
 | 
						|
            new_color_mode = CLAY_TB_OUTPUT_NOCOLOR;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const char *env_border_chars = getenv("CLAY_TB_BORDER_CHARS");
 | 
						|
    if (NULL != env_border_chars) {
 | 
						|
        if (0 == strcmp("DEFAULT", env_border_chars)) {
 | 
						|
            new_border_chars = CLAY_TB_BORDER_CHARS_DEFAULT;
 | 
						|
        } else if (0 == strcmp("ASCII", env_border_chars)) {
 | 
						|
            new_border_chars = CLAY_TB_BORDER_CHARS_ASCII;
 | 
						|
        } else if (0 == strcmp("UNICODE", env_border_chars)) {
 | 
						|
            new_border_chars = CLAY_TB_BORDER_CHARS_UNICODE;
 | 
						|
        } else if (0 == strcmp("BLANK", env_border_chars)) {
 | 
						|
            new_border_chars = CLAY_TB_BORDER_CHARS_BLANK;
 | 
						|
        } else if (0 == strcmp("NONE", env_border_chars)) {
 | 
						|
            new_border_chars = CLAY_TB_BORDER_CHARS_NONE;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const char *env_image_mode = getenv("CLAY_TB_IMAGE_MODE");
 | 
						|
    if (NULL != env_image_mode) {
 | 
						|
        if (0 == strcmp("DEFAULT", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_DEFAULT;
 | 
						|
        } else if (0 == strcmp("PLACEHOLDER", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_PLACEHOLDER;
 | 
						|
        } else if (0 == strcmp("BG", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_BG;
 | 
						|
        } else if (0 == strcmp("ASCII_FG", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG;
 | 
						|
        } else if (0 == strcmp("ASCII", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_ASCII;
 | 
						|
        } else if (0 == strcmp("UNICODE", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE;
 | 
						|
        } else if (0 == strcmp("ASCII_FG_FAST", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FG_FAST;
 | 
						|
        } else if (0 == strcmp("ASCII_FAST", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_ASCII_FAST;
 | 
						|
        } else if (0 == strcmp("UNICODE_FAST", env_image_mode)) {
 | 
						|
            new_image_mode = CLAY_TB_IMAGE_MODE_UNICODE_FAST;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const char *env_transparency = getenv("CLAY_TB_TRANSPARENCY");
 | 
						|
    if (NULL != env_transparency) {
 | 
						|
        if (0 == strcmp("1", env_transparency)) {
 | 
						|
            new_transparency = true;
 | 
						|
        } else if (0 == strcmp("0", env_transparency)) {
 | 
						|
            new_transparency = false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const char *env_cell_pixels = getenv("CLAY_TB_CELL_PIXELS");
 | 
						|
    if (NULL != env_cell_pixels) {
 | 
						|
        const char *str_width = env_cell_pixels;
 | 
						|
        const char *str_height = strstr(env_cell_pixels, "x") + 1;
 | 
						|
        if (NULL + 1 != str_height) {
 | 
						|
            bool missing_value = false;
 | 
						|
 | 
						|
            errno = 0;
 | 
						|
            float cell_width = strtof(str_width, NULL);
 | 
						|
            if (0 != errno || 0 > cell_width) {
 | 
						|
                missing_value = true;
 | 
						|
            }
 | 
						|
            float cell_height = strtof(str_height, NULL);
 | 
						|
            if (0 != errno || 0 >= cell_height) {
 | 
						|
                missing_value = true;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!missing_value) {
 | 
						|
                new_pixel_size = (clay_tb_pixel_dimensions) { cell_width, cell_height };
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // NO_COLOR indicates that ANSI colors shouldn't be used: https://no-color.org/
 | 
						|
    const char *env_nocolor = getenv("NO_COLOR");
 | 
						|
    if (NULL != env_nocolor && '\0' != env_nocolor[0]) {
 | 
						|
        new_color_mode = CLAY_TB_OUTPUT_NOCOLOR;
 | 
						|
    }
 | 
						|
 | 
						|
    tb_init();
 | 
						|
    tb_set_input_mode(TB_INPUT_MOUSE);
 | 
						|
 | 
						|
    // Enable mouse hover support
 | 
						|
    // - see https://github.com/termbox/termbox2/issues/71#issuecomment-2179581609
 | 
						|
    // - 1003 "Any-event tracking" mode
 | 
						|
    // - 1006 SGR extended coordinates (already enabled with TB_INPUT_MOUSE)
 | 
						|
    tb_sendf("\x1b[?%d;%dh", 1003, 1006);
 | 
						|
 | 
						|
    clay_tb_initialized = true;
 | 
						|
 | 
						|
    Clay_Termbox_Set_Color_Mode(new_color_mode);
 | 
						|
    Clay_Termbox_Set_Border_Mode(new_border_mode);
 | 
						|
    Clay_Termbox_Set_Border_Chars(new_border_chars);
 | 
						|
    Clay_Termbox_Set_Image_Mode(new_image_mode);
 | 
						|
    Clay_Termbox_Set_Transparency(new_transparency);
 | 
						|
    Clay_Termbox_Set_Cell_Pixel_Size(new_pixel_size.width, new_pixel_size.height);
 | 
						|
 | 
						|
    size_t size = (size_t)tb_width() * tb_height();
 | 
						|
    clay_tb_color_buffer_clay = tb_malloc(sizeof(Clay_Color) * size);
 | 
						|
    for (int i = 0; i < size; ++i) {
 | 
						|
        clay_tb_color_buffer_clay[i] = (Clay_Color) { 0, 0, 0, 0 };
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Close(void)
 | 
						|
{
 | 
						|
    if (clay_tb_initialized) {
 | 
						|
        // Disable mouse hover support
 | 
						|
        tb_sendf("\x1b[?%d;%dl", 1003, 1006);
 | 
						|
 | 
						|
        tb_free(clay_tb_color_buffer_clay);
 | 
						|
        tb_shutdown();
 | 
						|
        clay_tb_initialized = false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Render(Clay_RenderCommandArray commands)
 | 
						|
{
 | 
						|
    clay_tb_assert(clay_tb_initialized, "Clay_Termbox_Initialize must be run first");
 | 
						|
 | 
						|
    clay_tb_resize_buffer();
 | 
						|
    clay_tb_partial_image_drawn = false;
 | 
						|
    clay_tb_image_fuel_used = 0;
 | 
						|
 | 
						|
    for (int32_t i = 0; i < commands.length; ++i) {
 | 
						|
        const Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i);
 | 
						|
        const clay_tb_cell_bounding_box cell_box = cell_snap_bounding_box(command->boundingBox);
 | 
						|
 | 
						|
        int box_begin_x = CLAY__MAX(cell_box.x, 0);
 | 
						|
        int box_end_x = CLAY__MIN(cell_box.x + cell_box.width, tb_width());
 | 
						|
        int box_begin_y = CLAY__MAX(cell_box.y, 0);
 | 
						|
        int box_end_y = CLAY__MIN(cell_box.y + cell_box.height, tb_height());
 | 
						|
 | 
						|
        if (box_end_x < 0 || box_end_y < 0 || tb_width() < box_begin_x
 | 
						|
            || tb_height() < box_begin_y) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        switch (command->commandType) {
 | 
						|
            default: {
 | 
						|
                clay_tb_assert(false, "Unhandled command: %d\n", command->commandType);
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_NONE: {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
 | 
						|
                Clay_RectangleRenderData render_data = command->renderData.rectangle;
 | 
						|
                Clay_Color color_fg = { 0, 0, 0, 0 };
 | 
						|
                Clay_Color color_bg = render_data.backgroundColor;
 | 
						|
                uintattr_t color_tb_fg = TB_DEFAULT;
 | 
						|
                uintattr_t color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
 | 
						|
                for (int y = box_begin_y; y < box_end_y; ++y) {
 | 
						|
                    for (int x = box_begin_x; x < box_end_x; ++x) {
 | 
						|
                        clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color(
 | 
						|
                            x, y, (clay_tb_color_pair) { color_bg, color_tb_bg });
 | 
						|
                        clay_tb_set_cell(
 | 
						|
                            x, y, ' ', color_tb_fg, color_bg_new.termbox, color_bg_new.clay);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_BORDER: {
 | 
						|
                if (CLAY_TB_BORDER_CHARS_NONE == clay_tb_border_chars) {
 | 
						|
                    break;
 | 
						|
                }
 | 
						|
                Clay_BorderRenderData render_data = command->renderData.border;
 | 
						|
                Clay_Color color_fg = { 0, 0, 0, 1 };
 | 
						|
                Clay_Color color_bg = render_data.color;
 | 
						|
                uintattr_t color_tb_fg = TB_DEFAULT;
 | 
						|
                uintattr_t color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
 | 
						|
                int border_skip_begin_x = box_begin_x;
 | 
						|
                int border_skip_end_x = box_end_x;
 | 
						|
                int border_skip_begin_y = box_begin_y;
 | 
						|
                int border_skip_end_y = box_end_y;
 | 
						|
 | 
						|
                switch (clay_tb_border_mode) {
 | 
						|
                    default: {
 | 
						|
                        clay_tb_assert(false, "Invalid or unimplemented border mode (%d)",
 | 
						|
                            clay_tb_border_mode);
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    case CLAY_TB_BORDER_MODE_MINIMUM: {
 | 
						|
                        // Borders will be at least one cell wide if width is nonzero
 | 
						|
                        // and the bounding box is large enough to not be all borders
 | 
						|
                        if (0 < cell_box.width) {
 | 
						|
                            if (0 < render_data.width.left) {
 | 
						|
                                border_skip_begin_x = box_begin_x
 | 
						|
                                    + (int)CLAY__MAX(
 | 
						|
                                        1, (render_data.width.left / clay_tb_cell_size.width));
 | 
						|
                            }
 | 
						|
                            if (0 < render_data.width.right) {
 | 
						|
                                border_skip_end_x = box_end_x
 | 
						|
                                    - (int)CLAY__MAX(
 | 
						|
                                        1, (render_data.width.right / clay_tb_cell_size.width));
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        if (0 < cell_box.height) {
 | 
						|
                            if (0 < render_data.width.top) {
 | 
						|
                                border_skip_begin_y = box_begin_y
 | 
						|
                                    + (int)CLAY__MAX(
 | 
						|
                                        1, (render_data.width.top / clay_tb_cell_size.width));
 | 
						|
                            }
 | 
						|
                            if (0 < render_data.width.bottom) {
 | 
						|
                                border_skip_end_y = box_end_y
 | 
						|
                                    - (int)CLAY__MAX(
 | 
						|
                                        1, (render_data.width.bottom / clay_tb_cell_size.width));
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                    case CLAY_TB_BORDER_MODE_ROUND: {
 | 
						|
                        int halfwidth = clay_tb_roundf(clay_tb_cell_size.width / 2);
 | 
						|
                        int halfheight = clay_tb_roundf(clay_tb_cell_size.height / 2);
 | 
						|
 | 
						|
                        if (halfwidth < render_data.width.left) {
 | 
						|
                            border_skip_begin_x = box_begin_x
 | 
						|
                                + (int)CLAY__MAX(
 | 
						|
                                    1, (render_data.width.left / clay_tb_cell_size.width));
 | 
						|
                        }
 | 
						|
                        if (halfwidth < render_data.width.right) {
 | 
						|
                            border_skip_end_x = box_end_x
 | 
						|
                                - (int)CLAY__MAX(
 | 
						|
                                    1, (render_data.width.right / clay_tb_cell_size.width));
 | 
						|
                        }
 | 
						|
                        if (halfheight < render_data.width.top) {
 | 
						|
                            border_skip_begin_y = box_begin_y
 | 
						|
                                + (int)CLAY__MAX(
 | 
						|
                                    1, (render_data.width.top / clay_tb_cell_size.width));
 | 
						|
                        }
 | 
						|
                        if (halfheight < render_data.width.bottom) {
 | 
						|
                            border_skip_end_y = box_end_y
 | 
						|
                                - (int)CLAY__MAX(
 | 
						|
                                    1, (render_data.width.bottom / clay_tb_cell_size.width));
 | 
						|
                        }
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                // Draw border, skipping over the center of the bounding box
 | 
						|
                for (int y = box_begin_y; y < box_end_y; ++y) {
 | 
						|
                    for (int x = box_begin_x; x < box_end_x; ++x) {
 | 
						|
                        if ((border_skip_begin_x <= x && x < border_skip_end_x)
 | 
						|
                            && (border_skip_begin_y <= y && y < border_skip_end_y)) {
 | 
						|
                            x = border_skip_end_x - 1;
 | 
						|
                            continue;
 | 
						|
                        }
 | 
						|
 | 
						|
                        uint32_t ch;
 | 
						|
                        switch (clay_tb_border_chars) {
 | 
						|
                            default: {
 | 
						|
                                clay_tb_assert(false,
 | 
						|
                                    "Invalid or unimplemented border character mode (%d)",
 | 
						|
                                    clay_tb_border_chars);
 | 
						|
                            }
 | 
						|
                            case CLAY_TB_BORDER_CHARS_UNICODE: {
 | 
						|
                                if ((x < border_skip_begin_x)
 | 
						|
                                    && (y < border_skip_begin_y)) { // Top left
 | 
						|
                                    ch = U'\u250c';
 | 
						|
                                } else if ((x >= border_skip_end_x)
 | 
						|
                                    && (y < border_skip_begin_y)) { // Top right
 | 
						|
                                    ch = U'\u2510';
 | 
						|
                                } else if ((x < border_skip_begin_x)
 | 
						|
                                    && (y >= border_skip_end_y)) { // Bottom left
 | 
						|
                                    ch = U'\u2514';
 | 
						|
                                } else if ((x >= border_skip_end_x)
 | 
						|
                                    && (y >= border_skip_end_y)) { // Bottom right
 | 
						|
                                    ch = U'\u2518';
 | 
						|
                                } else if (x < border_skip_begin_x || x >= border_skip_end_x) {
 | 
						|
                                    ch = U'\u2502';
 | 
						|
                                } else if (y < border_skip_begin_y || y >= border_skip_end_y) {
 | 
						|
                                    ch = U'\u2500';
 | 
						|
                                }
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                            case CLAY_TB_BORDER_CHARS_DEFAULT:
 | 
						|
                            case CLAY_TB_BORDER_CHARS_ASCII: {
 | 
						|
                                if ((x < border_skip_begin_x || x >= border_skip_end_x)
 | 
						|
                                    && (y < border_skip_begin_y || y >= border_skip_end_y)) {
 | 
						|
                                    ch = '+';
 | 
						|
                                } else if (x < border_skip_begin_x || x >= border_skip_end_x) {
 | 
						|
                                    ch = '|';
 | 
						|
                                } else if (y < border_skip_begin_y || y >= border_skip_end_y) {
 | 
						|
                                    ch = '-';
 | 
						|
                                }
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                            case CLAY_TB_BORDER_CHARS_BLANK: {
 | 
						|
                                ch = ' ';
 | 
						|
                                break;
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
 | 
						|
                        clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color(
 | 
						|
                            x, y, (clay_tb_color_pair) { color_bg, color_tb_bg });
 | 
						|
                        clay_tb_set_cell(
 | 
						|
                            x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay);
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_TEXT: {
 | 
						|
                Clay_TextRenderData render_data = command->renderData.text;
 | 
						|
                Clay_Color color_fg = render_data.textColor;
 | 
						|
                uintattr_t color_tb_fg = clay_tb_color_convert(color_fg);
 | 
						|
 | 
						|
                Clay_StringSlice *text = &render_data.stringContents;
 | 
						|
                int32_t i = 0;
 | 
						|
                for (int y = box_begin_y; y < box_end_y; ++y) {
 | 
						|
                    for (int x = box_begin_x; x < box_end_x;) {
 | 
						|
                        uint32_t ch = ' ';
 | 
						|
                        if (i < text->length) {
 | 
						|
                            int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i);
 | 
						|
                            if (0 > codepoint_length) {
 | 
						|
                                clay_tb_assert(false, "Invalid utf8");
 | 
						|
                            }
 | 
						|
                            i += codepoint_length;
 | 
						|
 | 
						|
                            uintattr_t color_tb_bg = (clay_tb_transparency)
 | 
						|
                                ? TB_DEFAULT
 | 
						|
                                : clay_tb_color_convert(clay_tb_color_buffer_clay_get(x, y));
 | 
						|
                            Clay_Color color_bg = { 0 };
 | 
						|
                            clay_tb_color_pair color_bg_new = clay_tb_get_transparency_color(
 | 
						|
                                x, y, (clay_tb_color_pair) { color_bg, color_tb_bg });
 | 
						|
                            clay_tb_set_cell(
 | 
						|
                                x, y, ch, color_tb_fg, color_bg_new.termbox, color_bg_new.clay);
 | 
						|
                        }
 | 
						|
 | 
						|
                        int codepoint_width = tb_wcwidth(ch);
 | 
						|
                        if (-1 == codepoint_width) {
 | 
						|
                            // Nonprintable character, use REPLACEMENT CHARACTER (U+FFFD)
 | 
						|
                            ch = U'\ufffd';
 | 
						|
                            codepoint_width = tb_wcwidth(ch);
 | 
						|
                        }
 | 
						|
 | 
						|
                        x += codepoint_width;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
 | 
						|
                Clay_ImageRenderData render_data = command->renderData.image;
 | 
						|
                Clay_Color color_fg = { 0, 0, 0, 0 };
 | 
						|
                Clay_Color color_bg = render_data.backgroundColor;
 | 
						|
                uintattr_t color_tb_fg = clay_tb_color_convert(color_fg);
 | 
						|
                uintattr_t color_tb_bg;
 | 
						|
 | 
						|
                // Only set background to the provided color if it's non-default
 | 
						|
                bool color_specified
 | 
						|
                    = !(color_bg.r == 0 && color_bg.g == 0 && color_bg.b == 0 && color_bg.a == 0);
 | 
						|
                if (color_specified) {
 | 
						|
                    color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
                }
 | 
						|
 | 
						|
                bool use_placeholder = true;
 | 
						|
 | 
						|
                clay_tb_image *image = (clay_tb_image *)render_data.imageData;
 | 
						|
 | 
						|
                if (!(CLAY_TB_IMAGE_MODE_PLACEHOLDER == clay_tb_image_mode
 | 
						|
                        || CLAY_TB_OUTPUT_NOCOLOR == clay_tb_color_mode)) {
 | 
						|
                    bool convert_success = (NULL != image)
 | 
						|
                        ? clay_tb_image_convert(image, cell_box.width, cell_box.height)
 | 
						|
                        : false;
 | 
						|
                    if (convert_success) {
 | 
						|
                        use_placeholder = false;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                if (!use_placeholder) {
 | 
						|
                    // Render image
 | 
						|
                    for (int y = box_begin_y; y < box_end_y; ++y) {
 | 
						|
                        int y_offset = y - cell_box.y;
 | 
						|
                        for (int x = box_begin_x; x < box_end_x; ++x) {
 | 
						|
                            int x_offset = x - cell_box.x;
 | 
						|
                            // Fetch cells from the image's cache
 | 
						|
                            if (!color_specified) {
 | 
						|
                                if (CLAY_TB_IMAGE_MODE_ASCII_FG == clay_tb_image_mode
 | 
						|
                                    || CLAY_TB_IMAGE_MODE_ASCII_FG_FAST == clay_tb_image_mode) {
 | 
						|
                                    color_bg = (Clay_Color) { 0, 0, 0, 0 };
 | 
						|
                                    color_tb_bg = TB_DEFAULT;
 | 
						|
                                } else {
 | 
						|
                                    color_bg
 | 
						|
                                        = image->internal
 | 
						|
                                              .background[y_offset * cell_box.width + x_offset];
 | 
						|
                                    color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            color_tb_fg = clay_tb_color_convert(
 | 
						|
                                image->internal.foreground[y_offset * cell_box.width + x_offset]);
 | 
						|
                            uint32_t ch
 | 
						|
                                = image->internal.characters[y_offset * cell_box.width + x_offset];
 | 
						|
                            if (CLAY_TB_IMAGE_MODE_BG == clay_tb_image_mode) {
 | 
						|
                                ch = ' ';
 | 
						|
                            }
 | 
						|
 | 
						|
                            clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    // Render a placeholder pattern
 | 
						|
                    const char *placeholder_text = "[Image]";
 | 
						|
 | 
						|
                    int i = 0;
 | 
						|
                    unsigned long len = strlen(placeholder_text);
 | 
						|
                    for (int y = box_begin_y; y < box_end_y; ++y) {
 | 
						|
                        float percent_y = (float)(y - box_begin_y) / (float)cell_box.height;
 | 
						|
 | 
						|
                        for (int x = box_begin_x; x < box_end_x; ++x) {
 | 
						|
                            char ch = ' ';
 | 
						|
                            if (i < len) {
 | 
						|
                                ch = placeholder_text[i++];
 | 
						|
                            }
 | 
						|
 | 
						|
                            if (!color_specified) {
 | 
						|
                                // Use a placeholder pattern for the image
 | 
						|
                                float percent_x = (float)(cell_box.width - (x - box_begin_x))
 | 
						|
                                    / (float)cell_box.width;
 | 
						|
                                if (percent_x > percent_y) {
 | 
						|
                                    color_bg = (Clay_Color) { 0x94, 0xb4, 0xff, 0xff };
 | 
						|
                                    color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
                                } else {
 | 
						|
                                    color_bg = (Clay_Color) { 0x3f, 0xcc, 0x45, 0xff };
 | 
						|
                                    color_tb_bg = clay_tb_color_convert(color_bg);
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                            clay_tb_set_cell(x, y, ch, color_tb_fg, color_tb_bg, color_bg);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
 | 
						|
                clay_tb_scissor_box = (clay_tb_cell_bounding_box) {
 | 
						|
                    .x = box_begin_x,
 | 
						|
                    .y = box_begin_y,
 | 
						|
                    .width = box_end_x - box_begin_x,
 | 
						|
                    .height = box_end_y - box_begin_y,
 | 
						|
                };
 | 
						|
                clay_tb_scissor_enabled = true;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
 | 
						|
                clay_tb_scissor_enabled = false;
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
void Clay_Termbox_Waitfor_Event(void)
 | 
						|
{
 | 
						|
    if (clay_tb_partial_image_drawn) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    int termbox_ttyfd, termbox_resizefd;
 | 
						|
    tb_get_fds(&termbox_ttyfd, &termbox_resizefd);
 | 
						|
    int nfds = CLAY__MAX(termbox_ttyfd, termbox_resizefd) + 1;
 | 
						|
    fd_set monitor_set;
 | 
						|
    FD_ZERO(&monitor_set);
 | 
						|
    FD_SET(termbox_ttyfd, &monitor_set);
 | 
						|
    FD_SET(termbox_resizefd, &monitor_set);
 | 
						|
    select(nfds, &monitor_set, NULL, NULL, NULL);
 | 
						|
}
 |