// color names and functions for dealing with colors #include "ted-internal.h" typedef struct { ColorSetting setting; const char *name; } ColorName; static ColorName color_names[] = { {COLOR_UNKNOWN, "unknown"}, {COLOR_TEXT, "text"}, {COLOR_TEXT_SECONDARY, "text-secondary"}, {COLOR_BG, "bg"}, {COLOR_CURSOR, "cursor"}, {COLOR_CURSOR_ERROR, "cursor-error"}, {COLOR_CURSOR_LINE_BG, "cursor-line-bg"}, {COLOR_VIEW_ONLY_CURSOR, "view-only-cursor"}, {COLOR_VIEW_ONLY_SELECTION_BG, "view-only-selection-bg"}, {COLOR_MATCHING_BRACKET_HL, "matching-bracket-hl"}, {COLOR_BORDER, "border"}, {COLOR_TEXT_FOLDER, "text-folder"}, {COLOR_TEXT_OTHER, "text-other"}, {COLOR_SELECTION_BG, "selection-bg"}, {COLOR_MENU_BACKDROP, "menu-backdrop"}, {COLOR_MENU_BG, "menu-bg"}, {COLOR_MENU_HL, "menu-hl"}, {COLOR_ERROR_BG, "error-bg"}, {COLOR_ERROR_BORDER, "error-border"}, {COLOR_INFO_BG, "info-bg"}, {COLOR_INFO_BORDER, "info-border"}, {COLOR_WARNING_BG, "warning-bg"}, {COLOR_WARNING_BORDER, "warning-border"}, {COLOR_ACTIVE_TAB_HL, "active-tab-hl"}, {COLOR_SELECTED_TAB_HL, "selected-tab-hl"}, {COLOR_FIND_HL, "find-hl"}, {COLOR_KEYWORD, "keyword"}, {COLOR_BUILTIN, "builtin"}, {COLOR_COMMENT, "comment"}, {COLOR_PREPROCESSOR, "preprocessor"}, {COLOR_STRING, "string"}, {COLOR_CHARACTER, "character"}, {COLOR_CONSTANT, "constant"}, {COLOR_TODO, "todo"}, {COLOR_AUTOCOMPLETE_BG, "autocomplete-bg"}, {COLOR_AUTOCOMPLETE_HL, "autocomplete-hl"}, {COLOR_AUTOCOMPLETE_BORDER, "autocomplete-border"}, {COLOR_AUTOCOMPLETE_VARIABLE, "autocomplete-variable"}, {COLOR_AUTOCOMPLETE_FUNCTION, "autocomplete-function"}, {COLOR_AUTOCOMPLETE_TYPE, "autocomplete-type"}, {COLOR_HOVER_BORDER, "hover-border"}, {COLOR_HOVER_BG, "hover-bg"}, {COLOR_HOVER_TEXT, "hover-text"}, {COLOR_HOVER_HL, "hover-hl"}, {COLOR_HL_WRITE, "hl-write"}, {COLOR_YES, "yes"}, {COLOR_NO, "no"}, {COLOR_CANCEL, "cancel"}, {COLOR_LINE_NUMBERS, "line-numbers"}, {COLOR_CURSOR_LINE_NUMBER, "cursor-line-number"}, {COLOR_LINE_NUMBERS_SEPARATOR, "line-numbers-separator"}, }; static_assert_if_possible(arr_count(color_names) == COLOR_COUNT) int color_name_cmp(const void *av, const void *bv) { const ColorName *a = av, *b = bv; return strcmp(a->name, b->name); } void color_init(void) { qsort(color_names, arr_count(color_names), sizeof *color_names, color_name_cmp); } ColorSetting color_setting_from_str(const char *str) { int lo = 0; int hi = COLOR_COUNT; while (lo < hi) { int mid = (lo + hi) / 2; int cmp = strcmp(color_names[mid].name, str); if (cmp < 0) { lo = mid + 1; } else if (cmp > 0) { hi = mid; } else { return color_names[mid].setting; } } return COLOR_UNKNOWN; } const char *color_setting_to_str(ColorSetting s) { for (int i = 0; i < COLOR_COUNT; ++i) { ColorName const *n = &color_names[i]; if (n->setting == s) return n->name; } return "???"; } // converts #rrggbb/#rrggbbaa to a color. returns false if it's not in the right format. Status color_from_str(const char *str, u32 *color) { u32 r = 0, g = 0, b = 0, a = 0xff; bool success = false; switch (strlen(str)) { case 4: success = sscanf(str, "#%01x%01x%01x", &r, &g, &b) == 3; // extend single hex digit to double hex digit r |= r << 4; g |= g << 4; b |= b << 4; break; case 5: success = sscanf(str, "#%01x%01x%01x%01x", &r, &g, &b, &a) == 4; r |= r << 4; g |= g << 4; b |= b << 4; a |= a << 4; break; case 7: success = sscanf(str, "#%02x%02x%02x", &r, &g, &b) == 3; break; case 9: success = sscanf(str, "#%02x%02x%02x%02x", &r, &g, &b, &a) == 4; break; } if (!success || r > 0xff || g > 0xff || b > 0xff || a > 0xff) return false; if (color) *color = (u32)r << 24 | (u32)g << 16 | (u32)b << 8 | (u32)a; return true; } ColorSetting color_for_symbol_kind(SymbolKind kind) { switch (kind) { case SYMBOL_CONSTANT: return COLOR_CONSTANT; case SYMBOL_TYPE: return COLOR_AUTOCOMPLETE_TYPE; case SYMBOL_FIELD: case SYMBOL_VARIABLE: return COLOR_AUTOCOMPLETE_VARIABLE; case SYMBOL_FUNCTION: return COLOR_AUTOCOMPLETE_FUNCTION; case SYMBOL_OTHER: return COLOR_TEXT; case SYMBOL_KEYWORD: return COLOR_KEYWORD; } return COLOR_TEXT; } u32 color_blend(u32 bg, u32 fg) { u32 r1 = bg >> 24; u32 g1 = (bg >> 16) & 0xff; u32 b1 = (bg >> 8) & 0xff; u32 r2 = fg >> 24; u32 g2 = (fg >> 16) & 0xff; u32 b2 = (fg >> 8) & 0xff; u32 a2 = fg & 0xff; u32 r = (r1 * (255 - a2) + r2 * a2 + 127) / 255; u32 g = (g1 * (255 - a2) + g2 * a2 + 127) / 255; u32 b = (b1 * (255 - a2) + b2 * a2 + 127) / 255; return r << 24 | g << 16 | b << 8 | 0xff; } u32 color_apply_opacity(u32 color, float opacity) { opacity = clampf(opacity, 0.0f, 1.0f); return (color & 0xffffff00) | (u32)((color & 0xff) * opacity); } static float color_relative_luminance(const float rgb[3]) { // see https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef float c[3]; for (int i = 0; i < 3; ++i) { const float x = rgb[i]; c[i] = x <= 0.03928f ? x * (1.0f / 12.92f) : powf((x + 0.055f) * (1.0f / 1.055f), 2.4f); } return 0.2126f * c[0] + 0.7152f * c[1] + 0.0722f * c[2]; } float color_contrast_ratio(const float rgb1[3], const float rgb2[3]) { // see https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef float l1 = color_relative_luminance(rgb1); float l2 = color_relative_luminance(rgb2); if (l1 < l2) { float temp = l1; l1 = l2; l2 = temp; } return (l1 + 0.05f) / (l2 + 0.05f); } float color_contrast_ratio_u32(u32 color1, u32 color2) { float rgb1[4], rgb2[4]; color_u32_to_floats(color1, rgb1); color_u32_to_floats(color2, rgb2); return color_contrast_ratio(rgb1, rgb2); } void color_u32_to_floats(u32 rgba, float floats[4]) { floats[0] = (float)((rgba >> 24) & 0xff) / 255.f; floats[1] = (float)((rgba >> 16) & 0xff) / 255.f; floats[2] = (float)((rgba >> 8) & 0xff) / 255.f; floats[3] = (float)((rgba >> 0) & 0xff) / 255.f; } vec4 color_u32_to_vec4(u32 rgba) { float c[4]; color_u32_to_floats(rgba, c); return (vec4){c[0], c[1], c[2], c[3]}; } u32 color_vec4_to_u32(vec4 color) { return (u32)(color.x * 255) << 24 | (u32)(color.y * 255) << 16 | (u32)(color.z * 255) << 8 | (u32)(color.w * 255); } static vec4 color_rgba_to_hsva(vec4 rgba) { float R = rgba.x, G = rgba.y, B = rgba.z, A = rgba.w; float M = maxf(R, maxf(G, B)); float m = minf(R, minf(G, B)); float C = M - m; float H = 0; if (C == 0) H = 0; else if (M == R) H = fmodf((G - B) / C, 6); else if (M == G) H = (B - R) / C + 2; else if (M == B) H = (R - G) / C + 4; H *= 60; float V = M; float S = V == 0 ? 0 : C / V; return (vec4){H, S, V, A}; } static vec4 color_hsva_to_rgba(vec4 hsva) { float H = hsva.x, S = hsva.y, V = hsva.z, A = hsva.w; H /= 60; float C = S * V; float X = C * (1 - fabsf(fmodf(H, 2) - 1)); float R, G, B; if (H <= 1) R=C, G=X, B=0; else if (H <= 2) R=X, G=C, B=0; else if (H <= 3) R=0, G=C, B=X; else if (H <= 4) R=0, G=X, B=C; else if (H <= 5) R=X, G=0, B=C; else R=C, G=0, B=X; float m = V-C; R += m; G += m; B += m; return (vec4){R, G, B, A}; } u32 color_interpolate(float x, u32 color1, u32 color2) { x = x * x * (3 - 2*x); // hermite interpolation vec4 c1 = color_u32_to_vec4(color1), c2 = color_u32_to_vec4(color2); // to make it interpolate more nicely, convert to hsv, interpolate in that space, then convert back c1 = color_rgba_to_hsva(c1); c2 = color_rgba_to_hsva(c2); // v_1/2 named differently to avoid shadowing float h1 = c1.x, s1 = c1.y, v_1 = c1.z, a1 = c1.w; float h2 = c2.x, s2 = c2.y, v_2 = c2.z, a2 = c2.w; float s_out = lerpf(x, s1, s2); float v_out = lerpf(x, v_1, v_2); float a_out = lerpf(x, a1, a2); float h_out; // because hue is on a circle, we need to make sure we take the shorter route around the circle if (fabsf(h1 - h2) < 180) { h_out = lerpf(x, h1, h2); } else if (h1 > h2) { h_out = lerpf(x, h1, h2 + 360); } else { h_out = lerpf(x, h1 + 360, h2); } h_out = fmodf(h_out, 360); vec4 c_out = (vec4){h_out, s_out, v_out, a_out}; c_out = color_hsva_to_rgba(c_out); return color_vec4_to_u32(c_out); }