theme.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #include "lumacs/theme.hpp"
  2. #include <ncurses.h>
  3. #include <algorithm>
  4. #include <climits>
  5. namespace lumacs {
  6. static std::map<Color, int> color_cache;
  7. static int next_color_id = 8; // Start after the basic 8 colors
  8. int Color::to_ncurses_color() const {
  9. // Handle default colors
  10. if (r == -1 && g == -1 && b == -1) {
  11. return -1; // Use default
  12. }
  13. // Check cache first
  14. auto it = color_cache.find(*this);
  15. if (it != color_cache.end()) {
  16. return it->second;
  17. }
  18. // Check if we can create new colors
  19. if (can_change_color() && next_color_id < COLOR_PAIRS) {
  20. int color_id = next_color_id++;
  21. // Scale RGB values to 0-1000 range for ncurses
  22. int nr = (r * 1000) / 255;
  23. int ng = (g * 1000) / 255;
  24. int nb = (b * 1000) / 255;
  25. init_color(color_id, nr, ng, nb);
  26. color_cache[*this] = color_id;
  27. return color_id;
  28. }
  29. // Fallback to closest basic color
  30. int min_dist = INT_MAX;
  31. int closest = COLOR_WHITE;
  32. struct BasicColor { int id; int r, g, b; };
  33. BasicColor basic_colors[] = {
  34. {COLOR_BLACK, 0, 0, 0},
  35. {COLOR_RED, 255, 0, 0},
  36. {COLOR_GREEN, 0, 255, 0},
  37. {COLOR_YELLOW, 255, 255, 0},
  38. {COLOR_BLUE, 0, 0, 255},
  39. {COLOR_MAGENTA, 255, 0, 255},
  40. {COLOR_CYAN, 0, 255, 255},
  41. {COLOR_WHITE, 255, 255, 255}
  42. };
  43. for (auto& basic : basic_colors) {
  44. int dr = r - basic.r;
  45. int dg = g - basic.g;
  46. int db = b - basic.b;
  47. int dist = dr*dr + dg*dg + db*db;
  48. if (dist < min_dist) {
  49. min_dist = dist;
  50. closest = basic.id;
  51. }
  52. }
  53. return closest;
  54. }
  55. std::string Theme::element_to_face_name(ThemeElement element) {
  56. switch (element) {
  57. case ThemeElement::Normal: return "default";
  58. case ThemeElement::Keyword: return "font-lock-keyword-face";
  59. case ThemeElement::String: return "font-lock-string-face";
  60. case ThemeElement::Comment: return "font-lock-comment-face";
  61. case ThemeElement::Function: return "font-lock-function-name-face";
  62. case ThemeElement::Type: return "font-lock-type-face";
  63. case ThemeElement::Number: return "font-lock-constant-face"; // Mapping number to constant for now
  64. case ThemeElement::Constant: return "font-lock-constant-face";
  65. case ThemeElement::Error: return "error";
  66. case ThemeElement::StatusLine: return "mode-line";
  67. case ThemeElement::StatusLineInactive: return "mode-line-inactive";
  68. case ThemeElement::MessageLine: return "minibuffer-prompt";
  69. case ThemeElement::LineNumber: return "line-number";
  70. case ThemeElement::Cursor: return "cursor";
  71. case ThemeElement::Selection: return "region";
  72. case ThemeElement::SearchMatch: return "isearch";
  73. case ThemeElement::SearchFail: return "isearch-fail";
  74. case ThemeElement::WindowBorder: return "window-divider";
  75. case ThemeElement::WindowBorderActive: return "window-divider-active";
  76. case ThemeElement::Background: return "default"; // Background usually part of default
  77. default: return "default";
  78. }
  79. }
  80. void Theme::set_face(const std::string& name, const FaceAttributes& attrs) {
  81. faces_[name] = attrs;
  82. // Invalidate cache
  83. face_pairs_.erase(name);
  84. }
  85. std::optional<FaceAttributes> Theme::get_face(const std::string& name) const {
  86. return get_face_recursive(name, 0);
  87. }
  88. std::optional<FaceAttributes> Theme::get_face_recursive(const std::string& name, int depth) const {
  89. // Prevent infinite recursion
  90. if (depth > 10) {
  91. return std::nullopt;
  92. }
  93. auto it = faces_.find(name);
  94. if (it == faces_.end()) {
  95. return std::nullopt;
  96. }
  97. FaceAttributes current = it->second;
  98. // Handle inheritance
  99. if (current.inherit) {
  100. auto parent = get_face_recursive(*current.inherit, depth + 1);
  101. if (parent) {
  102. // Start with parent attributes
  103. FaceAttributes final_attrs = *parent;
  104. // Merge current (child) attributes on top, overriding parent
  105. final_attrs.merge(current);
  106. return final_attrs;
  107. }
  108. }
  109. return current;
  110. }
  111. void Theme::set_color(ThemeElement element, const Color& fg, const Color& bg) {
  112. std::string face_name = element_to_face_name(element);
  113. FaceAttributes attrs;
  114. attrs.foreground = fg;
  115. attrs.background = bg;
  116. // Merge with existing if possible, or set new
  117. auto existing = get_face(face_name);
  118. if (existing) {
  119. FaceAttributes merged = *existing;
  120. merged.foreground = fg;
  121. merged.background = bg;
  122. set_face(face_name, merged);
  123. } else {
  124. set_face(face_name, attrs);
  125. }
  126. }
  127. Color Theme::get_fg_color(ThemeElement element) const {
  128. auto face = get_face(element_to_face_name(element));
  129. if (face && face->foreground) return *face->foreground;
  130. return Color(255, 255, 255);
  131. }
  132. Color Theme::get_bg_color(ThemeElement element) const {
  133. auto face = get_face(element_to_face_name(element));
  134. if (face && face->background) return *face->background;
  135. return Color(-1, -1, -1);
  136. }
  137. int Theme::get_color_pair(ThemeElement element) const {
  138. return get_face_color_pair(element_to_face_name(element));
  139. }
  140. int Theme::get_face_color_pair(const std::string& name) const {
  141. // Check cache
  142. auto it = face_pairs_.find(name);
  143. if (it != face_pairs_.end()) {
  144. return COLOR_PAIR(it->second);
  145. }
  146. // Get attributes
  147. auto face = get_face(name);
  148. // If face not found, try default
  149. if (!face && name != "default") {
  150. face = get_face("default");
  151. }
  152. if (!face) {
  153. return 0; // No attributes found
  154. }
  155. // Create new color pair
  156. int pair_id = next_pair_id_++;
  157. if (pair_id >= COLOR_PAIRS) {
  158. return 0; // Fallback to default if out of pairs
  159. }
  160. // Resolve colors (fallback to default if missing)
  161. Color fg = face->foreground.value_or(Color(255, 255, 255));
  162. Color bg = face->background.value_or(Color(-1, -1, -1));
  163. // Special case: if this is a face that should inherit from default, we might need logic here.
  164. // For now, just use what we have.
  165. int fg_color = fg.to_ncurses_color();
  166. int bg_color = bg.to_ncurses_color();
  167. init_pair(pair_id, fg_color, bg_color);
  168. face_pairs_[name] = pair_id;
  169. return COLOR_PAIR(pair_id);
  170. }
  171. int Theme::get_face_attributes_ncurses(const std::string& name) const {
  172. int pair = get_face_color_pair(name);
  173. int attrs = pair;
  174. auto face = get_face(name);
  175. if (!face && name != "default") {
  176. face = get_face("default");
  177. }
  178. if (face) {
  179. if (face->weight == FontWeight::Bold) attrs |= A_BOLD;
  180. // A_ITALIC might not be defined on all ncurses versions, use ifdef or ignore for now
  181. #ifdef A_ITALIC
  182. if (face->slant == FontSlant::Italic) attrs |= A_ITALIC;
  183. #endif
  184. if (face->underline.value_or(false)) attrs |= A_UNDERLINE;
  185. if (face->inverse.value_or(false)) attrs |= A_REVERSE;
  186. }
  187. return attrs;
  188. }
  189. void Theme::initialize_ncurses_colors() {
  190. // Initialize known standard faces
  191. std::vector<std::string> standard_faces = {
  192. "default", "font-lock-keyword-face", "font-lock-string-face", "font-lock-comment-face",
  193. "font-lock-function-name-face", "font-lock-type-face", "font-lock-constant-face",
  194. "error", "mode-line", "mode-line-inactive", "minibuffer-prompt", "line-number",
  195. "cursor", "region", "isearch", "isearch-fail", "window-divider"
  196. };
  197. for (const auto& name : standard_faces) {
  198. get_face_color_pair(name);
  199. }
  200. }
  201. // ... (ThemeManager implementation remains mostly same, but ensures we set faces correctly)
  202. void ThemeManager::register_theme(std::shared_ptr<Theme> theme) {
  203. themes_[theme->name()] = theme;
  204. if (!active_theme_) {
  205. active_theme_ = theme;
  206. }
  207. }
  208. void ThemeManager::set_active_theme(const std::string& name) {
  209. auto it = themes_.find(name);
  210. if (it != themes_.end()) {
  211. active_theme_ = it->second;
  212. active_theme_->initialize_ncurses_colors();
  213. }
  214. }
  215. std::vector<std::string> ThemeManager::theme_names() const {
  216. std::vector<std::string> names;
  217. for (auto& [name, theme] : themes_) {
  218. names.push_back(name);
  219. }
  220. return names;
  221. }
  222. void ThemeManager::create_default_themes() {
  223. register_theme(create_default_theme());
  224. register_theme(create_everforest_theme());
  225. register_theme(create_dracula_theme());
  226. }
  227. std::shared_ptr<Theme> ThemeManager::create_everforest_theme() {
  228. auto theme = std::make_shared<Theme>("everforest-dark");
  229. // Everforest color palette
  230. Color bg0(45, 49, 48); // #2d3139
  231. Color bg1(52, 56, 56); // #343839
  232. Color fg(211, 198, 170); // #d3c6aa
  233. Color red(230, 126, 128); // #e67e80
  234. Color orange(230, 152, 117); // #e69875
  235. Color yellow(219, 188, 127); // #dbbc7f
  236. Color green(167, 192, 128); // #a7c080
  237. Color cyan(131, 192, 146); // #83c092
  238. Color blue(125, 174, 163); // #7fbbb3
  239. Color purple(208, 135, 162); // #d699b5
  240. Color grey(146, 131, 116); // #928374
  241. // Text elements via set_color (which now sets faces)
  242. theme->set_color(ThemeElement::Normal, fg, bg0);
  243. theme->set_color(ThemeElement::String, green, bg0);
  244. theme->set_color(ThemeElement::Function, blue, bg0);
  245. theme->set_color(ThemeElement::Type, yellow, bg0);
  246. theme->set_color(ThemeElement::Number, purple, bg0);
  247. theme->set_color(ThemeElement::Constant, orange, bg0);
  248. theme->set_color(ThemeElement::Error, Color(255, 255, 255), red);
  249. // Demonstrate rich faces (Bold/Italic)
  250. FaceAttributes keyword_attrs;
  251. keyword_attrs.foreground = red;
  252. keyword_attrs.background = bg0;
  253. keyword_attrs.weight = FontWeight::Bold;
  254. theme->set_face("font-lock-keyword-face", keyword_attrs);
  255. FaceAttributes comment_attrs;
  256. comment_attrs.foreground = grey;
  257. comment_attrs.background = bg0;
  258. comment_attrs.slant = FontSlant::Italic;
  259. theme->set_face("font-lock-comment-face", comment_attrs);
  260. // UI elements
  261. theme->set_color(ThemeElement::StatusLine, bg0, fg);
  262. theme->set_color(ThemeElement::StatusLineInactive, grey, bg1);
  263. theme->set_color(ThemeElement::MessageLine, fg, bg0);
  264. theme->set_color(ThemeElement::LineNumber, grey, bg0);
  265. theme->set_color(ThemeElement::Cursor, bg0, fg);
  266. theme->set_color(ThemeElement::Selection, Color(255, 255, 255), blue);
  267. theme->set_color(ThemeElement::SearchMatch, bg0, yellow);
  268. theme->set_color(ThemeElement::SearchFail, Color(255, 255, 255), red);
  269. // Window elements
  270. theme->set_color(ThemeElement::WindowBorder, grey, bg0);
  271. theme->set_color(ThemeElement::WindowBorderActive, cyan, bg0);
  272. theme->set_color(ThemeElement::Background, fg, bg0);
  273. return theme;
  274. }
  275. std::shared_ptr<Theme> ThemeManager::create_default_theme() {
  276. auto theme = std::make_shared<Theme>("default");
  277. // Simple default theme using basic colors
  278. theme->set_color(ThemeElement::Normal, Color(255, 255, 255), Color(-1, -1, -1));
  279. theme->set_color(ThemeElement::Keyword, Color(0, 0, 255), Color(-1, -1, -1));
  280. theme->set_color(ThemeElement::String, Color(0, 255, 0), Color(-1, -1, -1));
  281. theme->set_color(ThemeElement::Comment, Color(128, 128, 128), Color(-1, -1, -1));
  282. theme->set_color(ThemeElement::Function, Color(0, 255, 255), Color(-1, -1, -1));
  283. theme->set_color(ThemeElement::Type, Color(255, 255, 0), Color(-1, -1, -1));
  284. theme->set_color(ThemeElement::Number, Color(255, 0, 255), Color(-1, -1, -1));
  285. theme->set_color(ThemeElement::Constant, Color(255, 0, 255), Color(-1, -1, -1));
  286. theme->set_color(ThemeElement::Error, Color(255, 255, 255), Color(255, 0, 0));
  287. theme->set_color(ThemeElement::StatusLine, Color(0, 0, 0), Color(255, 255, 255));
  288. theme->set_color(ThemeElement::StatusLineInactive, Color(128, 128, 128), Color(255, 255, 255));
  289. theme->set_color(ThemeElement::MessageLine, Color(255, 255, 255), Color(-1, -1, -1));
  290. theme->set_color(ThemeElement::LineNumber, Color(128, 128, 128), Color(-1, -1, -1));
  291. theme->set_color(ThemeElement::Cursor, Color(0, 0, 0), Color(255, 255, 255));
  292. theme->set_color(ThemeElement::Selection, Color(255, 255, 255), Color(0, 0, 255));
  293. theme->set_color(ThemeElement::SearchMatch, Color(0, 0, 0), Color(255, 255, 0));
  294. theme->set_color(ThemeElement::SearchFail, Color(255, 255, 255), Color(255, 0, 0));
  295. theme->set_color(ThemeElement::WindowBorder, Color(255, 255, 255), Color(-1, -1, -1));
  296. theme->set_color(ThemeElement::WindowBorderActive, Color(0, 255, 255), Color(-1, -1, -1));
  297. theme->set_color(ThemeElement::Background, Color(255, 255, 255), Color(-1, -1, -1));
  298. return theme;
  299. }
  300. std::shared_ptr<Theme> ThemeManager::create_dracula_theme() {
  301. auto theme = std::make_shared<Theme>("dracula");
  302. // Dracula color palette
  303. Color bg(40, 42, 54); // #282a36 - Background
  304. Color current_line(68, 71, 90); // #44475a - Current Line
  305. Color fg(248, 248, 242); // #f8f8f2 - Foreground
  306. Color comment(98, 114, 164); // #6272a4 - Comment
  307. Color cyan(139, 233, 253); // #8be9fd - Cyan
  308. Color green(80, 250, 123); // #50fa7b - Green
  309. Color orange(255, 184, 108); // #ffb86c - Orange
  310. Color pink(255, 121, 198); // #ff79c6 - Pink
  311. Color purple(189, 147, 249); // #bd93f9 - Purple
  312. Color red(255, 85, 85); // #ff5555 - Red
  313. Color yellow(241, 250, 140); // #f1fa8c - Yellow
  314. // Text elements
  315. theme->set_color(ThemeElement::Normal, fg, bg);
  316. theme->set_color(ThemeElement::Keyword, pink, bg);
  317. theme->set_color(ThemeElement::String, yellow, bg);
  318. theme->set_color(ThemeElement::Comment, comment, bg);
  319. theme->set_color(ThemeElement::Function, green, bg);
  320. theme->set_color(ThemeElement::Type, cyan, bg);
  321. theme->set_color(ThemeElement::Number, purple, bg);
  322. theme->set_color(ThemeElement::Constant, orange, bg);
  323. theme->set_color(ThemeElement::Error, fg, red);
  324. // UI elements
  325. theme->set_color(ThemeElement::StatusLine, bg, purple);
  326. theme->set_color(ThemeElement::StatusLineInactive, comment, current_line);
  327. theme->set_color(ThemeElement::MessageLine, fg, bg);
  328. theme->set_color(ThemeElement::LineNumber, comment, bg);
  329. theme->set_color(ThemeElement::Cursor, bg, fg);
  330. theme->set_color(ThemeElement::Selection, fg, current_line);
  331. theme->set_color(ThemeElement::SearchMatch, bg, orange);
  332. theme->set_color(ThemeElement::SearchFail, fg, red);
  333. // Window elements
  334. theme->set_color(ThemeElement::WindowBorder, comment, bg);
  335. theme->set_color(ThemeElement::WindowBorderActive, pink, bg);
  336. theme->set_color(ThemeElement::Background, fg, bg);
  337. return theme;
  338. }
  339. } // namespace lumacs