theme.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. // The theme is already initialized (if necessary) when created by Lua
  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. // create_default_themes() and individual create_X_theme() methods are now removed.
  223. // Themes will be loaded from Lua scripts.
  224. } // namespace lumacs