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=b9f71b6ea9a2f9fffa2f6bef83bd96685c8bdf98;hb=HEAD;hpb=faa4ae356d464a80e08540d5eb8bbee6296c1dbc diff --git a/src/org/ibex/core/Box.java b/src/org/ibex/core/Box.java index b9f71b6..dffc33c 100644 --- a/src/org/ibex/core/Box.java +++ b/src/org/ibex/core/Box.java @@ -13,6 +13,11 @@ package org.ibex.core; // - textures // - align/origin // - clipping (all forms) +// - mouse events +// - fonts/text +// - vertical layout + +// - stroke clipping import java.util.*; import org.ibex.js.*; @@ -47,7 +52,7 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { // 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; @@ -83,8 +88,17 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { 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; @@ -122,115 +136,180 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { /** 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, min(child.test(HSHRINK) ? child.contentwidth : width, child.maxwidth)); - child.height = max(child.minheight, min(child.test(VSHRINK) ? child.contentheight : height, 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; - a = a.copy().premultiply(transform); - - //boolean relevant = packed() || ((fillcolor&0xff000000)!=0x0) || path!=null; - boolean relevant = true; - 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; - if (packed()) { - child.transform.e = 0; - child.transform.f = 0; - } - child.constrain(this, Affine.identity()); - if (xmin==Integer.MAX_VALUE||xmax==Integer.MIN_VALUE||ymin==Integer.MAX_VALUE||ymax== Integer.MIN_VALUE) continue; - if (packed()) { - 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; - - int cw = contentwidth = bound(minwidth, contentwidth, maxwidth); - int ch = contentheight = 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(PixelBuffer buf, Affine a, Mesh clipFrom) { render(buf, a, clipFrom, Affine.identity()); } - public void render(PixelBuffer buf, Affine a, Mesh clipFrom, Affine clipa) { + 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().multiply(transform); clipa = clipa.copy().multiply(transform); @@ -238,26 +317,29 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { 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, contentwidth, contentheight); - // long ret = font.rasterizeGlyphs(text, buf, a, null, 0x777777, 0); - // minwidth = maxwidth = font.textwidth(text); - // minheight = maxheight = font.textwidth(text); + 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 } if (mesh==null) { - for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(buf, a, clipFrom, clipa); + 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; } - // FIXME: mesh clipping code has been getting stuck in loops; to see this, - // uncomment the next two lines and swap the order of the last two lines. + 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); - //if (clipFrom != null) clipFrom.subtract(mesh, clipa); - //Mesh mesh = treeSize() > 0 ? this.mesh.copy() : this.mesh; mesh.fill(buf, a, null, fillcolor, true); - for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(buf, a, mesh, Affine.identity()); + 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 ////////////////////////////////////// @@ -373,9 +455,7 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { String s = value==null ? "" : JSU.toString(value); text = s; minwidth = maxwidth = font.textwidth(text); - System.out.println("width=" + width); minheight = maxheight = font.textheight(text); - System.out.println("height=" + height); RECONSTRAIN(); dirty(); } @@ -409,12 +489,12 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { case "y": transform.f = JSU.toInt(value); case "transform": { transform = Affine.parse(JSU.toString(value)); - //transform.e = 0; - //transform.f = 0; 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)); @@ -618,7 +698,7 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain { // 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); }