diff --git a/dom.d b/dom.d index 15847d2..c5c5f58 100644 --- a/dom.d +++ b/dom.d @@ -20,6 +20,12 @@ */ module arsd.dom; +version(with_arsd_jsvar) + import arsd.jsvar; +else { + enum Scriptable; +} + // FIXME: might be worth doing Element.attrs and taking opDispatch off that // so more UFCS works. @@ -539,6 +545,27 @@ struct DataSet { mixin JavascriptStyleDispatch!(); } +/// Proxy object for attributes which will replace the main opDispatch eventually +struct AttributeSet { + this(Element e) { + this._element = e; + } + + private Element _element; + string set(string name, string value) { + _element.setAttribute(name, value); + return value; + } + + string get(string name) const { + return _element.getAttribute(name); + } + + mixin JavascriptStyleDispatch!(); +} + + + /// for style, i want to be able to set it with a string like a plain attribute, /// but also be able to do properties Javascript style. @@ -1256,10 +1283,16 @@ class Element { /// Given: /// /// We get: assert(a.dataset.myProperty == "cool"); - DataSet dataset() { + @property DataSet dataset() { return DataSet(this); } + /// Gives dot/opIndex access to attributes + /// ele.attrs.largeSrc = "foo"; // same as ele.setAttribute("largeSrc", "foo") + @property AttributeSet attrs() { + return AttributeSet(this); + } + /// Provides both string and object style (like in Javascript) access to the style attribute. @property ElementStyle style() { return ElementStyle(this); @@ -5833,13 +5866,15 @@ class Event { foreach(handler; e.bubblingEventHandlers[eventName]) handler(e, this); - if(!defaultPrevented) - if(eventName in e.defaultEventHandlers) - e.defaultEventHandlers[eventName](e, this); - if(propagationStopped) break; } + + if(!defaultPrevented) + foreach(e; chain) { + if(eventName in e.defaultEventHandlers) + e.defaultEventHandlers[eventName](e, this); + } } } diff --git a/html.d b/html.d index ac51336..ac43f23 100644 --- a/html.d +++ b/html.d @@ -124,8 +124,7 @@ Element sanitizedHtml(/*in*/ Element userContent, string idPrefix = null, HtmlFe e.tagName == "textarea" || e.tagName == "label" || e.tagName == "fieldset" || - e.tagName == "legend" || - 1 + e.tagName == "legend" )) ) { e.innerText = e.innerText; // strips out non-text children diff --git a/http.d b/http.d index 0b94ce6..12186e4 100644 --- a/http.d +++ b/http.d @@ -5,6 +5,12 @@ version(with_openssl) { pragma(lib, "ssl"); } +ubyte[] getBinary(string url, string[string] cookies = null) { + auto hr = httpRequest("GET", url, null, cookies); + if(hr.code != 200) + throw new Exception(format("HTTP answered %d instead of 200 on %s", hr.code, url)); + return hr.content; +} /** Gets a textual document, ignoring headers. Throws on non-text or error. diff --git a/jsvar.d b/jsvar.d index 4cd4710..cd79378 100644 --- a/jsvar.d +++ b/jsvar.d @@ -37,6 +37,9 @@ import std.traits; import std.conv; import std.json; +// uda for wrapping classes +enum Scriptable; + /* PrototypeObject FIXME: make undefined variables reaction overloadable in PrototypeObject, not just a switch @@ -47,6 +50,8 @@ import std.json; it should consistently throw on missing semicolons + *) in operator + *) nesting comments, `` string literals *) opDispatch overloading *) properties???// @@ -105,6 +110,11 @@ version(test_script) } version(test_script) void main() { + { + var a = var.emptyObject; + a.qweq = 12; + } + // the WrappedNativeObject is disgusting // but works. /* @@ -545,6 +555,7 @@ struct var { auto obj = new PrototypeObject(); this._payload._object = obj; + static if((is(T == class) || is(T == struct))) foreach(member; __traits(allMembers, T)) { static if(__traits(compiles, __traits(getMember, t, member))) { static if(is(typeof(__traits(getMember, t, member)) == function)) { @@ -553,6 +564,11 @@ struct var { } else this[member] = __traits(getMember, t, member); } + } else { + // assoc array + foreach(l, v; t) { + this[var(l)] = var(v); + } } } else static if(isArray!T) { this._type = Type.Array; @@ -571,8 +587,8 @@ struct var { public var opOpAssign(string op, T)(T t) { if(payloadType() == Type.Object) { - var operator = this["opOpAssign"]; - if(operator._type == Type.Function) + var* operator = this._payload._object._peekMember("opOpAssign", true); + if(operator !is null && operator._type == Type.Function) return operator.call(this, op, t); } @@ -582,8 +598,8 @@ struct var { public var opBinary(string op, T)(T t) { var n; if(payloadType() == Type.Object) { - var operator = this["opBinary"]; - if(operator._type == Type.Function) { + var* operator = this._payload._object._peekMember("opBinary", true); + if(operator !is null && operator._type == Type.Function) { return operator.call(this, op, t); } } @@ -592,10 +608,13 @@ struct var { public var apply(var _this, var[] args) { if(this.payloadType() == Type.Function) { + assert(this._payload._function !is null); return this._payload._function(_this, args); } - // or we could throw + version(jsvar_throw) + throw new DynamicTypeException(this, Type.Function); + var ret; return ret; } @@ -616,7 +635,7 @@ struct var { return this.get!string; } - public T get(T)() { + public T get(T)() if(!is(T == void)) { static if(is(T == var)) { return this; } else @@ -648,8 +667,9 @@ struct var { return t; } else static if(isSomeString!T) { - // FIXME: is this best? - return this.toJson(); + if(this._object !is null) + return this._object.toString(); + return "null"; } return T.init; @@ -680,8 +700,13 @@ struct var { return to!string(pl); } else static if(isArray!T) { T ret; + static if(is(ElementType!T == void)) { + static assert(0, "try wrapping the function to get rid of void[] args"); + //alias getType = ubyte; + } else + alias getType = ElementType!T; foreach(item; pl) - ret ~= item.get!(ElementType!T); + ret ~= item.get!(getType); return ret; } @@ -759,6 +784,15 @@ struct var { this._type = Type.Function; } + /* + public void _function(var function(var, var[]) f) { + var delegate(var, var[]) dg; + dg.ptr = null; + dg.funcptr = f; + this._function = dg; + } + */ + public void _object(PrototypeObject obj) { this._type = Type.Object; this._payload._object = obj; @@ -847,6 +881,33 @@ struct var { *tmp = _payload._array.length; return *tmp; } + if(name == "__prop" && this.payloadType() == Type.Object) { + var* tmp = new var; + (*tmp)._function = delegate var(var _this, var[] args) { + if(args.length == 0) + return var(null); + if(args.length == 1) { + auto peek = this._payload._object._peekMember(args[0].get!string, false); + if(peek is null) + return var(null); + else + return *peek; + } + if(args.length == 2) { + auto peek = this._payload._object._peekMember(args[0].get!string, false); + if(peek is null) { + this._payload._object._properties[args[0].get!string] = args[1]; + return var(null); + } else { + *peek = args[1]; + return *peek; + } + + } + throw new Exception("too many args"); + }; + return *tmp; + } PrototypeObject from; if(this.payloadType() == Type.Object) @@ -869,16 +930,28 @@ struct var { if(_payload._object is null) throw new DynamicTypeException(var(null), Type.Object, file, line); - this._payload._object._getMember(name, false, false, file, line) = t; - return this._payload._object._properties[name]; + return this._payload._object._setMember(name, var(t), false, false, false, file, line); } + public ref var opIndexAssignNoOverload(T)(T t, string name, string file = __FILE__, size_t line = __LINE__) { + if(name.length && name[0] >= '0' && name[0] <= '9') + return opIndexAssign(t, to!size_t(name), file, line); + _requireType(Type.Object); // FIXME? + if(_payload._object is null) + throw new DynamicTypeException(var(null), Type.Object, file, line); + + return this._payload._object._setMember(name, var(t), false, false, true, file, line); + } + + public ref var opIndex(size_t idx, string file = __FILE__, size_t line = __LINE__) { if(_type == Type.Array) { auto arr = this._payload._array; if(idx < arr.length) return arr[idx]; } + version(jsvar_throw) + throw new DynamicTypeException(this, Type.Array, file, line); var* n = new var(); return *n; } @@ -891,15 +964,22 @@ struct var { this._payload._array[idx] = t; return this._payload._array[idx]; } + version(jsvar_throw) + throw new DynamicTypeException(this, Type.Array, file, line); var* n = new var(); return *n; } ref var _getOwnProperty(string name, string file = __FILE__, size_t line = __LINE__) { if(_type == Type.Object) { - if(_payload._object !is null) - return this._payload._object._getMember(name, false, false, file, line); + if(_payload._object !is null) { + auto peek = this._payload._object._peekMember(name, false); + if(peek !is null) + return *peek; + } } + version(jsvar_throw) + throw new DynamicTypeException(this, Type.Object, file, line); var* n = new var(); return *n; } @@ -1085,7 +1165,7 @@ class WrappedNativeObject(T, bool wrapData = true) : PrototypeObject { - ParameterTypeTuple!(__traits(getMember, nativeObject, member)) fargs; + ParameterTypeTuple!(func) fargs; foreach(idx, a; fargs) { if(idx == args.length) break; @@ -1116,6 +1196,7 @@ class WrappedNativeObject(T, bool wrapData = true) : PrototypeObject { foreach(member; __traits(allMembers, T)) { static if(__traits(compiles, __traits(getMember, nativeObject, member))) { static if(is(typeof(__traits(getMember, nativeObject, member)) == function)) { + static if(__traits(getOverloads, nativeObject, member).length == 1) this._getMember(member, false, false)._function = makeWrapper!(member)(); } else static if(wrapData) @@ -1157,6 +1238,32 @@ class WrappedNativeObject(T, bool wrapData = true) : PrototypeObject { } } + +class OpaqueNativeObject(T) : PrototypeObject { + T item; + + this(T t) { + this.item = t; + } + + override string toString() const { + return item.toString(); + } + + override OpaqueNativeObject!T copy() { + auto n = new OpaqueNativeObject!T(item); + // FIXME: what if it is a reference type? + return n; + } +} + +T getOpaqueNative(T)(var v, string file = __FILE__, size_t line = __LINE__) { + auto obj = cast(OpaqueNativeObject!T) v._object; + if(obj is null) + throw new DynamicTypeException(v, var.Type.Object, file, line); + return obj.item; +} + class PrototypeObject { string name; var _prototype; @@ -1174,6 +1281,23 @@ class PrototypeObject { return set; } + override string toString() { + + var* ts = _peekMember("toString", true); + if(ts) { + var _this; + _this._object = this; + return (*ts).call(_this).get!string; + } + + JSONValue val; + val.type = JSON_TYPE.OBJECT; + foreach(k, v; this._properties) + val.object[k] = v.toJsonValue(); + + return toJSON(&val); + } + var[string] _properties; PrototypeObject copy() { @@ -1193,11 +1317,9 @@ class PrototypeObject { return this; } - - // FIXME: maybe throw something else - /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { + var* _peekMember(string name, bool recurse) { if(name == "prototype") - return _prototype; + return &_prototype; auto curr = this; @@ -1219,7 +1341,7 @@ class PrototypeObject { else curr = curr.prototype; } else - return *prop; + return prop; } while(curr); if(possibleSecondary !is null) { @@ -1230,6 +1352,23 @@ class PrototypeObject { } } + return null; + } + + // FIXME: maybe throw something else + /*package*/ ref var _getMember(string name, bool recurse, bool throwOnFailure, string file = __FILE__, size_t line = __LINE__) { + var* mem = _peekMember(name, recurse); + + if(mem !is null) + return *mem; + + mem = _peekMember("opIndex", recurse); + if(mem !is null) { + auto n = new var; + *n = ((*mem)(name)); + return *n; + } + // if we're here, the property was not found, so let's implicitly create it if(throwOnFailure) throw new Exception("no such property " ~ name, file, line); @@ -1237,6 +1376,32 @@ class PrototypeObject { this._properties[name] = n; return this._properties[name]; } + + // FIXME: maybe throw something else + /*package*/ ref var _setMember(string name, var t, bool recurse, bool throwOnFailure, bool suppressOverloading, string file = __FILE__, size_t line = __LINE__) { + var* mem = _peekMember(name, recurse); + + if(mem !is null) { + *mem = t; + return *mem; + } + + if(!suppressOverloading) { + mem = _peekMember("opIndexAssign", true); + if(mem !is null) { + auto n = new var; + *n = ((*mem)(t, name)); + return *n; + } + } + + // if we're here, the property was not found, so let's implicitly create it + if(throwOnFailure) + throw new Exception("no such property " ~ name, file, line); + this._properties[name] = t; + return this._properties[name]; + } + } diff --git a/minigui.d b/minigui.d index 8ef8c53..26c31ea 100644 --- a/minigui.d +++ b/minigui.d @@ -2,8 +2,13 @@ module arsd.minigui; // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS +// FIXME: menus should prolly capture the mouse. ugh i kno. + import simpledisplay; +// this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default. +private bool lastDefaultPrevented; + version(Windows) { // use native widgets when available unless specifically asked otherwise version(custom_widgets) {} @@ -14,6 +19,20 @@ version(Windows) { version = win32_theming; } +/* + TextEdit needs: + + * carat manipulation + * selection control + * convenience functions for appendText, insertText, insertTextAtCarat, etc. + + For example: + + connect(paste, &textEdit.insertTextAtCarat); + + would be nice. +*/ + enum windowBackgroundColor = Color(190, 190, 190); private const(char)* toStringzInternal(string s) { return (s ~ '\0').ptr; } @@ -53,11 +72,12 @@ class Action { sortable table view maybe notification area icons + basic clipboard * radio box toggle buttons (optionally mutually exclusive, like in Paint) label, rich text display, multi line plain text (selectable) - fieldset + * fieldset * nestable grid layout single line text input * multi line text input @@ -67,7 +87,7 @@ class Action { drop down combo box auto complete box - progress bar + * progress bar terminal window/widget (on unix it might even be a pty but really idk) @@ -229,10 +249,17 @@ version(win32_widgets) { extern(Windows) int HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { if(auto te = hWnd in Widget.nativeMapping) { + if(iMessage == WM_SETFOCUS) + (*te).parentWindow.focusedWidget = *te; + auto pos = getChildPositionRelativeToParentOrigin(*te); - if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win)) - {} - return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam); + lastDefaultPrevented = false; + if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented) + return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam); + else { + // it was something we recognized, should only call the window procedure if the default was not prevented + } + return 0; } assert(0, "shouldn't be receiving messages for this window...."); //import std.conv; @@ -370,7 +397,7 @@ class Widget { child.newWindow(parent); } - void addChild(Widget w, int position = int.max) { + protected void addChild(Widget w, int position = int.max) { w.parent = this; if(position == int.max || position == children.length) children ~= w; @@ -596,9 +623,9 @@ class MainWindow : Window { defaultEventHandlers["mouseover"] = delegate void(Widget _this, Event event) { if(this.statusBar !is null && event.target.statusTip.length) - this.statusBar.content = event.target.statusTip; + this.statusBar.parts[0].content = event.target.statusTip; else if(this.statusBar !is null && _this.statusTip.length) - this.statusBar.content = _this.statusTip ~ " " ~ event.target.toString(); + this.statusBar.parts[0].content = _this.statusTip ~ " " ~ event.target.toString(); }; version(win32_widgets) @@ -645,7 +672,7 @@ class MainWindow : Window { super.addChild(_clientArea); - statusBar = new StatusBar("", this); + statusBar = new StatusBar(this); } override void addChild(Widget c, int position = int.max) { @@ -823,56 +850,105 @@ class MenuBar : Widget { /** Status bars appear at the bottom of a MainWindow. - - auto window = new MainWindow(100, 100); - window.statusBar = new StatusBar("Test", window); - - // two parts, spaced automatically - or new StatusBar(["test", "23"], window); - - // three parts, evenly spaced, no identifier - or new StatusBar(3, window); - - // part spacing - or new StatusBar([32, 0], window) - or new StatusBar([StatusBarPart(32, "foo"), StatusBarPart("test")]); - + They are made out of Parts, with a width and content. They can have multiple parts or be in simple mode. FIXME: implement + + + sb.parts[0].content = "Status bar text!"; */ class StatusBar : Widget { - private string _content; - @property string content() { return _content; } - @property void content(string s) { - version(win32_widgets) { - WPARAM wParam; - auto idx = 0; // see also SB_SIMPLEID - wParam = idx; - SendMessageA(hwnd, SB_SETTEXT, wParam, cast(LPARAM) toStringzInternal(s)); + private Part[] partsArray; + struct Parts { + @disable this(); + this(StatusBar owner) { this.owner = owner; } + //@disable this(this); + @property int length() { return owner.partsArray.length; } + private StatusBar owner; + private this(StatusBar owner, Part[] parts) { + this.owner.partsArray = parts; + this.owner = owner; + } + Part opIndex(int p) { + if(owner.partsArray.length == 0) + this ~= new StatusBar.Part(300); + return owner.partsArray[p]; + } - SendMessageA(hwnd, WM_USER + 4 /*SB_SETPARTS*/, 5, cast(int) [32, 100, 200, 400, -1].ptr); - } else { - _content = s; - redraw(); + Part opOpAssign(string op : "~" )(Part p) { + assert(owner.partsArray.length < 255); + p.owner = this.owner; + p.idx = owner.partsArray.length; + owner.partsArray ~= p; + version(win32_widgets) { + int[256] pos; + int cpos = 0; + foreach(idx, part; owner.partsArray) { + if(part.width) + cpos += part.width; + else + cpos += 100; + + if(idx + 1 == owner.partsArray.length) + pos[idx] = -1; + else + pos[idx] = cpos; + } + SendMessageA(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(int) pos.ptr); + } else { + owner.redraw(); + } + + return p; } } - version(win32_widgets) - this(string c, Widget parent = null) { - super(null); // FIXME - parentWindow = parent.parentWindow; - createWin32Window(this, "msctls_statusbar32", "D rox", 0); + private Parts _parts; + @property Parts parts() { + return _parts; } - else - this(string c, Widget parent = null) { - super(null); // is this right? - _content = c; - this.paint = (ScreenPainter painter) { - painter.outlineColor = Color.black; - painter.fillColor = windowBackgroundColor; - painter.drawRectangle(Point(0, 0), width, height); - painter.drawText(Point(4, 0), content, Point(width, height)); - }; + + static class Part { + int width; + StatusBar owner; + + this(int w = 100) { width = w; } + + private int idx; + private string _content; + @property string content() { return _content; } + @property void content(string s) { + version(win32_widgets) { + _content = s; + SendMessageA(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) toStringzInternal(s)); + } else { + _content = s; + owner.redraw(); + } + } + } + string simpleModeContent; + bool inSimpleMode; + + + this(Widget parent = null) { + super(null); // FIXME + _parts = Parts(this); + version(win32_widgets) { + parentWindow = parent.parentWindow; + createWin32Window(this, "msctls_statusbar32", "D rox", 0); + } else { + this.paint = (ScreenPainter painter) { + painter.outlineColor = Color.black; + painter.fillColor = windowBackgroundColor; + painter.drawRectangle(Point(0, 0), width, height); + int cpos = 4; + foreach(part; this.partsArray) { + painter.drawText(Point(cpos, 0), part.content, Point(width, height)); + cpos += part.width ? part.width : 100; + } + }; + } } override int maxHeight() { return Window.lineHeight; } @@ -1526,6 +1602,7 @@ class Event { /// Prevents the default event handler (if there is one) from being called void preventDefault() { + lastDefaultPrevented = true; defaultPrevented = true; } @@ -1609,13 +1686,15 @@ class Event { if(handler !is null) handler(e, this); - if(!defaultPrevented) - if(eventName in e.defaultEventHandlers) - e.defaultEventHandlers[eventName](e, this); - if(propagationStopped) break; } + + if(!defaultPrevented) + foreach(e; chain) { + if(eventName in e.defaultEventHandlers) + e.defaultEventHandlers[eventName](e, this); + } } } diff --git a/png.d b/png.d index 4e5cf59..c409105 100644 --- a/png.d +++ b/png.d @@ -16,14 +16,11 @@ void main(string[] args) { */ // By Adam D. Ruppe, 2009-2010, released into the public domain -import std.stdio; -import std.conv; -import std.file; +//import std.file; import std.zlib; -import std.array; -public import arsd.image; +public import arsd.color; /** The return value should be casted to indexed or truecolor depending on what the file is. You can @@ -33,7 +30,7 @@ public import arsd.image; auto i = cast(TrueColorImage) imageFromPng(readPng(cast(ubyte)[]) std.file.read("file.png"))); */ -Image imageFromPng(PNG* png) { +MemoryImage imageFromPng(PNG* png) { PngHeader h = getHeader(png); /** Types from the PNG spec: @@ -51,7 +48,7 @@ Image imageFromPng(PNG* png) { If type&4, it has an alpha channel in the datastream. */ - Image i; + MemoryImage i; ubyte[] idata; // FIXME: some duplication with the lazy reader below in the module @@ -693,7 +690,6 @@ PngHeader getHeader(PNG* p) { return h; } -public import arsd.color; /* struct Color { ubyte r; @@ -836,15 +832,15 @@ uint crc(in string lol, in ubyte[] buf){ //module arsd.lazypng; -import arsd.color; +//import arsd.color; -import std.stdio; +//import std.stdio; import std.range; import std.traits; import std.exception; import std.string; -import std.conv; +//import std.conv; /* struct Color { @@ -1165,7 +1161,7 @@ struct LazyPngFile(LazyPngChunksProvider) return isEmpty; } - int length() { + @property int length() { return header.height; } @@ -1561,7 +1557,7 @@ struct PngHeader { } } -void writePngLazy(OutputRange, InputRange)(OutputRange where, InputRange image) +void writePngLazy(OutputRange, InputRange)(ref OutputRange where, InputRange image) if( isOutputRange!(OutputRange, ubyte[]) && isInputRange!(InputRange) && @@ -1643,6 +1639,7 @@ immutable(ubyte)[] unfilter(ubyte filterType, in ubyte[] data, in ubyte[] previo return assumeUnique(arr); case 2: auto arr = data.dup; + if(previousLine.length) foreach(i; 0 .. arr.length) { arr[i] += previousLine[i]; } diff --git a/script.d b/script.d index 680776b..0f5facf 100644 --- a/script.d +++ b/script.d @@ -40,6 +40,12 @@ This forwards directly to the D function var.opCast. * some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D. + opIndex(name) + opIndexAssign(value, name) // same order as D, might some day support [n1, n2] => (value, n1, n2) + + obj.__prop("name", value); // bypasses operator overloading, useful for use inside the opIndexAssign especially + + Note: if opIndex is not overloaded, getting a non-existent member will actually add it to the member. This might be a bug but is needed right now in the D impl for nice chaining. Or is it? FIXME * if/else * array slicing, but note that slices are rvalues currently * variables must start with A-Z, a-z, _, or $, then must be [A-Za-z0-9_]*. @@ -101,6 +107,9 @@ FIXME: * make sure superclass ctors are called + Might be nice: + varargs + lambdas */ module arsd.script; @@ -205,6 +214,7 @@ class TokenStream(TextStream) { ScriptToken next; + // FIXME: might be worth changing this so i can peek far enough ahead to do () => expr lambdas. ScriptToken peek; bool peeked; void pushFront(ScriptToken f) { @@ -314,7 +324,7 @@ class TokenStream(TextStream) { int pos = 1; // skip the opening " bool escaped = false; // FIXME: escaping doesn't do the right thing lol. we should slice if we can, copy if not - while(pos < text.length && !escaped && text[pos] != '"') { + while(pos < text.length && (escaped || text[pos] != '"')) { if(escaped) escaped = false; else @@ -419,7 +429,42 @@ class StringLiteralExpression : Expression { } this(string s) { - literal = s; + char[] unescaped; + int lastPos; + bool changed = false; + bool inEscape = false; + foreach(pos, char c; s) { + if(c == '\\') { + if(!changed) { + changed = true; + unescaped.reserve(s.length); + } + unescaped ~= s[lastPos .. pos]; + inEscape = true; + continue; + } + if(inEscape) { + lastPos = pos + 1; + inEscape = false; + switch(c) { + case 'n': + unescaped ~= '\n'; + break; + case 't': + unescaped ~= '\t'; + break; + case '\\': + unescaped ~= '\\'; + break; + default: throw new ScriptCompileException("literal escape unknown " ~ c, 0, null, 0); + } + } + } + + if(changed) + literal = cast(string) unescaped; + else + literal = s; } override InterpretResult interpret(PrototypeObject sc) { @@ -734,10 +779,12 @@ class OpAssignExpression : Expression { class AssignExpression : Expression { Expression e1; Expression e2; + bool suppressOverloading; - this(Expression e1, Expression e2) { + this(Expression e1, Expression e2, bool suppressOverloading = false) { this.e1 = e1; this.e2 = e2; + this.suppressOverloading = suppressOverloading; } override string toString() { return e1.toString() ~ " = " ~ e2.toString(); } @@ -747,7 +794,7 @@ class AssignExpression : Expression { if(v is null) throw new ScriptRuntimeException("not an lvalue", 0 /* FIXME */); - auto ret = v.getVar(sc, false) = e2.interpret(sc).value; + auto ret = v.setVar(sc, e2.interpret(sc).value, false, suppressOverloading); return InterpretResult(ret, sc); } @@ -779,6 +826,10 @@ class VariableExpression : Expression { return sc._getMember(identifier, true /* FIXME: recurse?? */, true); } + ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { + return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading); + } + ref var getVarFrom(PrototypeObject sc, ref var v) { return v[identifier]; } @@ -829,6 +880,14 @@ class DotVarExpression : VariableExpression { } } + override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { + if(suppressOverloading) + return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier); + else + return e1.interpret(sc).value.opIndexAssign(t, e2.identifier); + } + + override ref var getVarFrom(PrototypeObject sc, ref var v) { return e2.getVarFrom(sc, v); } @@ -1264,7 +1323,7 @@ class CallExpression : Expression { } else if(auto ide = cast(IndexExpression) func) _this = ide.interpret(sc).value; - return InterpretResult(f.apply(var(_this), args), sc); + return InterpretResult(f.apply(_this, args), sc); } } @@ -1708,7 +1767,8 @@ Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool new VariableExpression(o), new VariableExpression(ident), false), - decl.initializers[i] + decl.initializers[i], + true // no overloading because otherwise an early opIndexAssign can mess up the decls ); } } @@ -2196,6 +2256,12 @@ var interpret(string code, var variables = null, string scriptFilename = null) { (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); } +var interpretFile(File file, var globals) { + import std.algorithm; + return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), + (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); +} + void repl(var globals) { import std.stdio; import std.algorithm; diff --git a/simpledisplay.d b/simpledisplay.d index 4f04cf2..a27892a 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -183,9 +183,9 @@ version(X11) { F10 = 0xffc7, F11 = 0xffc8, F12 = 0xffc9, - PrintScreen = -1, // FIXME - ScrollLock = -2, // FIXME - Pause = -3, // FIXME + PrintScreen = 0xff61, + ScrollLock = 0xff14, + Pause = 0xff13, Grave = 0x60, // number keys across the top of the keyboard N1 = 0x31, @@ -252,11 +252,11 @@ version(X11) { Slash = 0x2f, Shift_r = 0xffe2, // Note: this isn't sent on all computers, sometimes it just sends Shift, so don't rely on it Ctrl = 0xffe3, - Windows = -4, // FIXME + Windows = 0xffeb, Alt = 0xffe9, Space = 0x20, Alt_r = 0xffea, // ditto of shift_r - Windows_r = -5, // FIXME + Windows_r = 0xffec, Menu = 0xff67, Ctrl_r = 0xffe4, @@ -265,7 +265,7 @@ version(X11) { Multiply = 0xffaa, Minus = 0xffad, Plus = 0xffab, - PadEnter = -6, // FIXME + PadEnter = 0xff8d, Pad1 = 0xff9c, Pad2 = 0xff99, Pad3 = 0xff9b, @@ -559,6 +559,9 @@ struct MouseEvent { int x; int y; + int dx; + int dy; + int button; int buttonFlags; @@ -641,6 +644,19 @@ final class Image { putPixel(x, y, c); } + TrueColorImage toTrueColorImage() { + auto tci = new TrueColorImage(width, height); + convertToRgbaBytes(tci.imageData.bytes); + return tci; + } + + static Image fromMemoryImage(MemoryImage i) { + auto tci = i.getAsTrueColorImage(); + auto img = new Image(tci.width, tci.height); + img.setRgbaBytes(tci.imageData.bytes); + return img; + } + /// this is here for interop with arsd.image. where can be a TrueColorImage's data member /// if you pass in a buffer, it will put it right there. length must be width*height*4 already /// if you pass null, it will allocate a new one. @@ -1118,6 +1134,23 @@ class SimpleWindow { void delegate() handlePulse; + private { + int lastMouseX = int.min; + int lastMouseY = int.min; + void mdx(ref MouseEvent ev) { + if(lastMouseX == int.min || lastMouseY == int.min) { + ev.dx = 0; + ev.dy = 0; + } else { + ev.dx = ev.x - lastMouseX; + ev.dy = ev.y - lastMouseY; + } + + lastMouseX = ev.x; + lastMouseY = ev.y; + } + } + void delegate(MouseEvent) handleMouseEvent; void delegate() paintingFinished; // use to redraw child widgets if you use system apis to add stuff @@ -1437,9 +1470,8 @@ version(Windows) { version(without_opengl) {} else { - ghDC = hdc; - if(opengl == OpenGlOptions.yes) { + ghDC = hdc; PIXELFORMATDESCRIPTOR pfd; pfd.nSize = PIXELFORMATDESCRIPTOR.sizeof; @@ -1459,9 +1491,9 @@ version(Windows) { if (SetPixelFormat(hdc, pixelformat, &pfd) == 0) throw new Exception("SetPixelFormat"); + + ghRC = wglCreateContext(ghDC); } - - ghRC = wglCreateContext(ghDC); } if(opengl == OpenGlOptions.no) { @@ -1509,6 +1541,7 @@ version(Windows) { void mouseEvent() { mouse.x = LOWORD(lParam) + offsetX; mouse.y = HIWORD(lParam) + offsetY; + wind.mdx(mouse); mouse.buttonFlags = wParam; mouse.window = wind; @@ -1529,6 +1562,7 @@ version(Windows) { ev.key = cast(Key) wParam; ev.pressed = msg == WM_KEYDOWN; // FIXME + // ev.hardwareCode // ev.modifierState = ev.window = wind; if(wind.handleKeyEvent) @@ -1628,7 +1662,8 @@ version(Windows) { EndPaint(hwnd, &ps); } else { EndPaint(hwnd, &ps); - redrawOpenGlSceneNow(); + version(with_opengl) + redrawOpenGlSceneNow(); } } break; default: @@ -2201,9 +2236,13 @@ version(X11) { Atom atom = XInternAtom(display, "WM_DELETE_WINDOW".ptr, true); // FIXME: does this need to be freed? XSetWMProtocols(display, window, &atom, 1); + // What would be ideal here is if they only were + // selected if there was actually an event handler + // for them... XSelectInput(display, window, EventMask.ExposureMask | EventMask.KeyPressMask | + EventMask.KeyReleaseMask | EventMask.StructureNotifyMask | EventMask.PointerMotionMask // FIXME: not efficient | EventMask.ButtonPressMask @@ -2211,7 +2250,6 @@ version(X11) { ); XMapWindow(display, window); - XFlush(display); } void createOpenGlContext() { @@ -2317,6 +2355,7 @@ version(X11) { mouse.buttonFlags = event.state; if(auto win = e.xmotion.window in SimpleWindow.nativeMapping) { + (*win).mdx(mouse); if((*win).handleMouseEvent) (*win).handleMouseEvent(mouse); mouse.window = *win; @@ -2349,6 +2388,7 @@ version(X11) { //mouse.buttonFlags = event.detail; if(auto win = e.xbutton.window in SimpleWindow.nativeMapping) { + (*win).mdx(mouse); if((*win).handleMouseEvent) (*win).handleMouseEvent(mouse); mouse.window = *win; @@ -2361,6 +2401,7 @@ version(X11) { case EventType.KeyRelease: KeyEvent ke; ke.pressed = e.type == EventType.KeyPress; + ke.hardwareCode = e.xkey.keycode; auto sym = XKeycodeToKeysym( XDisplayConnection.get(), @@ -2381,7 +2422,7 @@ version(X11) { ke.character = cast(dchar) buffer[0]; } - else switch(sym) { + switch(sym) { case 0xff09: ke.character = '\t'; break; case 0xff8d: // keypad enter case 0xff0d: ke.character = '\n'; break; diff --git a/terminal.d b/terminal.d index 59ba189..bd0f8a6 100644 --- a/terminal.d +++ b/terminal.d @@ -9,6 +9,11 @@ */ module terminal; +// FIXME: SIGWINCH + +version(linux) + enum SIGWINCH = 28; // FIXME: confirm this is correct on other posix + // parts of this were taken from Robik's ConsoleD // https://github.com/robik/ConsoleD/blob/master/consoled.d @@ -214,6 +219,7 @@ enum ConsoleInputFlags { echo = 1, /// do you want to automatically echo input back to the user? mouse = 2, /// capture mouse events paste = 4, /// capture paste events (note: without this, paste can come through as keystrokes) + size = 8, /// window resize events } /// Defines how terminal output should be handled. @@ -897,6 +903,7 @@ struct RealTimeConsoleInput { DWORD mode = 0; mode |= ENABLE_PROCESSED_INPUT /* 0x01 */; // this gives Ctrl+C which we probably want to be similar to linux + //if(flags & ConsoleInputFlags.size) mode |= ENABLE_WINDOW_INPUT /* 0208 */; // gives size etc if(flags & ConsoleInputFlags.echo) mode |= ENABLE_ECHO_INPUT; // 0x4 @@ -934,6 +941,22 @@ struct RealTimeConsoleInput { // some weird bug breaks this, https://github.com/robik/ConsoleD/issues/3 //destructor ~= { tcsetattr(fd, TCSANOW, &old); }; + /+ + if(flags & ConsoleInputFlags.size) { + // FIXME: finish this + import core.sys.posix.signal; + sigaction n; + n.sa_handler = &handler; + n.sa_mask = 0; + n.sa_flags = 0; + sigaction o; + sigaction(SIGWINCH, &n, &o); + + // restoration + sigaction(SIGWINCH, &o, null); + } + +/ + if(flags & ConsoleInputFlags.mouse) { if(terminal.terminalInFamily("xterm", "rxvt", "screen", "linux")) { terminal.writeStringRaw("\033[?1000h"); // this is vt200 mouse, supported by xterm and linux + gpm