From 87899eefc570ea87f32b57a9dddb33f7f2823593 Mon Sep 17 00:00:00 2001 From: Shadman Date: Tue, 2 Mar 2021 19:01:34 +0600 Subject: [PATCH] feat: add automatic theme generation (#104) --- THEMES.md | 3 + lua/lualine/themes/auto.lua | 163 ++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 lua/lualine/themes/auto.lua diff --git a/THEMES.md b/THEMES.md index 831c5ea..fa38aac 100644 --- a/THEMES.md +++ b/THEMES.md @@ -2,6 +2,9 @@ All available themes are only best effort ports by myself/ other users. If you find a theme to be weird/ wrong please open an issue/ pr. +### auto +if your favourite colorcheme is not yet supported you can use `auto` to make +lualine try to match your colorscheme. ### 16color ![16normal_cropped](https://user-images.githubusercontent.com/41551030/108648240-02d2ae00-74bb-11eb-9ac1-495849621366.png) ![16insert_cropped](https://user-images.githubusercontent.com/41551030/108648219-f77f8280-74ba-11eb-84e4-978bf918c21f.png) diff --git a/lua/lualine/themes/auto.lua b/lua/lualine/themes/auto.lua new file mode 100644 index 0000000..6cdff15 --- /dev/null +++ b/lua/lualine/themes/auto.lua @@ -0,0 +1,163 @@ +local utils = require'lualine.utils.utils' + +--------------- +-- Constents -- +--------------- +-- fg and bg must have this much contrast range 0 < contrast_threshold < 0.5 +local contrast_threshold = 0.3 +-- how much brightness is changed in percentage for light and dark themes +local brightness_modifier_parameter = 10 + +-- retrives color value from highlight group name in syntax_list +-- first present highlight is returned +local function getHi( scope, syntaxlist ) + for _ , highlight_name in pairs( syntaxlist ) do + if vim.fn.hlexists(highlight_name) ~= 0 then + local color = utils.extract_highlight_colors(highlight_name) + if color.reverse then + if scope == 'guibg' then scope = 'guifg' else scope = 'guibg' end + end + if color[scope] then return color[scope] end + end + end + return '#000000' +end + +-- truns #rrggbb -> { red, green, blue } +local function rgb_str2num(rgb_color_str) + if rgb_color_str:find('#') == 1 then rgb_color_str = rgb_color_str:sub(2, #rgb_color_str) end + local red = tonumber(rgb_color_str:sub(1,2), 16) + local green = tonumber(rgb_color_str:sub(3,4), 16) + local blue = tonumber(rgb_color_str:sub(5,6), 16) + return { red = red, green = green, blue = blue, } +end + +-- turns { red, green, blue } -> #rrggbb +local function rgb_num2str(rgb_color_num) + local rgb_color_str = string.format('#%02x%02x%02x', rgb_color_num.red, + rgb_color_num.green, rgb_color_num.blue) + return rgb_color_str +end + +-- returns brightness lavel of color in range 0 to 1 +-- arbitary value it's basicaly an weighted average +local function get_color_brightness(rgb_color) + local color = rgb_str2num(rgb_color) + local brightness = (color.red * 2 + color.green * 3 + color.blue) / 6 + return brightness / 256 +end + +-- returns average of colors in range 0 to 1 +-- used to ditermine contrast lavel +local function get_color_avg(rgb_color) + local color = rgb_str2num(rgb_color) + return (color.red + color.green + color.blue) / 3 / 256 +end + +-- clamps the val between left and right +local function clamp(val, left, right) + if val > right then return right end + if val < left then return left end + return val +end + +-- changes braghtness of rgb_color by percentage +local function brightness_modifier(rgb_color, parcentage) + local color = rgb_str2num(rgb_color) + color.red = clamp(color.red + (color.red * parcentage / 100), 0, 255) + color.green = clamp(color.green + (color.green * parcentage / 100), 0, 255) + color.blue = clamp(color.blue + (color.blue * parcentage / 100), 0, 255) + return rgb_num2str(color) +end + +-- changes contrast of rgb_color by amount +local function contrast_modifier(rgb_color, amount) + local color = rgb_str2num(rgb_color) + color.red = clamp(color.red + amount, 0, 255) + color.green = clamp(color.green + amount, 0, 255) + color.blue = clamp(color.blue + amount, 0, 255) + return rgb_num2str(color) +end + +-- Changes brightness of foreground color to achive contrast +-- without changing the color +local function apply_contrast(highlight) + local hightlight_bg_avg = get_color_avg(highlight.bg) + local contrast_threshold_config = clamp(contrast_threshold, 0, 0.5) + local contranst_change_step = 5 + if hightlight_bg_avg > .5 then + contranst_change_step = -contranst_change_step + end + + -- donn't waste too much time here max 25 interation should be more than enough + local iteration_count = 1 + while (math.abs(get_color_avg(highlight.fg) - hightlight_bg_avg) < contrast_threshold_config and iteration_count < 25) do + highlight.fg = contrast_modifier(highlight.fg, contranst_change_step) + iteration_count = iteration_count + 1 + end +end + +-- Get the colors to create theme +local colors = { + normal = getHi( 'guibg', {'PmenuSel', 'PmenuThumb', 'TabLineSel' } ), + insert = getHi( 'guifg', {'String', 'MoreMsg' } ), + replace = getHi( 'guifg', {'Number', 'Type' } ), + visual = getHi( 'guifg', {'Special', 'Boolean', 'Constant' } ), + command = getHi( 'guifg', {'Identifier' } ), + back1 = getHi( 'guibg', {'Normal', 'StatusLineNC' } ), + fore = getHi( 'guifg', {'Normal', 'StatusLine' } ), + back2 = getHi( 'guibg', {'StatusLine' } ), +} + + +-- Change brightness of colors +-- darken incase of light theme lighten incase of dark theme + +if get_color_brightness(utils.extract_highlight_colors('Normal', 'guibg')) > 0.5 then + brightness_modifier_parameter = -brightness_modifier_parameter +end + +for name, color in pairs(colors) do + colors[name] = brightness_modifier(color, brightness_modifier_parameter) +end + +-- basic theme defination +local M = { + normal = { + a = { bg = colors.normal, fg = colors.back1, gui='bold' }, + b = { bg = colors.back1, fg = colors.normal }, + c = { bg = colors.back2, fg = colors.fore }, + }, + insert = { + a = { bg = colors.insert, fg = colors.back1, gui='bold' }, + b = { bg = colors.back1, fg = colors.insert }, + c = { bg = colors.back2, fg = colors.fore }, + }, + replace = { + a = { bg = colors.replace, fg= colors.back1, gui='bold' }, + b = { bg = colors.back1, fg= colors.replace }, + c = { bg = colors.back2, fg= colors.fore }, + }, + visual = { + a = { bg = colors.visual, fg= colors.back1, gui='bold' }, + b = { bg = colors.back1, fg= colors.visual }, + c = { bg = colors.back2, fg= colors.fore }, + }, + command = { + a = { bg = colors.command, fg = colors.back1, gui='bold' }, + b = { bg = colors.back1, fg = colors.command }, + c = { bg = colors.back2, fg = colors.fore }, + }, +} + +M.terminal = M.command +M.inactive = M.normal + +-- Apply prpper contrast so text is readable +for _, section in pairs(M) do + for _, highlight in pairs(section) do + apply_contrast(highlight) + end +end + +return M