X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=blobdiff_plain;f=src%2Forg%2Fibex%2Fcore%2FBox.java;h=dffc33c0cb011af9a06545a352ec565b6f8b890b;hp=76871cf50a8f898f31a6ba30dde33fccba893b67;hb=HEAD;hpb=026feed5d3ffb670533197a4ec733d1d03578c63 diff --git a/src/org/ibex/core/Box.java b/src/org/ibex/core/Box.java index 76871cf..dffc33c 100644 --- a/src/org/ibex/core/Box.java +++ b/src/org/ibex/core/Box.java @@ -9,9 +9,20 @@ package org.ibex.core; // FIXME: mouse move/release still needs to propagate to boxen in which the mouse was pressed and is still held down // FEATURE: reintroduce surface.abort +// Broken: +// - textures +// - align/origin +// - clipping (all forms) +// - mouse events +// - fonts/text +// - vertical layout + +// - stroke clipping + import java.util.*; import org.ibex.js.*; import org.ibex.util.*; +import org.ibex.plat.*; import org.ibex.graphics.*; /** @@ -33,11 +44,15 @@ import org.ibex.graphics.*; * trigger a Surface.abort; if rendering were done in the same pass, * rendering work done prior to the Surface.abort would be wasted. */ -public final class Box extends JS.Obj implements Callable { +public final class Box extends JS.Obj implements Callable, Mesh.Chain { + + public Mesh.Chain getMeshChainParent() { return parent; } + public Mesh getMesh() { return mesh; } + public Affine getAffine() { return transform; } // Macros ////////////////////////////////////////////////////////////////////// - final void REPLACE() { for(Box b2 = this; b2 != null && !b2.test(REPLACE); b2 = b2.parent) b2.set(REPLACE); } + final void REPLACE() { for(Box b2 = this; b2 != null && !b2.test(REPLACE); b2 = b2.parent) b2.set(REPLACE); } final void RECONSTRAIN() { for(Box b2 = this; b2 != null && !b2.test(RECONSTRAIN); b2 = b2.parent) b2.set(RECONSTRAIN); } //#define CHECKSET_SHORT(prop) short nu = (short)JSU.toInt(value); if (nu == prop) break; prop = nu; @@ -59,10 +74,12 @@ public final class Box extends JS.Obj implements Callable { private int strokecolor = 0xFF000000; public float flex = 1; private Path path = null; + private Path clippath = null; private Affine transform = Affine.identity(); // FEATURE: polygon caching - private Polygon polygon = null; + private Mesh polygon = null; + private Mesh mesh = null; // specified directly by user public int minwidth = 0; @@ -71,8 +88,17 @@ public final class Box extends JS.Obj implements Callable { public int maxheight = Integer.MAX_VALUE; // computed during reflow - public int width = 0; - public int height = 0; + //public int width = 0; // AS MEASURED IN PARENT SPACE! + //public int height = 0; // AS MEASURED IN PARENT SPACE! + float _width; + float _height; + private int width; + private int height; + private int rootwidth; + private int rootheight; + public int getRootWidth() { return rootwidth; } + public int getRootHeight() { return rootheight; } + public int contentwidth = 0; // == max(minwidth, textwidth, sum(child.contentwidth)) public int contentheight = 0; @@ -94,7 +120,7 @@ public final class Box extends JS.Obj implements Callable { public void dirty() { if (path==null) dirty(0, 0, contentwidth, contentheight); else dirty(path); } public void dirty(int x, int y, int w, int h) { } public void dirty(Path p) { - Affine a = transform; + Affine a = transform.copy(); for(Box cur = this; cur != null; cur = cur.parent) a.premultiply(cur.transform); long hbounds = p.horizontalBounds(a); long vbounds = p.verticalBounds(a); @@ -110,139 +136,212 @@ public final class Box extends JS.Obj implements Callable { /** should only be invoked on the root box */ public void reflow() { - constrain(this, Affine.identity()); - width = maxwidth; - height = maxheight; + constrain(this, Affine.identity(), new BoundingBox()); + width = rootwidth = maxwidth; + height = rootheight = maxheight; transform.e = 0; transform.f = 0; - place(); + place(0, 0, width, height, false); } - void place() { - if (!packed()) { - for(Box child = getChild(0); child != null; child = child.nextSibling()) { - child.width = max(child.minwidth, child.test(HSHRINK) ? child.contentwidth : child.maxwidth); - child.height = max(child.minheight, child.test(VSHRINK) ? child.contentheight : child.maxheight); - child.place(); + public float minWidth() { return Encode.longToFloat1(transform.rotateBox(minwidth, minheight)); } + public float minHeight() { return Encode.longToFloat2(transform.rotateBox(minwidth, minheight)); } + public float maxWidth() { return Encode.longToFloat1(transform.rotateBox(maxwidth, maxheight)); } + public float maxHeight() { return Encode.longToFloat2(transform.rotateBox(maxwidth, maxheight)); } + public float contentWidth() { return Encode.longToFloat1(transform.rotateBox(contentwidth, contentheight)); } + public float contentHeight() { return Encode.longToFloat2(transform.rotateBox(contentwidth, contentheight)); } + + /** used (single-threadedly) in constrain() */ + private static int xmin = 0, ymin = 0, xmax = 0, ymax = 0; + + private static class BoundingBox { + public int xmin, ymin, xmax, ymax; + public boolean unbounded() { + return xmin==Integer.MAX_VALUE||xmax==Integer.MIN_VALUE||ymin==Integer.MAX_VALUE||ymax==Integer.MIN_VALUE; } + public void reset() { + xmin = Integer.MAX_VALUE; ymin = Integer.MAX_VALUE; + xmax = Integer.MIN_VALUE; ymax = Integer.MIN_VALUE; + } + public void include(Affine a, float cw, float ch) { + //#repeat contentwidth/contentheight contentheight/contentwidth minwidth/minheight row/col col/row \ + // textwidth/textheight maxwidth/maxheight bounds/boundsy x1/y1 x2/y2 z1/q1 z2/q2 z3/q3 z4/q4 \ + // horizontalBounds/verticalBounds e/f multiply_px/multiply_py xmin/ymin xmax/ymax + float z1 = a.multiply_px(0, 0); + float z2 = a.multiply_px(cw, ch); + float z3 = a.multiply_px(cw, 0); + float z4 = a.multiply_px(0, ch); + xmin = min(xmin, (int)min(min(z1, z2), min(z3, z4))); + xmax = max(xmax, (int)max(max(z1, z2), max(z3, z4))); + //#end + } + public void include(Affine a, Path path) { + //#repeat contentwidth/contentheight contentheight/contentwidth minwidth/minheight row/col col/row \ + // textwidth/textheight maxwidth/maxheight bounds/boundsy x1/y1 x2/y2 z1/q1 z2/q2 z3/q3 z4/q4 \ + // horizontalBounds/verticalBounds e/f multiply_px/multiply_py xmin/ymin xmax/ymax + long bounds = path.horizontalBounds(a); + float z1 = Encode.longToFloat2(bounds); + float z2 = Encode.longToFloat1(bounds); + float z3 = Encode.longToFloat2(bounds); + float z4 = Encode.longToFloat1(bounds); + xmin = min(xmin, (int)min(min(z1, z2), min(z3, z4))); + xmax = max(xmax, (int)max(max(z1, z2), max(z3, z4))); + //#end + } + } + + /** expand the {x,y}{min,max} boundingbox in space a to include this box */ + public void constrain(Box b, Affine a, BoundingBox bbox) { + contentwidth = 0; + contentheight = 0; + a = a.copy().premultiply(transform); + + BoundingBox bbox2 = new BoundingBox(); + for(Box child = getChild(0); child != null; child = child.nextSibling()) { + child.constrain(this, Affine.identity(), bbox2); + if (bbox2.unbounded()) { /* FIXME: why? */ bbox2.reset(); continue; } + if (packed()) { + // packed boxes mush together their childrens' bounding boxes + if (test(XAXIS)) { + contentwidth = contentwidth + (bbox2.xmax-bbox2.xmin); + contentheight = max(bbox2.ymax-bbox2.ymin, contentheight); + } else { + contentwidth = max(bbox2.xmax-bbox2.xmin, contentwidth); + contentheight = contentheight + (bbox2.ymax-bbox2.ymin); + } + bbox2.reset(); } + } + if (!packed()) { + // unpacked boxes simply use the "cumulative" bounding box + contentwidth = bbox2.xmax-bbox2.xmin; + contentheight = bbox2.ymax-bbox2.ymin; + } + + contentwidth = bound(minwidth, contentwidth, maxwidth); + contentheight = bound(minheight, contentheight, maxheight); + bbox.include(a, contentwidth, contentheight); + if (path!=null) bbox.include(a, path); + } + + void place(float x, float y, float w, float h, boolean keep) { + int oldw = width; + int oldh = height; + width = bound(contentwidth, (int)Encode.longToFloat1(transform.inverse().rotateBox(w, h)), test(HSHRINK)?contentwidth:maxwidth); + height = bound(contentheight, (int)Encode.longToFloat2(transform.inverse().rotateBox(w, h)), test(VSHRINK)?contentheight:maxheight); + if (oldw!=width || oldh!=height) mesh = null; + if (!keep) { + Affine a = transform; + transform.e = 0; + transform.f = 0; + float e; + float f; + //#repeat e/f x/y multiply_px/multiply_py horizontalBounds/verticalBounds bounds/boundsy z1/z1y z2/z2y z3/z3y z4/z4y + long bounds = path==null ? 0 : path.horizontalBounds(transform); + float z1 = path==null ? a.multiply_px(0, 0) : Encode.longToFloat2(bounds); + float z2 = path==null ? a.multiply_px(width, height) : Encode.longToFloat1(bounds); + float z3 = path==null ? a.multiply_px(width, 0) : Encode.longToFloat2(bounds); + float z4 = path==null ? a.multiply_px(0, height) : Encode.longToFloat1(bounds); + e = (-1 * min(min(z1, z2), min(z3, z4))) + x; + //#end + transform.e = e; + transform.f = f; + } + + if (!packed()) { + for(Box child = getChild(0); child != null; child = child.nextSibling()) + child.place(0, 0, width, height, true); return; } - float slack = width, oldslack = 0, flex = 0, newflex = 0; + float slack = test(XAXIS)?width:height, oldslack = 0, flex = 0, newflex = 0; for(Box child = getChild(0); child != null; child = child.nextSibling()) { if (!child.test(VISIBLE)) continue; - slack -= (child.width = child.contentwidth); - if (child.width < (child.test(HSHRINK)?child.contentwidth:child.maxwidth)) flex += child.flex; - child.height = child.test(HSHRINK) ? child.contentheight : bound(child.contentheight, height, child.maxheight); + if (test(XAXIS)) { + child._width = child.contentWidth(); + child._height = height; + slack -= child._width; + } else { + child._height = child.contentHeight(); + child._width = width; + slack -= child._height; + } + flex += child.flex; } while(slack > 0 && flex > 0 && oldslack!=slack) { oldslack = slack; - slack = width; + slack = test(XAXIS) ? width : height; + newflex = 0; for(Box child = getChild(0); child != null; child = child.nextSibling()) { if (!child.test(VISIBLE)) continue; - if (child.width < child.maxwidth && !child.test(HSHRINK)) - child.width = bound(child.contentwidth, (int)(child.width + (slack/flex)), child.maxwidth); - newflex += child.width < child.maxwidth && !child.test(HSHRINK) ? child.flex : 0; - slack -= child.width; + if (test(XAXIS)) { + float oldwidth = child._width; + if (child.test(HSHRINK)) child._width = min(child.maxWidth(), child._width+(oldslack*child.flex)/flex); + slack -= child._width; + if (child._width > oldwidth) newflex += child.flex; + } else { + float oldheight = child._height; + if (child.test(VSHRINK)) child._height = min(child.maxHeight(), child._height+(oldslack*child.flex)/flex); + slack -= child._height; + if (child._height > oldheight) newflex += child.flex; + } } flex = newflex; } float pos = slack / 2; for(Box child = getChild(0); child != null; child = child.nextSibling()) { if (!child.test(VISIBLE)) continue; - child.transform.e += pos; - child.transform.f += (height-child.height)/2; - pos += child.width; - child.place(); - } - } - - /** used (single-threadedly) in constrain() */ - private static int xmin = 0, ymin = 0, xmax = 0, ymax = 0; - public void constrain(Box b, Affine a) { - contentwidth = 0; - contentheight = 0; - transform.e = 0; - transform.f = 0; - a = a.copy().premultiply(transform); - - boolean relevant = packed() || ((fillcolor&0xff000000)!=0x0) || path!=null; - int save_xmin = xmin, save_ymin = ymin, save_xmax = xmax, save_ymax = ymax; - if (!relevant) { - for(Box child = getChild(0); child != null; child = child.nextSibling()) { - child.constrain(b, a); - contentwidth = max(contentwidth, child.contentwidth); - contentheight = max(contentheight, child.contentheight); + if (test(XAXIS)) { + child.place(pos, 0, child._width, child._height, false); + pos += child._width; + } else { + child.place(0, pos, child._width, child._height, false); + pos += child._height; } - return; } - - for(Box child = getChild(0); child != null; child = child.nextSibling()) { - xmin = Integer.MAX_VALUE; ymin = Integer.MAX_VALUE; - xmax = Integer.MIN_VALUE; ymax = Integer.MIN_VALUE; - child.constrain(this, Affine.identity()); - if (xmin==Integer.MAX_VALUE||xmax==Integer.MIN_VALUE||ymin==Integer.MAX_VALUE||ymax== Integer.MIN_VALUE) continue; - child.transform.e = -1 * xmin; - child.transform.f = -1 * ymin; - contentwidth += (xmax-xmin); - contentheight = max(ymax-ymin, contentheight); - } - xmin = save_xmin; - ymin = save_ymin; - xmax = save_xmax; - ymax = save_ymax; - - contentwidth = bound(minwidth, contentwidth, maxwidth); - contentheight = bound(minheight, contentheight, maxheight); - - int cw = bound(minwidth, contentwidth, maxwidth), ch = bound(minheight, contentheight, maxheight); - //#repeat contentwidth/contentheight contentheight/contentwidth minwidth/minheight row/col col/row \ - // textwidth/textheight maxwidth/maxheight bounds/boundsy x1/y1 x2/y2 z1/q1 z2/q2 z3/q3 z4/q4 \ - // horizontalBounds/verticalBounds e/f multiply_px/multiply_py xmin/ymin xmax/ymax - long bounds = path==null ? 0 : path.horizontalBounds(a); - float z1 = path==null ? a.multiply_px(0, 0) : Encode.longToFloat2(bounds); - float z2 = path==null ? a.multiply_px(cw, ch) : Encode.longToFloat1(bounds); - float z3 = path==null ? a.multiply_px(cw, 0) : Encode.longToFloat2(bounds); - float z4 = path==null ? a.multiply_px(0, ch) : Encode.longToFloat1(bounds); - xmin = min(xmin, (int)min(min(z1, z2), min(z3, z4))); - xmax = max(xmax, (int)max(max(z1, z2), max(z3, z4))); - //#end } - + + + + // Rendering Pipeline ///////////////////////////////////////////////////////////////////// private static final boolean OPTIMIZE = false; /** Renders self and children within the specified region. All rendering operations are clipped to xIn,yIn,wIn,hIn */ - public void render(int cx1, int cy1, int cx2, int cy2, PixelBuffer buf, Affine a) { + public void render(PixelBuffer buf, Affine a, Mesh clipFrom) { render(buf, a, clipFrom, Affine.identity(), 0); } + public void render(PixelBuffer buf, Affine a, Mesh clipFrom, Affine clipa, int bg) { if (!test(VISIBLE)) return; - a = a.copy().premultiply(transform); - - // FIXME: clipping - if (path == null) { - if (((fillcolor & 0xFF000000) != 0x00000000 || parent == null)) { - if (OPTIMIZE && a.doesNotRotate()) { - int x = (int)a.multiply_px(0, 0); - int y = (int)a.multiply_py(0, 0); - int x2 = (int)a.multiply_px(contentwidth, contentheight); - int y2 = (int)a.multiply_py(contentwidth, contentheight); - buf.fillTrapezoid(x, x, y, x2, x2, y2, (fillcolor & 0xFF000000) == 0 ? 0xffffffff : fillcolor); - } else { - new Polygon().addrect(0, 0, contentwidth, contentheight, a).fill(buf, new Paint.SingleColorPaint(fillcolor)); + a = a.copy().multiply(transform); + clipa = clipa.copy().multiply(transform); + + if (mesh == null) + if (path != null) mesh = new Mesh(path, true); + else { + if (((fillcolor & 0xFF000000) != 0x00000000 || parent == null) && (text==null||"".equals(text))) { + mesh = new Mesh().addRect(0, 0, width, height); } + // if (ret == 0) Platform.Scheduler.add(this); + // FIXME: texture } - // FIXME: text - // FIXME: texture - } else { - Polygon p = new Polygon(path, a); - p.fill(buf, new Paint.SingleColorPaint(fillcolor)); - p.stroke(buf, strokecolor); + if (mesh==null) { + for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(buf, a, clipFrom, clipa, bg); + if (!(text==null||text.equals(""))) + font.rasterizeGlyphs(text, buf, a.copy(), clipFrom, clipa.copy(), strokecolor, 0); + return; } - for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(cx1, cy1, cx2, cy2, buf, a); + if (clipFrom != null) clipFrom.subtract(mesh, clipa); + Mesh mesh = treeSize() > 0 ? this.mesh.copy() : this.mesh; + + if ((fillcolor & 0xff000000)!=0) bg = fillcolor; + for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(buf, a, mesh, Affine.identity(), bg); + + mesh.fill(buf, a, null, fillcolor, true); + if ((strokecolor & 0xff000000) != 0) mesh.stroke(buf, a, strokecolor); + if (!(text==null||text.equals(""))) + font.rasterizeGlyphs(text, buf, a.copy(), clipFrom, clipa.copy(), strokecolor, bg); } - // Methods to implement org.ibex.js.JS ////////////////////////////////////// public JS call(JS method, JS[] args) throws JSExn { @@ -252,8 +351,7 @@ public final class Box extends JS.Obj implements Callable { case "indexof": Box b = (Box)args[0]; if (b.parent != this) - return (redirect == null || redirect == this) ? - JSU.N(-1) : redirect.call(method, args); + return (redirect == null || redirect == this) ? JSU.N(-1) : redirect.call(method, args); return JSU.N(b.getIndexInParent()); case "distanceto": @@ -353,13 +451,21 @@ public final class Box extends JS.Obj implements Callable { if (JSU.isInt(name)) { put(JSU.toInt(name), value); return; } //#switch(JSU.toString(name)) case "thisbox": if (value == null) removeSelf(); - case "text": { String s = value==null ? "" : JSU.toString(value); CHECKSET_STRING(text); RECONSTRAIN(); dirty(); } + case "text": { + String s = value==null ? "" : JSU.toString(value); + text = s; + minwidth = maxwidth = font.textwidth(text); + minheight = maxheight = font.textheight(text); + RECONSTRAIN(); + dirty(); + } case "strokecolor": value = JSU.N(Color.stringToColor(JSU.toString(value))); CHECKSET_INT(strokecolor); dirty(); case "textcolor": value = JSU.N(Color.stringToColor(JSU.toString(value))); CHECKSET_INT(strokecolor); dirty(); case "shrink": CHECKSET_FLAG(HSHRINK | VSHRINK); RECONSTRAIN(); case "hshrink": CHECKSET_FLAG(HSHRINK); RECONSTRAIN(); case "vshrink": CHECKSET_FLAG(VSHRINK); RECONSTRAIN(); case "path": { + mesh = null; path = new Path(JSU.toString(value)); float tx = -1 * Encode.longToFloat2(path.horizontalBounds(Affine.identity())); float ty = -1 * Encode.longToFloat2(path.verticalBounds(Affine.identity())); @@ -369,13 +475,26 @@ public final class Box extends JS.Obj implements Callable { dirty(); polygon = null; } + case "clippath": { + clippath = new Path(JSU.toString(value)); + //float tx = -1 * Encode.longToFloat2(clippath.horizontalBounds(Affine.identity())); + //float ty = -1 * Encode.longToFloat2(clippath.verticalBounds(Affine.identity())); + //clippath.transform(Affine.translate(tx, ty), true); + REPLACE(); + RECONSTRAIN(); + dirty(); + polygon = null; + } + case "x": transform.e = JSU.toInt(value); + case "y": transform.f = JSU.toInt(value); case "transform": { transform = Affine.parse(JSU.toString(value)); - transform.e = 0; - transform.f = 0; - if (getSurface() != null) getSurface().dirty(0, 0, 500, 500); // FIXME + if (getSurface() != null) // FIXME + getSurface().dirty(0, 0, getSurface().root.contentwidth, getSurface().root.contentheight); REPLACE(); - RECONSTRAIN(); dirty(); polygon = null; + RECONSTRAIN(); + dirty(); + polygon = null; } case "width": setWidth(JSU.toInt(value), JSU.toInt(value)); case "height": setHeight(JSU.toInt(value), JSU.toInt(value)); @@ -579,7 +698,7 @@ public final class Box extends JS.Obj implements Callable { // Trivial Helper Methods (should be inlined) ///////////////////////////////////////// public final int fontSize() { return font == null ? DEFAULT_FONT.pointsize : font.pointsize; } - public Enumeration keys() { throw new Error("you cannot apply for..in to a " + this.getClass().getName()); } + public Enumeration jskeys() { throw new Error("you cannot apply for..in to a " + this.getClass().getName()); } public Box getRoot() { return parent == null ? this : parent.getRoot(); } public Surface getSurface() { return Surface.fromBox(getRoot()); } private final boolean packed() { return test(YAXIS) || test(XAXIS); }