/// module TDev { export class LayoutMgr { static instance: LayoutMgr = new LayoutMgr(); // Set in initAsync(), default.str public onBoxSelected: () => void = () => { }; public onRendered: () => void = () => { }; public numBoxes: number = 0; public changedtext: string; boxes: BoxBase[]; rootBox: BoxBase; //relayoutingBoxes: WallBox[]; rootElement: HTMLElement; scrollspeculation = true; pcTable: any; currentAstNodeId = ""; selectedBox: BoxBase = null; boxMenu: HTMLElement = null; //originalX: number; //originalY: number; minimumScale: number; maximumScale: number; scale: number; // TODO: should be reset after loading the script. scrollTop: number = undefined; scrollLeft: number = undefined; // zoomfactor: number = undefined; public editMode: boolean; public sideview = false; public updateEditMode(rt: Runtime) { // Set edit mode to true, // if the script is stopped or is running in live mode. this.editMode = rt.liveViewSupported() && (rt.isStopped() || rt.liveMode()); // Dive into the edit mode! if (this.editMode) { } else { // Get out of the edit mode... if (this.currentAstNodeId) { this.clearRelatedBoxes(); } if (this.selectedBox !== null) { this.unselectBox(); } this.adjustForNormalView(); } if (!!this.rootBox) this.updateRootElement(); //if (!!this.rootBox) { // this.boxes.forEach((box: WallBox) => // { box.setEditMode(editMode); }); // } } private static renderexecutionmode = false; public static RenderExecutionMode(): boolean { return LayoutMgr.renderexecutionmode; } public static SetRenderExecutionMode(val: boolean) { LayoutMgr.renderexecutionmode = val; } private static needrelayout = false; public static QueueReLayout() { // keep it simple... do it immediately. //if (!LayoutMgr.renderexecutionmode && LayoutMgr.instance.rootBox) { // LayoutMgr.instance.CoreLayout(); //} var check = () => { if (LayoutMgr.needrelayout) { if (!LayoutMgr.renderexecutionmode && LayoutMgr.instance.rootBox) { LayoutMgr.instance.CoreLayout(); LayoutMgr.needrelayout = false; } else TDev.Util.setTimeout(100, check); } }; if (!LayoutMgr.needrelayout) { LayoutMgr.needrelayout = true; TDev.Util.setTimeout(50, check); } } // functions for altering scroll behavior of top elements (switching between live view and actual view) public adjustForSideView() { // DUAL-TODO: remove? if (!this.sideview) { this.rootElement.style.overflow = "auto"; this.rootElement.style.msContentZooming = "zoom"; if (this.rootBox instanceof WallBox) (this.rootBox).setRenderedSideView(true); this.updateRootElement(); this.sideview = true; } } public adjustForNormalView() { // DUAL-TODO: remove? if (this.sideview) { var rootelt = this.rootElement; rootelt.style.overflow = ""; rootelt.style.msContentZooming = ""; rootelt.scrollTop = 0; rootelt.scrollLeft = 0; rootelt.msContentZoomFactor = 1; if (this.rootBox instanceof WallBox) (this.rootBox).setRenderedSideView(false); this.updateRootElement(); this.sideview = false; } } //private static TestAndClear(): boolean { // var n = needrelayout; // needrelayout = false; // return n; //} // typing activity - we use this to catch race behavior on keystrokes public lastbox_edited: string; public FlagTypingActivity(id: string) { this.lastbox_edited = id; } public ClearTypingActivity(id: string) { if (this.lastbox_edited === id) this.lastbox_edited = undefined; } public CheckTypingActivity(id: string) { return this.lastbox_edited === id; } static lazyInitCurrentRanderBox = () => {}; static htmlTagName:string; static createOrRecycleContainerBoxDelayed(rt: Runtime, cur: BoxBase) { var pc = rt.getTopScriptPc(); LayoutMgr.lazyInitCurrentRanderBox = () => { LayoutMgr.lazyInitCurrentRanderBox = () => {}; var tag = LayoutMgr.htmlTagName || "div"; LayoutMgr.htmlTagName = undefined; var w = WallBox.CreateOrRecycleContainerBox(rt, cur, pc, tag) LayoutMgr.currentbox = w; } LayoutMgr.currentbox = null; } static setHtmlTagName(name:string) { if (LayoutMgr.currentbox) Util.userError(lf("you cannot set the HTML tag name here")) LayoutMgr.htmlTagName = name; } static currentbox: BoxBase; static getCurrentRenderBox(): BoxBase { LayoutMgr.lazyInitCurrentRanderBox() return LayoutMgr.currentbox; } static setCurrentRenderBox(box: BoxBase) { LayoutMgr.lazyInitCurrentRanderBox() LayoutMgr.currentbox = box; } public findFirstBoxByNodeId(astNodeId: string): WallBox { if (!this.pcTable) return null; var box = this.pcTable[astNodeId]; return box === undefined ? null : box; } public findAllBoxesByNodeId(astNodeId: string): WallBox[] { var boxes = []; if (this.boxes) this.boxes.forEach((box: WallBox) => { if (box.getAstNodeId() === astNodeId) boxes.push(box); }); return boxes; } public findAllBoxesByPropertyNodeId(astNodeId: string): WallBox[] { var boxes = []; if (this.boxes) this.boxes.forEach((box: WallBox) => { var pcTable = box.pcTable; for (var propName in pcTable) { if (pcTable[propName] === astNodeId) { boxes.push(box); break; } } }); return boxes; } public getSelectedBox(): BoxBase { return this.selectedBox; } public setCurrentId(astNodeId: string) { this.currentAstNodeId = astNodeId; } public getCurrentId(): string { return this.currentAstNodeId; } // GUI selection public selectBox(box: BoxBase): boolean { if (this.selectedBox !== null) { // Skip this box? (propagate to the parent box.) var parent: BoxBase = box; while (parent !== null) { if (parent === this.selectedBox) return false; parent = parent.parent; } // Cancel previous selection. this.unselectBox(); } // Clear other highlights if (this.currentAstNodeId) this.clearRelatedBoxes(); // If the selected box is the root or a single child in a non-root box, cancel selection. if (!box || box.depth <= 0 || (box.isSingleChild && box.isLeaf() && box.depth > 1)) return false; this.selectedBox = box; this.highlightSelectedBox(); //Util.log("box is selected" + box); // Show the menu. if (this.onBoxSelected) this.onBoxSelected(); return true; } public showBoxMenu(cb: () =>void ) { var editButton = HTML.mkRoundButton("svg:edit,currentColor", lf("edit"), Ticks.wallEdit, () => { if (cb) cb(); }); this.boxMenu = div("wall-selected", [editButton]); this.refreshBoxMenu(); } public refreshBoxMenu() { if (this.boxMenu !== null) { this.updateBoxMenuPosition(); this.rootElement.appendChild(this.boxMenu); this.checkBoxMenuPosition(); } } public hideBoxMenu() { if (this.boxMenu !== null) { Animation.fadeOut(this.boxMenu).begin(); this.boxMenu = null; } } private updateBoxMenuPosition() { if (this.selectedBox === null || this.boxMenu === null) return; var box = this.selectedBox; var offset = Util.offsetIn(box.element, this.rootElement); var renderedScale = 1; // This is not 1 when the wall is scaled by transformation matrix. var viewWidth = this.rootElement.clientWidth, viewHeight = this.rootElement.clientHeight; var boxWidth = box.getRenderedWidth() * renderedScale, boxHeight = box.getRenderedHeight() * renderedScale; var viewLeft = offset.x * renderedScale, viewTop = offset.y * renderedScale; var margin = SizeMgr.topFontSize * 0.2; // Show it on the right side. if (viewLeft + boxWidth / 2 < viewWidth / 2) { this.boxMenu.style.right = "auto"; this.boxMenu.style.left = (viewLeft + boxWidth + margin) + "px"; // Show it on the left side. } else { this.boxMenu.style.left = "auto"; this.boxMenu.style.right = (viewWidth - (viewLeft - margin)) + "px"; } // Show it on the lower side. if (viewTop + boxHeight / 2 < viewHeight / 2) { this.boxMenu.style.bottom = "auto"; this.boxMenu.style.top = viewTop + "px"; // Show it on the upper side. } else { this.boxMenu.style.top = "auto"; this.boxMenu.style.bottom = (viewHeight - (viewTop + boxHeight)) + "px"; } } private checkBoxMenuPosition() { if (this.boxMenu === null) return; var box = this.selectedBox; var offset = Util.offsetIn(this.boxMenu, this.rootElement); var renderedScale = 1; // This is not 1 when the wall is scaled by transformation matrix. var viewWidth = this.rootElement.clientWidth, viewHeight = this.rootElement.clientHeight; var menuWidth = this.boxMenu.clientWidth * renderedScale, menuHeight = this.boxMenu.clientHeight * renderedScale; var viewLeft = offset.x * renderedScale - this.rootElement.scrollLeft, viewTop = offset.y * renderedScale - this.rootElement.scrollTop; var viewRight = viewLeft + menuWidth, viewBottom = viewTop + menuHeight; // Stick to the left edge. if (viewLeft < 0) { this.boxMenu.style.left = "0px"; this.boxMenu.style.right = "auto"; // Stick to the right edge. } else if (viewRight > viewWidth) { this.boxMenu.style.right = "0px"; this.boxMenu.style.left = "auto"; } // Stick to the top edge. if (viewTop < 0) { this.boxMenu.style.top = "0px"; this.boxMenu.style.bottom = "auto"; // Stick to the bottom edge. } else if (viewBottom > viewHeight) { this.boxMenu.style.bottom = "0px"; this.boxMenu.style.top = "auto"; } } public unselectBox() { if (this.selectedBox !== null) { this.findAllBoxesByNodeId(this.selectedBox.getAstNodeId()) .forEach((box: BoxBase) => { box.clearHighlight(); }); this.selectedBox = null; this.hideBoxMenu(); } } public highlightSelectedBox() { if (this.selectedBox !== null) { this.selectedBox.setHighlight(); this.scrollToShow(this.selectedBox); this.findAllBoxesByNodeId(this.selectedBox.getAstNodeId()) .forEach((box: WallBox) => { if (this.selectedBox !== box) box.setHighlight(false); }); } } public highlightRelatedBoxes() { if (this.selectedBox !== null) this.unselectBox(); if (this.currentAstNodeId) { var firstBox = this.findFirstBoxByNodeId(this.currentAstNodeId); if (firstBox !== null) { firstBox.setHighlight(); this.selectedBox = firstBox; } this.findAllBoxesByPropertyNodeId(this.currentAstNodeId) .forEach((box: WallBox) => { if (firstBox !== box) box.setHighlight(false); }); } } private clearRelatedBoxes() { if (this.currentAstNodeId) { this.findAllBoxesByPropertyNodeId(this.currentAstNodeId) .forEach((box: WallBox) => { box.clearHighlight(); }); } this.currentAstNodeId = ""; } public updateslider: (zoom: number, adjustscroll: boolean) => void; public createZoomingUI(): HTMLElement { var scaleSpan = span(null, ""); var scaleSliderCursor = div("cursor"); var scaleSlider = div("slider", [scaleSliderCursor]); this.updateslider = (zoom: number, adjustscroll:boolean) => { var oldScale = this.scale; this.scale = Math.max(this.minimumScale, Math.min(zoom, this.maximumScale)); scaleSliderCursor.style.top = "0"; scaleSliderCursor.style.left = (scaleSlider.clientWidth - scaleSliderCursor.clientWidth) * this.fromZoomToSliderValue(this.scale) + "px"; scaleSpan.setChildren([Math.round(this.scale * 100).toString() + "%"]); if (adjustscroll) { this.updateScaling(this.scale); if (this.scrollTop !== undefined) { // Zoom in the center part of the live view. this.scrollTop = this.scrollTop * this.scale / oldScale + this.rootElement.offsetHeight / 2 * (this.scale / oldScale - 1); this.scrollLeft = this.scrollLeft * this.scale / oldScale + this.rootElement.offsetWidth / 2 * (this.scale / oldScale - 1); this.recoverScroll(); } } }; new DragHandler(scaleSliderCursor, (e, dx, dy) => { var pos = Util.offsetIn(scaleSliderCursor, scaleSlider); var max = scaleSlider.clientWidth - scaleSliderCursor.clientWidth; this.updateslider(this.fromSliderValueToZoom(pos.x / max), true); }); var zoomOutButton = HTML.mkButtonElt("wall-zoom-out", "-"); zoomOutButton.withClick(() => { this.updateslider(this.scale * 0.9, true); }); var zoomInButton = HTML.mkButtonElt("wall-zoom-in", "+"); zoomInButton.withClick(() => { this.updateslider(this.scale * 1.1, true); }); var ui = div("wall-zoom", scaleSpan, zoomOutButton, scaleSlider, zoomInButton); return ui } private fromSliderValueToZoom(value: number): number { return this.minimumScale * Math.exp(Math.log(this.maximumScale / this.minimumScale) * value); } private fromZoomToSliderValue(zoom: number): number { return this.minimumScale >= this.maximumScale ? 1 : Math.log(zoom / this.minimumScale) / Math.log(this.maximumScale / this.minimumScale); } public getRootElement() { return this.rootElement; } public getRootBox() { return this.rootBox; } public updateRootElement() { this.rootElement.setAttribute("livemode", this.editMode ? "true" : "false"); } private calcDefaultScaling() { if (this.sideview) { if (this.scale === undefined || isNaN(this.scale)) { var horizontalScale = this.rootElement.offsetWidth / this.rootBox.element.offsetWidth; var verticalScale = this.rootElement.offsetHeight / this.rootBox.element.offsetHeight; this.minimumScale = Math.min(horizontalScale, verticalScale) * 0.5; this.maximumScale = this.minimumScale * 100; var defaultScale = Math.max(horizontalScale, verticalScale); this.scale = defaultScale; this.updateScaling(this.scale); } } else { this.updateScaling(1); } } private updateScaling(scale: number) { if (this.sideview && this.rootElement.msContentZoomFactor) { Util.setTransform(this.rootBox.element, "scale(" + this.minimumScale*10 + ", " + this.minimumScale*10 + ")", "0% 0%"); this.rootElement.msContentZoomFactor = scale / (this.minimumScale*10); this.rootElement.style.msContentZoomLimit = "10% 1000%"; } else { Util.setTransform(this.rootBox.element, "scale(" + scale + ", " + scale + ")", "0% 0%"); } } public scrollbarWidth: number = undefined; public scrollbarHeight: number = undefined; private FindScrollbarSizes(elt: HTMLElement) { if (this.scrollbarWidth === undefined) { var saved = elt.style.overflow || ""; this.scrollbarWidth = elt.clientWidth; this.scrollbarHeight = elt.clientHeight; elt.style.overflow = "scroll"; this.scrollbarWidth -= elt.clientWidth; this.scrollbarHeight -= elt.clientHeight; elt.style.overflow = saved; } } private recoverScroll() { if (this.sideview) { if (this.scrollTop !== undefined) { this.rootElement.scrollTop = this.scrollTop; this.rootElement.scrollLeft = this.scrollLeft; } //if (this.zoomfactor !== undefined) // this.rootElement.msContentZoomFactor = this.zoomfactor; } } public onScroll() { this.scrollTop = this.rootElement.scrollTop; this.scrollLeft = this.rootElement.scrollLeft; var zoomfactor = this.rootElement.msContentZoomFactor; if (zoomfactor) this.updateslider(zoomfactor * this.minimumScale * 10, false); } // public onZoom() { // // } private scrollToShow(box: BoxBase) { // TODO: Implement this. } public render(box: BoxBase, e: HTMLElement) { // "box" should be the root box. if (box === null || (box.parent !== null && box.parent !== undefined)) { return; } this.rootElement = e; // div wall-page box-page this.rootBox = box; box.isRoot = true; // Prevent flickering. var el = box.element; // el.style.visibility = "hidden"; // fix element structure if necessary if (this.rootElement.firstChild != el || ! this.rootBox.structuring_done) { this.CreateOrFixElementStructure(); } // the core layout algorithm this.CoreLayout(); //el.style.visibility = "visible"; } public isOnScreen(node: HTMLElement):boolean { var top = document.documentElement; while (node) { if (node === top) return true; if (node.style.display === "none") return false; node = node.parentNode; } return false; } // the idempotent layout algorithm. Call this to recompute layout on non-structural changes. public CoreLayout(): void { if (this.rootBox instanceof HtmlBox) { // we are doing almost nothing, just set attributes this.rootBox.doLayout(); return; } if (!this.isOnScreen(this.rootElement)) return; // breaks if we are not on screen, because we need browser to tell us size of things Util.time("CoreLayout", () => { //Util.log("start layout"); // make sure we know the size of scroll bars this.FindScrollbarSizes(this.rootElement); // if this view is scaled (e.g. because of pinch-zoom), ensure scale is in place if (this.sideview && this.scale !== undefined && this.scale != 1) this.updateScaling(this.scale); // the layout algorithm this.rootBox.doLayout(); // redo layout if we misspeculated on vertical scrollbars if (this.rootBox instanceof WallBox && !( this.rootBox).speculationwascorrect(this.scrollspeculation)) { this.scrollspeculation = !this.scrollspeculation; this.rootBox.doLayout(); } // Calculate default scaling this.calcDefaultScaling(); if (this.sideview) { this.recoverScroll(); this.updateslider(this.scale, true); } if (this.onRendered) this.onRendered(); // Util.log("end layout"); }, false); // set to true to stop measuring } private CreateOrFixElementStructure() { this.boxes = []; this.pcTable = {}; // Remove previous content unless recycled var e = this.rootElement; var el = this.rootBox.element; while (e.hasChildNodes()) { var b = e.firstChild; if (b == el) break; e.removeChild(b); } while (e.hasChildNodes()) { var b = e.lastChild; if (b == el) break; e.removeChild(b); } // recurse this.passS(this.rootBox); // don't do it again this.rootBox.structuring_done = true; } private passS(box: BoxBase) { if (box.doLiveNavigation()) { this.boxes.push(box); this.recordmapping(box); } box.visitS(); for (var i = 0; i < box.children.length; i++) this.passS(box.children[i]); } /* private doLayout(rootBox: WallBox) { // Horizontal layout // 1. make box groups // 2. compute widths of leaf nodes without wrapping // 3. set widths if specified explicitly var bottomUpQueue = this.prepareHorizontalLayout(rootBox); // 4. make box lines // 5. for each line, distribute the rest horizontal space among springs this.calcHorizontalSprings(bottomUpQueue); // Vertical layout // 6. set heights if specified explicitly this.prepareVerticalLayout(rootBox); // 7. distribute the rest vertical space among springs this.calcVerticalSprings(bottomUpQueue); } */ // Put this box in the database. public recordmapping(box: BoxBase) { if (this.pcTable[box.getAstNodeId()] === undefined) this.pcTable[box.getAstNodeId()] = box; for (var propName in box.pcTable) if (this.pcTable[box.pcTable[propName]] === undefined) this.pcTable[box.pcTable[propName]] = box; } } export class WallPage { libName: string; pageName: string; drawFn: any; drawArgs: any[]; topDown: boolean = false; buttons: IPageButton[]; crashed = false; id = Random.uniqueId(); csslayout: boolean = false; model:any; // the compile sticks stuff in here private element: HTMLElement; private rootBox: BoxBase; private currentBox: BoxBase; public lastChildCount = -1; private runtime: Runtime; private _rtPage: TDev.RT.Page; private auto: boolean; public title = ""; public subtitle = ""; public chromeVisible = true; public backButtonVisible = true; public fgColor = "#000000"; public bgColor = "#ffffff"; public bgPictureUrl: string; public bgPicture: HTMLElement; public bgPictureWidth:number; public bgPictureHeight:number; public bgVideo: HTMLVideoElement; public fullScreenElement: HTMLElement; public renderCount = 0; public onNavigatedFrom: RT.Event_ = new RT.Event_(); constructor (rt: Runtime, auto:boolean) { this.runtime = rt; this.element = div("wall-page"); this.buttons = []; this.auto = auto; this.onNavigatedFrom.isPageEvent = true; this.clear(); } activate(): void { this.getElement().style.display = "block"; } deactivate(): void { this.getElement().style.display = "none"; } getElement(): HTMLElement { return this.element; } isAuto() { return this.auto; } public isReversed(): boolean { return this.topDown; } public setReversed(reversed: boolean) { if (this.topDown != reversed) { this.lastChildCount = -1; this.topDown = reversed; } } public rtPage() : TDev.RT.Page { if (!this._rtPage) this._rtPage = TDev.RT.Page.mk(this); return this._rtPage; } static applySizeUpdate(e: HTMLElement) { var walkHtml = (e: any) => { if (!e) return; if (e.updateSizes) e.updateSizes(); Util.childNodes(e).forEach(walkHtml) } walkHtml(e); } getFrame(prev: IStackFrame, ret: IContinuationFunction) { var rt = prev.rt; if (!this.isAuto()) { var frame: IStackFrame = {}; frame.previous = prev; frame.rt = prev.rt; frame.returnAddr = ret; frame.entryAddr = (s) => { return s.rt.leave() }; return frame; } if (!this.drawFn) { var f = prev.rt.compiled.lookupLibPage(this.libName, this.pageName); this.drawFn = (prev, ret) => { var newFrame = f(prev); return newFrame.invoke.apply(null, ([newFrame, ret]).concat(this.drawArgs)); }; } return this.drawFn(prev, ret); } refreshForNewScript() { this.drawFn = null; //this.element = div("wall-page"); NOOOO dont do this... it breaks incremental layout } getCurrentBox(): BoxBase { // Util.log("get current box" + this.currentBox.id); return this.currentBox; } setCurrentBox(box: BoxBase) { this.currentBox = box; //Util.log("set current box" + box.id); } setFullScreenElement(host: RuntimeHost, elt: HTMLElement) { this.fullScreenElement = elt; host.setFullScreenElement(elt); } clear() { this.rootBox = WallBox.CreateOrRecycleRoot(this.runtime, null); // null forces creation this.setCurrentBox(this.rootBox); this.lastChildCount = -1; } startrender() { this.renderCount++; this.rootBox = WallBox.CreateOrRecycleRoot(this.runtime, this.rootBox); this.setCurrentBox(this.rootBox); this.lastChildCount = -1; } render(host: RuntimeHost, popCount: number = 0) { Util.assertCode(popCount >= 0); var rootElt = this.getElement(); var getElt = (b: WallBox) => { return div("legacy-wall-box", b.getContent()); } // always apply wall background, foreground colors, // ignoring picture/video background for now. rootElt.style.background = "none"; // see through to the real background as set in applyPageAttributes rootElt.style.color = this.fgColor; if (this.isAuto()) { //if (this.rootBox.isGeneric()) { rootElt.className = "wall-page " + (this.csslayout ? " html-page" : "box-page"); this.setFullScreenElement(host, null); LayoutMgr.instance.render(this.rootBox, rootElt) } else { Util.assert(this.rootBox instanceof WallBox); var i = 0; var sz = (this.rootBox).size() var newElts = [] if (sz > 0) { var last = (this.rootBox).get(sz - 1); if ((last).fullScreen) { this.setFullScreenElement(host,div("wall-fullscreen", last.getContent())); this.lastChildCount = -1; return; } } this.setFullScreenElement(host,null); rootElt.className = "wall-page classic-page"; // push front boxes if (this.lastChildCount < 0) { var children: WallBox[] = [] for (i = 0; i < sz; ++i) children.push( (this.rootBox).get(this.isReversed() ? i : sz - i - 1)) rootElt.setChildren(children.map(getElt)) newElts = [rootElt] } else { for (i = this.lastChildCount - popCount; i < sz; ++i) { var ch = getElt( (this.rootBox).get(i)) newElts.push(ch) if (this.isReversed()) { rootElt.appendChild(ch) } else { var first = rootElt.firstChild; if (!first) rootElt.appendChild(ch) else rootElt.insertBefore(ch, first) } } // incremental pop back boxes for (var i = 0; i < popCount; ++i) { if (this.isReversed()) { var firstChild = rootElt.firstChild; if (firstChild) rootElt.removeChild(firstChild); } else { var lastChild = rootElt.lastChild; if (lastChild) rootElt.removeChild(lastChild); } } } this.lastChildCount = sz; newElts.forEach(WallPage.applySizeUpdate) } } } export interface BoxBackgroundImage { url: string; size?: string; repeat?: string; attachment?:string; position?: string; origin?: string; } export class BoxAttributes { public tappedEvent: RT.Event_; //public editEvent: RT.Event_; public textEditingEvent: RT.Event_; constructor() { this.tappedEvent = this.mkEv(); this.textEditingEvent = this.mkEv(); } // abstract methods public applyToStyle(b: BoxBase) { Util.oops("must override"); } public mkEv() { var r = new RT.Event_(); r.isPageEvent = true; return r; } } export class HtmlAttributes extends BoxAttributes { public classnames: string[]; public styles: StringMap; public attributes: StringMap; public applyToStyle(b: BoxBase) { Util.assert(b instanceof HtmlBox); var bb = b; if (bb.element.nodeType == Node.ELEMENT_NODE) { bb.setRenderedClassnames(this.classnames); bb.setRenderedStyles(this.styles); bb.setRenderedAttributes(this.attributes); } } } export class LayoutAttributes extends BoxAttributes { public flow:number; // FLOW_HORIZONTAL, FLOW_VERTICAL, FLOW_OVERLAY public textalign: number; // TEXT_LEFT, TEXT_CENTER, TEXT_RIGHT, TEXT_JUSTIFY public fontSize: number; public fontWeight: string; public fontFamily: string; public background: string; public backgroundImages: BoxBackgroundImage[]; public foreground: string; public border: string; public width: number[]; // width[MIN], width[MAX] public height: number[]; // height[MIN], height[MAX] public margin: number[]; // margin[T], margin[R], margin[B], margin[L] public padding: number[]; // padding[T], padding[R], padding[B], padding[L] public borderwidth: number[]; public stretchwidth: number; // -1 (auto) 0 (no stretching) >0 (stretch weight) public stretchheight: number; // -1 (auto) 0 (no stretching) >0 (stretch weight) public stretchmargin: number[]; // [T,R,B,L] each being 0 (no stretching) or >0 (stretch weight) public scroll: boolean[]; // [H,V] public arrangement: number[]; // [H,V] public wrap: boolean; public wraplimit: number; // for compatibility with old API public legacystretch: boolean[]; public legacybaseline: boolean; public textEditedEvent: RT.Event_; constructor(isroot: boolean) { super(); // Set to the default values this.flow = WallBox.FLOW_VERTICAL; //this.textalign = undefined; this.fontSize = 0; //this.fontWeight = undefined; this.fontFamily = ""; this.background = "transparent"; //this.foreground = undefined; //this.border = undefined; this.width = [0, Infinity]; this.height = [0, Infinity]; this.margin = [0, 0, 0, 0]; this.padding = [0, 0, 0, 0]; this.stretchwidth = -1; this.stretchheight = -1; this.stretchmargin = [0, 0, 0, 0]; this.borderwidth = [0, 0, 0, 0]; this.arrangement = [undefined, WallBox.ARRANGE_BASELINE]; this.scroll = [isroot, isroot]; this.legacystretch = [false, false]; this.legacybaseline = true; this.wrap = undefined; this.wraplimit = 15; this.textEditedEvent = this.mkEv(); } public applyToStyle(b: WallBox) { b.setRenderedPositionMode("absolute"); b.setRenderedTextAlign(this.textalign); b.setRenderedBackgroundColor(this.background); b.setRenderedBackgroundImages(this.backgroundImages); b.setRenderedColor(this.foreground); b.setRenderedFontWeight(this.fontWeight); b.setRenderedFontFamily(this.fontFamily); b.setRenderedFontSize(this.fontSize); b.setRenderedWrap(this.wrap, this.wraplimit); b.setRenderedBorder(this.border, this.borderwidth); } } export class BoxBase { // structural info public id: number; public depth: number; private astNodeId: string; public isRoot: boolean; public parent: BoxBase; public obsolete = false; // attributes and children public attributes: BoxAttributes; public children: BoxBase[]; // reuse of boxes public recycled = false; private prevchildren: BoxBase[]; private reuseindex = 0; private reusekey: any; private reuseversion: number; private replaces: BoxBase; // content private fresh = true; public element: HTMLElement; // other public runtime: Runtime; public pcTable: any; public structuring_done = false; public isSingleChild = false; layoutcompletehandler: (width: number, height: number) => void; public delayedlayout = false; constructor(rt: Runtime, parent: BoxBase, nodeId: string) { this.runtime = rt; this.id = LayoutMgr.instance.numBoxes++; this.astNodeId = nodeId; this.isRoot = false; this.parent = parent; if (this.parent) { this.parent.children.push(this); this.depth = this.parent.depth + 1; } else { this.depth = 0; } this.children = []; this.pcTable = { "": this.astNodeId }; } // for leaf boxes - overridden public getContent(): HTMLElement { return undefined; } public setContent(e: any) { } public RefreshOnScreen(): void { } public SwapImageContent(newcontent: HTMLElement): void { } public hookContent(): void { } public mayReplaceWith(tagname?: string): boolean { return false; } // overridden public isLeaf(): boolean { return false } // overridden // for live navigation public doLiveNavigation(): boolean { return true; } public getRenderedWidth(): number { return 0; } // overridden public getRenderedHeight(): number { return 0; } // overridden public setHighlight(strong: boolean = true) { }// overridden public clearHighlight() { }// overridden public Obsolete() { this.obsolete = true; this.children.forEach(c => c.Obsolete()); } public getAstNodeId(): string { return this.astNodeId; } // We don't use this since it fails when the default value for a method parameter is set. // (Strada compiler hides the callee name.) private onFunctionCall(f: (any) => any, pc: string) { var functionNames = f.toString().match(/function ([^\(]+)/); if (functionNames !== null) { var functionName = functionNames[1]; this.onCall(functionName, pc); } } public onCall(fName: string, pc: string) { if (!pc) return; this.pcTable[fName] = pc; } // --------------- tap events tapped() { var done = false; if (LayoutMgr.instance.editMode) { // live navigation if (LayoutMgr.instance.selectBox(this)) done = true; } else { if (this.obsolete || (this.runtime.eventQ && !this.runtime.eventQ.viewIsCurrent())) return; // box may have been deleted or not reflect proper action if (this.attributes.tappedEvent.handlers) { done = true; this.setRenderedTappable(true, true); this.runtime.queueLocalEvent(this.attributes.tappedEvent); this.runtime.forcePageRefresh(); } else if (this.contenttapapplies()) { this.contenttaphandler(); done = true; } } if (LayoutMgr.instance.editMode || this instanceof WallBox) // bubble up tap events if (!done && this.parent) this.parent.tapped(); } // click handlers that are installed by posted content private contenttaphandler: () => void; public withClick(h: () => void): void { this.contenttaphandler = h; } public contenttapapplies(): boolean { return this.contenttaphandler && !this.attributes.tappedEvent.handlers && !(this.isSingleChild && this.parent.attributes.tappedEvent.handlers); } // overridden by WallBox - creates grey shadow setRenderedTappable(tappable: boolean, tapped: boolean) { } // ---------- editable text binding private inputversions = new Array(); private lastqueuededit: RT.Event_; static debuginput: boolean = false; public bindEditableText(s: string, handler: any /* RT.TextAction or Ref */, pc: string) { this.addTextEditHandler(handler); this.setInputText(s); this.onCall("binding", pc); } private addTextEditHandler(handler: any /* RT.TextAction or Ref */) { if (handler instanceof RT.Ref) { this.attributes.textEditingEvent.addHandler(new RT.PseudoAction((rt: Runtime, args: any[]) => { (> handler)._set(args[0], rt.current); rt.forcePageRefresh(); })); } else // RT.TextAction this.attributes.textEditingEvent.addHandler(handler); } // virtual methods (these are specific to HtmlBox bs. LayoutBox) public getEditableContent(): string { Util.oops("virtual"); return undefined; } public setEditableContent(s: string) { Util.oops("virtual"); } public invalidateCachedLayout(triggerdelayedrelayout: boolean) { } onInputTextChange() { if (LayoutMgr.instance.editMode) return; // don't do anything in edit mode var text = this.getEditableContent(); if (this.obsolete) return; // box may be already gone from screen if (this.inputversions.length === 3) this.inputversions.pop(); // never keep more than 3 versions if (this.inputversions[this.inputversions.length - 1] === text) { if (WallBox.debuginput) Util.log("&&&" + this.id + " flag \"" + this.inputversions + "\""); LayoutMgr.instance.FlagTypingActivity("i" + this.id); // for catching race return; // already being processed } this.inputversions.push(text); if (WallBox.debuginput) Util.log("&&&" + this.id + " push \"" + this.inputversions + "\""); var parent = this.parent; if (this.attributes.textEditingEvent.handlers) { this.invalidateCachedLayout(false); if (this.inputversions.length >= 2) { this.runtime.queueLocalEvent(this.lastqueuededit = this.attributes.textEditingEvent, [this.inputversions[1]]); this.runtime.forcePageRefresh(); } } else { // no need for full refresh... just do a delayed relayout this.invalidateCachedLayout(true); } } setInputText(text: string) { var cur = this.getEditableContent(); var requeue = false; if (text === this.inputversions[1]) { this.inputversions.shift(); // prune history if (WallBox.debuginput) Util.log("&&&" + this.id + " prune \"" + this.inputversions + "\""); // record additional changes if (text !== cur) { this.inputversions[1] = cur; requeue = true; } } else if (this.inputversions.length === 1 && text != cur && LayoutMgr.instance.CheckTypingActivity("i" + this.id)) { this.inputversions = [text, cur]; // skip the version by changing the event argument LayoutMgr.instance.ClearTypingActivity("i" + this.id); if (WallBox.debuginput) Util.log("&&&" + this.id + " skip \"" + this.inputversions + "\""); requeue = true; } //else if (text === this.inputversions[0] && this.lastqueuededit && this.lastqueuededit.inQueue) { // an update event is pending //Util.oops("should not refresh screen while page events are pending"); //if (WallBox.debuginput) Util.log("&&&" + this.id + " wait \"" + this.inputversions + "\""); //} else { this.inputversions = [text]; // start new history, discard intermediate versions this.lastqueuededit = undefined; if (WallBox.debuginput) Util.log("&&&" + this.id + " set \"" + this.inputversions + "\""); } if (this.inputversions.length == 1) { // the current version is final - write it back if (text !== cur) { this.setEditableContent(text); this.invalidateCachedLayout(false); } LayoutMgr.instance.ClearTypingActivity("i" + this.id); } else if (this.inputversions.length == 2 && requeue) { // there were more edits since the last edit event -- requeue if (this.attributes.textEditingEvent.handlers) { if (WallBox.debuginput) Util.log("&&&" + this.id + " requeue \"" + this.inputversions + "\""); this.runtime.queueLocalEvent(this.lastqueuededit = this.attributes.textEditingEvent, [this.inputversions[1]]); //this.runtime.forcePageRefresh(); } } } // ----------- online tree diffing public static CreateOrRecycleRoot(rt: Runtime, p: BoxBase): BoxBase { var cssmode = rt.onCssPage(); if (LayoutMgr.RenderExecutionMode() && p && (cssmode == p instanceof HtmlBox)) { return p.recycle(rt, null, "", rt.onCssPage()); } else { return cssmode ? ( new HtmlBox(rt, null, "", "div")) : ( new WallBox(rt, null, "")); } } public static CreateOrRecycleContainerBox(rt: Runtime, cur: BoxBase, pc, tagName?:string):BoxBase { var candidate = null; if (LayoutMgr.RenderExecutionMode() && cur.recycled && cur.reuseindex < cur.prevchildren.length) { candidate = cur.prevchildren[cur.reuseindex]; cur.reuseindex = cur.reuseindex + 1; if (candidate.mayReplaceWith(tagName)) return candidate.recycle(rt, cur, pc, rt.onCssPage()); } var box = rt.onCssPage() ? new HtmlBox(rt, cur, pc, tagName) : new WallBox(rt, cur, pc); box.replaces = candidate; return box; } public static CreateOrRecycleLeafBox(rt: Runtime, val: any): BoxBase { var cur = rt.getCurrentBoxBase(true); var candidate = null; var pc = rt.getTopScriptPc(); if (LayoutMgr.RenderExecutionMode() && cur.recycled && cur.reuseindex < cur.prevchildren.length) { candidate = cur.prevchildren[cur.reuseindex]; cur.reuseindex = cur.reuseindex + 1; if (val !== null && candidate.content && candidate.reusekey === val && (!candidate.reuseversion || (candidate.reuseversion === (val).versioncounter))) { return candidate.recycle(rt, cur, pc, rt.onCssPage()); } } var cssmode = rt.onCssPage() var box = rt.onCssPage() ? new HtmlBox(rt, cur, pc) : new WallBox(rt, cur, pc); box.reusekey = val; box.reuseversion = val && val.versioncounter; box.replaces = candidate; return box; } public recycle(rt: Runtime, parent: BoxBase, nodeId: string, cssmode: boolean): BoxBase { Util.assert(!!cssmode === (this instanceof HtmlBox), "mixed up boxes"); this.runtime = rt; this.parent = parent; this.astNodeId = nodeId; if (this.parent) { this.parent.children.push(this); Util.assert(this.depth === this.parent.depth + 1); } else { Util.assert(this.depth === 0); } this.recycled = true; this.structuring_done = false; this.prevchildren = this.children; this.reuseindex = 0; this.children = []; this.attributes = cssmode ? new HtmlAttributes() : new LayoutAttributes(this.depth == 0); this.pcTable = { "": this.astNodeId }; return this; } // ----------- tree traversals public doLayout() { /*overridden*/ } public visitS() { // remove deleted children if (this.recycled) { for (var i = this.children.length; i < this.prevchildren.length; i++) { var b = this.prevchildren[i]; b.Obsolete(); this.element.removeChild(b.element); } } // add to parent if fresh, or replace var p = this.parent; if (this.fresh || (p && !p.recycled)) { // add into parent container if (p) { if (this.replaces) { this.replaces.Obsolete(); p.element.replaceChild(this.element, this.replaces.element); this.replaces = null; } else { p.element.appendChild(this.element); } } else { Util.assert(this.isRoot); LayoutMgr.instance.rootElement.appendChild(this.element); LayoutMgr.instance.rootElement.onscroll = (e: Event) => { LayoutMgr.instance.onScroll(); }; //LayoutMgr.instance.rootElement.onzoom = (e: Event) => { LayoutMgr.instance.onZoom(); }; } // set content, if fresh if (this.fresh) { this.hookContent(); this.fresh = false; } } } public visitI() { this.attributes.applyToStyle(this); } } export class HtmlBox extends BoxBase { // specialize types public attributes: HtmlAttributes; public tagName: string; public isLeaf(): boolean { return !this.tagName; } constructor(rt: Runtime, parent: BoxBase, nodeId: string, tagName?: string) { super(rt, parent, nodeId); if (parent) Util.assert(parent instanceof HtmlBox); this.attributes = new HtmlAttributes(); if (tagName !== undefined) { this.tagName = tagName; if (tagName && !HTML.allowedTagName(tagName)) Util.userError(lf("tag name {0} is not allowed", tagName)) this.element = document.createElement(tagName); this.element.id = this.id.toString(); this.attributes.applyToStyle(this); } } public mayReplaceWith(tagName?: string): boolean { return (this.tagName === tagName); } public doLiveNavigation(): boolean { return !!this.tagName; } public setContent(e: any) { Util.check(this.isLeaf()); Util.check(e != null); this.element = e; this.attributes.applyToStyle(this); } public getContent() { return this.element; } public RefreshOnScreen(): void { // no action needed.. html layout is always on } public invalidateCachedLayout(triggerdelayedrelayout: boolean) { // no action needed... html layout is always on } public withClick(h: () => void): void { //todo } public getRenderedWidth(): number { return this.element.clientWidth; } public getRenderedHeight(): number { return this.element.clientHeight; } public getEditableContent(): string { return (this.element).value; // return this.textarea ? (this.content).value : (this.content).value } public setEditableContent(text: string) { (this.element).value = text; // if (this.textarea) // (this.content).value = text; // else // (this.content).value = text; } public hookContent() { var tag = this.element && this.element.tagName; if (tag) { this.element.withClick(() => { this.tapped() }); if (/input|textarea/i.test(tag)) this.element.oninput = (e: Event) => { this.onInputTextChange(); }; } } public SwapImageContent(newcontent: HTMLElement): void { var p = this.element.parentNode; if (p) p.replaceChild(newcontent, this.element); this.element = newcontent; } public setHighlight(strong: boolean = true) { if (this.element && this.element.style) { this.element.style.border = strong ? "5px dotted #C00" : "5px dotted #rgba(204, 0, 0, 0.6)"; if (!strong) this.element.style.background = "rgba(204, 0, 0, 0.4)"; } } public clearHighlight() { this.element.style.cssText = this.rendered_styles; } public doLayout() { this.passA(); } private passA() { this.visitI(); for (var i = 0; i < this.children.length; i++) { (this.children[i]).passA(); } } public addClassName(s: string, pc = "") { if (!this.attributes.classnames) this.attributes.classnames = []; this.attributes.classnames.push(s); this.onCall("class name", pc); } public setAttribute(name: string, value: string, pc = "") { if (!this.attributes.attributes) this.attributes.attributes = {}; this.attributes.attributes[name] = value; this.onCall("attr:" + name, pc); } public setStyle(property: string, value:string, pc = "") { if (!this.attributes.styles) this.attributes.styles = {}; this.attributes.styles[property] = value; this.onCall("style:" + property, pc); } private rendered_classnames: string; private rendered_styles: string; private rendered_attributes: string; setRenderedClassnames(names: string[]) { var cmp = (names && names.length > 0) ? names.join(' ') : ''; if (cmp !== this.rendered_classnames) { if (cmp) this.element.className = cmp; else this.element.removeAttribute("class"); this.rendered_classnames = cmp; } } setRenderedStyles(styles: StringMap) { var s = styles ? Object.keys(styles).map(k => k + ": " + styles[k]).join("; ") : ""; if (s !== this.rendered_styles) { this.element.style.cssText = s; this.rendered_styles = s; } } setRenderedAttributes(attributes: StringMap) { var s = JSON.stringify(attributes); if (s !== this.rendered_attributes) { var prev = JSON.parse(this.rendered_attributes || "{}"); // set new keys Object.keys(attributes).forEach(k => { this.element.setAttribute(k, attributes[k]); delete prev[k]; }); // remove keys that weren't overrideen Object.keys(prev).forEach(k => this.element.removeAttribute(k)); // store new set of attributes this.rendered_attributes = s; } } } export class WallBox extends BoxBase { // specialize types public attributes: LayoutAttributes; // Constants static FLOW_HORIZONTAL: number = 0; static FLOW_VERTICAL: number = 1; static FLOW_OVERLAY: number = 2; static STRETCH_AUTO: number = -1; static ARRANGE_LEFT: number = 1; static ARRANGE_RIGHT: number = 2; static ARRANGE_CENTER: number = 3; static ARRANGE_JUSTIFY: number = 4; static ARRANGE_BASELINE: number = 5; static ARRANGE_TOP: number = 6; static ARRANGE_BOTTOM: number = 7; static ARRANGE_SPREAD: number = 8; static MIN: number = 0; static MAX: number = 1; static T: number = 0; static R: number = 1; static B: number = 2; static L: number = 3; static H: number = 0; static V: number = 1; static CONTENT_NONE: number = -1; static CONTENT_TEXT: number = 0; static CONTENT_IMAGE: number = 1; static CONTENT_INPUT: number = 2; // cached size info private cached_width = -1; // the natural (full) width of the element private cached_height = -1; // the natural height of the element, for the given cached_width private cached_baseline = -1; // the baseline of this element private cached_aspectratio = 0; // width / height, or zero if this element does not use a ratio // New layout algorithm private A_m: number; // requested minwidth private A_as: number;// requested alt width private A_s: number; // requested width private A_sl: number;// requested left margin private A_sr: number;// requested right margin private A_mc: number;// computed minwidth private A_asc: number;// computed alt width private A_sc: number;// computed width private A_scc: number;// computed width for content private A_fcc: number;// computed fill count for content private A_fcs: number;// computed fill count for spaces private A_f: number; // requested fill width for content private A_fl: number;// requested left margin fill private A_fr: number;// requested right margin fill private A_fp: number; // requested fill propagation private A_scr: number; // space needed for scrollbar private B_s: number; // granted width private B_x: number; // computed x coordinate of box private B_scr: boolean; // scrolling private C_m: number; // requested minheight private C_s: number; // requested height private C_st: number;// requested top margin private C_sb: number;// requested bottom margin private C_sc: number;// computed height private C_scc: number;// computed height for content private C_fcc: number;// computed fill count for content private C_fcs: number; // computed fill count for spaces private C_mc: number; // requested minheight private C_f: number; // requested fill height private C_ft: number;// requested top margin fill private C_fb: number;// requested bottom margin fill private C_fp: number;// requested fill propagation private C_scr: number; // space needed for scrollbar private C_zc: number; // number of z positions needed private C_bc: number; // computed baseline private C_b: number; // reported baseline private D_s: number; // granted height private D_y: number; // computed y coordinate of box private D_scr: boolean; // scrolling private D_z: number; // zrange private D_zmax: number; // zrange private D_b: number; // baseline // Fields that are computed and set during the layouting. private rendered_x: number; private rendered_y: number; private rendered_width: number; private rendered_height: number; private rendered_hmode: string; private rendered_vmode: string; private rendered_b: number; private rendered_fontfamily: string; private rendered_fontweight: string; private rendered_fontsize: number; private rendered_textalign: number; private rendered_foregroundcolor: string; private rendered_backgroundcolor: string; private rendered_background: string; private rendered_wrap: boolean; private rendered_wraplimit: number; private rendered_zindex: number; private rendered_border: string; private rendered_borderwidth: number[]; private rendered_sideview: boolean; private rendered_tappable: string; private rendered_positionmode: string; // content private contentType: number; private content: HTMLElement; private auxcontent: HTMLElement; private baselineprobe: HTMLElement; public textarea: boolean; //other public fullScreen: boolean; constructor (rt: Runtime, parent: BoxBase, nodeId: string) { super(rt, parent, nodeId); if (parent) Util.assert(parent instanceof WallBox); this.attributes = new LayoutAttributes(this.depth == 0); this.contentType = WallBox.CONTENT_NONE; this.content = null; this.element = document.createElement("div"); this.element.id = this.id.toString(); this.element.withClick(() => { this.tapped() }); this.attributes.applyToStyle(this); } public mayReplaceWith(tagname?: string): boolean { return (tagname === "div" && this.content === null && this.contentType === WallBox.CONTENT_NONE); } public isLeaf(): boolean { return this.contentType != WallBox.CONTENT_NONE; } public RefreshOnScreen(): void { if (this.contentType == WallBox.CONTENT_NONE) return; // too early... content not set yet Util.assert(this.contentType == WallBox.CONTENT_IMAGE); this.cached_height = -1; this.cached_width = -1; LayoutMgr.QueueReLayout(); } public SwapImageContent(newcontent: HTMLElement):void { Util.assert(this.contentType == WallBox.CONTENT_IMAGE); this.element.removeAllChildren(); this.element.appendChild(newcontent); this.content = newcontent; this.cached_height = -1; this.cached_width = -1; LayoutMgr.QueueReLayout(); } public hookContent(): void { if (this.content && this.content !== this.element) { var e = this.element; if (this.contentType === WallBox.CONTENT_TEXT) { } else if (this.contentType === WallBox.CONTENT_INPUT) { //this.content.onkeyup = (e: Event) => { // this.onInputTextChange(); //}; this.content.oninput = (e: Event) => { this.onInputTextChange(); }; this.content.onchange = (e: Event) => { this.onInputTextChangeDone(); }; this.content.onclick = (e: Event) => { if (LayoutMgr.instance.editMode) { this.tapped(); } else { if (this.attributes.tappedEvent.handlers) { this.runtime.queueLocalEvent(this.attributes.tappedEvent); } } }; } if (this.auxcontent) e.appendChild(this.auxcontent); e.appendChild(this.content); } } public setHighlight(strong: boolean = true) { this.element.style.zIndex = strong ? "2" : "1"; this.element.setAttribute("sel", strong ? "strong" : "weak"); } public clearHighlight() { this.element.setAttribute("sel", ""); this.element.style.zIndex = "auto"; } public visitI() { if (!this.isRoot) { //this.rendered_x = undefined; //this.rendered_y = undefined; //this.rendered_width = undefined; // this.rendered_height = undefined; //this.rendered_hscrollbar = false; //this.rendered_vscrollbar = false; // set fontfamily and fontsize if they were not specified if (this.getFontFamily() === "") this.setFontFamily(( this.parent).getFontFamily()); if (this.getFontSize() <= 0) this.setFontSize(( this.parent).getFontSize()); // set wrapping if this a multiline input and it was not specified } else { // Calculate size of the wall var width = this.runtime.host.fullWallWidth(); var height = this.runtime.host.userWallHeight(); // restore full element size (not sure if needed) this.setRenderedWidth(width); this.setRenderedHeight(height); // overrides any user-specified sizes... they are meaningless for root box //this.setWidth(width); // this.setHeight(height); // set fontfamily and fontsize if they were not specified if (this.getFontFamily() === "") this.setFontFamily('"Segoe UI", "Segoe WP", "Helvetica Neue", Sans-Serif') if (this.getFontSize() <= 0) this.setFontSize(SizeMgr.topFontSize); } // determine if this is a single child var parent = this.parent; if (parent && parent.children.length === 1) this.isSingleChild = true; // leaf boxes get some attributes from parent if (this.contentType == WallBox.CONTENT_TEXT) { var parentattributes = (parent.attributes); (this.attributes).textalign = parentattributes.textalign; this.attributes.wrap = parentattributes.wrap; this.attributes.wraplimit = parentattributes.wraplimit; } if (this.contentType == WallBox.CONTENT_INPUT) { var parentattributes = (parent.attributes); this.attributes.textalign = WallBox.ARRANGE_LEFT; // this is the only thing that works on input boxes this.attributes.wrap = this.textarea ? (parentattributes.wrap === undefined ? true : parentattributes.wrap) : false; this.attributes.wraplimit = parentattributes.wraplimit; } if (this.contentType == WallBox.CONTENT_IMAGE) { var parentattributes = (parent.attributes); this.attributes.textalign = parentattributes.textalign; this.attributes.width = parentattributes.width; this.attributes.height = parentattributes.height; if (parentattributes.stretchwidth !== -1) this.attributes.stretchwidth = parentattributes.stretchwidth; // influences min width if (parentattributes.stretchheight !== -1) this.attributes.stretchheight = parentattributes.stretchheight; this.attributes.wrap = parentattributes.wrap; this.attributes.wraplimit = parentattributes.wraplimit; } var numchildren = this.children.length; // arrangement sets stretch margins var harr = this.attributes.arrangement[WallBox.H]; if (harr !== undefined) { if (this.attributes.flow !== WallBox.FLOW_HORIZONTAL) { for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.attributes.stretchmargin[WallBox.L] = (harr == WallBox.ARRANGE_RIGHT || harr == WallBox.ARRANGE_CENTER || harr == WallBox.ARRANGE_SPREAD) ? 1 : 0; child.attributes.stretchmargin[WallBox.R] = (harr == WallBox.ARRANGE_LEFT || harr == WallBox.ARRANGE_CENTER || harr == WallBox.ARRANGE_SPREAD) ? 1 : 0; } } else { for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.attributes.stretchmargin[WallBox.L] = (harr == WallBox.ARRANGE_SPREAD || (i == 0 ? (harr == WallBox.ARRANGE_RIGHT || harr == WallBox.ARRANGE_CENTER) : (harr == WallBox.ARRANGE_JUSTIFY))) ? 1 : 0; child.attributes.stretchmargin[WallBox.R] = (harr == WallBox.ARRANGE_SPREAD || (i == (numchildren - 1) ? (harr == WallBox.ARRANGE_LEFT || harr == WallBox.ARRANGE_CENTER) : (harr == WallBox.ARRANGE_JUSTIFY || harr == WallBox.ARRANGE_CENTER))) ? 1 : 0; } //if (this.attributes.stretchwidth === -1 && (harr == WallBox.ARRANGE_LEFT || harr === WallBox.ARRANGE_RIGHT || harr === WallBox.ARRANGE_CENTER)) // this.attributes.stretchwidth = 1; } } var varr = this.attributes.arrangement[WallBox.V]; if (varr !== WallBox.ARRANGE_BASELINE) { if (this.attributes.flow !== WallBox.FLOW_VERTICAL) { for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.attributes.stretchmargin[WallBox.T] = (varr == WallBox.ARRANGE_BOTTOM || varr == WallBox.ARRANGE_CENTER || varr == WallBox.ARRANGE_SPREAD) ? 1 : 0; child.attributes.stretchmargin[WallBox.B] = (varr == WallBox.ARRANGE_TOP || varr == WallBox.ARRANGE_CENTER || varr == WallBox.ARRANGE_SPREAD) ? 1 : 0; } } else { for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.attributes.stretchmargin[WallBox.T] = (varr == WallBox.ARRANGE_SPREAD || (i == 0 ? (varr == WallBox.ARRANGE_BOTTOM || varr == WallBox.ARRANGE_CENTER) : (varr == WallBox.ARRANGE_JUSTIFY))) ? 1 : 0; child.attributes.stretchmargin[WallBox.B] = (varr == WallBox.ARRANGE_SPREAD || (i == (numchildren - 1) ? (varr == WallBox.ARRANGE_TOP || varr == WallBox.ARRANGE_CENTER) : (varr == WallBox.ARRANGE_JUSTIFY))) ? 1 : 0; } // if (this.attributes.stretchheight === -1 && (varr == WallBox.ARRANGE_TOP || varr === WallBox.ARRANGE_BOTTOM || varr === WallBox.ARRANGE_CENTER)) // this.attributes.stretchheight = 1; } } // disable baseline probe if not needed if (!this.attributes.legacybaseline && (this.attributes.flow !== WallBox.FLOW_HORIZONTAL || varr !== WallBox.ARRANGE_BASELINE || numchildren < 2)) for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.attributes.legacybaseline = false; } super.visitI(); // tappability visualization this.setRenderedTappable((this.attributes.tappedEvent.handlers || this.contenttapapplies()) ? true : false, false); } //isGeneric() { // return !this.wallLike; //} //makeGeneric() { // if (this.wallLike) { // this.wallLike = false; // if (this.parent) this.parent.makeGeneric(); // } //} public speculationwascorrect(scroll: boolean): boolean { return scroll === this.D_scr; } private bound(min: number, val: number, max: number): number { if (min > val) return min; if (max < val) return max; return val; } private tryPreserveAspectRatio(): boolean { return /img|canvas|video|audio|boardContainer|viewPicture/i.test(this.content.tagName) || /boardContainer|viewPicture/i.test(this.content.className); } private fillh(f: number): boolean { if (f <= 0) return false; if (Math.abs(1-f) < 0.0001 || this.isRoot) return true; var a = this.attributes.arrangement[WallBox.H]; return (a === WallBox.ARRANGE_LEFT || a === WallBox.ARRANGE_CENTER || a === WallBox.ARRANGE_SPREAD || a === WallBox.ARRANGE_RIGHT); } private fillv(f: number): boolean { if (f <= 0) return false; if (f == 1 || this.isRoot) return true; var a = this.attributes.arrangement[WallBox.V]; return (a === WallBox.ARRANGE_TOP || a === WallBox.ARRANGE_BOTTOM || a === WallBox.ARRANGE_CENTER || a === WallBox.ARRANGE_SPREAD ); } // new layout algorithm public doLayout() { this.passA(); this.passBC(); this.passD(); } private passA() { this.visitI(); for (var i = 0; i < this.children.length; i++) { (this.children[i]).passA(); } this.visitA(); } private passBC() { this.visitB(); for (var i = 0; i < this.children.length; i++) (this.children[i]).passBC(); this.visitC(); } private passD() { this.visitD(); for (var i = 0; i < this.children.length; i++) (this.children[i]).passD(); if (this.layoutcompletehandler) this.layoutcompletehandler(this.getRenderedWidth(), this.getRenderedHeight()); } private visitA() { var numchildren = this.children.length; if (numchildren === 0) { // determine width if (this.contentType != WallBox.CONTENT_NONE && this.cached_width === -1) { // remove all size settings this.setRenderedWidth(-1); this.setRenderedHeight(-1); if ((this.contentType === WallBox.CONTENT_TEXT || this.contentType === WallBox.CONTENT_INPUT)) { // update the auxiliary span we use to take measurements if (this.contentType === WallBox.CONTENT_INPUT) { var text = this.textarea ? (this.content).value : (this.content).value; text = text + " abc"; // guarantee some free space for typing ahead this.auxcontent.textContent = text; } this.setRenderedWrap(false, this.attributes.wraplimit); // compute the width it wants to be var newwidth = (this.contentType === WallBox.CONTENT_INPUT) ? this.auxcontent.scrollWidth + (this.textarea ? (LayoutMgr.instance.scrollbarWidth + 50) : 50) : this.element.scrollWidth; //if (newwidth > this.width[WallBox.MAX]) { // newwidth = this.attributes.wrap * SizeMgr.topFontSize; // this.setRenderedWrap(this.attributes.wrap); // this.setRenderedWidth(newwidth); // newwidth = (this.contentType === WallBox.CONTENT_INPUT) ? this.auxcontent.scrollWidth : this.element.scrollWidth; this.setRenderedWrap(this.attributes.wrap, this.attributes.wraplimit); this.cached_width = newwidth; this.cached_aspectratio = 0; // determine baseline if asked for if (this.attributes.legacybaseline && this.cached_baseline === -1) { if (!this.baselineprobe) { this.baselineprobe = span(null, "s"); this.baselineprobe.style.fontSize = "0"; var c = div(null, span(null, "L"), this.baselineprobe); c.style.visibility = "hidden"; c.style.position = "absolute"; this.element.appendChild(c); } var yPosition = 0; this.cached_baseline = this.baselineprobe.offsetTop - this.baselineprobe.scrollTop + this.baselineprobe.clientTop; this.element.removeChild(this.baselineprobe.parentElement); this.baselineprobe = null; } } else { // use attributes if present, or use browser-reported size otherwise var targetelt = (this.content.className === "viewPicture") ? (this.content.firstChild) : this.content; var ha = targetelt ? Number(targetelt.getAttribute("height")) : 0; var wa = targetelt ? Number(targetelt.getAttribute("width")) : 0; // look for attributes on elt if (targetelt && (targetelt.tagName == "IMG" || targetelt.tagName == "VIDEO" || targetelt.tagName == "AUDIO")) { ha = ha || (targetelt).height; wa = wa || (targetelt).width; } this.cached_width = wa || this.element.scrollWidth; this.cached_aspectratio = (this.tryPreserveAspectRatio() && this.cached_width) ? ((ha || this.element.scrollHeight) / this.cached_width) : 0; // if this is video or audio and we couldn't get any width reported, use default if ((this.cached_width == 0) && targetelt && (targetelt.tagName == "VIDEO" || targetelt.tagName == "AUDIO")) { this.cached_width = 300; } // if this is an image and we couldn't get any width reported, use default if ((this.cached_width == 0) && targetelt && (targetelt.tagName == "IMG")) { this.cached_width = 100; } } } // determine requested widths if (this.contentType === WallBox.CONTENT_IMAGE) { if (this.cached_aspectratio > 0) { var scmin = this.attributes.height[WallBox.MIN] / this.cached_aspectratio; var scmax = this.attributes.height[WallBox.MAX] / this.cached_aspectratio; if ((this.attributes.width[WallBox.MIN] <= scmax) && (this.attributes.width[WallBox.MAX] >= scmin)) // constraints are satisfiable, take them into account this.A_sc = this.bound(scmin, this.cached_width, scmax); else // constraints are not satisfiable, do not take them into account this.A_sc = this.cached_width; } else { this.A_sc = this.cached_width; } this.A_asc = this.A_sc; var flex = (this.attributes.stretchwidth == 1 || !this.tryPreserveAspectRatio() || (this.attributes.stretchwidth == -1 && this.content && this.content.tagName == "IMG")) ? 1 : 0; this.A_mc = flex ? 0 : this.A_asc; this.A_fcc = flex; } else if (this.contentType === WallBox.CONTENT_TEXT) { var statedwidth = this.cached_width; var safetywidth = statedwidth + 1; // need safety margin pixel ... browser are not good at this this.A_sc = safetywidth; this.A_asc = (this.attributes.wrap) ? Math.min(this.A_sc, this.attributes.wraplimit * SizeMgr.topFontSize) : this.A_sc; this.A_mc = this.A_asc; this.A_fcc = 1; } else if (this.contentType === WallBox.CONTENT_INPUT) { this.A_sc = this.cached_width; this.A_asc = (this.attributes.wrap) ? Math.min(this.A_sc, this.attributes.wraplimit * SizeMgr.topFontSize) : this.A_sc; this.A_mc = this.A_asc; this.A_fcc = 1; } else { this.A_sc = 0; this.A_mc = 0; this.A_asc = 0; this.A_fcc = 0; } } else { // composite node if (this.attributes.flow === WallBox.FLOW_HORIZONTAL) { var lm_s = this.attributes.padding[WallBox.L]; var lm_f = 0; this.A_mc = lm_s; this.A_sc = lm_s; this.A_scc = 0; this.A_asc = lm_s; this.A_fcc = 0; this.A_fcs = 0; this.A_fp = 0; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; var ws = Math.max(0, child.A_sl - lm_s) + child.A_sr; lm_s = child.A_sr; if (i == numchildren - 1) ws += Math.max(0, this.attributes.padding[WallBox.R] - lm_s); this.A_mc += child.A_m + ws; this.A_sc += child.A_s + ws; this.A_scc += child.A_s; this.A_asc += child.A_as + ws; this.A_fcc += child.A_f; this.A_fcs += Math.max(0, child.A_fl - lm_f) + child.A_fr; this.A_fp = this.A_fp || child.A_fp; lm_f = child.A_fr; } } else if (this.attributes.flow === WallBox.FLOW_VERTICAL || this.attributes.flow === WallBox.FLOW_OVERLAY) { this.A_mc = 0; this.A_sc = 0; this.A_asc = 0; this.A_fcc = 0; this.A_fp = 0; for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; var ws = Math.max(child.A_sl, this.attributes.padding[WallBox.L]) + Math.max(child.A_sr, this.attributes.padding[WallBox.R]); this.A_mc = Math.max(this.A_mc, child.A_m + ws); this.A_asc = Math.max(this.A_asc, child.A_as + ws); this.A_sc = Math.max(this.A_sc, child.A_s + ws); this.A_fcc = Math.max(this.A_fcc, child.A_f); this.A_fp = this.A_fp || child.A_fp; } } } var min = this.attributes.width[WallBox.MIN]; var max = this.attributes.width[WallBox.MAX]; var borderwidth = this.attributes.borderwidth[WallBox.L] + this.attributes.borderwidth[WallBox.R]; this.A_scr = (this.attributes.scroll[WallBox.V] && (!this.isRoot || LayoutMgr.instance.scrollspeculation)) ? LayoutMgr.instance.scrollbarWidth : 0; this.A_m = this.bound(min, (this.attributes.scroll[WallBox.H] ? Math.min(this.A_mc, SizeMgr.topFontSize*6) : this.A_mc) + this.A_scr, max) + borderwidth; this.A_as = this.bound(min, (this.attributes.scroll[WallBox.H] ? Math.min(this.A_mc, SizeMgr.topFontSize * 10) : this.A_asc) + this.A_scr, max) + borderwidth; this.A_s = this.bound(min, this.A_sc + this.A_scr, max) + borderwidth; this.A_sl = this.attributes.margin[WallBox.L]; this.A_sr = this.attributes.margin[WallBox.R]; this.A_f = (this.attributes.stretchwidth == -1) ? ((this.A_fp || (numchildren == 0)) ? (this.fillh(this.A_fcc) ? 1 : this.A_fcc) : 0) : this.attributes.stretchwidth; this.A_fp = (this.attributes.stretchwidth == -1) ? ((numchildren > 0) && (max > min) && this.A_fp) : (this.attributes.legacystretch[WallBox.H] ? 0 : this.attributes.stretchwidth); this.A_fl = this.attributes.stretchmargin[WallBox.L]; this.A_fr = this.attributes.stretchmargin[WallBox.R]; } private visitB() { if (this.isRoot) { // get algorithm inputs from window constraints this.B_x = 0; //this.B_s = window.innerWidth; this.B_s = this.runtime.host.fullWallWidth(); } var borderwidth = (this.attributes.borderwidth[WallBox.L] + this.attributes.borderwidth[WallBox.R]); var allowance = this.B_s - this.A_scr - borderwidth; var numchildren = this.children.length; var overflow = false; if (numchildren == 0) { // leaf node this.B_scr = false; } else { // composite node overflow = allowance < this.A_mc; this.B_scr = (overflow && this.attributes.scroll[WallBox.H]); if (this.attributes.flow === WallBox.FLOW_VERTICAL || this.attributes.flow === WallBox.FLOW_OVERLAY) { var pl = this.attributes.padding[WallBox.L]; var pr = this.attributes.padding[WallBox.R]; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; var ws = Math.max(Math.max(pl, child.A_sl) + Math.max(pr, child.A_sr)); if (allowance > child.A_s + ws) { // we have enough for requested width var extra_c = 0; if (child.A_f) { var proportion = (child.A_f < 1 && this.fillh(child.A_f)) ? child.A_f : 1; extra_c = Math.max(0, Math.min(proportion*allowance - ws, child.attributes.width[WallBox.MAX]) - child.A_s); } child.B_s = child.A_s + extra_c; var extra_s = (child.A_fl + child.A_fr) ? ((allowance - child.B_s - ws) / (child.A_fl + child.A_fr)) : 0; child.B_x = Math.max(pl,child.A_sl) + extra_s * child.A_fl; } else if (allowance > child.A_as + ws) { // we have enough for alt width child.B_x = Math.max(pl,child.A_sl); child.B_s = allowance - ws; } else if (this.B_scr) { // we are scrolling, use alt width child.B_x = Math.max(pl,child.A_sl); child.B_s = child.A_as; } else { // take what we can child.B_x = Math.max(pl,child.A_sl); child.B_s = Math.max(child.A_m, allowance - ws); } } } else if (this.attributes.flow === WallBox.FLOW_HORIZONTAL) { if (allowance >= this.A_sc) { // we have enough for requested width var apply_fractional_stretches = this.fillh(this.A_fcc); var space_for_content = allowance - (this.A_sc - this.A_scc); // first, give extra space to content that wants it var remainder = space_for_content; var wsum = this.A_fcc; var csum = this.A_scc; if (remainder === 0 || wsum === 0) this.children.forEach((c:WallBox) => remainder -= (c.B_s = c.A_s)); else { var limit = (c:WallBox) => { if (!apply_fractional_stretches || c.A_f >= 1) return c.attributes.width[WallBox.MAX]; else return Math.max(c.A_s, Math.min(c.attributes.width[WallBox.MAX], space_for_content * c.A_f)); }; var sortedlist = this.children.slice(0).sort((wb1: WallBox, wb2: WallBox) => limit(wb1) - limit(wb2)); for (var i = 0; i < sortedlist.length; i++) { var c = sortedlist[i]; c.B_s = (c.A_f == 0) ? c.A_s : ((!apply_fractional_stretches || c.A_f >= 1) ? Math.min(c.attributes.width[WallBox.MAX], c.A_s + ((remainder - csum) / wsum) * c.A_f) : Math.max(c.A_s, Math.min(c.attributes.width[WallBox.MAX], space_for_content * c.A_f))); remainder -= c.B_s; csum -= c.A_s; wsum -= c.A_f; } } // then, give extra space wo white space var extra_s = this.A_fcs ? (remainder / this.A_fcs) : 0; var ls = this.attributes.padding[WallBox.L]; var lf = 0; var x = ls; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; x = x + Math.max(0, child.A_sl - ls) + Math.max(0, extra_s * child.A_fl - lf); child.B_x = x; ls = child.A_sr; lf = extra_s * child.A_fr; x = x + child.B_s + ls + lf; } } else { if (allowance > this.A_asc) { // we have enough for requested alternate width this.distribute(allowance - this.A_asc, (child: WallBox) => child.A_s - child.A_as, (child: WallBox, give: number) => { child.B_s = child.A_as + give }); } else if (this.B_scr) { // scrolling - everybody gets alt width for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.B_s = child.A_as; } } else { // minimum width overflow = true; this.distribute(this.A_asc - allowance, (child: WallBox) => child.A_as - child.A_m, (child: WallBox, take: number) => { child.B_s = child.A_as - take }); } // assign position var x = 0; var lastmargin = this.attributes.padding[WallBox.L]; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; x = x + Math.max(lastmargin, child.A_sl); child.B_x = x; x = x + child.B_s; lastmargin = child.A_sr; } } } } // output to rendering if (!this.delayedlayout) this.setRenderedWidth(this.B_s - borderwidth); this.setRenderedHorizontalOverflow(this.B_scr ? "scroll" : (overflow ? "hidden" : "")); this.setRenderedX(this.B_x); } private distribute( amount: number, limit: (wb: WallBox) => number, apply: (wb: WallBox, amount: number) => void ) { if (amount === 0) { for (var i = 0; i < this.children.length; i++) apply(this.children[i], 0); } else { var takers = this.children.length; var sortedlist = this.children.slice(0).sort((wb1: WallBox, wb2: WallBox) => limit(wb1) - limit(wb2)); for (var i = 0; i < this.children.length; i++) { var child = sortedlist[i]; var take = Math.min(limit(child), amount / takers); amount -= take; apply(child, take); takers--; } } } private visitC() { var numchildren = this.children.length; if (numchildren === 0) { // we are on leaf // determine height if (this.contentType != WallBox.CONTENT_NONE && this.cached_height === -1) { if (this.contentType == WallBox.CONTENT_IMAGE) { var targetelt = (this.content.className === "viewPicture") ? (this.content.firstChild) : this.content; this.cached_height = targetelt ? Number(targetelt.getAttribute("height")) : 0; if (!this.cached_height && this.content && this.content.tagName == "IMG") { this.cached_height = ( targetelt).height; } if (!this.cached_height) { this.setRenderedHeight(-1); this.cached_height = this.element.clientHeight; } } else if (this.contentType == WallBox.CONTENT_INPUT) { this.setRenderedHeight(-1); this.cached_height = this.auxcontent.scrollHeight + (this.textarea ? 5 : 0); } else { this.setRenderedHeight(-1); this.cached_height = this.element.clientHeight; } } // initialize algorithm inputs if (this.contentType === WallBox.CONTENT_IMAGE) { if (this.cached_aspectratio > 0) // try to preserve aspect ratio this.C_sc = ((this.delayedlayout ? this.cached_width : this.getRenderedWidth()) * this.cached_aspectratio); else // cannot preserve aspect ratio this.C_sc = this.cached_height; this.C_mc = this.attributes.stretchheight == 1 ? 0 : this.C_sc; this.C_bc = 0; this.C_fcc = this.attributes.stretchheight == 1 ? 1 : 0; } else if (this.contentType === WallBox.CONTENT_TEXT) { this.C_sc = this.cached_height + 1; // add 1 for browser inaccuracy this.C_mc = this.C_sc; // want all of it this.C_bc = this.cached_baseline; this.C_fcc = 0; } else if (this.contentType === WallBox.CONTENT_INPUT) { this.C_sc = this.cached_height + (this.textarea ? 1 : 6); this.C_mc = this.C_sc; // want all of it this.C_bc = this.cached_baseline; this.C_fcc = 0; } else { this.C_sc = 0; this.C_mc = 0; this.C_bc = 0; this.C_fcc = 0; } this.C_zc = 1; } else { this.C_bc = 0; var dobaseline = (this.attributes.arrangement[WallBox.V] === WallBox.ARRANGE_BASELINE); if (this.attributes.flow === WallBox.FLOW_VERTICAL) { // find baseline adjustment var firstchild = this.children[0]; if (dobaseline && firstchild.C_b) { this.C_bc = firstchild.C_b + Math.max(this.attributes.padding[WallBox.T], firstchild.C_st); } var bm_s = this.attributes.padding[WallBox.T]; var bm_f = 0; this.C_sc = bm_s; this.C_scc = 0; this.C_mc = bm_s; this.C_fcc = 0; this.C_fcs = 0; this.C_fp = 0; this.C_zc = 1; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; var ws = Math.max(0, child.C_st - bm_s) + child.C_sb; bm_s = child.C_sb; if (i == numchildren-1) ws += Math.max(0, this.attributes.padding[WallBox.B] - bm_s); this.C_sc += child.C_s + ws; this.C_scc += child.C_s; this.C_mc += child.C_m + ws; this.C_fcc += child.C_f; this.C_fcs += Math.max(0, child.C_ft - bm_f) + child.C_fb; this.C_fp = this.C_fp || child.C_fp; bm_f = child.C_fb; this.C_zc = Math.max(this.C_zc, 1 + child.C_zc); } } else if (this.attributes.flow === WallBox.FLOW_HORIZONTAL || this.attributes.flow === WallBox.FLOW_OVERLAY) { // var pos = 0; //for (var line = 0; pos < this.children.length; line++) { //var lsa = 0; //var lst = 0; //var lsb = 0; //var lft = true; //var lf = true; //var lfb = true; //for (var col = 0; pos < this.children.length && this.children[pos].B_g == line; col++) { // var child = this.children[pos]; // if (child.B_g > line) break; // lsa = Math.max(lsa, child.C_st + child.C_s + child.C_sb); // lst = Math.max(lsa, child.C_st + child.C_s + child.C_sb); // lsb = Math.max(lsa, child.C_st + child.C_s + child.C_sb); // lft = lft && child.C_ft; if (dobaseline) for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; if (child.C_b) this.C_bc = Math.max(this.C_bc, child.C_b + Math.max(this.attributes.padding[WallBox.T], child.C_st)); } this.C_sc = 0; this.C_mc = 0; this.C_fcc = 0; this.C_fp = 0; this.C_zc = 1; for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; var wst = Math.max(this.attributes.padding[WallBox.T], child.C_st); var bsa = child.C_b ? Math.max(0, this.C_bc - (child.C_b + wst)) : 0; var ws = wst + bsa + Math.max(this.attributes.padding[WallBox.B], child.C_sb); this.C_sc = Math.max(this.C_sc, child.C_s + ws); this.C_mc = Math.max(this.C_mc, child.C_m + ws); this.C_fcc = Math.max(this.C_fcc, child.C_f); this.C_fp = this.C_fp || child.C_fp; if (this.attributes.flow === WallBox.FLOW_OVERLAY) this.C_zc = this.C_zc + child.C_zc; else this.C_zc = Math.max(this.C_zc, 1 + child.C_zc); } } } var min = this.attributes.height[WallBox.MIN]; var max = this.attributes.height[WallBox.MAX]; var borderwidth = this.attributes.borderwidth[WallBox.T] + this.attributes.borderwidth[WallBox.B]; this.C_scr = this.B_scr ? LayoutMgr.instance.scrollbarHeight : 0; this.C_m = this.bound(min, (this.attributes.scroll[WallBox.V] ? Math.min(this.C_mc, SizeMgr.topFontSize * 6) : this.C_mc) + this.C_scr, max) + borderwidth; this.C_s = this.bound(min, this.C_sc + this.C_scr, max) + borderwidth; this.C_st = this.attributes.margin[WallBox.T]; this.C_sb = this.attributes.margin[WallBox.B]; this.C_f = (this.attributes.stretchheight == -1) ? ((this.C_fp || (numchildren == 0)) ? (this.fillv(this.C_fcc) ? 1 : this.C_fcc) : 0) : this.attributes.stretchheight; this.C_fp = (this.attributes.stretchheight == -1) ? ((numchildren > 0) && (max > min) && this.C_fp) : (this.attributes.legacystretch[WallBox.V] ? 0 : this.attributes.stretchheight); this.C_ft = this.attributes.stretchmargin[WallBox.T]; this.C_fb = this.attributes.stretchmargin[WallBox.B]; this.C_b = (this.attributes.legacybaseline && this.C_bc) ? (this.C_bc + this.attributes.borderwidth[WallBox.T]) : 0; } private visitD() { // input from the top element if (this.isRoot) { this.D_y = 0; //this.D_s = window.innerHeight - SizeMgr.topFontSize * 4; this.D_s = this.runtime.host.userWallHeight(); this.D_z = 1; this.D_b = this.C_b; } var overflow = false; var borderwidth = (this.attributes.borderwidth[WallBox.T] + this.attributes.borderwidth[WallBox.B]); var allowance = this.D_s - this.C_scr - borderwidth; var baseline = this.attributes.legacybaseline ? ((this.attributes.arrangement[WallBox.V]===WallBox.ARRANGE_BASELINE) ? Math.max(this.C_bc,this.D_b - this.attributes.borderwidth[WallBox.T]) : 0) : this.C_bc; var numchildren = this.children.length; if (numchildren === 0) { // leaf node this.D_scr = false; } else { // composite node overflow = allowance < this.C_mc + this.C_scr; this.D_scr = (overflow && this.attributes.scroll[WallBox.V]); var nextz = this.D_z + 1; if (this.attributes.flow === WallBox.FLOW_HORIZONTAL || this.attributes.flow === WallBox.FLOW_OVERLAY) { var pt = this.attributes.padding[WallBox.T]; var pb = this.attributes.padding[WallBox.B]; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; var wst = Math.max(pt, child.C_st, child.C_b ? Math.max(0, baseline - child.C_b) : 0); var ws = wst + Math.max(pb, child.C_sb); if (allowance > child.C_s + ws) { // we have enough for requested width var extra_c = 0; if (child.C_f) { var proportion = (child.C_f < 1 && this.fillv(child.C_f)) ? child.C_f : 1; extra_c = Math.max(0, Math.min(allowance*proportion - ws, child.attributes.height[WallBox.MAX]) - child.C_s); } child.D_s = child.C_s + extra_c; var extra_s = (child.C_ft + child.C_fb) ? ((allowance - child.D_s - ws) / (child.C_ft + child.C_fb)) : 0; child.D_y = wst + extra_s * child.C_ft; } else if (this.D_scr) { // we are scrolling - give full requested size child.D_y = wst; child.D_s = child.C_s; } else { // shrink as much as possible child.D_y = wst; child.D_s = Math.max(child.C_m, allowance - ws); } child.D_b = Math.max(0, baseline - child.D_y); child.D_z = nextz; if (this.attributes.flow === WallBox.FLOW_OVERLAY) nextz = nextz + child.C_zc; } } else if (this.attributes.flow === WallBox.FLOW_VERTICAL) { var firstchild = this.children[0]; var bsa = firstchild.C_b ? Math.max(0, baseline - firstchild.C_b) : 0; var pt = Math.max(this.attributes.padding[WallBox.T], bsa); if (allowance >= this.C_sc) { // we have enough for requested width var apply_fractional_stretches = this.fillv(this.C_fcc); var space_for_content = allowance - (this.C_sc - this.C_scc); // first, give extra space to content that wants it var remainder = space_for_content; var wsum = this.C_fcc; var csum = this.C_scc; if (remainder === 0 || wsum === 0) this.children.forEach((c:WallBox) => remainder -= (c.D_s = c.C_s)); else { var limit = c => { if (!apply_fractional_stretches || c.C_f >= 1) return c.attributes.height[WallBox.MAX]; else return Math.max(c.C_s, Math.min(c.attributes.height[WallBox.MAX], space_for_content * c.C_f)); }; var sortedlist = this.children.slice(0).sort((wb1: WallBox, wb2: WallBox) => limit(wb1) - limit(wb2)); for (var i = 0; i < sortedlist.length; i++) { var c = sortedlist[i]; c.D_s = (c.C_f == 0) ? c.C_s : ((!apply_fractional_stretches || c.C_f >= 1) ? Math.min(c.attributes.height[WallBox.MAX], c.C_s + ((remainder - csum) / wsum) * c.C_f) : Math.max(c.C_s, Math.min(c.attributes.height[WallBox.MAX], space_for_content * c.C_f))); remainder -= c.D_s; csum -= c.C_s; wsum -= c.C_f; } } // then, give extra space to white space that wants it var extra_s = this.C_fcs ? (remainder / this.C_fcs) : 0; var y = pt; var ls = pt; var lf = 0; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; y = y + Math.max(0, child.C_st - ls) + Math.max(0, extra_s * child.C_ft - lf); child.D_y = y; ls = child.C_sb; lf = extra_s * child.C_fb; y = y + child.D_s + ls + lf; child.D_z = nextz; child.D_b = Math.max(0, baseline - child.D_y); } } else { if (this.D_scr) { // we are scrolling - give full requested size for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.D_s = child.C_s; } } else if (allowance > this.C_mc) { this.distribute(allowance - this.C_mc, (child: WallBox) => child.C_s - child.C_m, (child: WallBox, give: number) => { child.D_s = child.C_m + give }); } else { // give minimum width overflow = allowance < this.C_mc; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; child.D_s = child.C_m; } } // assign position var y = 0; var lastmargin = pt; for (var i = 0; i < numchildren; i++) { var child = this.children[i]; y = y + Math.max(lastmargin, child.C_st); child.D_y = y; child.D_b = Math.max(0, baseline - child.D_y); y = y + child.D_s; lastmargin = child.C_sb; child.D_z = nextz; child.D_b = Math.max(0, baseline - child.D_y); } } } } // output to the HTML element if (!this.delayedlayout) this.setRenderedHeight(this.D_s - borderwidth); this.setRenderedVerticalOverflow(this.D_scr ? "scroll" : (overflow ? "hidden" : "")); this.setRenderedY(this.D_y); this.setRenderedZIndex(this.D_z); } public getEditableContent(): string { Util.assert(this.contentType == WallBox.CONTENT_INPUT); return this.textarea ? (this.content).value : (this.content).value } public setEditableContent(text: string) { Util.assert(this.contentType == WallBox.CONTENT_INPUT); if (this.textarea) (this.content).value = text; else (this.content).value = text; } public invalidateCachedLayout(triggerdelayedrelayout: boolean) { this.cached_width = -1; this.cached_height = -1; if (triggerdelayedrelayout) TDev.Util.setTimeout(100, () => { if (this.cached_width === -1) LayoutMgr.QueueReLayout(); }); } onInputTextChangeDone() { if (this.obsolete) return; // box may be already gone from screen if (LayoutMgr.instance.editMode) { // don't do anything in edit mode } else { this.cached_width = -1; this.cached_height = -1; var parent = this.parent; var text = this.textarea ? (this.content).value : (this.content).value; if (parent && (parent.attributes).textEditedEvent.handlers) { this.runtime.queueLocalEvent((parent.attributes).textEditedEvent, [text]); } this.runtime.forcePageRefresh(); // we need to ensure display is updated. } } // Getters public size(): number { return this.children.length; } public get(index: number): BoxBase { return this.children[index]; } public shift(): void { if (this.children.length > 0) this.children.shift(); } public forEachChild(f: (WallBox) =>any) { this.children.forEach(f); } public getDepth(): number { return this.depth; } public getId(): number { return this.id; } public getElement(): HTMLElement { return this.element; } public getFlow(): number { return this.attributes.flow; } public getAlign(): number { return this.attributes.textalign; } public getBackground(): string { return this.attributes.background; } public getForeground(): string { return this.attributes.foreground; } public getFontSize(): number { return this.attributes.fontSize; } public getFontWeight(): string { return this.attributes.fontWeight; } public getFontFamily(): string { return this.attributes.fontFamily; } /* public getMinWidth(): number { return this.width[WallBox.MIN]; } public getMaxWidth(): number { return this.width[WallBox.MAX]; } public getMinHeight(): number { return this.height[WallBox.MIN]; } public getMaxHeight(): number { return this.height[WallBox.MAX]; } public getMargin(direction: number): number { return this.margin[direction]; } public getTopMargin(): number { return this.margin[WallBox.T]; } public getRightMargin(): number { return this.margin[WallBox.R]; } public getBottomMargin(): number { return this.margin[WallBox.B]; } public getLeftMargin(): number { return this.margin[WallBox.L]; } */ // public getX(): number { return this.rendered_x; } // public getY(): number { return this.rendered_y; } public getRenderedWidth(): number { return this.rendered_width; } public getRenderedHeight(): number { return this.rendered_height; } //public getRenderedMargin(direction: number): number { return this.rendered_margin[direction]; } //public getRenderedTopMargin(): number { return this.rendered_margin[WallBox.T]; } //public getRenderedRightMargin(): number { return this.rendered_margin[WallBox.R]; } //public getRenderedBottomMargin(): number { return this.rendered_margin[WallBox.B]; } //public getRenderedLeftMargin(): number { return this.rendered_margin[WallBox.L]; } //public getHorizontalScrollbar(): boolean { return this.rendered_hscrollbar; } // public getVerticalScrollbar(): boolean { return this.rendered_vscrollbar; } //public getContentType(): number { return this.contentType; } public getContent(): HTMLElement { return this.content; } // Functions for setting box attributes. Called from user code. public setFlow(flow: number, pc = "") { this.attributes.flow = flow; this.onCall("flow", pc); } public setBackground(background: string, pc = "") { this.attributes.background = background; this.onCall("background", pc); } public addBackgroundImage(img: BoxBackgroundImage, pc = "") { if (!this.attributes.backgroundImages) this.attributes.backgroundImages = []; this.attributes.backgroundImages.splice(0, 0, img); this.onCall("background image", pc); } public setForeground(foreground: string, pc = "") { this.attributes.foreground = foreground; this.onCall("foreground", pc); } public setFontSize(fontSize: number, pc = "") { this.attributes.fontSize = fontSize; this.onCall("font size", pc); } public setFontWeight(fontWeight: string, pc = "") { this.attributes.fontWeight = fontWeight; this.onCall("font weight", pc); } public setFontFamily(fontFamily: string, pc = "") { this.attributes.fontFamily = fontFamily; this.onCall("font family", pc); } public setScrolling(h: boolean, v: boolean, pc = "") { this.attributes.scroll = [h, v]; this.onCall("scrolling", pc); } public setEmBorder(color: string, width: number, pc = "") { this.attributes.border = color; var bw = SizeMgr.topFontSize * width; this.attributes.borderwidth = [bw, bw, bw, bw]; this.onCall("border", pc); } public setBorderWidth(top: number, right: number, bottom: number, left: number, pc = "") { this.attributes.borderwidth = [top, right, bottom, left]; this.onCall("border widths", pc); } public setAllMargins(top: number, right: number, bottom: number, left: number, pc = "") { this.attributes.margin = [top, right, bottom, left]; this.onCall("margins", pc); } public setPadding(top: number, right: number, bottom: number, left: number, pc = "") { this.attributes.padding = [top, right, bottom, left]; this.onCall("margins", pc); } public setWrap(wrap: boolean, width: number, pc = "") { this.attributes.wrap = wrap; this.attributes.wraplimit = Math.max(width,1); this.onCall("text wrap", pc); } public setWidth(width: number, pc = "") { this.attributes.width = [width, width]; this.onCall("width", pc); } public setWidthRange(minWidth: number, maxWidth: number, pc = "") { this.attributes.width[WallBox.MIN] = minWidth; this.attributes.width[WallBox.MAX] = maxWidth; this.onCall("width range", pc); } public setHorizontalStretch(n: number, pc = "") { this.attributes.stretchwidth = n; this.onCall("horizontal stretch", pc); } public setVerticalStretch(n: number, pc = "") { this.attributes.stretchheight = n; this.onCall("vertical stretch", pc); } public setHorizontalAlignment(left: number, right: number, pc = "") { left = Math.max(0, Math.min(1,left)); right = Math.max(0, Math.min(1,right)); if (left < 1) this.attributes.stretchmargin[WallBox.L] = (1 - left); if (right < 1) this.attributes.stretchmargin[WallBox.R] = (1 - left); if (left > 0 && right > 0) this.attributes.stretchwidth = 1; if (right == 0 && left != 0) this.attributes.textalign = WallBox.ARRANGE_LEFT; else if (right != 0 && left == 0) this.attributes.textalign = WallBox.ARRANGE_RIGHT; else if (right == 0 && left == 0) this.attributes.textalign = WallBox.ARRANGE_CENTER; else this.attributes.textalign = WallBox.ARRANGE_JUSTIFY; this.attributes.legacystretch[WallBox.H] = true; this.onCall("horizontal alignment", pc); } public setVerticalAlignment(top: number, bottom: number, pc = "") { top = Math.max(0, Math.min(1,top)); bottom = Math.max(0, Math.min(1,bottom)); if (top < 1) this.attributes.stretchmargin[WallBox.T] = (1 - top); if (bottom < 1) this.attributes.stretchmargin[WallBox.B] = (1 - bottom); if (top > 0 && bottom > 0) this.attributes.stretchheight = 1; this.attributes.legacybaseline = false; this.attributes.legacystretch[WallBox.V] = true; this.onCall("vertical alignment", pc); } public setHorizontalArrangement(what: number, pc = "") { this.attributes.arrangement[WallBox.H] = what; this.attributes.textalign = what; this.onCall("horizontal arrangement", pc); } public setVerticalArrangement(what: number, pc = "") { this.attributes.arrangement[WallBox.V] = what; if (what != WallBox.ARRANGE_BASELINE) this.attributes.legacybaseline = false; this.onCall("vertical arrangement", pc); } // public stretchAllMargins(top: number, right: number, bottom: number, left: number, pc = "") { // this.attributes.stretchmargin = [top, right, bottom, left]; // this.onCall("stretch margins", pc); //} //public setWidthStretch(weight: number, pc = "") { this.attributes.stretchwidth = ((weight < 0) ? WallBox.STRETCH_AUTO : weight); this.onCall("stretch width", pc); } //public setHeightStretch(weight: number, pc = "") { this.attributes.stretchheight = ((weight < 0) ? WallBox.STRETCH_AUTO : weight); this.onCall("stretch height", pc); } public setHeight(height: number, pc = "") { this.attributes.height = [height, height]; this.onCall("height", pc); } public setHeightRange(minHeight: number, maxHeight: number, pc = "") { this.attributes.height[WallBox.MIN] = minHeight; this.attributes.height[WallBox.MAX] = maxHeight; this.onCall("height range", pc); } public setEmFontSize(fontSize: number, pc = "") { this.setFontSize(SizeMgr.topFontSize * fontSize, pc); } public setEmBorderWidth(top: number, right: number, bottom: number, left: number, pc = "") { this.setBorderWidth(SizeMgr.topFontSize * top, SizeMgr.topFontSize * right, SizeMgr.topFontSize * bottom, SizeMgr.topFontSize * left, pc); } public setAllEmMargins(top: number, right: number, bottom: number, left: number, pc = "") { this.setAllMargins(SizeMgr.topFontSize * top, SizeMgr.topFontSize * right, SizeMgr.topFontSize * bottom, SizeMgr.topFontSize * left, pc); } public setEmPadding(top: number, right: number, bottom: number, left: number, pc = "") { this.setPadding(SizeMgr.topFontSize * top, SizeMgr.topFontSize * right, SizeMgr.topFontSize * bottom, SizeMgr.topFontSize * left, pc); } public setEmWidth(width: number, pc = "") { this.setWidth(SizeMgr.topFontSize * width, pc); } public setEmWidthRange(minWidth: number, maxWidth: number, pc = "") { this.setWidthRange(SizeMgr.topFontSize * minWidth, SizeMgr.topFontSize * maxWidth, pc); } public setEmHeight(height: number, pc = "") { this.setHeight(SizeMgr.topFontSize * height, pc); } public setEmHeightRange(minHeight: number, maxHeight: number, pc = "") { this.setHeightRange(SizeMgr.topFontSize * minHeight, SizeMgr.topFontSize * maxHeight, pc); } public setContent(e: any) { Util.check(e != null); if (e instanceof HTMLTextAreaElement || e instanceof HTMLInputElement) { this.contentType = WallBox.CONTENT_INPUT; this.content = e; this.auxcontent = span("wall-text", ""); this.auxcontent.style.visibility = "hidden"; // make this visible for debugging this.auxcontent.style.position = "absolute"; this.auxcontent.style.left = "0px"; this.auxcontent.style.top = "0px"; this.auxcontent.style.padding = "1px"; this.auxcontent.style.color = "red"; this.auxcontent.style.zIndex = "-1"; } else if (e instanceof HTMLElement) { this.contentType = WallBox.CONTENT_IMAGE; this.content = e; } else { this.contentType = WallBox.CONTENT_TEXT; var str = (e || "").toString(); this.content = span("wall-text", str); } } // functions for manipulating the appearance of the HTML element. Called by layout algorithm. setRenderedX(x: number) { if (x !== this.rendered_x) { if (typeof (this.rendered_x) === "invalid" && !this.isRoot) { this.element.style.position = "absolute"; } this.element.style.left = x + "px"; this.rendered_x = x; } } setRenderedY(y: number) { if (y !== this.rendered_y) { this.element.style.top = y + "px"; this.rendered_y = y; } } setRenderedWidth(width: number) { if (width !== this.rendered_width) { this.element.style.width = (width >= 0) ? (width + "px") : ""; if (this.contentType != WallBox.CONTENT_NONE && this.contentType != WallBox.CONTENT_TEXT) { var extra = (this.contentType == WallBox.CONTENT_INPUT && !this.textarea) ? 6 : 0; var targetelt = (this.content.className === "viewPicture") ? (this.content.firstChild) : this.content; if (targetelt) targetelt.style.width = (width > 0) ? ((width - extra) + "px") : ""; if (this.contentType === WallBox.CONTENT_INPUT && this.textarea) this.auxcontent.style.width = (width >= 0) ? ((width - 10) + "px") : ""; } this.rendered_width = width; this.cached_height = -1; } } setRenderedHeight(height: number) { if (height !== this.rendered_height) { this.element.style.height = (height > 0) ? (height + "px") : ""; if (this.contentType != WallBox.CONTENT_NONE && this.contentType != WallBox.CONTENT_TEXT) { var extra = (this.contentType == WallBox.CONTENT_INPUT && !this.textarea) ? 6 : 0; var targetelt = (this.content.className === "viewPicture") ? (this.content.firstChild) : this.content; if (targetelt) targetelt.style.height = (height > 0) ? ((height - extra) + "px") : ""; if (this.contentType == WallBox.CONTENT_INPUT && this.textarea && height >= this.cached_height) { this.content.style.overflow = "hidden"; // make sure IE does not display gray scroll bars } } this.rendered_height = height; } } setRenderedFontFamily(family: string) { if (family !== this.rendered_fontfamily) { this.element.style.fontFamily = (family === "Default" ? '"Segoe UI", "Segoe WP", "Helvetica Neue", Sans-Serif' : family); this.rendered_fontfamily = family; this.cached_height = -1; this.cached_baseline = -1; this.cached_width = -1; } } setRenderedFontWeight(fw: string) { var fontweight = fw || "inherit"; if (fontweight !== this.rendered_fontweight) { this.element.style.fontWeight = fontweight; this.rendered_fontweight = fontweight; this.cached_height = -1; this.cached_baseline = -1; this.cached_width = -1; } } setRenderedFontSize(size: number) { if (size !== this.rendered_fontsize) { this.element.style.fontSize = (size > 0) ? (size + "px") :"inherit"; this.rendered_fontsize = size; this.cached_height = -1; this.cached_baseline = -1; this.cached_width = -1; } } setRenderedColor(clr: string) { var color = clr || "inherit"; if (color !== this.rendered_foregroundcolor) { this.element.style.color = color; this.rendered_foregroundcolor = color; } } setRenderedBackgroundColor(color: string) { if (color !== this.rendered_backgroundcolor) { this.element.style.backgroundColor = color; this.rendered_backgroundcolor = color; } } setRenderedBackgroundImages(images: BoxBackgroundImage[]) { var css = images ? images.map(img => HTML.cssImage(img.url) + ' ' + (img.position || 'center') + ' / ' + (img.size || 'cover') + ' ' + (img.repeat || 'no-repeat') + ' ' + (img.attachment || 'scroll') ).join(', ') : ''; if(css !== this.rendered_background) { this.element.style.background = css; this.rendered_background = css; } } //setRenderedTagname(name: string) { // if (this.element.tagName !== name) { // var original = this.element; // var copy = document.createElement(name); // if (original.attributes) // for (var i = 0; i < original.attributes.length; i++) { // var a = original.attributes.item(i); // copy.setAttribute(a.nodeName, a.nodeValue); // } // while (original.firstChild) { // copy.appendChild(original.firstChild); //} // if (original.parentNode) // original.parentNode.replaceChild(copy, original); // this.element = copy; // } //} setRenderedTappable(tappable: boolean, tapped: boolean) { var x = "wall-box" + (tappable ? " tappable " : "") + (tapped ? " tapped" : ""); if (x !== this.rendered_tappable) { this.element.className = x; this.rendered_tappable = x; } } setRenderedPositionMode(positionmode: string) { if (positionmode !== this.rendered_positionmode) { this.element.style.position = positionmode; this.rendered_positionmode = positionmode; } } //clearCss() //{ // if (this.element.style.cssText) // this.element.style.cssText = "" //} min1pixel(x: number) { return ((x > 0 && x < 1) ? "1" : x.toString()) + "px"; } setRenderedBorder(clr: string, width: number[]) { var color = clr || "black"; if (color !== this.rendered_border) { this.element.style.borderColor = color; this.rendered_border = color; } if (!this.rendered_borderwidth || width[0] !== this.rendered_borderwidth[0] || width[1] !== this.rendered_borderwidth[1] || width[2] !== this.rendered_borderwidth[2] || width[3] !== this.rendered_borderwidth[3]) { var visible = (width[0] || width[1] || width[2] || width[3]); this.element.style.borderStyle = visible ? "solid" : "none"; this.element.style.borderTopWidth = visible ? this.min1pixel(width[WallBox.T]) : ""; this.element.style.borderRightWidth = visible ? this.min1pixel(width[WallBox.R]) : ""; this.element.style.borderBottomWidth = visible ? this.min1pixel(width[WallBox.B]) : ""; this.element.style.borderLeftWidth = visible ? this.min1pixel(width[WallBox.L]) : ""; this.rendered_borderwidth = width.slice(0); } } setRenderedTextAlign(alignment: number) { if (alignment !== this.rendered_textalign) { switch (alignment) { case WallBox.ARRANGE_RIGHT: this.element.style.textAlign = "right"; break; case WallBox.ARRANGE_CENTER: this.element.style.textAlign = "center"; break; case WallBox.ARRANGE_JUSTIFY: this.element.style.textAlign = "justify"; break; default: this.element.style.textAlign = "left"; break; } this.cached_height = -1; this.cached_width = -1; this.rendered_textalign = alignment; } } setRenderedHorizontalOverflow(mode: string) { if (mode != this.rendered_hmode) { this.element.style.overflowX = (this.rendered_sideview || (mode == "scroll" && (this.contentType == WallBox.CONTENT_INPUT))) ? "" : mode; this.rendered_hmode = mode; } } setRenderedVerticalOverflow(mode: string) { if (mode != this.rendered_vmode) { this.element.style.overflowY = (this.rendered_sideview || (mode == "scroll" && (this.contentType == WallBox.CONTENT_INPUT))) ? "" : mode; this.rendered_vmode = mode; } } setRenderedSideView(sideview: boolean) { // called on root box to adjust side view if (this.rendered_sideview != sideview) { this.element.style.overflowX = (sideview || (this.rendered_hmode == "scroll" && (this.contentType == WallBox.CONTENT_INPUT))) ? "" : this.rendered_hmode; this.element.style.overflowY = (sideview || (this.rendered_vmode == "scroll" && (this.contentType == WallBox.CONTENT_INPUT))) ? "" : this.rendered_vmode; this.rendered_sideview = sideview; } } setRenderedWrap(wrap: boolean, wraplimit: number) { wrap = wrap || false; if (wrap != this.rendered_wrap || wraplimit != this.rendered_wraplimit) { this.element.style.whiteSpace = wrap ? (this.element.style.textAlign === "justify" ? "pre-line" : "pre-wrap") : "pre"; this.element.style.wordWrap = wrap ? "break-word" : ""; this.rendered_wrap = wrap; this.rendered_wraplimit = wraplimit; this.cached_height = -1; this.cached_width = -1; } } setRenderedZIndex(zi: number) { if (zi != this.rendered_zindex) { this.element.style.zIndex = zi ? zi.toString() : ""; this.rendered_zindex = zi; } } //setRenderedTopMargin(margin: number) { this.rendered_margin[WallBox.T] = Math.max(0, margin); } //setRenderedRightMargin(margin: number) { this.rendered_margin[WallBox.R] = Math.max(0, margin); } //setRenderedBottomMargin(margin: number) { this.rendered_margin[WallBox.B] = Math.max(0, margin); } //setRenderedLeftMargin(margin: number) { this.rendered_margin[WallBox.L] = Math.max(0, margin); } } }