/** My old toy html widget build out of my libraries. Not great, you probably don't want to use it. This module has a lot of dependencies dmd yourapp.d arsd/htmlwidget.d arsd/simpledisplay.d arsd/curl.d arsd/color.d arsd/dom.d arsd/characterencodings.d arsd/imagedraft.d -J. -version=browser -version=browser is important so dom.d has the extensibility hook this module uses. The idea here is to be a quick html window, displayed using the simpledisplay.d module. Nothing fancy, the html+css support is spotty and it has some text layout bugs... but it can work for a simple thing. It has no javascript support, but you can (and must, for even links to work) add event listeners in your D code. */ module arsd.htmlwidget; public import arsd.simpledisplay; import arsd.image; public import arsd.dom; import std.range; import std.conv; import std.stdio; import std.string; import std.algorithm : max, min; alias void delegate(Element, Event) EventHandler; struct CssSize { string definition; int getPixels(int oneEm, int oneHundredPercent) // out (ret) { assert(ret >= 0, to!string(ret) ~ " " ~ definition); } body { if(definition.length == 0 || definition == "none") return 0; if(definition == "auto") return 0; if(isNumeric(definition)) return to!int(definition); if(definition[$-1] == '%') { if(oneHundredPercent < 0) return 0; return cast(int) (to!real(definition[0 .. $-1]) * oneHundredPercent); } if(definition[$-2 .. $] == "px") return to!int(definition[0 .. $ - 2]); if(definition[$-2 .. $] == "em") return cast(int) (to!real(definition[0 .. $-2]) * oneEm); // FIXME: other units of measure... return 0; } } Color readColor(string v) { v = v.toLower; switch(v) { case "transparent": return Color(0, 0, 0, 0); case "red": return Color(255, 0, 0); case "green": return Color(0, 255, 0); case "blue": return Color(0, 0, 255); case "yellow": return Color(255, 255, 0); case "white": return Color(255, 255, 255); case "black": return Color(0, 0, 0); default: if(v[0] == '#') { return Color.fromString(v); } else { goto case "transparent"; } } } enum TableDisplay : int { table = 1, row = 2, cell = 3, caption = 4 } class LayoutData { Element element; this(Element parent) { element = parent; element.expansionHook = cast(void*) this; parseStyle; } void parseStyle() { // reset to defaults renderInline = true; outsideNormalFlow = false; renderValueAsText = false; doNotDraw = false; if(element.nodeType != 1) { return; // only tags get style } // legitimate attributes FIXME: do these belong here? if(element.hasAttribute("colspan")) tableColspan = to!int(element.attrs.colspan); else tableColspan = 1; if(element.hasAttribute("rowspan")) tableRowspan = to!int(element.attrs.rowspan); else tableRowspan = 1; if(element.tagName == "img") { try { auto bytes = cast(ubyte[]) curl(absolutizeUrl(element.src, _contextHack.currentUrl)); auto i = loadImageFromMemory(bytes); image = Image.fromMemoryImage(i); width = CssSize(to!string(image.width) ~ "px"); height = CssSize(to!string(image.height) ~ "px"); } catch (Throwable t) { writeln(t.toString); image = null; } } CssSize readSize(string v) { return CssSize(v); /* if(v.indexOf("px") == -1) return 0; return to!int(v[0 .. $-2]); */ } auto style = element.computedStyle; //if(element.tagName == "a") //assert(0, style.toString); foreach(item; style.properties) { string value = item.value; Element curr = element; while(value == "inherit" && curr.parentNode !is null) { curr = curr.parentNode; value = curr.computedStyle.getValue(item.name); } if(value == "inherit") assert(0, item.name ~ " came in as inherit all the way up the chain"); switch(item.name) { case "attribute-as-text": renderValueAsText = true; break; case "margin-bottom": marginBottom = readSize(value); break; case "margin-top": marginTop = readSize(value); break; case "margin-left": marginLeft = readSize(value); break; case "margin-right": marginRight = readSize(value); break; case "padding-bottom": paddingBottom = readSize(value); break; case "padding-top": paddingTop = readSize(value); break; case "padding-left": paddingLeft = readSize(value); break; case "padding-right": paddingRight = readSize(value); break; case "visibility": if(value == "hidden") doNotDraw = true; else doNotDraw = false; break; case "width": if(value == "auto") width = CssSize(); else width = readSize(value); break; case "height": if(value == "auto") height = CssSize(); else height = readSize(value); break; case "display": tableDisplay = 0; switch(value) { case "block": renderInline = false; break; case "inline": renderInline = true; break; case "none": doNotRender = true; break; case "list-item": renderInline = false; // FIXME - show the list marker too break; case "inline-block": renderInline = false; // FIXME break; case "table": renderInline = false; goto case; case "inline-table": tableDisplay = TableDisplay.table; break; case "table-row": tableDisplay = TableDisplay.row; break; case "table-cell": tableDisplay = TableDisplay.cell; break; case "table-caption": tableDisplay = TableDisplay.caption; break; case "run-in": /* do these even matter? */ case "table-header-group": case "table-footer-group": case "table-row-group": case "table-column": case "table-column-group": default: // FIXME } if(value == "table-row") renderInline = false; break; case "position": position = value; if(position == "absolute" || position == "fixed") outsideNormalFlow = true; break; case "top": top = CssSize(value); break; case "bottom": bottom = CssSize(value); break; case "right": right = CssSize(value); break; case "left": left = CssSize(value); break; case "color": foregroundColor = readColor(value); break; case "background-color": backgroundColor = readColor(value); break; case "float": switch(value) { case "none": cssFloat = 0; outsideNormalFlow = false; break; case "left": cssFloat = 1; outsideNormalFlow = true; break; case "right": cssFloat = 2; outsideNormalFlow = true; break; default: assert(0); } break; case "clear": switch(value) { case "none": floatClear = 0; break; case "left": floatClear = 1; break; case "right": floatClear = 2; break; case "both": floatClear = 1; break; // FIXME default: assert(0); } break; case "border": borderWidth = CssSize("1px"); break; default: } } // FIXME if(tableDisplay == TableDisplay.row) { renderInline = false; } else if(tableDisplay == TableDisplay.cell) renderInline = true; } static LayoutData get(Element e) { if(e.expansionHook is null) return new LayoutData(e); return cast(LayoutData) e.expansionHook; } EventHandler[][string] bubblingEventHandlers; EventHandler[][string] capturingEventHandlers; EventHandler[string] defaultEventHandlers; int absoluteLeft() { int a = offsetLeft; // FIXME: dead wrong /* auto p = offsetParent; while(p) { auto l = LayoutData.get(p); a += l.offsetLeft; p = l.offsetParent; }*/ return a; } int absoluteTop() { int a = offsetTop; /* auto p = offsetParent; while(p) { auto l = LayoutData.get(p); a += l.offsetTop; p = l.offsetParent; }*/ return a; } int offsetWidth; int offsetHeight; int offsetLeft; int offsetTop; Element offsetParent; CssSize borderWidth; CssSize paddingLeft; CssSize paddingRight; CssSize paddingTop; CssSize paddingBottom; CssSize marginLeft; CssSize marginRight; CssSize marginTop; CssSize marginBottom; CssSize width; CssSize height; string position; CssSize left; CssSize top; CssSize right; CssSize bottom; Color borderColor; Color backgroundColor; Color foregroundColor; int zIndex; /* pseudo classes */ bool hover; bool active; bool focus; bool link; bool visited; bool selected; bool checked; /* done */ /* CSS styles */ bool doNotRender; bool doNotDraw; bool renderInline; bool renderValueAsText; int tableDisplay; // 1= table, 2 = table-row, 3 = table-cell, 4 = table-caption int tableColspan; int tableRowspan; int cssFloat; int floatClear; string textToRender; bool outsideNormalFlow; /* Efficiency flags */ static bool someRepaintRequired; bool repaintRequired; void invalidate() { repaintRequired = true; someRepaintRequired = true; } void paintCompleted() { repaintRequired = false; someRepaintRequired = false; // FIXME } Image image; } Element elementFromPoint(Document document, int x, int y) { int winningZIndex = int.min; Element winner; foreach(element; document.mainBody.tree) { if(element.nodeType == 3) // do I want this? continue; auto e = LayoutData.get(element); if(e.doNotRender) continue; if( e.zIndex >= winningZIndex && x >= e.absoluteLeft() && x < e.absoluteLeft() + e.offsetWidth && y >= e.absoluteTop() && y < e.absoluteTop() + e.offsetHeight ) { winner = e.element; winningZIndex = e.zIndex; } } return winner; } int longestLine(string a) { int longest = 0; foreach(l; a.split("\n")) if(l.length > longest) longest = cast(int) l.length; return longest; } int getTableCells(Element row) { int count; foreach(c; row.childNodes) { auto l = LayoutData.get(c); if(l.tableDisplay == TableDisplay.cell) count += l.tableColspan; } return count; } // returns: dom structure changed bool layout(Element element, int containerWidth, int containerHeight, int cx, int cy, bool canWrap, int parentContainerWidth = 0) { auto oneEm = 16; if(element.tagName == "head") return false; auto l = LayoutData.get(element); if(l.doNotRender) return false; if(element.nodeType == 3 && element.nodeValue.strip.length == 0) { l.doNotRender = true; return false; } if(!l.renderInline) { cx += l.marginLeft.getPixels(oneEm, containerWidth); // FIXME: does this belong here? //cy += l.marginTop.getPixels(oneEm, containerHeight); containerWidth -= l.marginLeft.getPixels(oneEm, containerWidth) + l.marginRight.getPixels(oneEm, containerWidth); //containerHeight -= l.marginTop.getPixels(oneEm, containerHeight) + l.marginBottom.getPixels(oneEm, containerHeight); } l.offsetLeft = cx; l.offsetTop = cy; //if(!l.renderInline) { cx += l.paddingLeft.getPixels(oneEm, containerWidth); cy += l.paddingTop.getPixels(oneEm, containerHeight); containerWidth -= l.paddingLeft.getPixels(oneEm, containerWidth) + l.paddingRight.getPixels(oneEm, containerWidth); containerHeight -= l.paddingTop.getPixels(oneEm, containerHeight) + l.paddingBottom.getPixels(oneEm, containerHeight); //} auto initialX = cx; auto initialY = cy; auto availableWidth = containerWidth; auto availableHeight = containerHeight; int fx; // current position for floats int fy; int boundingWidth; int boundingHeight; int biggestWidth; int biggestHeight; int lastMarginBottom; int lastMarginApplied; bool hasContentLeft; int cssWidth = l.width.getPixels(oneEm, containerWidth); int cssHeight = l.height.getPixels(oneEm, containerHeight); bool widthSet = false; if(l.tableDisplay == TableDisplay.cell && !widthSet) { l.offsetWidth = l.tableColspan * parentContainerWidth / getTableCells(l.element.parentNode); widthSet = true; containerWidth = l.offsetWidth; availableWidth = containerWidth; } int skip; startAgain: // now, we layout the children to collect all that info together foreach(i, child; element.childNodes) { if(skip) { skip--; continue; } auto childLayout = LayoutData.get(child); if(!childLayout.outsideNormalFlow && !childLayout.renderInline && hasContentLeft) { cx = initialX; cy += biggestHeight; availableWidth = containerWidth; availableHeight -= biggestHeight; hasContentLeft = false; biggestHeight = 0; } if(childLayout.floatClear) { cx = initialX; if(max(fy, cy) != cy) availableHeight -= fy - cy; cy = max(fy, cy); hasContentLeft = false; biggestHeight = 0; } auto currentMargin = childLayout.marginTop.getPixels(oneEm, containerHeight); currentMargin = max(currentMargin, lastMarginBottom) - lastMarginBottom; if(currentMargin < 0) currentMargin = 0; if(!lastMarginApplied && max(currentMargin, lastMarginBottom) > 0) currentMargin = max(currentMargin, lastMarginBottom); lastMarginApplied = currentMargin; cy += currentMargin; containerHeight -= currentMargin; bool changed = layout(child, availableWidth, availableHeight, cx, cy, !l.renderInline, containerWidth); if(childLayout.cssFloat) { childLayout.offsetTop += fy; foreach(bele; child.tree) { auto lolol = LayoutData.get(bele); lolol.offsetTop += fy; } fx += childLayout.offsetWidth; fy += childLayout.offsetHeight; } if(childLayout.doNotRender || childLayout.outsideNormalFlow) continue; //if(childLayout.offsetHeight < 0) //childLayout.offsetHeight = 0; //if(childLayout.offsetWidth < 0) //childLayout.offsetWidth = 0; assert(childLayout.offsetHeight >= 0); assert(childLayout.offsetWidth >= 0); // inline elements can't have blocks inside //if(!childLayout.renderInline) //l.renderInline = false; lastMarginBottom = childLayout.marginBottom.getPixels(oneEm, containerHeight); if(childLayout.offsetWidth > biggestWidth) biggestWidth = childLayout.offsetWidth; if(childLayout.offsetHeight > biggestHeight) biggestHeight = childLayout.offsetHeight; availableWidth -= childLayout.offsetWidth; if(cx + childLayout.offsetWidth > boundingWidth) boundingWidth = cx + childLayout.offsetWidth; // if the dom was changed, it was to wrap... if(changed || availableWidth <= 0) { // gotta move to a new line availableWidth = containerWidth; cx = initialX; cy += biggestHeight; biggestHeight = 0; availableHeight -= childLayout.offsetHeight; hasContentLeft = false; //writeln("new line now at ", cy); } else { // can still use this one cx += childLayout.offsetWidth; hasContentLeft = true; } if(changed) { skip = cast(int) i; writeln("dom changed"); goto startAgain; } } if(hasContentLeft) cy += biggestHeight; // line-height boundingHeight = cy - initialY + l.paddingTop.getPixels(oneEm, containerHeight) + l.paddingBottom.getPixels(oneEm, containerHeight); // And finally, layout this element itself if(element.nodeType == 3) { bool wrapIt; if(element.computedStyle.getValue("white-space") == "pre") { l.textToRender = element.nodeValue; } else { l.textToRender = replace(element.nodeValue,"\n", " ").replace("\t", " ").replace("\r", " ");//.squeeze(" "); // FIXME wrapIt = true; } if(l.textToRender.length == 0) { l.doNotRender = true; return false; } if(wrapIt) { auto lineWidth = containerWidth / 6; bool startedWithSpace = l.textToRender[0] == ' '; if(l.textToRender.length > lineWidth) l.textToRender = wrap(l.textToRender, lineWidth); if(l.textToRender[$-1] == '\n') l.textToRender = l.textToRender[0 .. $-1]; if(startedWithSpace && l.textToRender[0] != ' ') l.textToRender = " " ~ l.textToRender; } bool contentChanged = false; // we can wrap so let's do it /* auto lineIdx = l.textToRender.indexOf("\n"); if(canWrap && lineIdx != -1) { writeln("changing ***", l.textToRender, "***"); auto remaining = l.textToRender[lineIdx + 1 .. $]; l.textToRender = l.textToRender[0 .. lineIdx]; Element[] txt; txt ~= new TextNode(element.parentDocument, l.textToRender); txt ~= new TextNode(element.parentDocument, "\n"); txt ~= new TextNode(element.parentDocument, remaining); element.parentNode.replaceChild(element, txt); contentChanged = true; } */ if(l.textToRender.length != 0) { l.offsetHeight = cast(int) count(l.textToRender, "\n") * 16 + 16; // lines * line-height l.offsetWidth = l.textToRender.longestLine * 6; // inline } else { l.offsetWidth = 0; l.offsetHeight = 0; } l.renderInline = true; //writefln("Text %s at (%s, %s) with size %sx%s", element.tagName, l.offsetLeft, l.offsetTop, l.offsetWidth, l.offsetHeight); return contentChanged; } // images get special treatment too if(l.image !is null) { if(!widthSet) l.offsetWidth = l.image.width; l.offsetHeight = l.image.height; //writefln("Image %s at (%s, %s) with size %sx%s", element.tagName, l.offsetLeft, l.offsetTop, l.offsetWidth, l.offsetHeight); return false; } /* // tables constrain floats... if(l.tableDisplay == TableDisplay.cell) { l.offsetHeight += fy; } */ // layout an inline element... if(l.renderInline) { //if(l.tableDisplay == TableDisplay.cell) { //auto ow = widthSet ? l.offsetWidth : 0; //l.offsetWidth = min(ow, boundingWidth - initialX); //if(l.offsetWidth < 0) //l.offsetWidth = 0; //} else if(!widthSet) { l.offsetWidth = boundingWidth - initialX; // FIXME: padding? if(l.offsetWidth < 0) l.offsetWidth = 0; } l.offsetHeight = max(boundingHeight, biggestHeight); //writefln("Inline element %s at (%s, %s) with size %sx%s", element.tagName, l.offsetLeft, l.offsetTop, l.offsetWidth, l.offsetHeight); // and layout a block element } else { l.offsetWidth = containerWidth; l.offsetHeight = boundingHeight; //writefln("Block element %s at (%s, %s) with size %sx%s", element.tagName, l.offsetLeft, l.offsetTop, l.offsetWidth, l.offsetHeight); } if(l.position == "absolute") { l.offsetTop = l.top.getPixels(oneEm, containerHeight); l.offsetLeft = l.left.getPixels(oneEm, containerWidth); // l.offsetRight = l.right.getPixels(oneEm, containerWidth); // l.offsetBottom = l.bottom.getPixels(oneEm, containerHeight); } else if(l.position == "relative") { l.offsetTop = l.top.getPixels(oneEm, containerHeight); l.offsetLeft = l.left.getPixels(oneEm, containerWidth); // l.offsetRight = l.right.getPixels(oneEm, containerWidth); // l.offsetBottom = l.bottom.getPixels(oneEm, containerHeight); } // table cells need special treatment if(!l.tableDisplay) { if(cssWidth) { l.offsetWidth = cssWidth; containerWidth = min(containerWidth, cssWidth); // not setting widthSet since this is just a hint } if(cssHeight) { l.offsetHeight = cssHeight; containerHeight = min(containerHeight, cssHeight); } } /* // table cell if(l.tableDisplay == 2) { l.offsetWidth = containerWidth; } */ // a table row, and all it's cell children, have the same height if(l.tableDisplay == TableDisplay.row) { int maxHeight = 0; foreach(e; element.childNodes) { auto el = LayoutData.get(e); if(el.tableDisplay == TableDisplay.cell) { if(el.offsetHeight > maxHeight) maxHeight = el.offsetHeight; } } foreach(e; element.childNodes) { auto el = LayoutData.get(e); if(el.tableDisplay == TableDisplay.cell) { el.offsetHeight = maxHeight; } } l.offsetHeight = maxHeight; } // every column in a table has equal width // assert(l.offsetHeight == 0 || l.offsetHeight > 10, format("%s on %s %s", l.offsetHeight, element.tagName, element.id ~ "." ~ element.className)); return false; } int scrollTop = 0; void drawElement(ScreenPainter p, Element ele, int startingX, int startingY) { auto oneEm = 1; // margin is handled in the layout phase, but border, padding, and obviously, content are handled here auto l = LayoutData.get(ele); if(l.doNotDraw) return; if(l.doNotRender) return; startingX = 0; // FIXME startingY = 0; // FIXME why does this fix things? int cx = l.offsetLeft + startingX, cy = l.offsetTop + startingY, cw = l.offsetWidth, ch = l.offsetHeight; if(l.image !is null) { p.drawImage(Point(cx, cy - scrollTop), l.image); } //if(cw <= 0 || ch <= 0) // return; if(l.borderWidth.getPixels(oneEm, 1) > 0) { p.fillColor = Color(0, 0, 0, 0); p.outlineColor = l.borderColor; // FIXME: handle actual widths by selecting a pen p.drawRectangle(Point(cx, cy - scrollTop), cw, ch); // draws the border } int sx = cx, sy = cy; cx += l.borderWidth.getPixels(oneEm, 1); cy += l.borderWidth.getPixels(oneEm, 1); cw -= l.borderWidth.getPixels(oneEm, 1) * 2; ch -= l.borderWidth.getPixels(oneEm, 1) * 2; p.fillColor = l.backgroundColor; p.outlineColor = Color(0, 0, 0, 0); if(ele.tagName == "body") { // HACK to make the body bg apply to the whole window cx = 0; cy = 0; cw = p.window.width; ch = p.window.height; p.drawRectangle(Point(0, 0), p.window.width, p.window.height); // draw the padding box } else p.drawRectangle(Point(cx, cy - scrollTop), cw, ch); // draw the padding box if(l.renderValueAsText && ele.value.length) { p.outlineColor = l.foregroundColor; p.drawText(Point( cx + l.paddingLeft.getPixels(oneEm, 1), cy + l.paddingTop.getPixels(oneEm, 1) - scrollTop), ele.value); } //p.fillColor = Color(255, 255, 255); //p.drawRectangle(Point(cx, cy), cw, ch); // draw the content box foreach(e; ele.childNodes) { if(e.nodeType == 3) { auto thisL = LayoutData.get(e); p.outlineColor = LayoutData.get(e.parentNode).foregroundColor; p.drawText(Point(thisL.offsetLeft, thisL.offsetTop - scrollTop), toAscii(LayoutData.get(e).textToRender)); } else drawElement(p, e, sx, sy); } l.repaintRequired = false; } string toAscii(string s) { string ret; foreach(dchar c; s) { if(c < 128 && c > 0) ret ~= cast(char) c; else switch(c) { case '\u00a0': // nbsp ret ~= ' '; break; case '\u2018': case '\u2019': ret ~= "'"; break; case '\u201c': case '\u201d': ret ~= "\""; break; default: // skip non-ascii } } return ret; } class Event { this(string eventName, Element target) { this.eventName = eventName; this.srcElement = target; } void preventDefault() { defaultPrevented = true; } void stopPropagation() { propagationStopped = true; } bool defaultPrevented; bool propagationStopped; string eventName; Element srcElement; alias srcElement target; Element relatedTarget; int clientX; int clientY; int button; bool isBubbling; void send() { if(srcElement is null) return; auto e = LayoutData.get(srcElement); if(eventName in e.bubblingEventHandlers) foreach(handler; e.bubblingEventHandlers[eventName]) handler(e.element, this); if(!defaultPrevented) if(eventName in e.defaultEventHandlers) e.defaultEventHandlers[eventName](e.element, this); } void dispatch() { if(srcElement is null) return; // first capture, then bubble LayoutData[] chain; Element curr = srcElement; while(curr) { auto l = LayoutData.get(curr); chain ~= l; curr = curr.parentNode; } isBubbling = false; foreach(e; chain.retro) { if(eventName in e.capturingEventHandlers) foreach(handler; e.capturingEventHandlers[eventName]) handler(e.element, this); // the default on capture should really be to always do nothing //if(!defaultPrevented) // if(eventName in e.defaultEventHandlers) // e.defaultEventHandlers[eventName](e.element, this); if(propagationStopped) break; } isBubbling = true; if(!propagationStopped) foreach(e; chain) { if(eventName in e.bubblingEventHandlers) foreach(handler; e.bubblingEventHandlers[eventName]) handler(e.element, this); if(!defaultPrevented) if(eventName in e.defaultEventHandlers) e.defaultEventHandlers[eventName](e.element, this); if(propagationStopped) break; } } } void addEventListener(string event, Element what, EventHandler handler, bool bubble = true) { if(event.length > 2 && event[0..2] == "on") event = event[2 .. $]; auto l = LayoutData.get(what); if(bubble) l.bubblingEventHandlers[event] ~= handler; else l.capturingEventHandlers[event] ~= handler; } void addEventListener(string event, Element[] what, EventHandler handler, bool bubble = true) { foreach(w; what) addEventListener(event, w, handler, bubble); } bool isAParentOf(Element a, Element b) { if(a is null || b is null) return false; while(b !is null) { if(a is b) return true; b = b.parentNode; } return false; } void runHtmlWidget(SimpleWindow win, BrowsingContext context) { Element mouseLastOver; win.eventLoop(0, (MouseEvent e) { auto ele = elementFromPoint(context.document, e.x, e.y + scrollTop); if(mouseLastOver !is ele) { Event event; if(ele !is null) { if(!isAParentOf(ele, mouseLastOver)) { //writeln("mouseenter on ", ele.tagName); event = new Event("mouseenter", ele); event.relatedTarget = mouseLastOver; event.send(); } } if(mouseLastOver !is null) { if(!isAParentOf(mouseLastOver, ele)) { event = new Event("mouseleave", mouseLastOver); event.relatedTarget = ele; event.send(); } } if(ele !is null) { event = new Event("mouseover", ele); event.relatedTarget = mouseLastOver; event.dispatch(); } if(mouseLastOver !is null) { event = new Event("mouseout", mouseLastOver); event.relatedTarget = ele; event.dispatch(); } mouseLastOver = ele; } if(ele !is null) { auto l = LayoutData.get(ele); auto event = new Event( e.type == 0 ? "mousemove" : e.type == 1 ? "mousedown" : e.type == 2 ? "mouseup" : impossible , ele); event.clientX = e.x; event.clientY = e.y; event.button = e.button; event.dispatch(); if(l.someRepaintRequired) { auto p = win.draw(); p.clear(); drawElement(p, context.document.mainBody, 0, 0); l.paintCompleted(); } } }, (dchar key) { auto s = scrollTop; if(key == 'j') scrollTop += 16; else if(key == 'k') scrollTop -= 16; if(key == 'n') scrollTop += 160; else if(key == 'm') scrollTop -= 160; if(context.focusedElement !is null) { context.focusedElement.value = context.focusedElement.value ~ cast(char) key; auto p = win.draw(); drawElement(p, context.focusedElement, 0, 0); } if(s != scrollTop) { auto p = win.draw(); p.clear(); drawElement(p, context.document.mainBody, 0, 0); } if(key == 'q') win.close(); }); } class BrowsingContext { string currentUrl; Document document; Element focusedElement; } string absolutizeUrl(string url, string currentUrl) { if(url.length == 0) return null; auto current = currentUrl; auto idx = current.lastIndexOf("/"); if(idx != -1 && idx > 7) current = current[0 .. idx + 1]; if(url[0] == '/') { auto i = current[8 .. $].indexOf("/"); if(i != -1) current = current[0 .. i + 8]; } if(url.length < 7 || url[0 .. 7] != "http://") url = current ~ url; return url; } BrowsingContext _contextHack; // FIXME: the images aren't done sanely import arsd.curl; Document gotoSite(SimpleWindow win, BrowsingContext context, string url, string post = null) { _contextHack = context; auto p = win.draw; p.fillColor = Color(255, 255, 255); p.outlineColor = Color(0, 0, 0); p.drawRectangle(Point(0, 0), 800, 800); auto document = new Document(curl(url.absolutizeUrl(context.currentUrl), post)); context.document = document; context.currentUrl = url.absolutizeUrl(context.currentUrl); string styleSheetText = import("default.css"); foreach(ele; document.querySelectorAll("head link[rel=stylesheet]")) { if(!ele.hasAttribute("media") || ele.attrs.media().indexOf("screen") != -1) styleSheetText ~= curl(ele.href.absolutizeUrl(context.currentUrl)); } foreach(ele; document.getElementsByTagName("style")) styleSheetText ~= ele.innerHTML; styleSheetText = styleSheetText.replace(`@import "/style_common.css";`, curl("http://arsdnet.net/style_common.css")); auto styleSheet = new StyleSheet(styleSheetText); styleSheet.apply(document); foreach(e; document.root.tree) LayoutData.get(e); // initializing the css here return document; } string impossible() { assert(0); //return null; }