diff --git a/extra/DefaultThemeDark.oss b/extra/DefaultThemeDark.oss index 417aa3c..8e8aee6 100644 --- a/extra/DefaultThemeDark.oss +++ b/extra/DefaultThemeDark.oss @@ -9,9 +9,17 @@ const $darkColor = #223322FF const $accentColorLight = #87FF5CFF const $accentColorDark = #3B7500FF const $backgroundColor = rgba(30, 30, 30, 255) +const $crimsonAccent = rgba(220, 20, 60, 150) % =================== +macro set_focus_border(color, test = "ciccio,ciao", width = 1, width2 = false) { + focusBorderColor = $color; + showFocusBorder = $width2; + focusBorderWidth = $width; +} + + % ====== Root Widget ====== (window) { backgroundColor = $backgroundColor @@ -383,5 +391,6 @@ const $backgroundColor = rgba(30, 30, 30, 255) branchLineColor = $textColor showBranchLines = true branchLineThickness = 1.0 + set_focus_border(#FF0000.lighten(0.5), "bella balla", 5) } % ====================== diff --git a/other/TODO.txt b/other/TODO.txt index f0d12f7..c7114fe 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -56,6 +56,7 @@ Add ToolBar::addButton overload that takes a ogfx::Icon wrapper Redesign theme overrides to be more robust FIX: Animated icons not working in TreeView Add modifiers to Event.keyboard path +Check if possible, then change all WindowCore& refs to Window& diff --git a/src/ogfx/gui/ContextMenu.cpp b/src/ogfx/gui/ContextMenu.cpp index 61d8734..5447eba 100644 --- a/src/ogfx/gui/ContextMenu.cpp +++ b/src/ogfx/gui/ContextMenu.cpp @@ -304,6 +304,7 @@ namespace ogfx push_panel(m_data.entries, pos, false); m_visible = true; m_animClock.startCount(ostd::eTimeUnits::Milliseconds); + cast(m_window).getFocusManager().clearFocus(); } void ContextMenu::hide(void) diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index 4760716..27b2953 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -39,10 +39,10 @@ namespace ogfx Widget::~Widget(void) { if (m_window && m_tabIndex >= 0) - cast(*m_window).getFocusManager().unregisterTabIndex(*this); - auto& fm = cast(*m_window).getFocusManager(); - if (fm.getFocused() == this) - fm.clearFocus(); // you'd add this method + cast(*m_window).getFocusManager().unregisterTabIndex(*this); + auto& fm = cast(*m_window).getFocusManager(); + if (fm.getFocused() == this) + fm.clearFocus(); // you'd add this method } bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback) @@ -316,9 +316,8 @@ namespace ogfx if (m_showBorder) gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); afterDraw(gfx); - - if (isFocused()) - gfx.drawRoundRect({ getGlobalPosition(), getSize() }, Colors::Red, m_borderRadius, 3); + if (isFocused() && isFocusBorderEnabled()) + gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_focusBorderColor, m_borderRadius, m_focusBorderWidth); } void Widget::__update(void) @@ -527,6 +526,9 @@ namespace ogfx setBorderWidth(getThemeValue(theme, "borderWidth", m_borderWidth)); enableBorder(getThemeValue(theme, "showBorder", m_showBorder)); setBorderColor(getThemeValue(theme, "borderColor", m_borderColor)); + enableFocusBorder(getThemeValue(theme, "showFocusBorder", m_showFocusBorder)); + setFocusBorderColor(getThemeValue(theme, "focusBorderColor", m_focusBorderColor)); + setFocusBorderWidth(getThemeValue(theme, "focusBorderWidth", m_focusBorderWidth)); enableBackground(getThemeValue(theme, "showBackground", m_showBackground)); setPadding(getThemeValue(theme, "padding", m_padding)); setMargin(getThemeValue(theme, "margin", m_margin)); diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index cc12a6e..4c58504 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -172,8 +172,10 @@ namespace ogfx OSTD_PARAM_GETSET(Color, TextColor, m_textColor); OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor); OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor); + OSTD_PARAM_GETSET(Color, FocusBorderColor, m_focusBorderColor); OSTD_PARAM_GETSET(i32, BorderRadius, m_borderRadius); OSTD_PARAM_GETSET(i32, BorderWidth, m_borderWidth); + OSTD_PARAM_GETSET(i32, FocusBorderWidth, m_focusBorderWidth); OSTD_PARAM_GETSET(i32, FontSize, m_fontSize); OSTD_PARAM_GETSET(ostd::ColorGradient, BackgroundGradient, m_backgroundGradient); OSTD_PARAM_GETSET(Rectangle, Padding, m_padding); @@ -199,6 +201,7 @@ namespace ogfx OSTD_BOOL_PARAM_GETSET_E(DragAndDrop, m_acceptDragAndDrop); OSTD_BOOL_PARAM_GETSET_E(Background, m_showBackground); OSTD_BOOL_PARAM_GETSET_E(Border, m_showBorder); + OSTD_BOOL_PARAM_GETSET_E(FocusBorder, m_showFocusBorder); OSTD_BOOL_PARAM_GETSET_E(ManualDraw, m_manualDraw); OSTD_BOOL_PARAM_GETSET_E(TopMost, m_topMost); OSTD_BOOL_PARAM_GETSET_E(IgnoreScroll, m_ignoreScroll); @@ -300,6 +303,9 @@ namespace ogfx i32 m_borderWidth { 2 }; Color m_borderColor { 255, 255, 255 }; bool m_showBorder { false }; + i32 m_focusBorderWidth { 2 }; + Color m_focusBorderColor { 0, 150, 20 }; + bool m_showFocusBorder { false }; Color m_backgroundColor { Colors::Transparent }; bool m_showBackground { false }; i32 m_fontSize { 20 }; diff --git a/src/ostd/io/Stylesheet.cpp b/src/ostd/io/Stylesheet.cpp index ec49469..c0c17e1 100644 --- a/src/ostd/io/Stylesheet.cpp +++ b/src/ostd/io/Stylesheet.cpp @@ -22,6 +22,7 @@ #include "FileSystem.hpp" #include "Logger.hpp" #include +#include namespace ostd { @@ -65,6 +66,8 @@ namespace ostd bool debug_print = false; bool brokenLine = false; String brokenLineBuffer = ""; + String macroCode = ""; + bool inMacro = false; for (auto& line : lines) { lineNumber++; @@ -87,6 +90,30 @@ namespace ostd brokenLineBuffer = line.new_trim().substr(0, line.len() - 1).trim(); continue; } + if (inMacro) + { + for (const auto& c : line) + { + macroCode += c; + if (c == '}') + { + inMacro = false; + macroCode.trim(); + if (!parseMacro(macroCode)) + l_warn("Invalid macro"); + macroCode = ""; + break; + } + } + continue; + } + if (line.startsWith("macro ")) + { + line.substr(6).trim(); + macroCode += line; + inMacro = true; + continue; + } if (line.startsWith("const ") || line.startsWith("$")) { bool is_const = false; @@ -249,14 +276,6 @@ namespace ostd bool Stylesheet::parseThemeFileLine(const String& line, const VariableList& variables, bool exitCondition) { - if (!line.contains("=")) - return false; - String key = line.new_substr(0, line.indexOf("=")).trim(); - String valuePreserveCase = line.new_substr(line.indexOf("=") + 1).trim(); - String value = valuePreserveCase.new_toLower(); - if (key == "") - return false; - String themeID = ""; auto l_isVec2 = [this](String& value) -> bool { value.trim(); if (value.new_toLower().startsWith("vec2(") && value.new_toLower().endsWith(")")) @@ -324,6 +343,55 @@ namespace ostd } return false; }; + auto l_findMacroCall = [this](String& value, String& outKey) -> std::optional { + for (const auto& m : m_macros) + { + stdvec matches; + stdvec indices = value.regexFind("^(.+\\.)?" + m.first + "[ \\t]*\\(", false, &matches); + + if (!indices.empty()) + { + u32 paren_index = indices[0] + matches[0].len() - 1; + String from_paren = value.new_substr(paren_index); + String match_text = value.new_substr(indices[0], paren_index); + i32 dot_index = match_text.lastIndexOf('.'); + if (dot_index >= 0) + outKey = value.new_substr(0, dot_index); + if (from_paren.startsWith("(") && from_paren.endsWith(")")) + { + from_paren.substr(1, from_paren.len() - 1).trim(); + value = from_paren; + return m.second; + } + return std::nullopt; + } + } + return std::nullopt; + }; + + { + String tmpLine = line.new_trim(), key = ""; + if (auto m = l_findMacroCall(tmpLine, key); m != std::nullopt) + { + auto& macro = m.value(); + auto expanded = parseMacroCall(tmpLine, macro); + for (const auto& l : expanded) + { + if (!parseThemeFileLine(key + "." + l, variables, false)) + return false; + } + return true; + } + } + + if (!line.contains("=")) + return false; + String key = line.new_substr(0, line.indexOf("=")).trim(); + String valuePreserveCase = line.new_substr(line.indexOf("=") + 1).trim(); + String value = valuePreserveCase.new_toLower(); + if (key == "") + return false; + String themeID = ""; if (key.startsWith("@")) { @@ -708,6 +776,224 @@ namespace ostd return lineCopy.trim(); } + bool Stylesheet::parseMacro(const String& macroCode) + { + auto l_isWhiteSpace = [](char c) { return c == ' ' || c == '\n' || c == '\t'; }; + + String name = ""; + String params = ""; + String body = ""; + + i32 index = 0; + bool valid = false; + for ( ; index < macroCode.len(); index++) + { + char c = macroCode[index]; + if (l_isWhiteSpace(c)) + continue; + if (c == '(') + { + index++; + if (index == macroCode.len()) + return false; + valid = true; + break; + } + name += c; + } + if (!valid) return false; + if (!name.regexMatches(m_validNameRegex)) + return false; + + valid = false; + bool inStr = false; + for ( ; index < macroCode.len(); index++) + { + char c = macroCode[index]; + if (inStr) + { + params += c; + inStr = inStr && c != '"'; + continue; + } + if (l_isWhiteSpace(c)) + continue; + if (c == '"') + { + params += c; + inStr = true; + continue; + } + if (c == ',') + { + params += '\n'; + continue; + } + if (c == ')') + { + index++; + if (index == macroCode.len()) + return false; + valid = true; + break; + } + params += c; + } + if (!valid) return false; + + valid = false; + inStr = false; + bool open = false; + for ( ; index < macroCode.len(); index++) + { + char c = macroCode[index]; + if (!open) + { + if (c == '{') + open = true; + continue; + } + if (inStr) + { + body += c; + inStr = inStr && c != '"'; + continue; + } + if (l_isWhiteSpace(c)) + continue; + if (c == '"') + { + body += c; + inStr = true; + continue; + } + if (c == ';') + { + body += '\n'; + continue; + } + if (c == '}') + { + index++; + if (index != macroCode.len()) + return false; + valid = true; + break; + } + body += c; + } + if (!valid) return false; + + Macro macro; + macro.body = body; + + auto l_paramExists = [](const String& param, const stdvec>& list) -> bool { + for (const auto& p : list) + { + if (p.first == param) + return true; + } + return false; + }; + + auto tokens = params.tokenize("\n"); + bool defaultVal = false; + for (auto& tok : tokens) + { + if (tok.contains("=")) + { + if (tok.count("=") != 1) + return false; + i32 i = tok.indexOf("="); + if (i < 1 || i > tok.len() - 2) + return false; + String pname = tok.new_substr(0, i).trim(); + String pval = tok.new_substr(i + 1).trim(); + if (pname == "" || pval == "") + return false; + if (!pname.regexMatches(m_validNameRegex)) + return false; + if (l_paramExists("$" + pname, macro.params)) + return false; + macro.params.push_back({ "$" + pname, pval }); + defaultVal = true; + } + else + { + if (defaultVal) + return false; + if (tok == "") + return false; + if (!tok.regexMatches(m_validNameRegex)) + return false; + if (l_paramExists("$" + tok, macro.params)) + return false; + macro.params.push_back({ "$" + tok, MacroParamDefault }); + } + } + // std::sort(macro.params.begin(), macro.params.end(), [](const auto& a, const auto& b) { + // return a.first.len() > b.first.len(); + // }); + m_macros[name] = macro; + return true; + } + + stdvec Stylesheet::parseMacroCall(const String& call, const Macro& macro) + { + stdvec lines; + + auto l_splitByTopLevelComma = [](const String& call) -> stdvec { + stdvec callArgs; + i32 openP = 0; + String arg = ""; + for (i32 i = 0; i < call.len(); i++) + { + char c = call[i]; + if (c == '(') + openP++; + if (c == ')') + openP--; + if (openP < 0) + return {}; + if (c == ',' && openP == 0) + { + if (arg.trim() == "") + return {}; + callArgs.push_back(arg); + arg = ""; + continue; + } + arg += c; + } + if (arg.trim() == "") + return {}; + callArgs.push_back(arg); + return callArgs; + }; + + auto callArgs = l_splitByTopLevelComma(call); + auto tokens = macro.body.tokenize("\n"); + for (auto& line : tokens) + { + i32 argIndex = 0; + for (const auto&[pname, pval] : macro.params) + { + if (argIndex < callArgs.size()) + { + line.replaceAll(pname, callArgs[argIndex++]); + std::cout << pname << " " << callArgs[argIndex - 1] << "\n " << line << "\n"; + continue; + } + if (pval == MacroParamDefault) + return {}; + line.replaceAll(pname, pval); + } + std::cout << "\n"; + lines.push_back(line); + } + return lines; + } + void Stylesheet::debugPrint(void) { for (const auto&[key, value] : m_values) diff --git a/src/ostd/io/Stylesheet.hpp b/src/ostd/io/Stylesheet.hpp index fb5dcfb..112a699 100644 --- a/src/ostd/io/Stylesheet.hpp +++ b/src/ostd/io/Stylesheet.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -34,6 +35,13 @@ namespace ostd public: using QualifierList = stdvec>; public: using VariableList = stdumap>; public: using TypeVariant = std::variant; + private: struct Macro + { + String body { "" }; + stdvec> params; + + inline i32 argCount(void) const { return params.size(); } + }; public: Stylesheet(void); Stylesheet& clear(void); @@ -82,9 +90,14 @@ namespace ostd ColorGradient parseColorGradient(const String& _value, const VariableList& variables); AnimationData parseAnim(const String& _value, bool& outError, const VariableList& variables); String replaceVariables(const String& line, const VariableList& variables, bool stop_at_first_match = true); + bool parseMacro(const String& macroCode); + stdvec parseMacroCall(const String& call, const Macro& macro); private: stdumap m_values; + stdumap m_macros; const String m_validNameRegex { "^[A-Za-z_][A-Za-z0-9_]*$" }; + + inline static const String MacroParamDefault { "NO_VAL" }; }; }