// - textures
// - align/origin
// - clipping (all forms)
+// - mouse events
+// - fonts/text
+// - vertical layout
+
+// - stroke clipping
import java.util.*;
import org.ibex.js.*;
// 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;
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;
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);
/** 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);
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);
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 //////////////////////////////////////
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();
}
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));
// 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); }