questionable patch: merge of a lot of stuff from the svg branch
[org.ibex.core.git] / src / org / ibex / core / Box.java
index 595e101..dffc33c 100644 (file)
@@ -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,111 +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 <tt>a</tt> 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;
-        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;
-            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;
-
-        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);
@@ -234,23 +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;
         }
 
-        //if (clipFrom != null) clipFrom.subtract(mesh, clipa);
+        if (clipFrom != null) clipFrom.subtract(mesh, clipa);
         Mesh mesh = treeSize() > 0 ? this.mesh.copy() : this.mesh;
-        for(Box b = getChild(0); b != null; b = b.nextSibling()) b.render(buf, a, mesh, Affine.identity());
+
+        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 //////////////////////////////////////
@@ -366,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();
         }
@@ -398,14 +485,16 @@ public final class Box extends JS.Obj implements Callable, Mesh.Chain {
             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)  // 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));
@@ -609,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); }