| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- #include "lumacs/theme.hpp"
- #include <ncurses.h>
- #include <algorithm>
- #include <climits>
- namespace lumacs {
- static std::map<Color, int> color_cache;
- static int next_color_id = 8; // Start after the basic 8 colors
- int Color::to_ncurses_color() const {
- // Handle default colors
- if (r == -1 && g == -1 && b == -1) {
- return -1; // Use default
- }
-
- // Check cache first
- auto it = color_cache.find(*this);
- if (it != color_cache.end()) {
- return it->second;
- }
-
- // Check if we can create new colors
- if (can_change_color() && next_color_id < COLOR_PAIRS) {
- int color_id = next_color_id++;
- // Scale RGB values to 0-1000 range for ncurses
- int nr = (r * 1000) / 255;
- int ng = (g * 1000) / 255;
- int nb = (b * 1000) / 255;
-
- init_color(color_id, nr, ng, nb);
- color_cache[*this] = color_id;
- return color_id;
- }
-
- // Fallback to closest basic color
- int min_dist = INT_MAX;
- int closest = COLOR_WHITE;
-
- struct BasicColor { int id; int r, g, b; };
- BasicColor basic_colors[] = {
- {COLOR_BLACK, 0, 0, 0},
- {COLOR_RED, 255, 0, 0},
- {COLOR_GREEN, 0, 255, 0},
- {COLOR_YELLOW, 255, 255, 0},
- {COLOR_BLUE, 0, 0, 255},
- {COLOR_MAGENTA, 255, 0, 255},
- {COLOR_CYAN, 0, 255, 255},
- {COLOR_WHITE, 255, 255, 255}
- };
-
- for (auto& basic : basic_colors) {
- int dr = r - basic.r;
- int dg = g - basic.g;
- int db = b - basic.b;
- int dist = dr*dr + dg*dg + db*db;
- if (dist < min_dist) {
- min_dist = dist;
- closest = basic.id;
- }
- }
-
- return closest;
- }
- std::string Theme::element_to_face_name(ThemeElement element) {
- switch (element) {
- case ThemeElement::Normal: return "default";
- case ThemeElement::Keyword: return "font-lock-keyword-face";
- case ThemeElement::String: return "font-lock-string-face";
- case ThemeElement::Comment: return "font-lock-comment-face";
- case ThemeElement::Function: return "font-lock-function-name-face";
- case ThemeElement::Type: return "font-lock-type-face";
- case ThemeElement::Number: return "font-lock-constant-face"; // Mapping number to constant for now
- case ThemeElement::Constant: return "font-lock-constant-face";
- case ThemeElement::Error: return "error";
- case ThemeElement::StatusLine: return "mode-line";
- case ThemeElement::StatusLineInactive: return "mode-line-inactive";
- case ThemeElement::MessageLine: return "minibuffer-prompt";
- case ThemeElement::LineNumber: return "line-number";
- case ThemeElement::Cursor: return "cursor";
- case ThemeElement::Selection: return "region";
- case ThemeElement::SearchMatch: return "isearch";
- case ThemeElement::SearchFail: return "isearch-fail";
- case ThemeElement::WindowBorder: return "window-divider";
- case ThemeElement::WindowBorderActive: return "window-divider-active";
- case ThemeElement::Background: return "default"; // Background usually part of default
- default: return "default";
- }
- }
- void Theme::set_face(const std::string& name, const FaceAttributes& attrs) {
- faces_[name] = attrs;
- // Invalidate cache
- face_pairs_.erase(name);
- }
- std::optional<FaceAttributes> Theme::get_face(const std::string& name) const {
- return get_face_recursive(name, 0);
- }
- std::optional<FaceAttributes> Theme::get_face_recursive(const std::string& name, int depth) const {
- // Prevent infinite recursion
- if (depth > 10) {
- return std::nullopt;
- }
- auto it = faces_.find(name);
- if (it == faces_.end()) {
- return std::nullopt;
- }
- FaceAttributes current = it->second;
- // Handle inheritance
- if (current.inherit) {
- auto parent = get_face_recursive(*current.inherit, depth + 1);
- if (parent) {
- // Start with parent attributes
- FaceAttributes final_attrs = *parent;
- // Merge current (child) attributes on top, overriding parent
- final_attrs.merge(current);
- return final_attrs;
- }
- }
- return current;
- }
- void Theme::set_color(ThemeElement element, const Color& fg, const Color& bg) {
- std::string face_name = element_to_face_name(element);
- FaceAttributes attrs;
- attrs.foreground = fg;
- attrs.background = bg;
-
- // Merge with existing if possible, or set new
- auto existing = get_face(face_name);
- if (existing) {
- FaceAttributes merged = *existing;
- merged.foreground = fg;
- merged.background = bg;
- set_face(face_name, merged);
- } else {
- set_face(face_name, attrs);
- }
- }
- Color Theme::get_fg_color(ThemeElement element) const {
- auto face = get_face(element_to_face_name(element));
- if (face && face->foreground) return *face->foreground;
- return Color(255, 255, 255);
- }
- Color Theme::get_bg_color(ThemeElement element) const {
- auto face = get_face(element_to_face_name(element));
- if (face && face->background) return *face->background;
- return Color(-1, -1, -1);
- }
- int Theme::get_color_pair(ThemeElement element) const {
- return get_face_color_pair(element_to_face_name(element));
- }
- int Theme::get_face_color_pair(const std::string& name) const {
- // Check cache
- auto it = face_pairs_.find(name);
- if (it != face_pairs_.end()) {
- return COLOR_PAIR(it->second);
- }
-
- // Get attributes
- auto face = get_face(name);
-
- // If face not found, try default
- if (!face && name != "default") {
- face = get_face("default");
- }
-
- if (!face) {
- return 0; // No attributes found
- }
-
- // Create new color pair
- int pair_id = next_pair_id_++;
- if (pair_id >= COLOR_PAIRS) {
- return 0; // Fallback to default if out of pairs
- }
-
- // Resolve colors (fallback to default if missing)
- Color fg = face->foreground.value_or(Color(255, 255, 255));
- Color bg = face->background.value_or(Color(-1, -1, -1));
-
- // Special case: if this is a face that should inherit from default, we might need logic here.
- // For now, just use what we have.
-
- int fg_color = fg.to_ncurses_color();
- int bg_color = bg.to_ncurses_color();
-
- init_pair(pair_id, fg_color, bg_color);
- face_pairs_[name] = pair_id;
-
- return COLOR_PAIR(pair_id);
- }
- int Theme::get_face_attributes_ncurses(const std::string& name) const {
- int pair = get_face_color_pair(name);
- int attrs = pair;
-
- auto face = get_face(name);
- if (!face && name != "default") {
- face = get_face("default");
- }
-
- if (face) {
- if (face->weight == FontWeight::Bold) attrs |= A_BOLD;
- // A_ITALIC might not be defined on all ncurses versions, use ifdef or ignore for now
- #ifdef A_ITALIC
- if (face->slant == FontSlant::Italic) attrs |= A_ITALIC;
- #endif
- if (face->underline.value_or(false)) attrs |= A_UNDERLINE;
- if (face->inverse.value_or(false)) attrs |= A_REVERSE;
- }
-
- return attrs;
- }
- void Theme::initialize_ncurses_colors() {
- // Initialize known standard faces
- std::vector<std::string> standard_faces = {
- "default", "font-lock-keyword-face", "font-lock-string-face", "font-lock-comment-face",
- "font-lock-function-name-face", "font-lock-type-face", "font-lock-constant-face",
- "error", "mode-line", "mode-line-inactive", "minibuffer-prompt", "line-number",
- "cursor", "region", "isearch", "isearch-fail", "window-divider"
- };
-
- for (const auto& name : standard_faces) {
- get_face_color_pair(name);
- }
- }
- // ... (ThemeManager implementation remains mostly same, but ensures we set faces correctly)
- void ThemeManager::register_theme(std::shared_ptr<Theme> theme) {
- themes_[theme->name()] = theme;
- if (!active_theme_) {
- active_theme_ = theme;
- }
- }
- void ThemeManager::set_active_theme(const std::string& name) {
- auto it = themes_.find(name);
- if (it != themes_.end()) {
- active_theme_ = it->second;
- active_theme_->initialize_ncurses_colors();
- }
- }
- std::vector<std::string> ThemeManager::theme_names() const {
- std::vector<std::string> names;
- for (auto& [name, theme] : themes_) {
- names.push_back(name);
- }
- return names;
- }
- void ThemeManager::create_default_themes() {
- register_theme(create_default_theme());
- register_theme(create_everforest_theme());
- register_theme(create_dracula_theme());
- }
- std::shared_ptr<Theme> ThemeManager::create_everforest_theme() {
- auto theme = std::make_shared<Theme>("everforest-dark");
-
- // Everforest color palette
- Color bg0(45, 49, 48); // #2d3139
- Color bg1(52, 56, 56); // #343839
- Color fg(211, 198, 170); // #d3c6aa
-
- Color red(230, 126, 128); // #e67e80
- Color orange(230, 152, 117); // #e69875
- Color yellow(219, 188, 127); // #dbbc7f
- Color green(167, 192, 128); // #a7c080
- Color cyan(131, 192, 146); // #83c092
- Color blue(125, 174, 163); // #7fbbb3
- Color purple(208, 135, 162); // #d699b5
- Color grey(146, 131, 116); // #928374
-
- // Text elements via set_color (which now sets faces)
- theme->set_color(ThemeElement::Normal, fg, bg0);
- theme->set_color(ThemeElement::String, green, bg0);
- theme->set_color(ThemeElement::Function, blue, bg0);
- theme->set_color(ThemeElement::Type, yellow, bg0);
- theme->set_color(ThemeElement::Number, purple, bg0);
- theme->set_color(ThemeElement::Constant, orange, bg0);
- theme->set_color(ThemeElement::Error, Color(255, 255, 255), red);
- // Demonstrate rich faces (Bold/Italic)
- FaceAttributes keyword_attrs;
- keyword_attrs.foreground = red;
- keyword_attrs.background = bg0;
- keyword_attrs.weight = FontWeight::Bold;
- theme->set_face("font-lock-keyword-face", keyword_attrs);
- FaceAttributes comment_attrs;
- comment_attrs.foreground = grey;
- comment_attrs.background = bg0;
- comment_attrs.slant = FontSlant::Italic;
- theme->set_face("font-lock-comment-face", comment_attrs);
-
- // UI elements
- theme->set_color(ThemeElement::StatusLine, bg0, fg);
- theme->set_color(ThemeElement::StatusLineInactive, grey, bg1);
- theme->set_color(ThemeElement::MessageLine, fg, bg0);
- theme->set_color(ThemeElement::LineNumber, grey, bg0);
- theme->set_color(ThemeElement::Cursor, bg0, fg);
- theme->set_color(ThemeElement::Selection, Color(255, 255, 255), blue);
- theme->set_color(ThemeElement::SearchMatch, bg0, yellow);
- theme->set_color(ThemeElement::SearchFail, Color(255, 255, 255), red);
-
- // Window elements
- theme->set_color(ThemeElement::WindowBorder, grey, bg0);
- theme->set_color(ThemeElement::WindowBorderActive, cyan, bg0);
- theme->set_color(ThemeElement::Background, fg, bg0);
-
- return theme;
- }
- std::shared_ptr<Theme> ThemeManager::create_default_theme() {
- auto theme = std::make_shared<Theme>("default");
-
- // Simple default theme using basic colors
- theme->set_color(ThemeElement::Normal, Color(255, 255, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Keyword, Color(0, 0, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::String, Color(0, 255, 0), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Comment, Color(128, 128, 128), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Function, Color(0, 255, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Type, Color(255, 255, 0), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Number, Color(255, 0, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Constant, Color(255, 0, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Error, Color(255, 255, 255), Color(255, 0, 0));
-
- theme->set_color(ThemeElement::StatusLine, Color(0, 0, 0), Color(255, 255, 255));
- theme->set_color(ThemeElement::StatusLineInactive, Color(128, 128, 128), Color(255, 255, 255));
- theme->set_color(ThemeElement::MessageLine, Color(255, 255, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::LineNumber, Color(128, 128, 128), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Cursor, Color(0, 0, 0), Color(255, 255, 255));
- theme->set_color(ThemeElement::Selection, Color(255, 255, 255), Color(0, 0, 255));
- theme->set_color(ThemeElement::SearchMatch, Color(0, 0, 0), Color(255, 255, 0));
- theme->set_color(ThemeElement::SearchFail, Color(255, 255, 255), Color(255, 0, 0));
- theme->set_color(ThemeElement::WindowBorder, Color(255, 255, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::WindowBorderActive, Color(0, 255, 255), Color(-1, -1, -1));
- theme->set_color(ThemeElement::Background, Color(255, 255, 255), Color(-1, -1, -1));
-
- return theme;
- }
- std::shared_ptr<Theme> ThemeManager::create_dracula_theme() {
- auto theme = std::make_shared<Theme>("dracula");
-
- // Dracula color palette
- Color bg(40, 42, 54); // #282a36 - Background
- Color current_line(68, 71, 90); // #44475a - Current Line
- Color fg(248, 248, 242); // #f8f8f2 - Foreground
- Color comment(98, 114, 164); // #6272a4 - Comment
- Color cyan(139, 233, 253); // #8be9fd - Cyan
- Color green(80, 250, 123); // #50fa7b - Green
- Color orange(255, 184, 108); // #ffb86c - Orange
- Color pink(255, 121, 198); // #ff79c6 - Pink
- Color purple(189, 147, 249); // #bd93f9 - Purple
- Color red(255, 85, 85); // #ff5555 - Red
- Color yellow(241, 250, 140); // #f1fa8c - Yellow
-
- // Text elements
- theme->set_color(ThemeElement::Normal, fg, bg);
- theme->set_color(ThemeElement::Keyword, pink, bg);
- theme->set_color(ThemeElement::String, yellow, bg);
- theme->set_color(ThemeElement::Comment, comment, bg);
- theme->set_color(ThemeElement::Function, green, bg);
- theme->set_color(ThemeElement::Type, cyan, bg);
- theme->set_color(ThemeElement::Number, purple, bg);
- theme->set_color(ThemeElement::Constant, orange, bg);
- theme->set_color(ThemeElement::Error, fg, red);
-
- // UI elements
- theme->set_color(ThemeElement::StatusLine, bg, purple);
- theme->set_color(ThemeElement::StatusLineInactive, comment, current_line);
- theme->set_color(ThemeElement::MessageLine, fg, bg);
- theme->set_color(ThemeElement::LineNumber, comment, bg);
- theme->set_color(ThemeElement::Cursor, bg, fg);
- theme->set_color(ThemeElement::Selection, fg, current_line);
- theme->set_color(ThemeElement::SearchMatch, bg, orange);
- theme->set_color(ThemeElement::SearchFail, fg, red);
-
- // Window elements
- theme->set_color(ThemeElement::WindowBorder, comment, bg);
- theme->set_color(ThemeElement::WindowBorderActive, pink, bg);
- theme->set_color(ThemeElement::Background, fg, bg);
-
- return theme;
- }
- } // namespace lumacs
|