/* OmniaFramework - A collection of useful functionality Copyright (C) 2025 OmniaX-Dev This file is part of OmniaFramework. OmniaFramework is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmniaFramework is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OmniaFramework. If not, see . */ #include "Stylesheet.hpp" #include "FileSystem.hpp" #include "Logger.hpp" namespace ostd { Stylesheet::Stylesheet(void) { } Stylesheet& Stylesheet::clear(void) { m_values.clear(); return *this; } Stylesheet& Stylesheet::loadFromFile(const ostd::String& filePath, bool clearCurrentRules) { ostd::String outContent = ""; ostd::FileSystem::readTextFileRaw(filePath, outContent); return loadFromString(outContent, filePath, clearCurrentRules); } Stylesheet& Stylesheet::loadFromString(const ostd::String& content, const ostd::String& filePath, bool clearCurrentRules) { if (clearCurrentRules) m_values.clear(); std::vector lines = content.tokenize("\n", false, true).getRawData(); uint32_t lineNumber = 0; ostd::String lineCopy = ""; std::unordered_map variables; auto l_warn = [&](const ostd::String& msg) -> void { OX_WARN("%s in theme line. File: <%s:%d>:\n\t%s", msg.c_str(), filePath.c_str(), lineNumber, lineCopy.c_str()); }; auto l_parseLine = [&](ostd::String& line) -> void { for (const auto&[var, val] : variables) line.replaceAll(var, val); if (!parseThemeFileLine(line)) l_warn("Error"); }; ostd::String groupSelector = ""; bool groupLines = false; std::vector group; for (auto& line : lines) { lineNumber++; lineCopy = line; line.trim(); if (line.startsWith("%")) continue; if (line.contains("%")) line.substr(0, line.indexOf("%")).trim(); if (line == "") continue; if (line.startsWith("$")) { line.substr(1); if (line.count("=") != 1 || line.endsWith("=")) { l_warn("Invalid variable"); continue; } ostd::String varName = line.new_substr(0, line.indexOf("=")).trim(); ostd::String varValue = line.new_substr(line.indexOf("=") + 1).trim(); if (!varName.regexMatches(m_validNameRegex)) { l_warn("Invalid variable name"); continue; } variables["$" + varName] = varValue; continue; } if (groupSelector != "") { if (groupLines) { if (line == "}") { groupLines = false; auto newLines = parseGroup(groupSelector, group); for (auto& l : newLines) l_parseLine(l); groupSelector = ""; group.clear(); continue; } group.push_back(line); } else { if (line == "{") groupLines = true; } continue; } if (line.startsWith("(")) { if (line.count(")") != 1 || line.indexOf(")") < 3) { l_warn("Invalid group selector"); continue; } ostd::String rawSelector = line.new_substr(1, line.indexOf(")")).trim(); groupSelector = parseGroupSelector(rawSelector); if (line.contains("{")) { line.substr(line.indexOf("{")).trim(); if (line != "{") { l_warn("Invalid group selector"); continue; } groupLines = true; } continue; } l_parseLine(line); } return *this; } void Stylesheet::set(const std::string& key, TypeVariant value, const ostd::String& themeID) { ostd::String fullKey = "@" + themeID + "." + key; // std::cout << "SET: " << fullKey << "\n"; setFull(fullKey, value); } void Stylesheet::setFull(const ostd::String& fullKey, TypeVariant value) { m_values[fullKey] = std::move(value); } const Stylesheet::TypeVariant* Stylesheet::getVariant(const ostd::String& key, const ostd::String& themeID, const QualifierList& qualifierList) const { for (const auto&[qualifier, state] : qualifierList) { const TypeVariant* v = (state ? getFull("@" + themeID + ":" + qualifier + "." + key) : nullptr); // std::cout << "0: " << "@" + themeID + ":" + qualifier + "." + key << "\n"; if (v) { std::cout << "1: " << "@" + themeID + ":" + qualifier + "." + key << "\n"; return v; } v = (state ? getFull("@:" + qualifier + "." + key) : nullptr); if (v) { std::cout << "2: " << "@:" + qualifier + "." + key << "\n"; return v; } } if (auto v = getFull("@" + themeID + "." + key)) { std::cout << "3: " << "@" + themeID + "." + key << "\n"; return v; } else if (auto v = getFull("@." + key)) { std::cout << "4: " << "@." + key << "\n"; return v; } std::cout << "\n\n"; return nullptr; } const Stylesheet::TypeVariant* Stylesheet::getFull(const ostd::String& fullKey) const { auto it = m_values.find(fullKey); // if (it != m_values.end()) // std::cout << " GET: " << fullKey << "\n"; return it != m_values.end() ? &it->second : nullptr; } bool Stylesheet::parseThemeFileLine(const ostd::String& line) { if (!line.contains("=")) return false; ostd::String key = line.new_substr(0, line.indexOf("=")).trim(); ostd::String valuePreserveCase = line.new_substr(line.indexOf("=") + 1).trim(); ostd::String value = line.new_substr(line.indexOf("=") + 1).trim().toLower(); if (key == "") return false; ostd::String themeID = ""; if (key.startsWith("@")) { if (key.indexOf(" ") < 2) return false; themeID = key.new_substr(1, key.indexOf(" ")).trim(); key.substr(key.indexOf(" ") + 1).trim(); } if (value.isInt()) set(key, static_cast(value.toInt()), themeID); else if (value.isNumeric(true)) set(key, value.toFloat(), themeID); else if (value == "true" || value == "false") set(key, value == "true", themeID); else if (value.startsWith("\"") && value.endsWith("\"")) { valuePreserveCase.substr(1, value.len() - 1); set(key, valuePreserveCase, themeID); } else if (value.startsWith("color(") && value.endsWith(")")) { value.substr(6, value.len() - 1).trim(); set(key, ostd::Color(value), themeID); } else if (value.startsWith("vec2(") && value.endsWith(")")) { value.substr(4, value.len() - 1).trim(); auto tokens = value.tokenize(","); if (tokens.count() != 2) return false; std::vector vec; for (const auto& tok : tokens) { if (!tok.isNumeric(true)) return false; vec.push_back(tok.toFloat()); } set(key, ostd::Vec2(vec[0], vec[1]), themeID); } else if (value.startsWith("rect(") && value.endsWith(")")) { value.substr(5, value.len() - 1).trim(); auto tokens = value.tokenize(","); if (tokens.count() != 4) return false; std::vector vec; for (const auto& tok : tokens) { if (!tok.isNumeric(true)) return false; vec.push_back(tok.toFloat()); } set(key, ostd::Rectangle(vec[0], vec[1], vec[2], vec[3]), themeID); } else return false; return true; } ostd::String Stylesheet::parseGroupSelector(const ostd::String& rawSelector) const { ostd::String sel = rawSelector.new_trim(); if (sel == "") return ""; ostd::String id = ""; ostd::String name = sel; ostd::String qual = ""; if (sel.contains(" ")) { id = sel.new_substr(0, sel.indexOf(" ")).trim(); name = sel.new_substr(sel.indexOf(" ") + 1).trim(); } if (name.contains(":")) { if (name.startsWith(":") || name.endsWith(":") || name.count(":") != 1) return ""; qual = name.new_substr(name.indexOf(":") + 1).trim(); name.substr(0, name.indexOf(":")).trim(); } if (qual != "") { if (!id.startsWith("@")) id = "@" + id; id += ":" + qual; } id += " " + name; id.trim(); return id; } std::vector Stylesheet::parseGroup(const ostd::String& selector, const std::vector& group) { if (selector.new_trim() == "" || group.size() == 0) return {}; std::vector newLines; for (const auto& property : group) newLines.push_back(selector.new_add(".").new_add(property)); return newLines; } void Stylesheet::debugPrint(void) { for (const auto&[key, value] : m_values) { std::cout << key << "\n"; } } }