X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2FBox.java;h=d3f3450943a54e5df019c2e606d19fd41df6957f;hb=8eacea507ec91136304f86c7933e85c2060ba7e4;hp=b3f5fd4973a7c47214c4cb14baeef34d8a48107e;hpb=707b1064f56e88db0cf555a99cb4aa224484f51a;p=org.ibex.core.git
diff --git a/src/org/ibex/Box.java b/src/org/ibex/Box.java
index b3f5fd4..d3f3450 100644
--- a/src/org/ibex/Box.java
+++ b/src/org/ibex/Box.java
@@ -2,13 +2,13 @@
// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
package org.ibex;
-// FEATURE: reflow before allowing js to read from width/height
-// FEATURE: fastpath for rows=1/cols=1
+// FIXME: are traps on x/y meaningful?
+// FIXME: if we trap on cols, then set rows to 0 (forcing cols to 1), does the cols trap get triggered?
+// FIXME: if we change min{width/height}, thereby forcing a change to max{min/height}, does a trap on those get triggered?
+// FIXME: trap on numchildren? replaces ChildChanged?
+// FIXME: trap on visible, trigger when parent visibility changes
+
// FEATURE: mark to reflow starting with a certain child
-// FEATURE: separate mark_for_reflow and mark_for_resize
-// FEATURE: make all methods final
-// FEATURE: use a linked list for the "frontier" when packing
-// FEATURE: or else have a way to mark a column "same as last one"?
// FEATURE: reintroduce surface.abort
import java.util.*;
@@ -24,32 +24,25 @@ import org.ibex.translators.*;
*
*
The rendering process consists of four phases; each requires
* one DFS pass over the tree
- * - repacking: children of a box are packed into columns
- * and rows according to their colspan/rowspan attributes and
- * ordering.
- *
- reconstraining: Minimum and maximum sizes of columns are computed.
- *
- resizing: width/height and x/y positions of children
- * are assigned, and PosChange/SizeChanges are triggered.
- *
- repainting: children draw their content onto the PixelBuffer.
+ *
- pack(): each box sets its childrens' row/col
+ *
- constrain(): contentwidth is computed
+ *
- resize(): width/height and x/y positions are set
+ *
- render(): children draw their content onto the PixelBuffer.
*
*
- * The first three passes together are called the reflow phase.
- * Reflowing is done in a seperate pass since PosChanges and
- * SizeChanges trigger an Surface.abort; if rendering were done in the same
- * pass, rendering work done prior to the Surface.abort would be wasted.
+ * The first three passes together are called the reflow
+ * phase. Reflowing is done in a seperate pass since SizeChanges
+ * 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 JSScope implements Scheduler.Task {
// Macros //////////////////////////////////////////////////////////////////////
- //#define LENGTH int
- //#define MARK_REPACK for(Box b2 = this; b2 != null && !b2.test(REPACK); b2 = b2.parent) b2.set(REPACK);
- //#define MARK_REPACK_b for(Box b2 = b; b2 != null && !b2.test(REPACK); b2 = b2.parent) b2.set(REPACK);
- //#define MARK_REPACK_parent for(Box b2 = parent; b2 != null && !b2.test(REPACK); b2 = b2.parent) b2.set(REPACK);
- //#define MARK_REFLOW for(Box b2 = this; b2 != null && !b2.test(REFLOW); b2 = b2.parent) b2.set(REFLOW);
- //#define MARK_REFLOW_b for(Box b2 = b; b2 != null && !b2.test(REFLOW); b2 = b2.parent) b2.set(REFLOW);
- //#define MARK_RESIZE for(Box b2 = this; b2 != null && !b2.test(RESIZE); b2 = b2.parent) b2.set(RESIZE);
- //#define MARK_RESIZE_b for(Box b2 = b; b2 != null && !b2.test(RESIZE); b2 = b2.parent) b2.set(RESIZE);
+ 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); }
+ final void REPACK() { for(Box b2 = this; b2 != null && !b2.test(REPACK); b2 = b2.parent) b2.set(REPACK); }
+
//#define CHECKSET_SHORT(prop) short nu = (short)toInt(value); if (nu == prop) break; prop = nu;
//#define CHECKSET_INT(prop) int nu = toInt(value); if (nu == prop) break; prop = nu;
//#define CHECKSET_FLAG(flag) boolean nu = toBoolean(value); if (nu == test(flag)) break; if (nu) set(flag); else clear(flag);
@@ -59,24 +52,7 @@ public final class Box extends JSScope implements Scheduler.Task {
protected Box() { super(null); }
static Hash boxToCursor = new Hash(500, 3); // FIXME memory leak
- public static final int MAX_LENGTH = Integer.MAX_VALUE;
- static final Font DEFAULT_FONT;
-
- static {
- Font f = null;
- try { f = Font.getFont((Stream)Main.builtin.get("fonts/vera/Vera.ttf"), 10); }
- catch(JSExn e) { Log.info(Box.class, "should never happen: "+e); }
- DEFAULT_FONT = f;
- }
-
- // FIXME update these
- // box properties can not be trapped
- static final String[] props = new String[] {
- "shrink", "hshrink", "vshrink", "x", "y", "width", "height", "cols", "rows",
- "colspan", "rowspan", "align", "visible", "packed", "globalx", "globaly",
- "minwidth", "maxwidth", "minheight", "maxheight", "indexof", "thisbox", "clip",
- "numchildren", "redirect", "cursor", "mouse"
- };
+ static final Font DEFAULT_FONT = Font.getFont((Stream)Main.builtin.get("fonts/vera/Vera.ttf"), 10);
// FIXME update these
// events can have write traps, but not read traps
@@ -105,9 +81,9 @@ public final class Box extends JSScope implements Scheduler.Task {
static final int ISROOT = 0x00000080;
static final int REPACK = 0x00000100;
- static final int REFLOW = 0x00000200;
- static final int RESIZE = 0x00000400;
- static final int RECONSTRAIN = 0x00000800;
+ static final int RECONSTRAIN = 0x00000200;
+ static final int REPLACE = 0x00000400;
+
static final int ALIGN_TOP = 0x00001000;
static final int ALIGN_BOTTOM = 0x00002000;
static final int ALIGN_LEFT = 0x00004000;
@@ -123,7 +99,7 @@ public final class Box extends JSScope implements Scheduler.Task {
Box parent = null;
Box redirect = this;
- int flags = VISIBLE | PACKED | REPACK | REFLOW | RESIZE | FIXED /* ROWS */ | STOP_UPWARD_PROPAGATION | CLIP | MOVED;
+ int flags = VISIBLE | PACKED | REPACK | RECONSTRAIN | REPLACE | FIXED | STOP_UPWARD_PROPAGATION | CLIP | MOVED;
private String text = null;
private Font font = DEFAULT_FONT;
@@ -135,10 +111,10 @@ public final class Box extends JSScope implements Scheduler.Task {
private int aspect = 0;
// specified directly by user
- public LENGTH minwidth = 0;
- public LENGTH maxwidth = MAX_LENGTH;
- public LENGTH minheight = 0;
- public LENGTH maxheight = MAX_LENGTH;
+ public int minwidth = 0;
+ public int maxwidth = Integer.MAX_VALUE;
+ public int minheight = 0;
+ public int maxheight = Integer.MAX_VALUE;
private short rows = 1;
private short cols = 0;
private short rowspan = 1;
@@ -147,14 +123,14 @@ public final class Box extends JSScope implements Scheduler.Task {
// computed during reflow
private short row = 0;
private short col = 0;
- public LENGTH x = 0;
- public LENGTH y = 0;
- public LENGTH ax = 0; // FEATURE: roll these into x/y; requires lots of changes
- public LENGTH ay = 0; // FEATURE: roll these into x/y; requires lots of changes; perhaps y()?
- public LENGTH width = 0;
- public LENGTH height = 0;
- private LENGTH contentwidth = 0; // == max(minwidth, textwidth, sum(child.contentwidth))
- private LENGTH contentheight = 0;
+ public int x = 0;
+ public int y = 0;
+ public int ax = 0; // FEATURE: roll these into x/y; requires lots of changes
+ public int ay = 0; // FEATURE: roll these into x/y; requires lots of changes; perhaps y()?
+ public int width = 0;
+ public int height = 0;
+ private int contentwidth = 0; // == max(minwidth, textwidth, sum(child.contentwidth))
+ private int contentheight = 0;
/*
private VectorGraphics.VectorPath path = null;
@@ -163,6 +139,8 @@ public final class Box extends JSScope implements Scheduler.Task {
private VectorGraphics.Affine rtransform = null;
*/
+ //#define DIRTY dirty()
+
// Instance Methods /////////////////////////////////////////////////////////////////////
public final int fontSize() { return font == null ? DEFAULT_FONT.pointsize : font.pointsize; }
@@ -170,7 +148,10 @@ public final class Box extends JSScope implements Scheduler.Task {
/** invoked when a resource needed to render ourselves finishes loading */
public void perform() throws JSExn {
if (texture == null) { Log.warn(Box.class, "perform() called with null texture"); return; }
- if (texture.isLoaded) { setMinWidth(max(texture.width, maxwidth)); setMinHeight(max(texture.height, maxheight)); }
+ if (texture.isLoaded) {
+ setMinWidth(max(texture.width, minwidth));
+ setMinHeight(max(texture.height, minheight));
+ DIRTY; }
else { JS res = texture.stream; texture = null; throw new JSExn("image not found: "+res.unclone()); }
}
@@ -196,11 +177,17 @@ public final class Box extends JSScope implements Scheduler.Task {
// Reflow ////////////////////////////////////////////////////////////////////////////////////////
+ /** should only be invoked on the root box */
+ void reflow() { pack(); resize(x, y, maxwidth, maxheight); place(); }
+
private static Box[] frontier = new Box[65535];
/** pack the boxes into rows and columns, compute contentwidth */
void pack() {
- for(Box child = getChild(0); child != null; child = child.nextSibling()) child.pack();
- int frontier_size = 0; contentwidth = 0; contentheight = 0;
+ if (!test(REPACK)) { constrain(); return; }
+ boolean haskid = false;
+ for(Box child = getChild(0); child != null; child = child.nextSibling()) { haskid = true; child.pack(); }
+ if (!haskid) { clear(REPACK); constrain(); return; }
+ int frontier_size = 0;
//#repeat COLS/ROWS rows/cols cols/rows col/row row/col colspan/rowspan rowspan/colspan \
// contentheight/contentwidth contentwidth/contentheight
if (test(FIXED) == COLS) {
@@ -228,6 +215,13 @@ public final class Box extends JSScope implements Scheduler.Task {
for(int i=0; i 0) do {
- computeRegions();
- int nc = numregions * 3 + numkids * 2 + 3;
- if (coeff == null || nc+1>coeff.length) coeff = new float[nc+1];
- lp_h.init(nc);
-
- for(int i=0; i= child.col && regions[r+1] <= min(child.col+child.colspan,cols))
- coeff[r] = (float)(regions[r+1] - regions[r]);
- lp_h.add_constraint(coeff, Simplex.GE, (float)child.contentwidth);
- if (!findMinimum) {
- // priority 2: honor maxwidths
- int child_maxwidth = child.test(HSHRINK) ? min(child.maxwidth, child.contentwidth) : child.maxwidth;
- if (child_maxwidth < Integer.MAX_VALUE) {
- for(int i=0; i= child.col && regions[r+1] <= min(child.col+child.colspan,cols))
- coeff[r] = (float)(regions[r+1] - regions[r]);
- coeff[numregions*2+childnum] = (float)-1.0;
- lp_h.add_constraint(coeff, Simplex.LE, (float)child_maxwidth);
- }
- }
- childnum++;
+ } else if (cols > 1) do {
+
+ // FIXME: cache these?
+ // compute regions
+ numregions = 0;
+ for(Box c = firstPackedChild(); c != null; c = c.nextPackedSibling()) {
+ regions[numregions++] = c.col;
+ regions[numregions++] = min(cols, c.col+c.colspan);
}
-
- if (!findMinimum) {
- // priority 3: equalize columns
- float avg = ((float)width)/((float)numregions);
- for(int r=0; r= min(child.col+child.colspan,cols)) { minregion = r; break; }
+ total -= sizes[r];
+ int child_maxwidth = child.test(HSHRINK)?child.contentwidth:child.maxwidth;
+ if (sizes[r] <= (float)(targetColumnSize*(regions[r+1]-regions[r])))
+ if ((child.colspan * targetColumnSize) > (child_maxwidth + (float)0.5))
+ sizes[r] = (float)Math.min(sizes[r], (regions[r+1]-regions[r])*(child_maxwidth/child.colspan));
+ if ((child.colspan * targetColumnSize) < (child.contentwidth - (float)0.5))
+ sizes[r] = (float)Math.max(sizes[r], (regions[r+1]-regions[r])*(child.contentwidth/child.colspan));
+ total += sizes[r];
+ }
+ float save = targetColumnSize;
+ if (Math.abs(total - target) <= (float)1.0) break;
+ if (!first) {
+ if (Math.abs(total - last_total) <= (float)1.0) break;
+ } else {
+ last_columnsize = ((total - target) / (float)cols) + targetColumnSize;
+ }
+ if (total < target) targetColumnSize += Math.abs((last_columnsize - targetColumnSize) / (float)1.1);
+ else if (total > target) targetColumnSize -= Math.abs((last_columnsize - targetColumnSize) / (float)1.1);
+ last_columnsize = save;
+ last_total = total;
+ first = false;
}
-
- if (findMinimum) contentwidth = Math.round(lp_h.solution[lp_h.rows + (2*numregions + numkids + 1)]);
+ if (findMinimum) contentwidth = Math.round(total);
+ else this.targetColumnSize = targetColumnSize;
} while(false);
//#end
}
@@ -375,6 +349,7 @@ public final class Box extends JSScope implements Scheduler.Task {
solve(false);
for(Box child = getChild(0); child != null; child = child.nextSibling()) {
if (!child.test(VISIBLE)) continue;
+ if (!child.test(REPLACE)) continue;
int child_width, child_height, child_x, child_y;
if (!child.test(PACKED)) {
child_width = child.test(HSHRINK) ? child.contentwidth : min(child.maxwidth, width - Math.abs(child.ax));
@@ -390,18 +365,16 @@ public final class Box extends JSScope implements Scheduler.Task {
//#repeat col/row colspan/rowspan contentwidth/contentheight width/height colMaxWidth/rowMaxHeight \
// child_x/child_y x/y HSHRINK/VSHRINK maxwidth/maxheight cols/rows minwidth/minheight x_slack/y_slack \
// child_width/child_height ALIGN_RIGHT/ALIGN_BOTTOM ALIGN_LEFT/ALIGN_TOP lp_h/lp \
- // numregions/numregions_v regions/regions_v
- child_width = 0;
+ // numregions/numregions_v regions/regions_v targetColumnSize/targetRowSize sizes/sizes_v
child_x = 0;
if (cols == 1) {
- child_x = 0;
child_width = width;
} else {
- for(int r=0; r= child.col && regions[r+1] <= min(child.col+child.colspan,cols))
- child_width += Math.round(lp_h.solution[lp_h.rows+r+1] * (regions[r+1] - regions[r]));
- else if (regions[r+1] <= child.col)
- child_x += Math.round(lp_h.solution[lp_h.rows+r+1] * (regions[r+1] - regions[r]));
+ child_width = 0;
+ for(int r=0; r 0) { CHECKSET_SHORT(colspan); if (parent != null) parent.REPACK(); }
+ case "rowspan": if (toInt(value) > 0) { CHECKSET_SHORT(rowspan); if (parent != null) parent.REPACK(); }
+ case "rows": CHECKSET_SHORT(rows); if (rows==0){set(FIXED, COLS);if(cols==0)cols=1;} else set(FIXED, ROWS); REPACK();
+ case "cols": CHECKSET_SHORT(cols); if (cols==0){set(FIXED, ROWS);if(rows==0)rows=1;} else set(FIXED, COLS); REPACK();
+ case "clip": CHECKSET_FLAG(CLIP); if (parent == null) DIRTY; else parent.DIRTY;
+ case "visible": CHECKSET_FLAG(VISIBLE); RECONSTRAIN(); DIRTY;
+ case "packed": CHECKSET_FLAG(PACKED); if (parent != null) parent.REPACK();
+ case "align": clear(ALIGNS); setAlign(value == null ? "center" : value); REPLACE();
case "cursor": setCursor(value);
case "fill": setFill(value);
case "mouse":
@@ -635,29 +586,27 @@ public final class Box extends JSScope implements Scheduler.Task {
case "font":
if(!(value instanceof Stream)) throw new JSExn("You can only put streams to the font property");
font = value == null ? null : Font.getFont((Stream)value, font == null ? 10 : font.pointsize);
- MARK_RESIZE;
- dirty();
- case "fontsize": font = Font.getFont(font == null ? null : font.stream, toInt(value)); MARK_RESIZE; dirty();
+ RECONSTRAIN();
+ DIRTY;
+ case "fontsize": font = Font.getFont(font == null ? null : font.stream, toInt(value)); RECONSTRAIN(); DIRTY;
case "x": if (parent==null && Surface.fromBox(this)!=null) {
CHECKSET_INT(x);
} else {
if (test(PACKED) && parent != null) return;
- dirty(); CHECKSET_INT(ax);
- dirty(); MARK_RESIZE;
- dirty();
+ CHECKSET_INT(ax);
+ REPLACE();
}
case "y": if (parent==null && Surface.fromBox(this)!=null) {
CHECKSET_INT(y);
} else {
if (test(PACKED) && parent != null) return;
- dirty(); CHECKSET_INT(ay);
- dirty(); MARK_RESIZE;
- dirty();
+ CHECKSET_INT(ay);
+ REPLACE();
}
case "titlebar":
if (getSurface() != null && value != null) getSurface().setTitleBarText(JS.toString(value));
super.put(name,value);
-
+
case "Press1": if (!test(STOP_UPWARD_PROPAGATION) && parent != null) parent.putAndTriggerTraps(name, value);
case "Press2": if (!test(STOP_UPWARD_PROPAGATION) && parent != null) parent.putAndTriggerTraps(name, value);
case "Press3": if (!test(STOP_UPWARD_PROPAGATION) && parent != null) parent.putAndTriggerTraps(name, value);
@@ -698,7 +647,7 @@ public final class Box extends JSScope implements Scheduler.Task {
case "_VScroll": propagateDownward(name, value, false);
case "SizeChange": return;
- case "ChildChange": return;
+ case "ChildChange": return;
case "Enter": return;
case "Leave": return;
@@ -767,7 +716,7 @@ public final class Box extends JSScope implements Scheduler.Task {
} else {
throw new JSExn("fill must be null, a String, or a stream, not a " + value.getClass());
}
- dirty();
+ DIRTY;
}
// FIXME: mouse move/release still needs to propagate to boxen in which the mouse was pressed and is still held down
@@ -879,7 +828,7 @@ public final class Box extends JSScope implements Scheduler.Task {
// Trivial Helper Methods (should be inlined) /////////////////////////////////////////
- void mark_for_repack() { MARK_REPACK; }
+ void mark_for_repack() { REPACK(); }
public Enumeration keys() { 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()); }
@@ -931,12 +880,12 @@ public final class Box extends JSScope implements Scheduler.Task {
/** remove the i^th child */
public void removeChild(int i) {
Box b = getChild(i);
- MARK_REFLOW_b;
- b.dirty();
+ b.RECONSTRAIN();
+ b.DIRTY;
b.clear(MOUSEINSIDE);
deleteNode(i);
b.parent = null;
- MARK_REFLOW;
+ RECONSTRAIN();
putAndTriggerTrapsAndCatchExceptions("ChildChange", b);
}
@@ -989,10 +938,10 @@ public final class Box extends JSScope implements Scheduler.Task {
b.parent = this;
// need both of these in case child was already uncalc'ed
- MARK_REFLOW_b;
- MARK_REFLOW;
+ b.RECONSTRAIN();
+ RECONSTRAIN();
- b.dirty();
+ b.DIRTY;
putAndTriggerTrapsAndCatchExceptions("ChildChange", b);
}
}