|
|
@@ -0,0 +1,231 @@
|
|
|
+#include "lumacs/theme.hpp"
|
|
|
+#include <ncurses.h>
|
|
|
+#include <algorithm>
|
|
|
+
|
|
|
+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
|
|
|
+ // This is a simple approximation - could be improved
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+bool Color::operator<(const Color& other) const {
|
|
|
+ if (r != other.r) return r < other.r;
|
|
|
+ if (g != other.g) return g < other.g;
|
|
|
+ return b < other.b;
|
|
|
+}
|
|
|
+
|
|
|
+void Theme::set_color(ThemeElement element, const Color& fg, const Color& bg) {
|
|
|
+ colors_[element] = {fg, bg};
|
|
|
+ // Invalidate cached color pair
|
|
|
+ color_pairs_.erase(element);
|
|
|
+}
|
|
|
+
|
|
|
+Color Theme::get_fg_color(ThemeElement element) const {
|
|
|
+ auto it = colors_.find(element);
|
|
|
+ if (it != colors_.end()) {
|
|
|
+ return it->second.first;
|
|
|
+ }
|
|
|
+ // Default foreground
|
|
|
+ return Color(255, 255, 255);
|
|
|
+}
|
|
|
+
|
|
|
+Color Theme::get_bg_color(ThemeElement element) const {
|
|
|
+ auto it = colors_.find(element);
|
|
|
+ if (it != colors_.end()) {
|
|
|
+ return it->second.second;
|
|
|
+ }
|
|
|
+ // Default background
|
|
|
+ return Color(-1, -1, -1);
|
|
|
+}
|
|
|
+
|
|
|
+int Theme::get_color_pair(ThemeElement element) const {
|
|
|
+ auto it = color_pairs_.find(element);
|
|
|
+ if (it != color_pairs_.end()) {
|
|
|
+ return COLOR_PAIR(it->second);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create new color pair
|
|
|
+ int pair_id = next_pair_id_++;
|
|
|
+ if (pair_id >= COLOR_PAIRS) {
|
|
|
+ return 0; // Fallback to default
|
|
|
+ }
|
|
|
+
|
|
|
+ Color fg = get_fg_color(element);
|
|
|
+ Color bg = get_bg_color(element);
|
|
|
+
|
|
|
+ int fg_color = fg.to_ncurses_color();
|
|
|
+ int bg_color = bg.to_ncurses_color();
|
|
|
+
|
|
|
+ init_pair(pair_id, fg_color, bg_color);
|
|
|
+ color_pairs_[element] = pair_id;
|
|
|
+
|
|
|
+ return COLOR_PAIR(pair_id);
|
|
|
+}
|
|
|
+
|
|
|
+void Theme::initialize_ncurses_colors() {
|
|
|
+ // Pre-initialize all color pairs for this theme
|
|
|
+ for (auto& [element, colors] : colors_) {
|
|
|
+ get_color_pair(element);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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());
|
|
|
+}
|
|
|
+
|
|
|
+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 bg2(67, 72, 71); // #434847
|
|
|
+ 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
|
|
|
+ theme->set_color(ThemeElement::Normal, fg, bg0);
|
|
|
+ theme->set_color(ThemeElement::Keyword, red, bg0);
|
|
|
+ theme->set_color(ThemeElement::String, green, bg0);
|
|
|
+ theme->set_color(ThemeElement::Comment, grey, 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);
|
|
|
+
|
|
|
+ // 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;
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace lumacs
|