From fe84ef77079e4b49b97d2afae31078660497c96d Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 29 Jan 2022 15:36:36 -0500 Subject: [PATCH] good stuff --- cgi.d | 25 +++++++-- dom.d | 14 +++-- jsvar.d | 10 ++-- minigui.d | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++ script.d | 26 ++++++++- sqlite.d | 3 +- webtemplate.d | 43 +++++++++++++-- 7 files changed, 243 insertions(+), 20 deletions(-) diff --git a/cgi.d b/cgi.d index 480a309..5eb4ecb 100644 --- a/cgi.d +++ b/cgi.d @@ -6845,11 +6845,14 @@ void freeIoOp(ref IoOp* ptr) { version(Posix) version(with_addon_servers_connections) void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) { + + //import std.stdio : writeln; writeln(cast(string) data); + import core.sys.posix.unistd; auto ret = write(connection, data.ptr, data.length); if(ret != data.length) { - if(ret == 0 || errno == EPIPE) { + if(ret == 0 || (ret == -1 && (errno == EPIPE || errno == ETIMEDOUT))) { // the file is closed, remove it eis.fileClosed(connection); } else @@ -7875,7 +7878,9 @@ final class EventSourceServerImplementation : EventSourceServer, EventIoServer { } return false; } - void handleLocalConnectionClose(IoOp* op) {} + void handleLocalConnectionClose(IoOp* op) { + fileClosed(op.fd); + } void handleLocalConnectionComplete(IoOp* op) {} void wait_timeout() { @@ -7883,9 +7888,9 @@ final class EventSourceServerImplementation : EventSourceServer, EventIoServer { foreach(url, connections; eventConnectionsByUrl) foreach(connection; connections) if(connection.needsChunking) - nonBlockingWrite(this, connection.fd, "2\r\n:\n\r\n"); + nonBlockingWrite(this, connection.fd, "1b\r\nevent: keepalive\ndata: ok\n\n\r\n"); else - nonBlockingWrite(this, connection.fd, ":\n\r\n"); + nonBlockingWrite(this, connection.fd, "event: keepalive\ndata: ok\n\n\r\n"); } void fileClosed(int fd) { @@ -8151,11 +8156,20 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS ioops[sock] = acceptOp; } + import core.time : MonoTime, seconds; + + MonoTime timeout = MonoTime.currTime + 15.seconds; + while(true) { // FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently - int timeout_milliseconds = 15000; // -1; // infinite + int timeout_milliseconds = 0; // -1; // infinite + + timeout_milliseconds = cast(int) (timeout - MonoTime.currTime).total!"msecs"; + if(timeout_milliseconds < 0) + timeout_milliseconds = 0; + //writeln("waiting for ", name); version(linux) { @@ -8172,6 +8186,7 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS if(nfds == 0) { eis.wait_timeout(); + timeout += 15.seconds; } foreach(idx; 0 .. nfds) { diff --git a/dom.d b/dom.d index 002d079..5d4a75a 100644 --- a/dom.d +++ b/dom.d @@ -3400,8 +3400,8 @@ class Element : DomParent { return null; // at the top we don't have anything to really do - if(parent_ is null) - return null; + //if(parent_ is null) + //return null; // I've used isEmpty before but this other check seems better.... //|| this.isEmpty()) @@ -8115,9 +8115,17 @@ unittest { `); } + foreach(test; [ + "Hello\n \n \n How are you?\n \n \n Good & you?\n \n \n \n", + "HelloHow are you?Good & you?", + ] ) { - auto document = new XmlDocument("Hello\n \n \n How are you?\n \n \n Good & you?\n \n \n \n"); + auto document = new XmlDocument(test); assert(document.root.toPrettyString(false, 0, " ") == "\n \n Hello\n \n \n How are you?\n \n \n Good & you?\n \n \n \n"); + assert(document.toPrettyString(false, 0, " ") == "\n\n \n Hello\n \n \n How are you?\n \n \n Good & you?\n \n \n \n"); + auto omg = document.root; + omg.parent_ = null; + assert(omg.toPrettyString(false, 0, " ") == "\n \n Hello\n \n \n How are you?\n \n \n Good & you?\n \n \n \n"); } { diff --git a/jsvar.d b/jsvar.d index fbb1dc8..742e9ba 100644 --- a/jsvar.d +++ b/jsvar.d @@ -444,7 +444,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { _this._payload._integral = l; return _this; } else static if(isFloatingPoint!T) { - static if(op == "&" || op == "|" || op == "^") { + static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { this2._type = var.Type.Integral; long f = l; mixin("f "~op~"= cast(long) t;"); @@ -465,7 +465,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { _this._type = var.Type.Integral; _this._payload._integral = l; } else{ - static if(op == "&" || op == "|" || op == "^") { + static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { long f = l; mixin("f "~op~"= cast(long) rhs;"); _this._type = var.Type.Integral; @@ -484,7 +484,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { auto f = this._payload._floating; static if(isIntegral!T || isFloatingPoint!T) { - static if(op == "&" || op == "|" || op == "^") { + static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { long argh = cast(long) f; mixin("argh "~op~"= cast(long) t;"); _this._type = var.Type.Integral; @@ -498,7 +498,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { } else static if(isSomeString!T) { auto rhs = stringToNumber(t); - static if(op == "&" || op == "|" || op == "^") { + static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { long pain = cast(long) f; mixin("pain "~op~"= cast(long) rhs;"); _this._type = var.Type.Integral; @@ -511,7 +511,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") { return _this; } else static assert(0); } else if(this2.payloadType() == var.Type.String) { - static if(op == "&" || op == "|" || op == "^") { + static if(op == "&" || op == "|" || op == "^" || op == "<<" || op == ">>" || op == ">>>") { long r = cast(long) stringToNumber(this2._payload._string); long rhs; } else { diff --git a/minigui.d b/minigui.d index 25b1273..087974d 100644 --- a/minigui.d +++ b/minigui.d @@ -4227,6 +4227,148 @@ class OpenGlWidget : NestedChildWindowWidget { } } +/++ + This demo shows how to draw text in an opengl scene. ++/ +unittest { + import arsd.minigui; + import arsd.ttf; + + void main() { + auto window = new Window(); + + auto widget = new OpenGlWidget(window); + + // old means non-shader code so compatible with glBegin etc. + // tbh I haven't implemented new one in font yet... + // anyway, declaring here, will construct soon. + OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont; + + // this is a little bit awkward, calling some methods through + // the underlying SimpleWindow `win` method, and you can't do this + // on a nanovega widget due to conflicts so I should probably fix + // the api to be a bit easier. But here it will work. + // + // Alternatively, you could load the font on the first draw, inside + // the redrawOpenGlScene, and keep a flag so you don't do it every + // time. That'd be a bit easier since the lib sets up the context + // by then guaranteed. + // + // But still, I wanna show this. + widget.win.visibleForTheFirstTime = delegate { + // must set the opengl context + widget.win.setAsCurrentOpenGlContext(); + + // if you were doing a OpenGL 3+ shader, this + // gets especially important to do in order. With + // old-style opengl, I think you can even do it + // in main(), but meh, let's show it more correctly. + + // Anyway, now it is time to load the font from the + // OS (you can alternatively load one from a .ttf file + // you bundle with the application), then load the + // font into texture for drawing. + + auto osfont = new OperatingSystemFont("DejaVu Sans", 18); + + assert(!osfont.isNull()); // make sure it actually loaded + + // using typeof to avoid repeating the long name lol + glfont = new typeof(glfont)( + // get the raw data from the font for loading in here + // since it doesn't use the OS function to draw the + // text, we gotta treat it more as a file than as + // a drawing api. + osfont.getTtfBytes(), + 18, // need to respecify size since opengl world is different coordinate system + + // these last two numbers are why it is called + // "Limited" font. It only loads the characters + // in the given range, since the texture atlas + // it references is all a big image generated ahead + // of time. You could maybe do the whole thing but + // idk how much memory that is. + // + // But here, 0-128 represents the ASCII range, so + // good enough for most English things, numeric labels, + // etc. + 0, + 128 + ); + }; + + widget.redrawOpenGlScene = () { + // now we can use the glfont's drawString function + + // first some opengl setup. You can do this in one place + // on window first visible too in many cases, just showing + // here cuz it is easier for me. + + // gonna need some alpha blending or it just looks awful + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0,0,0,0); + glDepthFunc(GL_LEQUAL); + + // Also need to enable 2d textures, since it draws the + // font characters as images baked in + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + + // the orthographic matrix is best for 2d things like text + // so let's set that up. This matrix makes the coordinates + // in the opengl scene be one-to-one with the actual pixels + // on screen. (Not necessarily best, you may wish to scale + // things, but it does help keep fonts looking normal.) + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, widget.width, widget.height, 0, 0, 1); + + // you can do other glScale, glRotate, glTranslate, etc + // to the matrix here of course if you want. + + // note the x,y coordinates here are for the text baseline + // NOT the upper-left corner. The baseline is like the line + // in the notebook you write on. Most the letters are actually + // above it, but some, like p and q, dip a bit below it. + // + // So if you're used to the upper left coordinate like the + // rest of simpledisplay/minigui usually do, do the + // y + glfont.ascent to bring it down a little. So this + // example puts the string in the upper left of the window. + glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green); + + // re color btw: the function sets a solid color internally, + // but you actually COULD do your own thing for rainbow effects + // and the sort if you wanted too, by pulling its guts out. + // Just view its source for an idea of how it actually draws: + // http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332 + + // it gets a bit complicated with the character positioning, + // but the opengl parts are fairly simple: bind a texture, + // set the color, draw a quad for each letter. + + + // the last optional argument there btw is a bounding box + // it will/ use to word wrap and return an object you can + // use to implement scrolling or pagination; it tells how + // much of the string didn't fit in the box. But for simple + // labels we can just ignore that. + + + // I'd suggest drawing text as the last step, after you + // do your other drawing. You might use the push/pop matrix + // stuff to keep your place. You, in theory, should be able + // to do text in a 3d space but I've never actually tried + // that.... + }; + + window.loop(); + } +} + version(custom_widgets) private alias ListWidgetBase = ScrollableWidget; else diff --git a/script.d b/script.d index 13d7305..8dba246 100644 --- a/script.d +++ b/script.d @@ -1462,7 +1462,7 @@ class BinaryExpression : Expression { sw: switch(op) { // I would actually kinda prefer this to be static foreach, but normal // tuple foreach here has broaded compiler compatibility. - foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^", "%")) //, ">>", "<<", ">>>")) // FIXME + foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^", "%", ">>", "<<", ">>>")) // FIXME case ctOp: { n = mixin("left "~ctOp~" right"); break sw; @@ -2824,6 +2824,9 @@ Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { if(peek.type == ScriptToken.Type.symbol) { switch(peek.str) { + case "<<": + case ">>": + case ">>>": case "*": case "/": case "%": @@ -3510,7 +3513,6 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin case "continue": case "break": case "return": - return parseExpression(tokens); // unary prefix operators case "!": @@ -3536,6 +3538,26 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin assert(0); } +// FIXME someday this should work, my parser is so bad +// until then put parens around your == stuff. +version(none) +unittest { + interpret(q{ + var a = 5; + var b = false; + assert(a == 5 || b); + }); +} +version(none) +unittest { + interpret(q{ + var a = 5; + var b = false; + assert(((a == 5) || b)); + }); +} + + struct CompoundStatementRange(MyTokenStreamHere) { // FIXME: if MyTokenStreamHere is not a class, this fails! MyTokenStreamHere tokens; diff --git a/sqlite.d b/sqlite.d index b565bf0..d67ef65 100644 --- a/sqlite.d +++ b/sqlite.d @@ -372,7 +372,8 @@ struct Statement { break; case SQLITE_NULL: - v = null; + string n = null; + v = n; break; } diff --git a/webtemplate.d b/webtemplate.d index f2b29f9..2c02c98 100644 --- a/webtemplate.d +++ b/webtemplate.d @@ -13,7 +13,7 @@ whatever == false - + <%= item %> @@ -34,7 +34,7 @@ ``` Functions available: - `encodeURIComponent`, `formatDate`, `dayOfWeek`, `formatTime` + `encodeURIComponent`, `formatDate`, `dayOfWeek`, `formatTime`, `filterKeys` History: Things inside script tag were added on January 7, 2022. @@ -65,6 +65,39 @@ void addDefaultFunctions(var context) { import std.conv; // FIXME: I prolly want it to just set the prototype or something + /+ + foo |> filterKeys(["foo", "bar"]); + + It needs to match the filter, then if it is -pattern, it is removed and if it is +pattern, it is retained. + + First one that matches applies to the key, so the last one in the list is your default. + + Default is to reject. Putting a "*" at the end will keep everything not removed though. + + ["-foo", "*"] // keep everything except foo + +/ + context.filterKeys = function var(var f, string[] filters) { + import std.path; + var o = var.emptyObject; + foreach(k, v; f) { + bool keep = false; + foreach(filter; filters) { + if(filter.length == 0) + throw new Exception("invalid filter"); + bool filterOff = filter[0] == '-'; + if(filterOff) + filter = filter[1 .. $]; + if(globMatch(k.get!string, filter)) { + keep = !filterOff; + break; + } + } + if(keep) + o[k] = v; + } + return o; + }; + context.encodeURIComponent = function string(var f) { import std.uri; return encodeComponent(f.get!string); @@ -213,9 +246,11 @@ void expandTemplate(Element root, var context) { var nc = var.emptyObject(context); lastBoolResult = false; auto got = interpret(ele.attrs.over, context); - foreach(item; got) { + foreach(k, item; got) { lastBoolResult = true; nc[ele.attrs.as] = item; + if(ele.attrs.index.length) + nc[ele.attrs.index] = k; auto clone = ele.cloneNode(true); clone.tagName = "root"; // it certainly isn't a for-each anymore! expandTemplate(clone, nc); @@ -274,7 +309,7 @@ void expandTemplate(Element root, var context) { check_more: auto idx = source.indexOf("<%="); if(idx != -1) { - newCode = source[0 .. idx]; + newCode ~= source[0 .. idx]; auto remaining = source[idx + 3 .. $]; idx = remaining.indexOf("%>"); if(idx == -1)