From 001bc6cfcb9cf58fcf0ebfe879a5249eb0a0f3b1 Mon Sep 17 00:00:00 2001 From: ketmar Date: Sat, 20 Nov 2021 00:08:34 +0000 Subject: [PATCH] style inheritance fixes; it is now possible to load style from the external file with "load_style" console command FossilOrigin-Name: 16aa53c85a54ecd808b5fa615eac337c856f4b0beed71f34efdd05ab77cdc0e8 --- chiroptera.d | 35 +- egui/dialogs.d | 33 -- egui/style.d | 1571 +++++++++++++++++++++++++++-------------------------- egui/subwindows.d | 17 +- 4 files changed, 854 insertions(+), 802 deletions(-) diff --git a/chiroptera.d b/chiroptera.d index 6e08b3c..da9e424 100644 --- a/chiroptera.d +++ b/chiroptera.d @@ -627,6 +627,34 @@ void initConsole () { // //////////////////////////////////////////////////////////////////// // + conRegFunc!((ConString filename) { + if (filename.length == 0) return; + import std.path : buildPath; + auto fname = buildPath(mailRootDir, filename); + char[] text; + scope(exit) delete text; + try { + auto fl = VFile(fname); + conwriteln("loading style file '", fname, "'..."); + auto sz = fl.size; + if (sz > 1024*1024*32) { conwriteln("ERROR: style file too big!"); return; } + if (sz == 0) return; + text = new char[cast(usize)sz]; + fl.rawReadExact(text); + fl.close(); + } catch (Exception) { + conwriteln("ERROR reading style file!"); + return; + } + try { + defaultColorStyle.parseStyle(text); + } catch (Exception e) { + conwriteln("ERROR parsing style: ", e.msg); + } + })("load_style", "load widget style"); + + + // //////////////////////////////////////////////////////////////////// // conRegFunc!(() { import core.memory : GC; conwriteln("starting GC collection..."); @@ -640,7 +668,7 @@ void initConsole () { conRegFunc!(() { auto qww = new YesNoWindow("Quit?", "Do you really want to quit?", true); qww.onYes = () { concmd("quit"); }; - qww.addModal(); + //qww.addModal(); })("quit_prompt", "quit with prompt"); @@ -1241,7 +1269,7 @@ void initConsole () { .bindConstText(":email", fromMail.getData) .bindConstText(":name", fromName.getData); foreach (auto row; stFindTwit.st.range) { - conwriteln("!!!", row.twitid!uint, "; mail=", row.email!SQ3Text, "; name=", row.name!SQ3Text, "; title=", row.title!SQ3Text.recodeToKOI8); + //conwriteln("!!!", row.twitid!uint, "; mail=", row.email!SQ3Text, "; name=", row.name!SQ3Text, "; title=", row.title!SQ3Text.recodeToKOI8); if (!globmatch(tagname.getData, row.tagglob!SQ3Text)) continue; twitid = row.twitid!uint; twtitle = row.title!SQ3Text; @@ -1258,7 +1286,7 @@ void initConsole () { .bindConstText(":email", fromMail.getData) .bindConstText(":name", ""); foreach (auto row; stFindTwit.st.range) { - conwriteln("!!!", row.twitid!uint, "; mail=", row.email!SQ3Text, "; name=", row.name!SQ3Text, "; title=", row.title!SQ3Text.recodeToKOI8); + //conwriteln("!!!", row.twitid!uint, "; mail=", row.email!SQ3Text, "; name=", row.name!SQ3Text, "; title=", row.title!SQ3Text.recodeToKOI8); if (!globmatch(tagname.getData, row.tagglob!SQ3Text)) continue; twitid = row.twitid!uint; twtitle = row.title!SQ3Text; @@ -3249,6 +3277,7 @@ void main (string[] args) { setupDefaultBindings(); concmd("exec chiroptera.rc tan"); + concmd("load_style userstyle.rc"); //FIXME //scanFolders(); diff --git a/egui/dialogs.d b/egui/dialogs.d index e7e3735..a51e37c 100644 --- a/egui/dialogs.d +++ b/egui/dialogs.d @@ -156,39 +156,6 @@ public: super(atitle); } - override void createStyle () { - appendStyle(` - SubWindow { - frame: rgb(0x40, 0x70, 0xcf); - title-back: rgb(0x73, 0x96, 0xdc); - title-text: rgb(0xff, 0xff, 0xff); - - .inactive { - frame: rgb(192, 192, 192); - title-back: @frame; - title-text: rgb(0, 0, 0); - } - - back: rgb(0xbf, 0xcf, 0xef); - text: rgb(0x16, 0x2d, 0x59); - } - - /* - LabelWidget { - back: _g-o_L-D__; - - .focused { - back: r_e_d; - } - - .disabled { - back: rgb(255, 0, 0); - } - } - */ - `); - } - override void createWidgets () { new SpacerWidget(rootWidget, 8); diff --git a/egui/style.d b/egui/style.d index 5b80b44..87e13f7 100644 --- a/egui/style.d +++ b/egui/style.d @@ -24,918 +24,977 @@ import iv.xcolornames; // ////////////////////////////////////////////////////////////////////////// // -struct FuiSimpleParser { -private: - const(char)[] text; - const(char)[] str; // text left +static immutable defaultStyleText = ` +SubWindow { + frame: rgb(255, 255, 255); + title-back: @frame; + title-text: rgb(0, 0, 0); -public: - this (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); setText(atext); } + .inactive { + frame: rgb(192, 192, 192); + title-back: @frame; + title-text: rgb(0, 0, 0); + } - int getCurrentLine () pure const nothrow @safe @nogc { - int res = 0; - foreach (immutable char ch; text[0..$-str.length]) if (ch == '\n') ++res; - return res; + back: rgb(0, 0, 180); + text: rgb(255, 255, 255); + + /* + .focused { + back: rgb(0, 0, 0); + text: rgb(255, 255, 255); } + */ - void error (string msg) const { - import std.conv : to; - version(none) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "===\n%.*s\n===\n", cast(uint)str.length, str.ptr); } - throw new Exception("parse error around line "~getCurrentLine.to!string~": "~msg); + .disabled { + text: rgb(128, 128, 128); } +} - void setText (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); text = atext; str = atext; } - bool isEOT () { - skipBlanks(); - return (str.length == 0); - } +YesNoWindow { + frame: rgb(0x40, 0x70, 0xcf); + title-back: rgb(0x73, 0x96, 0xdc); + title-text: rgb(0xff, 0xff, 0xff); - void skipBlanks () { - while (str.length) { - if (str[0] <= ' ') { str = str.xstripleft; continue; } - if (str.length < 2 || str[0] != '/') break; - // single-line comment? - if (str[1] == '/') { - str = str[2..$]; - while (str.length && str[0] != '\n') str = str[1..$]; - continue; - } - // multiline comment? - if (str[1] == '*') { - bool endFound = false; - auto svs = str; - str = str[2..$]; - while (str.length) { - if (str.length > 1 && str[0] == '*' && str[1] == '/') { - endFound = true; - str = str[2..$]; - break; - } - str = str[1..$]; - } - if (!endFound) { str = svs; error("unfinished comment"); } - continue; - } - // multiline nested comment? - if (str[1] == '+') { - bool endFound = false; - auto svs = str; - int level = 0; - while (str.length) { - if (str.length > 1) { - if (str[0] == '/' && str[1] == '+') { str = str[2..$]; ++level; continue; } - if (str[0] == '+' && str[1] == '/') { str = str[2..$]; if (--level == 0) { endFound = true; break;} continue; } - } - str = str[1..$]; - } - if (!endFound) { str = svs; error("unfinished comment"); } - continue; - } - break; - } + .inactive { + frame: rgb(192, 192, 192); + title-back: @frame; + title-text: rgb(0, 0, 0); } - bool checkNoEat (const(char)[] tk) { - assert(tk.length); - skipBlanks(); - return (str.length >= tk.length && str[0..tk.length] == tk); - } + back: rgb(0xbf, 0xcf, 0xef); + text: rgb(0x16, 0x2d, 0x59); +} - bool checkNoEat (in char ch) { - skipBlanks(); - return (str.length > 0 && str[0] == ch); - } - bool check (const(char)[] tk) { - if (!checkNoEat(tk)) return false; - str = str[tk.length..$]; - skipBlanks(); - return true; +Widget { + back: @parent:; + text: @parent:; + + .focused { + back: @parent:.null; + text: @parent:.null; } - bool check (in char ch) { - if (!checkNoEat(ch)) return false; - str = str[1..$]; - skipBlanks(); - return true; + .disabled { + back: @parent:; + text: @parent:; } +} - void expect (const(char)[] tk) { - skipBlanks(); - auto svs = str; - if (!check(tk)) { str = svs; error("`"~tk.idup~"` expected"); } + +LabelWidget { + back: transparent; + + .focused { + back: transparent; } - void expect (in char ch) { - skipBlanks(); - auto svs = str; - if (!check(ch)) { str = svs; error("`"~ch~"` expected"); } + .disabled { + back: transparent; } +} - const(char)[] expectIdNoCopy () { - skipBlanks(); - if (str.length == 0) error("identifier expected"); - if (!isalpha(str[0]) && str[0] != '_' && str[0] != '-') error("identifier expected"); - usize pos = 1; - while (pos < str.length) { - if (!isalnum(str[pos]) && str[pos] != '_' && str[pos] != '-') break; - ++pos; - } - const(char)[] res = str[0..pos]; - str = str[pos..$]; - skipBlanks(); - return res; + +ButtonWidget { + back: rgb(180, 180, 180); + text: rgb(0, 0, 0); + hotline: @text; + + .focused { + back: rgb(250, 250, 250); + text: rgb(0, 0, 60); + hotline: @text; } - string expectId () { - pragma(inline, true); - return expectIdNoCopy().idup; + .disabled { + back: rgb(142, 142, 142); + text: rgb(32, 32, 32); + hotline: @text; } +} - uint parseColor () { - skipBlanks(); - if (str.length == 0) error("color expected"); - // html-like color? - if (check('#')) return parseHtmlColor(); +ButtonExWidget { + back: rgb(0x73, 0x96, 0xdc); + text: rgb(0xff, 0xff, 0xff); + rect: rgb(0x40, 0x70, 0xcf); + shadowline: rgb(0x83, 0xa6, 0xec); - auto svs = str; - auto id = expectIdNoCopy(); + .focused { + back: rgb(0x93, 0xb6, 0xfc); + shadowline: rgb(0xa3, 0xc6, 0xff); + } +} - if (id.strEquCI("transparent")) return gxTransparent; - // `rgb()` or `rgba()`? - bool allowAlpha; - if (id.strEquCI("rgba")) allowAlpha = true; - else if (id.strEquCI("rgb")) allowAlpha = false; - else { - auto xc = xFindColorByName(id); - if (xc is null) { str = svs; error("invalid color definition"); } - return gxrgb(xc.r, xc.g, xc.b); - } +CheckboxWidget { + back: transparent; + text: rgb(192, 192, 192); + mark: rgb(0, 192, 0); - skipBlanks(); - if (!check('(')) { str = svs; error("invalid color definition"); } - immutable uint clr = parseColorRGB(allowAlpha); - if (!check(')')) { str = svs; error("invalid color definition"); } - return clr; + .focused { + back: rgb(0, 0, 64); + text: @SubWindow:; + mark: rgb(0, 255, 0); } -private: - // '#' skipped - uint parseHtmlColor () { - auto svs = str; - skipBlanks(); - ubyte[3] rgb = 0; - // first 3 digits - foreach (immutable n; 0..3) { - while (str.length && str[0] == '_') str = str[1..$]; - if (str.length == 0) { str = svs; error("invalid color"); } - immutable int dg = digitInBase(str[0], 16); - if (dg < 0) { str = svs; error("invalid color"); } - rgb[n] = cast(ubyte)dg; - str = str[1..$]; - } - while (str.length && str[0] == '_') str = str[1..$]; - // second 3 digits? - if (str.length && digitInBase(str[0], 16) >= 0) { - foreach (immutable n; 0..3) { - while (str.length && str[0] == '_') str = str[1..$]; - if (str.length == 0) { str = svs; error("invalid color"); } - immutable int dg = digitInBase(str[0], 16); - if (dg < 0) { str = svs; error("invalid color"); } - rgb[n] = cast(ubyte)(rgb[n]*16+dg); - str = str[1..$]; - } - while (str.length && str[0] == '_') str = str[1..$]; - } else { - foreach (immutable n; 0..3) rgb[n] = cast(ubyte)(rgb[n]*16+rgb[n]); - } - skipBlanks(); - return gxrgb(rgb[0], rgb[1], rgb[2]); + .disabled { + back: @SubWindow:; + text: @SubWindow:; + mark: @SubWindow:text; } +} - // "(" skipped - uint parseColorRGB (bool allowAlpha) { - auto svs = str; - ubyte[4] rgba = 0; - foreach (immutable n; 0..3+(allowAlpha ? 1 : 0)) { - if (n && !check(',')) { str = svs; error("invalid color"); } - skipBlanks(); - if (str.length == 0 || !isdigit(str[0])) { str = svs; error("invalid color"); } - uint val = 0; - uint base = 10; - if (str[0] == '0' && str.length >= 2 && (str[1] == 'x' || str[1] == 'X')) { - str = str[2..$]; - if (str.length == 0 || digitInBase(str[0], 16) < 0) { str = svs; error("invalid color"); } - base = 16; - } - while (str.length) { - if (str[0] != '_') { - immutable int dg = digitInBase(str[0], cast(int)base); - if (dg < 0) break; - val = val*base+cast(uint)dg; - if (val > 255) { str = svs; error("invalid color"); } - } - str = str[1..$]; - } - while (str.length && str[0] == '_') str = str[1..$]; - rgba[n] = cast(ubyte)val; - } - skipBlanks(); - if (allowAlpha) return gxrgba(rgba[0], rgba[1], rgba[2], rgba[3]); - return gxrgb(rgba[0], rgba[1], rgba[2]); + +SimpleListBoxWidget { + back: rgb(0, 0, 60); + text: rgb(255, 255, 0); + cursor-back: rgb(0, 110, 110); + cursor-text: rgb(255, 255, 255); + + .focused { + back:@.null; + text:@.null; + cursor-back: rgb(0, 60, 60); + cursor-text: rgb(192, 192, 192); } } -// ////////////////////////////////////////////////////////////////////////// // -class ColorStyle { -public: - struct ColorItem { - string widget; // "Widget" - string type; // "text" - string mod; // "inactive", "disabled", etc. - bool redirect; - uint color; - // redirect - string rewidget; // null means "this" - string retype; // null means "this" - string remod; // null means "this" - // loop protection - uint seenCount; +EditorWidget { + back: #007; + + status-back: white; + status-color: black; + + text: rgb(220, 220, 0); + quote0-text: rgb(128, 128, 0); + quote1-text: rgb(0, 128, 128); + wrap-mark-text: rgb(0, 90, 220); + + attach-file-text: rgb(0x6e, 0x00, 0xff); + attach-bad-text: red; + + // marked block + mark-back: rgb(0, 160, 160); + mark-text: white; +} + + +LineEditWidget : EditorWidget { + title-back: transparent; + title-text: white; + + back: black; +} + + +MainPaneWindow { + //// group list //// + grouplist-divline: white; + grouplist-back: #222; + // group with unread messages + grouplist-unread-text: #0ff; + // normal group + grouplist-normal-text: rgb(255, 187, 0); + grouplist-normal-child-text: rgb(225, 97, 0); + // spam group + grouplist-spam-text: #800; + // main accounts group + grouplist-accounts-text: rgb(220, 220, 0); + grouplist-accounts-child-text: rgb(160, 160, 250); + // account inbox group + grouplist-inbox-text: rgb(90, 90, 180); + + grouplist-dots: rgb(80, 80, 80); + + .cursor { + grouplist-back: #088; + grouplist-outline: black; } -protected: - ColorItem[string] colors; - string[string] parents; - uint seenCount; - uint[string] styleCache; - char[] tempccstr; + //// thread list //// + threadlist-divline: white; + threadlist-back: #222; + threadlist-dots: #444; -protected: - final void clearStyleCache () { - pragma(inline, true); - styleCache = null; + .normal { + threadlist-back: transparent; + threadlist-subj-text: rgb(215, 87, 0); + threadlist-from-text: rgb(215, 87, 0); + threadlist-mail-text: rgb(155, 27, 0); + threadlist-time-text: rgb(215, 87, 0); + threadlist-strike-line: transparent; } - final const(char)[] buildcc (const(char)[] ctname, const(char)[] type, const(char)[] mod) { - pragma(inline, true); - immutable usize strsize = ctname.length+1+type.length+1+mod.length; - if (tempccstr.length < strsize) tempccstr.length = (strsize|0xff)+1; - usize spos = ctname.length; - tempccstr[0..spos] = ctname; - tempccstr[spos++] = 0; - tempccstr[spos..spos+type.length] = type; - spos += type.length; - tempccstr[spos++] = 0; - tempccstr[spos..spos+mod.length] = mod; - return tempccstr[0..spos+mod.length]; + .unread { + threadlist-back: transparent; + threadlist-subj-text: white; + threadlist-from-text: white; + threadlist-mail-text: yellow; + threadlist-time-text: white; + threadlist-strike-line: transparent; } - final bool findCachedColor (in TypeInfo_Class ct, const(char)[] type, const(char)[] mod, out uint res) { - pragma(inline, true); - if (auto cp = buildcc(ct.name, type, mod) in styleCache) { - res = *cp; - return true; - } - return false; + .soft-del { + threadlist-back: transparent; + threadlist-subj-text: #800; + threadlist-from-text: #800; + threadlist-mail-text: #800; + threadlist-time-text: #800; + threadlist-strike-line: #800; } - final void cacheColor (in uint clr, in TypeInfo_Class ct, const(char)[] type, const(char)[] mod) { - pragma(inline, true); - auto key = buildcc(ct.name, type, mod); - if (auto cp = key in styleCache) { - *cp = clr; - } else { - styleCache[key.idup] = clr; - } + .hard-del { + threadlist-back: transparent; + threadlist-subj-text: red; + threadlist-from-text: red; + threadlist-mail-text: red; + threadlist-time-text: red; + threadlist-strike-line: red; } -protected: - final void resetSeenCount () { - seenCount = 1; - foreach (ref ColorItem ci; colors.byValue) ci.seenCount = 0; + .twit { + threadlist-back: transparent; + threadlist-subj-text: #400; + threadlist-from-text: #400; + threadlist-mail-text: #400; + threadlist-time-text: #400; + threadlist-strike-line: transparent; } - final void incSeenCount () { - pragma(inline, true); - if (++seenCount == 0) resetSeenCount(); + .cursor-normal { + threadlist-back: #088; + threadlist-subj-text: @.normal; + threadlist-from-text: @.normal; + threadlist-mail-text: @.normal; + threadlist-time-text: @.normal; + threadlist-strike-line: @normal; + threadlist-outline: black; } -protected: - void addColorItem (in ColorItem ci) { - clearStyleCache(); - auto nm = buildcc(ci.widget, ci.type, ci.mod); - if (auto cptr = nm in colors) { - cptr.redirect = ci.redirect; - cptr.color = ci.color; - cptr.rewidget = ci.rewidget; - cptr.retype = ci.retype; - cptr.remod = ci.remod; - cptr.seenCount = 0; - } else { - colors[nm.idup] = ci; - } + .cursor-unread { + threadlist-back: @.cursor-normal; + threadlist-subj-text: @.unread; + threadlist-from-text: @.unread; + threadlist-mail-text: @.unread; + threadlist-time-text: @.unread; + threadlist-strike-line: @.unread; + threadlist-outline: black; } -public: - void parseStyle (const(char)[] str) { - auto par = FuiSimpleParser(str); - while (!par.isEOT) { - string widget = par.expectId(); - string parent = null; - if (par.check(':')) { - parent = par.expectId(); - if (parent == widget) par.error("invalid parent `"~parent~"` for `"~widget~"`"); - parents[widget] = parent; - } - par.expect('{'); - string mod = null; - for (;;) { - if (par.check('}')) { - if (mod is null) break; - mod = null; - continue; - } - //{ import std.stdio; writeln("WIDGET: <", widget, ">"); } - if ((mod is null) && par.check('.')) { - mod = par.expectId(); - //{ import std.stdio; writeln("NEW MOD: <", mod, ">"); } - par.expect('{'); - continue; - } - string type = par.expectId(); - //{ import std.stdio; writeln("TYPE: <", type, ">"); } - par.expect(':'); - ColorItem ci; - ci.widget = widget; - ci.type = type; - ci.mod = mod; - // redirect? - if (par.check('@')) { - // redirect - ci.redirect = true; - if (par.checkNoEat('.')) { - // .mod - ci.rewidget = null; - ci.retype = type; - } else { - string rd0 = par.expectId(); - if (par.check(':')) { - // widget:... - ci.rewidget = rd0; - if (par.checkNoEat('.')) { - ci.retype = type; - } else { - // has type? - if (par.checkNoEat(';')) { - ci.retype = type; - } else { - ci.retype = par.expectId(); - } - } - } else { - // type... - ci.rewidget = null; - ci.retype = rd0; - } - } - if (par.check('.')) { - ci.remod = par.expectId(); - } else { - ci.remod = mod; - } - } else { - // color - ci.redirect = false; - ci.color = par.parseColor(); - } - par.expect(';'); - if (ci.mod.strEquCI("none") || ci.mod.strEquCI("null")) ci.mod = null; - if (ci.remod.strEquCI("none") || ci.remod.strEquCI("null")) ci.remod = null; - version(none) { - import std.stdio; - write("SRC: <", ci.widget, ":", ci.type, ".", ci.mod, ">"); - if (ci.redirect) { - writeln(" --> <", ci.rewidget, ":", ci.retype, ".", ci.remod, ">"); - } else { - writeln(" : rgba(", gxGetRed(ci.color), ",", gxGetGreen(ci.color), ",", gxGetBlue(ci.color), ",", gxGetAlpha(ci.color), ")"); - } - } - addColorItem(ci); - } - } + .cursor-soft-del { + threadlist-back: #066; + threadlist-subj-text: @.soft-del; + threadlist-from-text: @.soft-del; + threadlist-mail-text: @.soft-del; + threadlist-time-text: @.soft-del; + threadlist-strike-line: @.soft-del; } -public: - this () {} + .cursor-hard-del { + threadlist-back: @.cursor-soft-del; + threadlist-subj-text: @.hard-del; + threadlist-from-text: @.hard-del; + threadlist-mail-text: @.hard-del; + threadlist-time-text: @.hard-del; + threadlist-strike-line: @.hard-del; + } - void cloneFrom (ColorStyle st) { - if (st is null || st is this) return; - colors = null; - foreach (const ref ColorItem ci; st.colors.byValue) addColorItem(ci); - parents = null; - foreach (auto s; st.parents.byKeyValue) parents[s.key] = s.value; - styleCache = null; - resetSeenCount(); - seenCount = 0; + .cursor-twit { + threadlist-back: #066; + threadlist-subj-text: @.twit; + threadlist-from-text: @.twit; + threadlist-mail-text: @.twit; + threadlist-time-text: @.twit; + threadlist-strike-line: @.twit; } - protected uint findColorIntr (TypeInfo_Class ctsrc, TypeInfo_Class ctwdt, const(char)[] type, const(char)[] mod=null, bool* found=null) { - if (ctwdt is null) { - if (found !is null) *found = false; - return gxUnknown; - } - string sname = classShortName(ctwdt); + //// message header //// + msg-header-back: rgb(20, 20, 20); + msg-header-from: #088; + msg-header-to: #088; + msg-header-subj: #088; + msg-header-date: #088; + msg-header-divline: #bbb; - ColorItem *mcit = void; - auto nm = buildcc(sname, type, mod); - mcit = (nm in colors); - // if there is a modifier, try without it - if (mcit is null && mod.length) { - nm = buildcc(sname, type, null); - mcit = (nm in colors); - } - if (mcit !is null) { - if (!mcit.redirect) { - // not a redirect - mcit.seenCount = seenCount; - if (found !is null) *found = true; - return mcit.color; - } + //// message text //// + msg-text-back: rgb(37, 37, 37); + msg-text-text: rgb(174, 174, 174); + msg-text-quote0: #880; + msg-text-quote1: #088; + msg-text-link: rgb(0, 200, 200); + msg-text-html-sign: rgb(128, 0, 128); - // check for infinite loop - if (mcit.seenCount == seenCount) { - // infinite loop - if (found !is null) *found = false; - return gxUnknown; - } - mcit.seenCount = seenCount; + .hover { + msg-text-link: rgb(0, 255, 255); + } - // empty redirect widget name means "source widget" - if (mcit.rewidget.length == 0) { - return findColorIntr(ctsrc, ctsrc, mcit.retype, mcit.remod, found); - } + .pressed { + msg-text-link: rgb(255, 0, 255); + } - // special "parent" redirect widget name - if (mcit.rewidget == "parent") { - if (auto pp = sname in parents) { - return findColorIntr(ctsrc, findWidgetClass(*pp), mcit.retype, mcit.remod, found); - } else { - return findColorIntr(ctsrc, ctwdt.base, mcit.retype, mcit.remod, found); - } - } + //// message text twit //// + twit-shade: rgba(0, 0, 80, 127); + twit-text: red; + twit-outline: black; +} - // normal-named redirect - return findColorIntr(ctsrc, findWidgetClass(mcit.rewidget), mcit.retype, mcit.remod, found); - } - // try parent - if (auto pp = sname in parents) { - return findColorIntr(ctsrc, findWidgetClass(*pp), type, mod, found); - } +HintWindow { + frame: white; + title-back: @frame; + title-text: black; - // try inheritance parent - if (ctwdt.base !is null) return findColorIntr(ctsrc, ctwdt.base, type, mod, found); + back: rgb(0, 0, 80); + text: rgb(155, 155, 155); +} - // nothing was found - if (found !is null) *found = false; - return gxUnknown; - } - final uint findColor (TypeInfo_Class ctsrc, const(char)[] type, const(char)[] mod=null, bool* foundp=null) { - if (ctsrc is null) { - if (foundp !is null) *foundp = false; - return gxUnknown; - } +MessageWindow { + frame: white; + title-back: @frame; + title-text: black; - uint clr; - if (findCachedColor(ctsrc, type, mod, out clr)) { - if (foundp !is null) *foundp = true; - return clr; - } + back: rgb(0, 0, 80); + text: rgb(255, 255, 0); + bar-back: rgb(90, 90, 180); +} +`; - incSeenCount(); - clr = findColorIntr(ctsrc, ctsrc, type, mod, foundp); - cacheColor(clr, ctsrc, type, mod); +// ////////////////////////////////////////////////////////////////////////// // +struct FuiSimpleParser { +private: + const(char)[] text; + const(char)[] str; // text left - return clr; - } +public: + this (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); setText(atext); } - final uint findColor (in Object obj, const(char)[] type, const(char)[] mod=null, bool* foundp=null) { - pragma(inline, true); - TypeInfo_Class ct = (obj !is null ? cast(TypeInfo_Class)typeid(obj) : null); - return findColor(ct, type, mod, foundp); + int getCurrentLine () pure const nothrow @safe @nogc { + int res = 0; + foreach (immutable char ch; text[0..$-str.length]) if (ch == '\n') ++res; + return res; } -static: - __gshared TypeInfo_Class[string] classNameCache; - - static bool isGoodClassType (TypeInfo_Class ct) pure nothrow @trusted @nogc { - while (ct !is null) { - if (ct.name == "egui.subwindows.SubWindow" || - ct.name == "egui.widgets.Widget") - { - return true; - } - ct = ct.base; - } - return false; + void error (string msg) const { + import std.conv : to; + version(none) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "===\n%.*s\n===\n", cast(uint)str.length, str.ptr); } + throw new Exception("parse error around line "~getCurrentLine.to!string~": "~msg); } - static string classShortName (in TypeInfo_Class ct) nothrow @trusted @nogc { - pragma(inline, true); - if (ct is null) return null; - string name = ct.name; - auto dpos = name.lastIndexOf('.'); - return (dpos < 0 ? name : name[dpos+1..$]); + void setText (const(char)[] atext) nothrow @safe @nogc { pragma(inline, true); text = atext; str = atext; } + + bool isEOT () { + skipBlanks(); + return (str.length == 0); } - static TypeInfo_Class findWidgetClass (string cname) { - if (cname.length == 0) return null; - if (auto ctp = cname in classNameCache) { - version(none) { import core.stdc.stdio : printf; - printf("findWidgetClass<%.*s>: CACHE HIT! %p\n", cast(uint)cname.length, cname.ptr, *ctp); - } - return *ctp; - } - foreach (ModuleInfo* m; ModuleInfo) { - string mname = m.name; - if (mname.startsWith("std.") || - mname.startsWith("core.") || - mname.startsWith("rt.") || - mname.startsWith("gc.") || - mname.startsWith("arsd.") || - false) - { + void skipBlanks () { + while (str.length) { + if (str[0] <= ' ') { str = str.xstripleft; continue; } + if (str.length < 2 || str[0] != '/') break; + // single-line comment? + if (str[1] == '/') { + str = str[2..$]; + while (str.length && str[0] != '\n') str = str[1..$]; continue; } - foreach (TypeInfo_Class ct; m.localClasses()) { - if (!ct.name.endsWith(cname)) continue; - version(none) { import core.stdc.stdio : printf; - printf("findWidgetClass<%.*s>: checking <%.*s>\n", - cast(uint)cname.length, cname.ptr, - cast(uint)ct.name.length, ct.name.ptr); - } - if (ct.name.length == cname.length || ct.name[$-cname.length-1] == '.') { - version(none) { import core.stdc.stdio : printf; - printf("findWidgetClass<%.*s>: found <%.*s>\n", - cast(uint)cname.length, cname.ptr, - cast(uint)ct.name.length, ct.name.ptr); - } - // final check - if (isGoodClassType(ct)) { - // cache it - classNameCache[cname.idup] = ct; - return ct; + // multiline comment? + if (str[1] == '*') { + bool endFound = false; + auto svs = str; + str = str[2..$]; + while (str.length) { + if (str.length > 1 && str[0] == '*' && str[1] == '/') { + endFound = true; + str = str[2..$]; + break; } + str = str[1..$]; } + if (!endFound) { str = svs; error("unfinished comment"); } + continue; } - } - version(none) { import core.stdc.stdio : printf; - printf("findWidgetClass<%.*s>: NOT FOUND!\n", cast(uint)cname.length, cname.ptr); - } - // cache "unknown" - classNameCache[cname] = null; - return null; - } -} - - -__gshared ColorStyle defaultColorStyle; - -shared static this () { - defaultColorStyle = new ColorStyle; - with (defaultColorStyle) { - parseStyle(` -SubWindow { - frame: rgb(255, 255, 255); - title-back: @frame; - title-text: rgb(0, 0, 0); - - .inactive { - frame: rgb(192, 192, 192); - title-back: @frame; - title-text: rgb(0, 0, 0); + // multiline nested comment? + if (str[1] == '+') { + bool endFound = false; + auto svs = str; + int level = 0; + while (str.length) { + if (str.length > 1) { + if (str[0] == '/' && str[1] == '+') { str = str[2..$]; ++level; continue; } + if (str[0] == '+' && str[1] == '/') { str = str[2..$]; if (--level == 0) { endFound = true; break;} continue; } + } + str = str[1..$]; + } + if (!endFound) { str = svs; error("unfinished comment"); } + continue; + } + break; + } } - back: rgb(0, 0, 180); - text: rgb(255, 255, 255); - - /* - .focused { - back: rgb(0, 0, 0); - text: rgb(255, 255, 255); + bool checkNoEat (const(char)[] tk) { + assert(tk.length); + skipBlanks(); + return (str.length >= tk.length && str[0..tk.length] == tk); } - */ - .disabled { - text: rgb(128, 128, 128); + bool checkNoEat (in char ch) { + skipBlanks(); + return (str.length > 0 && str[0] == ch); } -} - -Widget : SubWindow { - back: @parent:; - text: @parent:; - .focused { - back: @parent:.null; - text: @parent:.null; + bool check (const(char)[] tk) { + if (!checkNoEat(tk)) return false; + str = str[tk.length..$]; + skipBlanks(); + return true; } - .disabled { - back: @parent:; - text: @parent:; + bool check (in char ch) { + if (!checkNoEat(ch)) return false; + str = str[1..$]; + skipBlanks(); + return true; } -} - - -LabelWidget { - back: transparent; - .focused { - back: transparent; + void expect (const(char)[] tk) { + skipBlanks(); + auto svs = str; + if (!check(tk)) { str = svs; error("`"~tk.idup~"` expected"); } } - .disabled { - back: transparent; + void expect (in char ch) { + skipBlanks(); + auto svs = str; + if (!check(ch)) { str = svs; error("`"~ch~"` expected"); } } -} - -ButtonWidget { - back: rgb(180, 180, 180); - text: rgb(0, 0, 0); - hotline: @text; - - .focused { - back: rgb(250, 250, 250); - text: rgb(0, 0, 60); - hotline: @text; + const(char)[] expectIdNoCopy () { + skipBlanks(); + if (str.length == 0) error("identifier expected"); + if (!isalpha(str[0]) && str[0] != '_' && str[0] != '-') error("identifier expected"); + usize pos = 1; + while (pos < str.length) { + if (!isalnum(str[pos]) && str[pos] != '_' && str[pos] != '-') break; + ++pos; + } + const(char)[] res = str[0..pos]; + str = str[pos..$]; + skipBlanks(); + return res; } - .disabled { - back: rgb(142, 142, 142); - text: rgb(32, 32, 32); - hotline: @text; + string expectId () { + pragma(inline, true); + return expectIdNoCopy().idup; } -} + uint parseColor () { + skipBlanks(); + if (str.length == 0) error("color expected"); -ButtonExWidget { - back: rgb(0x73, 0x96, 0xdc); - text: rgb(0xff, 0xff, 0xff); - rect: rgb(0x40, 0x70, 0xcf); - shadowline: rgb(0x83, 0xa6, 0xec); + // html-like color? + if (check('#')) return parseHtmlColor(); - .focused { - back: rgb(0x93, 0xb6, 0xfc); - shadowline: rgb(0xa3, 0xc6, 0xff); - } -} + auto svs = str; + auto id = expectIdNoCopy(); + if (id.strEquCI("transparent")) return gxTransparent; -CheckboxWidget { - back: transparent; - text: rgb(192, 192, 192); - mark: rgb(0, 192, 0); + // `rgb()` or `rgba()`? + bool allowAlpha; + if (id.strEquCI("rgba")) allowAlpha = true; + else if (id.strEquCI("rgb")) allowAlpha = false; + else { + auto xc = xFindColorByName(id); + if (xc is null) { str = svs; error("invalid color definition"); } + return gxrgb(xc.r, xc.g, xc.b); + } - .focused { - back: rgb(0, 0, 64); - text: @SubWindow:; - mark: rgb(0, 255, 0); + skipBlanks(); + if (!check('(')) { str = svs; error("invalid color definition"); } + immutable uint clr = parseColorRGB(allowAlpha); + if (!check(')')) { str = svs; error("invalid color definition"); } + return clr; } - .disabled { - back: @SubWindow:; - text: @SubWindow:; - mark: @SubWindow:text; +private: + // '#' skipped + uint parseHtmlColor () { + auto svs = str; + skipBlanks(); + ubyte[3] rgb = 0; + // first 3 digits + foreach (immutable n; 0..3) { + while (str.length && str[0] == '_') str = str[1..$]; + if (str.length == 0) { str = svs; error("invalid color"); } + immutable int dg = digitInBase(str[0], 16); + if (dg < 0) { str = svs; error("invalid color"); } + rgb[n] = cast(ubyte)dg; + str = str[1..$]; + } + while (str.length && str[0] == '_') str = str[1..$]; + // second 3 digits? + if (str.length && digitInBase(str[0], 16) >= 0) { + foreach (immutable n; 0..3) { + while (str.length && str[0] == '_') str = str[1..$]; + if (str.length == 0) { str = svs; error("invalid color"); } + immutable int dg = digitInBase(str[0], 16); + if (dg < 0) { str = svs; error("invalid color"); } + rgb[n] = cast(ubyte)(rgb[n]*16+dg); + str = str[1..$]; + } + while (str.length && str[0] == '_') str = str[1..$]; + } else { + foreach (immutable n; 0..3) rgb[n] = cast(ubyte)(rgb[n]*16+rgb[n]); + } + skipBlanks(); + return gxrgb(rgb[0], rgb[1], rgb[2]); } -} - - -SimpleListBoxWidget { - back: rgb(0, 0, 60); - text: rgb(255, 255, 0); - cursor-back: rgb(0, 110, 110); - cursor-text: rgb(255, 255, 255); - .focused { - back:@.null; - text:@.null; - cursor-back: rgb(0, 60, 60); - cursor-text: rgb(192, 192, 192); + // "(" skipped + uint parseColorRGB (bool allowAlpha) { + auto svs = str; + ubyte[4] rgba = 0; + foreach (immutable n; 0..3+(allowAlpha ? 1 : 0)) { + if (n && !check(',')) { str = svs; error("invalid color"); } + skipBlanks(); + if (str.length == 0 || !isdigit(str[0])) { str = svs; error("invalid color"); } + uint val = 0; + uint base = 10; + if (str[0] == '0' && str.length >= 2 && (str[1] == 'x' || str[1] == 'X')) { + str = str[2..$]; + if (str.length == 0 || digitInBase(str[0], 16) < 0) { str = svs; error("invalid color"); } + base = 16; + } + while (str.length) { + if (str[0] != '_') { + immutable int dg = digitInBase(str[0], cast(int)base); + if (dg < 0) break; + val = val*base+cast(uint)dg; + if (val > 255) { str = svs; error("invalid color"); } + } + str = str[1..$]; + } + while (str.length && str[0] == '_') str = str[1..$]; + rgba[n] = cast(ubyte)val; + } + skipBlanks(); + if (allowAlpha) return gxrgba(rgba[0], rgba[1], rgba[2], rgba[3]); + return gxrgb(rgba[0], rgba[1], rgba[2]); } } -EditorWidget { - back: #007; - - status-back: white; - status-color: black; - - text: rgb(220, 220, 0); - quote0-text: rgb(128, 128, 0); - quote1-text: rgb(0, 128, 128); - wrap-mark-text: rgb(0, 90, 220); - - attach-file-text: rgb(0x6e, 0x00, 0xff); - attach-bad-text: red; - - // marked block - mark-back: rgb(0, 160, 160); - mark-text: white; -} +// ////////////////////////////////////////////////////////////////////////// // +class ColorStyle { +public: + struct ColorItem { + string widget; // "Widget" + string type; // "text" + string mod; // "inactive", "disabled", etc. + bool redirect; + uint color; + // redirect + string rewidget; // null means "this" + string retype; // null means "this" + string remod; // null means "this" + // loop protection + uint seenCount; + } +protected: + ColorItem[string] colors; + string[string] parents; + uint seenCount; -LineEditWidget : EditorWidget { - title-back: transparent; - title-text: white; + uint[string] styleCache; + char[] tempccstr; - back: black; -} +protected: + final void clearStyleCache () { + pragma(inline, true); + styleCache = null; + } + final const(char)[] buildcc (const(char)[] defparent, const(char)[] ctname, const(char)[] type, const(char)[] mod) { + pragma(inline, true); + immutable usize strsize = defparent.length+1+ctname.length+1+type.length+1+mod.length; + if (tempccstr.length < strsize) tempccstr.length = (strsize|0xff)+1; + // default parent + usize spos = defparent.length; + tempccstr[0..spos] = defparent; + tempccstr[spos++] = 0; + // class name + tempccstr[spos..spos+ctname.length] = ctname; + spos += ctname.length; + tempccstr[spos++] = 0; + // type + tempccstr[spos..spos+type.length] = type; + spos += type.length; + tempccstr[spos++] = 0; + // modifier + tempccstr[spos..spos+mod.length] = mod; + // done + return tempccstr[0..spos+mod.length]; + } -MainPaneWindow { - //// group list //// - grouplist-divline: white; - grouplist-back: #222; - // group with unread messages - grouplist-unread-text: #0ff; - // normal group - grouplist-normal-text: rgb(255, 187, 0); - grouplist-normal-child-text: rgb(225, 97, 0); - // spam group - grouplist-spam-text: #800; - // main accounts group - grouplist-accounts-text: rgb(220, 220, 0); - grouplist-accounts-child-text: rgb(160, 160, 250); - // account inbox group - grouplist-inbox-text: rgb(90, 90, 180); + final bool findCachedColor (in TypeInfo_Class defpar, in TypeInfo_Class ct, const(char)[] type, const(char)[] mod, out uint res) { + pragma(inline, true); + if (auto cp = buildcc((defpar !is null ? defpar.name : null), (ct !is null ? ct.name : null), type, mod) in styleCache) { + res = *cp; + return true; + } + return false; + } - grouplist-dots: rgb(80, 80, 80); + final void cacheColor (in uint clr, in TypeInfo_Class defpar, in TypeInfo_Class ct, const(char)[] type, const(char)[] mod) { + pragma(inline, true); + auto key = buildcc((defpar !is null ? defpar.name : null), (ct !is null ? ct.name : null), type, mod); + if (auto cp = key in styleCache) { + *cp = clr; + } else { + styleCache[key.idup] = clr; + } + } - .cursor { - grouplist-back: #088; - grouplist-outline: black; +protected: + final void resetSeenCount () { + seenCount = 1; + foreach (ref ColorItem ci; colors.byValue) ci.seenCount = 0; } + final void incSeenCount () { + pragma(inline, true); + if (++seenCount == 0) resetSeenCount(); + } - //// thread list //// - threadlist-divline: white; - threadlist-back: #222; - threadlist-dots: #444; +protected: + void addColorItem (in ColorItem ci) { + clearStyleCache(); + auto nm = buildcc(null, ci.widget, ci.type, ci.mod); + if (auto cptr = nm in colors) { + cptr.redirect = ci.redirect; + cptr.color = ci.color; + cptr.rewidget = ci.rewidget; + cptr.retype = ci.retype; + cptr.remod = ci.remod; + cptr.seenCount = 0; + } else { + colors[nm.idup] = ci; + } + } - .normal { - threadlist-back: transparent; - threadlist-subj-text: rgb(215, 87, 0); - threadlist-from-text: rgb(215, 87, 0); - threadlist-mail-text: rgb(155, 27, 0); - threadlist-time-text: rgb(215, 87, 0); - threadlist-strike-line: transparent; +public: + void parseStyle (const(char)[] str) { + auto par = FuiSimpleParser(str); + while (!par.isEOT) { + if (par.check('!')) { + auto cmd = par.expectIdNoCopy(); + if (cmd.strEquCI("clear-style")) { + clear(); + } else { + par.error("invalid command: '"~cmd.idup~"'"); + } + par.expect(';'); + continue; + } + string widget = par.expectId(); + string parent = null; + if (par.check(':')) { + parent = par.expectId(); + if (parent == widget) par.error("invalid parent `"~parent~"` for `"~widget~"`"); + parents[widget] = parent; + } + par.expect('{'); + string mod = null; + for (;;) { + if (par.check('}')) { + if (mod is null) break; + mod = null; + continue; + } + //{ import std.stdio; writeln("WIDGET: <", widget, ">"); } + if ((mod is null) && par.check('.')) { + mod = par.expectId(); + //{ import std.stdio; writeln("NEW MOD: <", mod, ">"); } + par.expect('{'); + continue; + } + string type = par.expectId(); + //{ import std.stdio; writeln("TYPE: <", type, ">"); } + par.expect(':'); + ColorItem ci; + ci.widget = widget; + ci.type = type; + ci.mod = mod; + // redirect? + if (par.check('@')) { + // redirect + ci.redirect = true; + if (par.checkNoEat('.')) { + // .mod + ci.rewidget = null; + ci.retype = type; + } else { + string rd0 = par.expectId(); + if (par.check(':')) { + // widget:... + ci.rewidget = rd0; + if (par.checkNoEat('.')) { + ci.retype = type; + } else { + // has type? + if (par.checkNoEat(';')) { + ci.retype = type; + } else { + ci.retype = par.expectId(); + } + } + } else { + // type... + ci.rewidget = null; + ci.retype = rd0; + } + } + if (par.check('.')) { + ci.remod = par.expectId(); + } else { + ci.remod = mod; + } + } else { + // color + ci.redirect = false; + ci.color = par.parseColor(); + } + par.expect(';'); + if (ci.mod.strEquCI("none") || ci.mod.strEquCI("null")) ci.mod = null; + if (ci.remod.strEquCI("none") || ci.remod.strEquCI("null")) ci.remod = null; + version(none) { + import std.stdio; + write("SRC: <", ci.widget, ":", ci.type, ".", ci.mod, ">"); + if (ci.redirect) { + writeln(" --> <", ci.rewidget, ":", ci.retype, ".", ci.remod, ">"); + } else { + writeln(" : rgba(", gxGetRed(ci.color), ",", gxGetGreen(ci.color), ",", gxGetBlue(ci.color), ",", gxGetAlpha(ci.color), ")"); + } + } + addColorItem(ci); + } + } } - .unread { - threadlist-back: transparent; - threadlist-subj-text: white; - threadlist-from-text: white; - threadlist-mail-text: yellow; - threadlist-time-text: white; - threadlist-strike-line: transparent; - } +public: + this () {} - .soft-del { - threadlist-back: transparent; - threadlist-subj-text: #800; - threadlist-from-text: #800; - threadlist-mail-text: #800; - threadlist-time-text: #800; - threadlist-strike-line: #800; + void cloneFrom (ColorStyle st) { + if (st is null || st is this) return; + colors = null; + foreach (const ref ColorItem ci; st.colors.byValue) addColorItem(ci); + parents = null; + foreach (auto s; st.parents.byKeyValue) parents[s.key] = s.value; + styleCache = null; + resetSeenCount(); + seenCount = 0; } - .hard-del { - threadlist-back: transparent; - threadlist-subj-text: red; - threadlist-from-text: red; - threadlist-mail-text: red; - threadlist-time-text: red; - threadlist-strike-line: red; + void clear () { + colors = null; + parents = null; + styleCache = null; + seenCount = 0; } - .twit { - threadlist-back: transparent; - threadlist-subj-text: #400; - threadlist-from-text: #400; - threadlist-mail-text: #400; - threadlist-time-text: #400; - threadlist-strike-line: transparent; + protected static struct BaseInfo { + TypeInfo_Class defaultParent = void; + TypeInfo_Class ctsrc = void; } - .cursor-normal { - threadlist-back: #088; - threadlist-subj-text: @.normal; - threadlist-from-text: @.normal; - threadlist-mail-text: @.normal; - threadlist-time-text: @.normal; - threadlist-strike-line: @normal; - threadlist-outline: black; - } + protected uint findColorIntr (ref BaseInfo nfo, TypeInfo_Class ctwdt, + const(char)[] type, const(char)[] mod=null, bool* found=null) + { + if (ctwdt is null) { + ctwdt = nfo.defaultParent; + if (ctwdt is null) { + if (found !is null) *found = false; + return gxUnknown; + } + } - .cursor-unread { - threadlist-back: @.cursor-normal; - threadlist-subj-text: @.unread; - threadlist-from-text: @.unread; - threadlist-mail-text: @.unread; - threadlist-time-text: @.unread; - threadlist-strike-line: @.unread; - threadlist-outline: black; - } + string sname = classShortName(ctwdt); - .cursor-soft-del { - threadlist-back: #066; - threadlist-subj-text: @.soft-del; - threadlist-from-text: @.soft-del; - threadlist-mail-text: @.soft-del; - threadlist-time-text: @.soft-del; - threadlist-strike-line: @.soft-del; - } + ColorItem *mcit = void; + auto nm = buildcc(null, sname, type, mod); + mcit = (nm in colors); + // if there is a modifier, try without it + if (mcit is null && mod.length) { + nm = buildcc(null, sname, type, null); + mcit = (nm in colors); + } - .cursor-hard-del { - threadlist-back: @.cursor-soft-del; - threadlist-subj-text: @.hard-del; - threadlist-from-text: @.hard-del; - threadlist-mail-text: @.hard-del; - threadlist-time-text: @.hard-del; - threadlist-strike-line: @.hard-del; - } + if (mcit !is null) { + if (!mcit.redirect) { + // not a redirect + mcit.seenCount = seenCount; + if (found !is null) *found = true; + return mcit.color; + } - .cursor-twit { - threadlist-back: #066; - threadlist-subj-text: @.twit; - threadlist-from-text: @.twit; - threadlist-mail-text: @.twit; - threadlist-time-text: @.twit; - threadlist-strike-line: @.twit; + // check for infinite loop + if (mcit.seenCount == seenCount) { + // infinite loop + if (found !is null) *found = false; + return gxUnknown; + } + mcit.seenCount = seenCount; + + // empty redirect widget name means "source widget" + if (mcit.rewidget.length == 0) { + return findColorIntr(ref nfo, nfo.ctsrc, mcit.retype, mcit.remod, found); + } + + // special "parent" redirect widget name + if (mcit.rewidget == "parent") { + if (auto pp = sname in parents) { + return findColorIntr(ref nfo, findWidgetClass(*pp), mcit.retype, mcit.remod, found); + } else { + return findColorIntr(ref nfo, ctwdt.base, mcit.retype, mcit.remod, found); + } + } + + // normal-named redirect + return findColorIntr(ref nfo, findWidgetClass(mcit.rewidget), mcit.retype, mcit.remod, found); + } + + // try parent + if (auto pp = sname in parents) { + return findColorIntr(ref nfo, findWidgetClass(*pp), type, mod, found); + } + + // try inheritance parent + return findColorIntr(ref nfo, ctwdt.base, type, mod, found); } + final uint findColor (TypeInfo_Class defparent, TypeInfo_Class ctsrc, const(char)[] type, const(char)[] mod=null, bool* foundp=null) { + if (defparent is ctsrc) defparent = null; - //// message header //// - msg-header-back: rgb(20, 20, 20); - msg-header-from: #088; - msg-header-to: #088; - msg-header-subj: #088; - msg-header-date: #088; - msg-header-divline: #bbb; + if (ctsrc is null) { + if (defparent is null) { + if (foundp !is null) *foundp = false; + return gxUnknown; + } + ctsrc = defparent; + defparent = null; + } + uint clr; + if (findCachedColor(defparent, ctsrc, type, mod, out clr)) { + if (foundp !is null) *foundp = true; + return clr; + } - //// message text //// - msg-text-back: rgb(37, 37, 37); - msg-text-text: rgb(174, 174, 174); - msg-text-quote0: #880; - msg-text-quote1: #088; - msg-text-link: rgb(0, 200, 200); - msg-text-html-sign: rgb(128, 0, 128); + incSeenCount(); + BaseInfo nfo; + nfo.defaultParent = defparent; + nfo.ctsrc = ctsrc; + clr = findColorIntr(ref nfo, ctsrc, type, mod, foundp); + cacheColor(clr, defparent, ctsrc, type, mod); - .hover { - msg-text-link: rgb(0, 255, 255); + return clr; } - .pressed { - msg-text-link: rgb(255, 0, 255); + final uint findColor (in Object defpar, in Object obj, const(char)[] type, const(char)[] mod=null, bool* foundp=null) { + pragma(inline, true); + TypeInfo_Class dp = (defpar !is null ? cast(TypeInfo_Class)typeid(defpar) : null); + TypeInfo_Class ct = (obj !is null ? cast(TypeInfo_Class)typeid(obj) : null); + return findColor(dp, ct, type, mod, foundp); } - //// message text twit //// - twit-shade: rgba(0, 0, 80, 127); - twit-text: red; - twit-outline: black; -} +static: + __gshared TypeInfo_Class[string] classNameCache; + static bool isGoodClassType (TypeInfo_Class ct) pure nothrow @trusted @nogc { + while (ct !is null) { + if (ct.name == "egui.subwindows.SubWindow" || + ct.name == "egui.widgets.Widget") + { + return true; + } + ct = ct.base; + } + return false; + } -HintWindow { - frame: white; - title-back: @frame; - title-text: black; + static string classShortName (in TypeInfo_Class ct) nothrow @trusted @nogc { + pragma(inline, true); + if (ct is null) return null; + string name = ct.name; + auto dpos = name.lastIndexOf('.'); + return (dpos < 0 ? name : name[dpos+1..$]); + } - back: rgb(0, 0, 80); - text: rgb(155, 155, 155); + static TypeInfo_Class findWidgetClass (string cname) { + if (cname.length == 0) return null; + if (auto ctp = cname in classNameCache) { + version(none) { import core.stdc.stdio : printf; + printf("findWidgetClass<%.*s>: CACHE HIT! %p\n", cast(uint)cname.length, cname.ptr, *ctp); + } + return *ctp; + } + foreach (ModuleInfo* m; ModuleInfo) { + string mname = m.name; + if (mname.startsWith("std.") || + mname.startsWith("core.") || + mname.startsWith("rt.") || + mname.startsWith("gc.") || + mname.startsWith("arsd.") || + false) + { + continue; + } + foreach (TypeInfo_Class ct; m.localClasses()) { + if (!ct.name.endsWith(cname)) continue; + version(none) { import core.stdc.stdio : printf; + printf("findWidgetClass<%.*s>: checking <%.*s>\n", + cast(uint)cname.length, cname.ptr, + cast(uint)ct.name.length, ct.name.ptr); + } + if (ct.name.length == cname.length || ct.name[$-cname.length-1] == '.') { + version(none) { import core.stdc.stdio : printf; + printf("findWidgetClass<%.*s>: found <%.*s>\n", + cast(uint)cname.length, cname.ptr, + cast(uint)ct.name.length, ct.name.ptr); + } + // final check + if (isGoodClassType(ct)) { + // cache it + classNameCache[cname.idup] = ct; + return ct; + } + } + } + } + version(none) { import core.stdc.stdio : printf; + printf("findWidgetClass<%.*s>: NOT FOUND!\n", cast(uint)cname.length, cname.ptr); + } + // cache "unknown" + classNameCache[cname] = null; + return null; + } } -MessageWindow { - frame: white; - title-back: @frame; - title-text: black; +__gshared ColorStyle defaultColorStyle; - back: rgb(0, 0, 80); - text: rgb(255, 255, 0); - bar-back: rgb(90, 90, 180); -} - `); - } +shared static this () { + defaultColorStyle = new ColorStyle; + defaultColorStyle.parseStyle(defaultStyleText); } diff --git a/egui/subwindows.d b/egui/subwindows.d index 4157674..dbd0474 100644 --- a/egui/subwindows.d +++ b/egui/subwindows.d @@ -569,7 +569,6 @@ protected: RootWidget mRoot; ColorStyle colorStyle; - bool colorStyleCloned; public: int winminx, winminy; @@ -583,8 +582,9 @@ public: // color getters final uint getStyleColor (in Object obj, const(char)[] type, const(char)[] mod=null) { pragma(inline, true); - if (colorStyle is null) colorStyle = defaultColorStyle; - return colorStyle.findColor(obj, type, mod); + ColorStyle st = colorStyle; + if (st is null) st = defaultColorStyle; + return st.findColor(this, obj, type, mod); } final uint getColor (const(char)[] type, const(char)[] mod) { @@ -608,7 +608,6 @@ protected: } void setStyle (ColorStyle stl) { - if (stl is null) stl = defaultColorStyle; colorStyle = stl; } @@ -736,13 +735,11 @@ public: void relayoutResize () { relayout(true); } + // this clones the style void appendStyle (const(char)[] str) { - if (colorStyle is null) colorStyle = defaultColorStyle; - if (!colorStyleCloned) { - ColorStyle nc = new ColorStyle; - nc.cloneFrom(colorStyle); - colorStyle = nc; - colorStyleCloned = true; + if (colorStyle is null) { + colorStyle = new ColorStyle; + colorStyle.cloneFrom(defaultColorStyle); } colorStyle.parseStyle(str); } -- 2.11.4.GIT