aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Tile.vue33
-rw-r--r--src/components/TileTree.vue110
-rw-r--r--src/layout.ts354
-rw-r--r--src/types.ts42
4 files changed, 281 insertions, 258 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 09d6128..3ca02ce 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -1,43 +1,44 @@
<script lang="ts">
import {defineComponent, PropType} from 'vue';
-import {LayoutNode} from '../types';
+import {LayoutNode} from '../layout';
-const TRANSITION_DURATION = 300;
export default defineComponent({
name: 'tile',
data(){
return {
zIdx: 0,
- transitionDuration: TRANSITION_DURATION,
overFlow: 'visible',
}
},
props: {
layoutNode: {type: Object as PropType<LayoutNode>, required: true},
+ transitionDuration: {type: Number, required: true},
+ headerSz: {type: Number, required: true},
+ tileSpacing: {type: Number, required: true},
},
computed: {
name(){return this.layoutNode.tolNode.name.replaceAll('\'', '\\\'')}
},
methods: {
onImgClick(){
- this.$emit('tile-clicked', [this.layoutNode]);
+ this.$emit('tile-clicked', this.layoutNode);
//increase z-index and hide overflow during transition
this.zIdx = 1;
this.overFlow = 'hidden';
setTimeout(() => {this.zIdx = 0; this.overFlow = 'visible'}, this.transitionDuration);
},
- onInnerTileClicked(nodeList: LayoutNode[]){
- this.$emit('tile-clicked', [...nodeList, this.layoutNode]);
+ onInnerTileClicked(node: LayoutNode){
+ this.$emit('tile-clicked', node);
},
onHeaderClick(){
- this.$emit('header-clicked', [this.layoutNode]);
+ this.$emit('header-clicked', this.layoutNode);
//increase z-index and hide overflow during transition
this.zIdx = 1;
this.overFlow = 'hidden';
setTimeout(() => {this.zIdx = 0; this.overFlow = 'visible'}, this.transitionDuration);
},
- onInnerHeaderClicked(nodeList: LayoutNode[]){
- this.$emit('header-clicked', [...nodeList, this.layoutNode]);
+ onInnerHeaderClicked(node: LayoutNode){
+ this.$emit('header-clicked', node);
}
}
})
@@ -57,32 +58,30 @@ export default defineComponent({
/>
<div v-else>
<div
- v-if="(layoutNode.headerSz && !layoutNode.sepSweptArea) ||
+ v-if="(layoutNode.showHeader && !layoutNode.sepSweptArea) ||
(layoutNode.sepSweptArea && layoutNode.sepSweptArea.sweptLeft)"
- :style="{height: layoutNode.headerSz+'px'}"
+ :style="{height: headerSz+'px'}"
class="text-center hover:cursor-pointer bg-stone-300" @click="onHeaderClick">
{{layoutNode.tolNode.name}}
</div>
<div v-if="layoutNode.sepSweptArea"
:style="{position: 'absolute',
left: layoutNode.sepSweptArea.pos[0]+'px', top: layoutNode.sepSweptArea.pos[1]+'px',
- width: (layoutNode.sepSweptArea.dims[0] +
- (layoutNode.sepSweptArea.sweptLeft ? layoutNode.sepSweptArea.tileSpacing+1 : 0))+'px',
- height: (layoutNode.sepSweptArea.dims[1] +
- (layoutNode.sepSweptArea.sweptLeft ? 0 : layoutNode.sepSweptArea.tileSpacing+1))+'px',
+ width: (layoutNode.sepSweptArea.dims[0]+(layoutNode.sepSweptArea.sweptLeft ? tileSpacing+1 : 0))+'px',
+ height: (layoutNode.sepSweptArea.dims[1]+(layoutNode.sepSweptArea.sweptLeft ? 0 : tileSpacing+1))+'px',
borderRightColor: (layoutNode.sepSweptArea.sweptLeft ? 'white' : 'currentColor'),
borderBottomColor: (layoutNode.sepSweptArea.sweptLeft ? 'currentColor' : 'white'),
transitionDuration: transitionDuration+'ms'}"
class="transition-[left,top,width,height] ease-out border border-stone-900 bg-white">
- <div v-if="!layoutNode.sepSweptArea.sweptLeft" :style="{height: layoutNode.headerSz+'px'}"
+ <div v-if="!layoutNode.sepSweptArea.sweptLeft" :style="{height: headerSz+'px'}"
class="text-center hover:cursor-pointer bg-stone-300" @click="onHeaderClick">
{{layoutNode.tolNode.name}}
</div>
</div>
<tile v-for="child in layoutNode.children" :key="child.tolNode.name" :layoutNode="child"
+ :headerSz="headerSz" :tileSpacing="tileSpacing" :transitionDuration="transitionDuration"
@tile-clicked="onInnerTileClicked" @header-clicked="onInnerHeaderClicked"
></tile>
</div>
</div>
</template>
-
diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue
index d662f50..0e5b793 100644
--- a/src/components/TileTree.vue
+++ b/src/components/TileTree.vue
@@ -2,12 +2,7 @@
import {defineComponent} from 'vue';
import Tile from './Tile.vue';
-import {TolNode, LayoutNode} from '../types';
-import {genLayout, layoutInfoHooks} from '../layout';
-//regarding importing a file f1.ts:
- //using 'import f1.ts' makes vue-tsc complain, and 'import f1.js' makes vite complain
- //using 'import f1' might cause problems with build systems other than vite
-
+import {TolNode} from '../types';
import tol from '../tol.json';
function preprocessTol(tree: any): void {
if (!tree.children){
@@ -18,96 +13,67 @@ function preprocessTol(tree: any): void {
}
preprocessTol(tol);
+import {LayoutTree, LayoutNode} from '../layout';
+import type {LayoutOptions} from '../layout';
+//regarding importing a file f1.ts:
+ //using 'import f1.ts' makes vue-tsc complain, and 'import f1.js' makes vite complain
+ //using 'import f1' might cause problems with build systems other than vite
+
+let defaultLayoutOptions: LayoutOptions = {
+ tileSpacing: 5,
+ headerSz: 20,
+ minTileSz: 50,
+ maxTileSz: 200,
+ layoutType: 'sweep', //'sqr' | 'rect' | 'sweep'
+ rectMode: 'auto', //'horz' | 'vert' | 'linear' | 'auto'
+ rectSpaceShifting: true,
+ sweepMode: 'left', //'left' | 'top' | 'shorter' | 'auto'
+ sweepingToParent: true,
+};
+let defaultOtherOptions = {
+ transitionDuration: 300,
+};
+
export default defineComponent({
data(){
return {
- layoutTree: this.initLayoutTree(tol as TolNode, 1),
+ layoutOptions: defaultLayoutOptions,
+ otherOptions: defaultOtherOptions,
+ layoutTree: new LayoutTree(tol as TolNode, 1, defaultLayoutOptions),
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
resizeThrottled: false,
}
},
methods: {
- initLayoutTree(tol: TolNode, lvl: number): LayoutNode {
- let node = new LayoutNode(tol, []);
- function initRec(node: LayoutNode, lvl: number){
- if (lvl > 0)
- node.children = node.tolNode.children.map(
- (n: TolNode) => initRec(new LayoutNode(n, []), lvl-1));
- return node;
- }
- initRec(node, lvl);
- layoutInfoHooks.initLayoutInfo(node)
- return node;
- },
onResize(){
if (!this.resizeThrottled){
this.width = document.documentElement.clientWidth;
this.height = document.documentElement.clientHeight;
- this.tryLayout();
+ if (!this.layoutTree.tryLayout([0,0], [this.width,this.height]))
+ console.log('Unable to layout tree');
//prevent re-triggering until after a delay
this.resizeThrottled = true;
setTimeout(() => {this.resizeThrottled = false;}, 100);
}
},
- onInnerTileClicked(nodeList: LayoutNode[]){
- //nodeList is an array of layout-nodes, from the clicked-on-tile's node upward
- let numNewTiles = nodeList[0].tolNode.children.length;
- if (numNewTiles == 0){
+ onInnerTileClicked(node: LayoutNode){
+ if (node.tolNode.children.length == 0){
console.log('Tile-to-expand has no children');
return;
}
- //add children
- nodeList[0].children = nodeList[0].tolNode.children.map((n: TolNode) => new LayoutNode(n, []));
- layoutInfoHooks.updateLayoutInfoOnExpand(nodeList);
- //try to re-layout
- if (!this.tryLayout()){
- nodeList[0].children = [];
- layoutInfoHooks.updateLayoutInfoOnCollapse(nodeList);
- }
- },
- onInnerHeaderClicked(nodeList: LayoutNode[]){
- //nodeList is an array of layout-nodes, from the clicked-on-tile's node upward
- let children = nodeList[0].children;
- nodeList[0].children = [];
- layoutInfoHooks.updateLayoutInfoOnCollapse(nodeList);
- if (!this.tryLayout()){
- nodeList[0].children = children;
- layoutInfoHooks.updateLayoutInfoOnExpand(nodeList);
- }
+ if (!this.layoutTree.tryLayoutOnExpand([0,0], [this.width,this.height], node))
+ console.log('Unable to layout tree');
},
- tryLayout(){
- let newLayout = genLayout(this.layoutTree, [0,0], [this.width,this.height], true);
- if (newLayout == null){
+ onInnerHeaderClicked(node: LayoutNode){
+ if (!this.layoutTree.tryLayoutOnCollapse([0,0], [this.width,this.height], node))
console.log('Unable to layout tree');
- return false;
- } else {
- this.applyLayout(newLayout, this.layoutTree);
- return true;
- }
},
- applyLayout(newLayout: LayoutNode, layoutTree: LayoutNode){
- layoutTree.pos = newLayout.pos;
- layoutTree.dims = newLayout.dims;
- layoutTree.headerSz = newLayout.headerSz;
- newLayout.children.forEach((n,i) => this.applyLayout(n, layoutTree.children[i]));
- //handle case where leaf nodes placed in leftover space from parent-sweep
- if (newLayout.sepSweptArea != null){
- //add parent area coords
- layoutTree.sepSweptArea = newLayout.sepSweptArea;
- //move leaf node children to parent area
- layoutTree.children.filter(n => n.children.length == 0).map(n => {
- n.pos[0] += newLayout.sepSweptArea!.pos[0],
- n.pos[1] += newLayout.sepSweptArea!.pos[1]
- });
- } else {
- layoutTree.sepSweptArea = null;
- }
- }
},
created(){
window.addEventListener('resize', this.onResize);
- this.tryLayout();
+ if (!this.layoutTree.tryLayout([0,0], [this.width,this.height]))
+ console.log('Unable to layout tree');
},
unmounted(){
window.removeEventListener('resize', this.onResize);
@@ -120,7 +86,9 @@ export default defineComponent({
<template>
<div class="h-[100vh]">
- <tile :layoutNode="layoutTree" @tile-clicked="onInnerTileClicked" @header-clicked="onInnerHeaderClicked"></tile>
+ <tile :layoutNode="layoutTree.root"
+ :headerSz="layoutOptions.headerSz" :tileSpacing="layoutOptions.tileSpacing"
+ :transitionDuration="otherOptions.transitionDuration"
+ @tile-clicked="onInnerTileClicked" @header-clicked="onInnerHeaderClicked"></tile>
</div>
</template>
-
diff --git a/src/layout.ts b/src/layout.ts
index 95e15d0..961083b 100644
--- a/src/layout.ts
+++ b/src/layout.ts
@@ -1,52 +1,141 @@
-import {TolNode, LayoutNode, SepSweptArea} from './types';
-export {genLayout, layoutInfoHooks};
+import {TolNode} from './types';
-type LayoutFn = (node: LayoutNode, pos: [number, number], dims: [number, number], hideHeader: boolean,
- options?: {subLayoutFn?: LayoutFn, sepSweptArea?: SepSweptArea|null}) => LayoutNode | null;
-
-let TILE_SPACING = 5;
-let HEADER_SZ = 20;
-let MIN_TILE_SZ = 50;
-let MAX_TILE_SZ = 200;
-let RECT_MODE = 'auto'; //'horz', 'vert', 'linear', 'auto'
-let SWEEP_MODE = 'left'; //'left', 'top', 'shorter', 'auto'
-let ALLOW_SWEEP_TO_PARENT = true;
-let RECT_SPC_SHIFTING = true;
-
-const layoutInfoHooks = { //made common-across-layout-types for layout inter-usability
- initLayoutInfo(node: LayoutNode){
- if (node.children.length > 0){
- node.children.forEach((n: LayoutNode) => this.initLayoutInfo(n));
+export class LayoutTree {
+ root: LayoutNode;
+ options: LayoutOptions;
+ constructor(tol: TolNode, depth: number, options: LayoutOptions){
+ this.root = this.initHelper(tol, depth);
+ this.options = options;
+ }
+ initHelper(tolNode: TolNode, depth: number): LayoutNode {
+ if (depth > 0){
+ let children = tolNode.children.map(
+ (n: TolNode) => this.initHelper(n, depth-1));
+ let node = new LayoutNode(tolNode, children);
+ children.forEach(n => n.parent = node);
+ return node;
+ } else {
+ return new LayoutNode(tolNode, []);
}
- this.updateLayoutInfo(node);
- },
- updateLayoutInfoOnExpand(nodeList: LayoutNode[]){
- //given list of layout-nodes from expanded_child-to-parent, update layout-info
- nodeList[0].children.forEach(this.updateLayoutInfo);
- for (let node of nodeList){
- this.updateLayoutInfo(node);
+ }
+ tryLayout(pos: [number,number], dims: [number,number]){
+ let newLayout: LayoutNode | null;
+ switch (this.options.layoutType){
+ case 'sqr': newLayout = sqrLayoutFn(this.root, pos, dims, false, this.options); break;
+ case 'rect': newLayout = rectLayoutFn(this.root, pos, dims, false, this.options); break;
+ case 'sweep': newLayout = sweepLayoutFn(this.root, pos, dims, false, this.options); break;
}
- },
- updateLayoutInfoOnCollapse(nodeList: LayoutNode[]){
- //given list of layout-nodes from child_to_collapse-to-parent, update layout-info
- for (let node of nodeList){
- this.updateLayoutInfo(node);
+ if (newLayout == null)
+ return false;
+ this.copyTreeForRender(newLayout, this.root);
+ return true;
+ }
+ tryLayoutOnExpand(pos: [number,number], dims: [number,number], node: LayoutNode){
+ //add children
+ node.children = node.tolNode.children.map((n: TolNode) => new LayoutNode(n, []));
+ node.children.forEach(n => n.parent = node);
+ this.updateDCounts(node, node.children.length-1);
+ //try layout
+ let success = this.tryLayout(pos, dims);
+ if (!success){ //remove children
+ node.children = [];
+ this.updateDCounts(node, -node.tolNode.children.length+1);
}
- },
- updateLayoutInfo(node: LayoutNode){
- if (node.children.length == 0){
- node.tileCount = 1;
- } else {
- node.tileCount = node.children.map(n => n.tileCount).reduce((x,y) => x+y);
+ return success;
+ }
+ tryLayoutOnCollapse(pos: [number,number], dims: [number,number], node: LayoutNode){
+ //remove children
+ let children = node.children;
+ node.children = [];
+ this.updateDCounts(node, -children.length+1);
+ //try layout
+ let success = this.tryLayout(pos, dims);
+ if (!success){ //add children
+ node.children = children;
+ this.updateDCounts(node, node.children.length-1);
+ }
+ return success;
+ }
+ copyTreeForRender(node: LayoutNode, target: LayoutNode): void {
+ target.pos = node.pos;
+ target.dims = node.dims;
+ target.showHeader = node.showHeader;
+ target.sepSweptArea = node.sepSweptArea;
+ //these are arguably redundant
+ target.dCount = node.dCount;
+ target.usedDims = node.usedDims;
+ target.empSpc = node.empSpc;
+ //recurse on children
+ node.children.forEach((n,i) => this.copyTreeForRender(n, target.children[i]));
+ }
+ updateDCounts(node: LayoutNode | null, diff: number): void{
+ while (node != null){
+ node.dCount += diff;
+ node = node.parent;
}
}
}
+export type LayoutOptions = {
+ tileSpacing: number;
+ //showHeader: 'all' | 'non-root' | 'expanded' | 'expanded non-root' | 'leaf' | 'none'?
+ headerSz: number;
+ minTileSz: number;
+ maxTileSz: number;
+ layoutType: 'sqr' | 'rect' | 'sweep';
+ rectMode: 'horz' | 'vert' | 'linear' | 'auto';
+ rectSpaceShifting: boolean;
+ sweepMode: 'left' | 'top' | 'shorter' | 'auto';
+ sweepingToParent: boolean;
+};
+export class LayoutNode {
+ //structure-related
+ tolNode: TolNode;
+ children: LayoutNode[];
+ parent: LayoutNode | null;
+ //used for rendering
+ pos: [number, number];
+ dims: [number, number];
+ showHeader: boolean;
+ sepSweptArea: SepSweptArea | null;
+ //used for layout heuristics
+ dCount: number; //number of descendant leaf nodes
+ usedDims: [number, number];
+ empSpc: number;
+ //
+ constructor(
+ tolNode: TolNode, children: LayoutNode[], pos=[0,0] as [number,number], dims=[0,0] as [number,number],
+ {showHeader=false, sepSweptArea=null as SepSweptArea|null, usedDims=[0,0] as [number,number], empSpc=0} = {}){
+ this.tolNode = tolNode;
+ this.children = children;
+ this.parent = null;
+ this.pos = pos;
+ this.dims = dims;
+ this.showHeader = showHeader;
+ this.sepSweptArea = sepSweptArea;
+ this.dCount = children.length == 0 ? 1 : children.map(n => n.dCount).reduce((x,y) => x+y);
+ this.usedDims = usedDims;
+ this.empSpc = empSpc;
+ }
+}
+export class SepSweptArea {
+ pos: [number, number];
+ dims: [number, number];
+ sweptLeft: boolean;
+ constructor(pos: [number, number], dims: [number, number], sweptLeft: boolean, tileSpacing: number){
+ this.pos = pos;
+ this.dims = dims;
+ this.sweptLeft = sweptLeft;
+ }
+}
+
+type LayoutFn = (node: LayoutNode, pos: [number, number], dims: [number, number], showHeader: boolean,
+ opts: LayoutOptions, ownOpts?: {subLayoutFn?: LayoutFn, sepSweptArea?: SepSweptArea|null}) => LayoutNode | null;
//lays out nodes as squares in a rectangle, with spacing
-let sqrLayoutFn: LayoutFn = function (node, pos, dims, hideHeader){
+let sqrLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts){
//get number-of-columns with lowest leftover empty space
- let headerSz = (hideHeader ? 0 : HEADER_SZ);
- let availW = dims[0] - TILE_SPACING, availH = dims[1] - headerSz - TILE_SPACING;
+ let headerSz = showHeader ? opts.headerSz : 0;
+ let availW = dims[0] - opts.tileSpacing, availH = dims[1] - headerSz - opts.tileSpacing;
if (availW*availH <= 0)
return null;
let numChildren = node.children.length, ar = availW/availH;
@@ -55,12 +144,12 @@ let sqrLayoutFn: LayoutFn = function (node, pos, dims, hideHeader){
let nr = Math.ceil(numChildren/nc);
let ar2 = nc/nr;
let frac = ar > ar2 ? ar2/ar : ar/ar2;
- let tileSz = ar > ar2 ? availH/nr-TILE_SPACING : availW/nc-TILE_SPACING;
- if (tileSz < MIN_TILE_SZ)
+ let tileSz = ar > ar2 ? availH/nr-opts.tileSpacing : availW/nc-opts.tileSpacing;
+ if (tileSz < opts.minTileSz)
continue;
- else if (tileSz > MAX_TILE_SZ)
- tileSz = MAX_TILE_SZ;
- let empSpc = (1-frac)*availW*availH + (nc*nr-numChildren)*(tileSz - TILE_SPACING)**2;
+ else if (tileSz > opts.maxTileSz)
+ tileSz = opts.maxTileSz;
+ let empSpc = (1-frac)*availW*availH + (nc*nr-numChildren)*(tileSz - opts.tileSpacing)**2;
if (empSpc < lowestEmp){
lowestEmp = empSpc;
numCols = nc;
@@ -70,35 +159,37 @@ let sqrLayoutFn: LayoutFn = function (node, pos, dims, hideHeader){
}
if (lowestEmp == Number.POSITIVE_INFINITY)
return null;
- let childLayouts = arrayOf(0, numChildren);
+ let childLayouts = arrayOf(null, numChildren);
for (let i = 0; i < numChildren; i++){
let child = node.children[i];
- let childX = TILE_SPACING + (i % numCols)*(tileSize + TILE_SPACING);
- let childY = TILE_SPACING + headerSz + Math.floor(i / numCols)*(tileSize + TILE_SPACING);
+ let childX = opts.tileSpacing + (i % numCols)*(tileSize + opts.tileSpacing);
+ let childY = opts.tileSpacing + headerSz + Math.floor(i / numCols)*(tileSize + opts.tileSpacing);
if (child.children.length == 0){
childLayouts[i] = new LayoutNode(child.tolNode, [], [childX,childY], [tileSize,tileSize],
- {headerSz: 0, usedDims: [tileSize,tileSize], empSpc: 0});
+ {usedDims: [tileSize,tileSize], empSpc: 0});
} else {
- childLayouts[i] = sqrLayoutFn(child, [childX,childY], [tileSize,tileSize], false);
+ childLayouts[i] = sqrLayoutFn(child, [childX,childY], [tileSize,tileSize], true, opts);
if (childLayouts[i] == null)
return null;
lowestEmp += childLayouts[i].empSpc;
}
}
- return new LayoutNode(node.tolNode, childLayouts, pos, dims, {
- headerSz,
- usedDims: [numCols * (tileSize + TILE_SPACING) + TILE_SPACING,
- numRows * (tileSize + TILE_SPACING) + TILE_SPACING + headerSz],
+ let newNode = new LayoutNode(node.tolNode, childLayouts, pos, dims, {
+ showHeader,
+ usedDims: [numCols * (tileSize + opts.tileSpacing) + opts.tileSpacing,
+ numRows * (tileSize + opts.tileSpacing) + opts.tileSpacing + headerSz],
empSpc: lowestEmp,
});
+ childLayouts.forEach(n => n.parent = newNode);
+ return newNode;
}
//lays out nodes as rectangles organised into rows, partially using other layouts for children
-let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={subLayoutFn: rectLayoutFn}){
+let rectLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts, ownOpts={subLayoutFn: rectLayoutFn}){
if (node.children.every(n => n.children.length == 0))
- return sqrLayoutFn(node, pos, dims, hideHeader);
+ return sqrLayoutFn(node, pos, dims, showHeader, opts);
//find grid-arrangement with lowest leftover empty space
- let headerSz = (hideHeader ? 0 : HEADER_SZ);
- let availW = dims[0] - TILE_SPACING, availH = dims[1] - TILE_SPACING - headerSz;
+ let headerSz = showHeader ? opts.headerSz : 0;
+ let availW = dims[0] - opts.tileSpacing, availH = dims[1] - opts.tileSpacing - headerSz;
let numChildren = node.children.length;
let rowBrks: number[]|null = null; //will holds node indices at which each row starts
let lowestEmp = Number.POSITIVE_INFINITY, rowBreaks = null, childLayouts = null;
@@ -106,19 +197,20 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
while (true){
//update rowBrks or exit loop
if (rowBrks == null){
- if (RECT_MODE == 'vert'){
+ if (opts.rectMode == 'vert'){
rowBrks = seq(numChildren);
} else {
rowBrks = [0];
}
} else {
- if (RECT_MODE == 'horz' || RECT_MODE == 'vert'){
+ if (opts.rectMode == 'horz' || opts.rectMode == 'vert'){
break rowBrksLoop;
- } else if (RECT_MODE == 'linear'){
- if (rowBrks.length == 1 && numChildren > 1)
+ } else if (opts.rectMode == 'linear'){
+ if (rowBrks.length == 1 && numChildren > 1){
rowBrks = seq(numChildren);
- else
+ } else {
break rowBrksLoop;
+ }
} else {
let i = rowBrks.length-1;
while (true){
@@ -138,15 +230,15 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
}
}
}
- //create list-of-lists representing each row's cells' tileCounts
+ //create list-of-lists representing each row's cells' dCounts
let rowsOfCnts: number[][] = arrayOf([], rowBrks.length);
for (let r = 0; r < rowBrks.length; r++){
let numNodes = (r == rowBrks.length-1) ? numChildren-rowBrks[r] : rowBrks[r+1]-rowBrks[r];
let rowNodeIdxs = seq(numNodes).map(i => i+rowBrks![r]);
- rowsOfCnts[r] = rowNodeIdxs.map(idx => node.children[idx].tileCount);
+ rowsOfCnts[r] = rowNodeIdxs.map(idx => node.children[idx].dCount);
}
//get cell dims
- let totalTileCount = node.children.map(n => n.tileCount).reduce((x,y) => x+y);
+ let totalTileCount = node.children.map(n => n.dCount).reduce((x,y) => x+y);
let cellHs = rowsOfCnts.map(row => row.reduce((x,y) => x+y) / totalTileCount * availH);
let cellWs = arrayOf(0, numChildren);
for (let r = 0; r < rowsOfCnts.length; r++){
@@ -156,12 +248,12 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
}
}
//impose min-tile-size
- cellHs = limitVals(cellHs, MIN_TILE_SZ, Number.POSITIVE_INFINITY)!;
+ cellHs = limitVals(cellHs, opts.minTileSz, Number.POSITIVE_INFINITY)!;
if (cellHs == null)
continue rowBrksLoop;
for (let r = 0; r < rowsOfCnts.length; r++){
let temp = limitVals(cellWs.slice(rowBrks[r], rowBrks[r] + rowsOfCnts[r].length),
- MIN_TILE_SZ, Number.POSITIVE_INFINITY);
+ opts.minTileSz, Number.POSITIVE_INFINITY);
if (temp == null)
continue rowBrksLoop;
cellWs.splice(rowBrks[r], rowsOfCnts[r].length, ...temp);
@@ -179,29 +271,29 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
cellYs[r] = cellYs[r-1] + cellHs[r-1];
}
//get child layouts and empty-space
- let childLyts = arrayOf(0, numChildren);
+ let childLyts = arrayOf(null, numChildren);
let empVTotal = 0, empSpc = 0;
for (let r = 0; r < rowBrks.length; r++){
let empHorzTotal = 0;
for (let c = 0; c < rowsOfCnts[r].length; c++){
let nodeIdx = rowBrks[r]+c;
let child = node.children[nodeIdx];
- let childX = cellXs[nodeIdx] + TILE_SPACING, childY = cellYs[r] + TILE_SPACING + headerSz,
- childW = cellWs[nodeIdx] - TILE_SPACING, childH = cellHs[r] - TILE_SPACING;
+ let childX = cellXs[nodeIdx] + opts.tileSpacing, childY = cellYs[r] + opts.tileSpacing + headerSz,
+ childW = cellWs[nodeIdx] - opts.tileSpacing, childH = cellHs[r] - opts.tileSpacing;
if (child.children.length == 0){
let contentSz = Math.min(childW, childH);
childLyts[nodeIdx] = new LayoutNode(child.tolNode, [], [childX,childY], [childW,childH],
- {headerSz: 0, usedDims: [contentSz,contentSz], empSpc: childW*childH - contentSz**2});
+ {usedDims: [contentSz,contentSz], empSpc: childW*childH - contentSz**2})
} else if (child.children.every(n => n.children.length == 0)){
- childLyts[nodeIdx] = sqrLayoutFn(child, [childX,childY], [childW,childH], false);
+ childLyts[nodeIdx] = sqrLayoutFn(child, [childX,childY], [childW,childH], true, opts);
} else {
- let layoutFn = (options && options.subLayoutFn) || rectLayoutFn;
- childLyts[nodeIdx] = layoutFn(child, [childX,childY], [childW,childH], false);
+ let layoutFn = (ownOpts && ownOpts.subLayoutFn) || rectLayoutFn;
+ childLyts[nodeIdx] = layoutFn(child, [childX,childY], [childW,childH], true, opts);
}
if (childLyts[nodeIdx] == null)
continue rowBrksLoop;
//handle horizontal empty-space-shifting
- if (RECT_SPC_SHIFTING){
+ if (opts.rectSpaceShifting){
let empHorz = childLyts[nodeIdx].dims[0] - childLyts[nodeIdx].usedDims[0];
childLyts[nodeIdx].dims[0] -= empHorz;
childLyts[nodeIdx].empSpc -= empHorz * childLyts[nodeIdx].dims[1];
@@ -214,7 +306,7 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
}
}
//handle vertical empty-space-shifting
- if (RECT_SPC_SHIFTING){
+ if (opts.rectSpaceShifting){
let nodeIdxs = seq(rowsOfCnts[r].length).map(i => rowBrks![r]+i);
let empVerts = nodeIdxs.map(idx => childLyts[idx].dims[1] - childLyts[idx].usedDims[1]);
let minEmpVert = Math.min(...empVerts);
@@ -254,54 +346,60 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sub
l.dims[1] = l.usedDims[1];
});
//determine layout
- return new LayoutNode(node.tolNode, childLayouts, pos, dims,
- {headerSz, usedDims: dims, empSpc: lowestEmp});
+ let newNode = new LayoutNode(node.tolNode, childLayouts, pos, dims,
+ {showHeader, usedDims: dims, empSpc: lowestEmp});
//trying to shrink usedDims causes problems with swept-to-parent-area div-alignment
+ childLayouts.forEach(n => n.parent = newNode);
+ return newNode;
}
//lays out nodes by pushing leaves to one side, partially using other layouts for children
-let sweepLeavesLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, options={sepSweptArea: null}){
+let sweepLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts, ownOpts={sepSweptArea: null}){
//separate leaf and non-leaf nodes
let leaves: LayoutNode[] = [], nonLeaves: LayoutNode[] = [];
node.children.forEach(n => (n.children.length == 0 ? leaves : nonLeaves).push(n));
//determine layout
let tempTree: LayoutNode;
- if (nonLeaves.length == 0){ //if all leaves, use squares-layout
- return sqrLayoutFn(node, pos, dims, hideHeader);
+ if (nonLeaves.length == 0){
+ return sqrLayoutFn(node, pos, dims, showHeader, opts);
} else if (leaves.length == 0){
- tempTree = new LayoutNode(new TolNode('SWEEP_REM_' + node.tolNode.name), nonLeaves);
- return rectLayoutFn(tempTree, pos, dims, hideHeader, {subLayoutFn: sweepLeavesLayoutFn});
+ return rectLayoutFn(node, pos, dims, showHeader, opts, {subLayoutFn:sweepLayoutFn});
} else {
- let ratio = leaves.length / (leaves.length + nonLeaves.map(n => n.tileCount).reduce((x,y) => x+y));
- let headerSz = (hideHeader ? 0 : HEADER_SZ);
+ let ratio = leaves.length / (leaves.length + nonLeaves.map(n => n.dCount).reduce((x,y) => x+y));
+ let headerSz = showHeader ? opts.headerSz : 0;
let sweptLayout = null, nonLeavesLayout = null, sweptLeft = false;
//get swept-area layout
- let parentArea = options && options.sepSweptArea, usingParentArea = false;
- if (ALLOW_SWEEP_TO_PARENT && parentArea){
+ let parentArea = ownOpts && ownOpts.sepSweptArea, usingParentArea = false;
+ if (opts.sweepingToParent && parentArea){
tempTree = new LayoutNode(new TolNode('SWEEP_' + node.tolNode.name), leaves);
+ //not updating the children to point to tempTree as a parent seems acceptable here
sweptLeft = parentArea.sweptLeft;
- sweptLayout = sqrLayoutFn(tempTree, [0,0], parentArea.dims, sweptLeft);
+ sweptLayout = sqrLayoutFn(tempTree, [0,0], parentArea.dims, sweptLeft, opts);
if (sweptLayout != null){
- let newDims: [number,number] = [dims[0], dims[1]-headerSz];
- if (!sweptLeft) //no remaining-area header if swept-upward
- newDims[1] = dims[1];
+ //move leaves to parent area
+ sweptLayout.children.map(n => {
+ n.pos[0] += parentArea!.pos[0];
+ n.pos[1] += parentArea!.pos[1];
+ });
//get remaining-area layout
+ let newDims: [number,number] = [dims[0], dims[1] - (sweptLeft ? headerSz : 0)];
tempTree = new LayoutNode(new TolNode('SWEEP_REM_' + node.tolNode.name), nonLeaves);
if (nonLeaves.length > 1){
- nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, true, {subLayoutFn: sweepLeavesLayoutFn});
+ nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, false, opts,
+ {subLayoutFn: sweepLayoutFn});
} else {
//get leftover swept-layout-area to propagate
let leftOverWidth = parentArea.dims[0] - sweptLayout.usedDims[0];
let leftOverHeight = parentArea.dims[1] - sweptLayout.usedDims[1];
let leftoverArea = sweptLeft ?
new SepSweptArea(
- [parentArea.pos[0], parentArea.pos[1]+sweptLayout.usedDims[1]-TILE_SPACING-headerSz],
- [parentArea.dims[0], leftOverHeight-TILE_SPACING], sweptLeft, TILE_SPACING) :
+ [parentArea.pos[0], parentArea.pos[1]+sweptLayout.usedDims[1]-opts.tileSpacing-headerSz],
+ [parentArea.dims[0], leftOverHeight-opts.tileSpacing], sweptLeft) :
new SepSweptArea(
- [parentArea.pos[0]+sweptLayout.usedDims[0]-TILE_SPACING, parentArea.pos[1] + headerSz],
- [leftOverWidth-TILE_SPACING, parentArea.dims[1] - headerSz], sweptLeft, TILE_SPACING);
- //call genLayout
- nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, true,
- {subLayoutFn: (n,p,d,h) => sweepLeavesLayoutFn(n,p,d,h,{sepSweptArea:leftoverArea})});
+ [parentArea.pos[0]+sweptLayout.usedDims[0]-opts.tileSpacing, parentArea.pos[1]+headerSz],
+ [leftOverWidth-opts.tileSpacing, parentArea.dims[1]-headerSz], sweptLeft);
+ //generate layout
+ nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, false, opts,
+ {subLayoutFn: (n,p,d,h,o) => sweepLayoutFn(n,p,d,h,o,{sepSweptArea:leftoverArea})});
}
if (nonLeavesLayout != null){
nonLeavesLayout.children.forEach(layout => {layout.pos[1] += (sweptLeft ? headerSz : 0)});
@@ -312,18 +410,19 @@ let sweepLeavesLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, optio
if (!usingParentArea){
let newDims: [number,number] = [dims[0], dims[1]-headerSz];
tempTree = new LayoutNode(new TolNode('SWEEP_' + node.tolNode.name), leaves);
- let xyChg: [number, number];
+ let xyChg: [number,number];
//get swept-area layout
let leftLayout = null, topLayout = null;
let documentAR = document.documentElement.clientWidth / document.documentElement.clientHeight;
- if (SWEEP_MODE == 'left' || (SWEEP_MODE == 'shorter' && documentAR >= 1) || SWEEP_MODE == 'auto'){
+ if (opts.sweepMode=='left' || (opts.sweepMode=='shorter' && documentAR >= 1) || opts.sweepMode=='auto'){
leftLayout = sqrLayoutFn(tempTree, [0,0],
- [Math.max(newDims[0]*ratio, MIN_TILE_SZ+TILE_SPACING*2), newDims[1]], true);
- } else if (SWEEP_MODE == 'top' || (SWEEP_MODE == 'shorter' && documentAR < 1) || SWEEP_MODE == 'auto'){
+ [Math.max(newDims[0]*ratio, opts.minTileSz+opts.tileSpacing*2), newDims[1]], false, opts);
+ }
+ if (opts.sweepMode=='top' || (opts.sweepMode=='shorter' && documentAR < 1) || opts.sweepMode=='auto'){
topLayout = sqrLayoutFn(tempTree, [0,0],
- [newDims[0], Math.max(newDims[1]*ratio, MIN_TILE_SZ+TILE_SPACING*2)], true);
+ [newDims[0], Math.max(newDims[1]*ratio, opts.minTileSz+opts.tileSpacing*2)], false, opts);
}
- if (SWEEP_MODE == 'auto'){
+ if (opts.sweepMode == 'auto'){
sweptLayout =
(leftLayout && topLayout && ((leftLayout.empSpc < topLayout.empSpc) ? leftLayout : topLayout)) ||
leftLayout || topLayout;
@@ -336,37 +435,38 @@ let sweepLeavesLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, optio
sweptLayout.children.forEach(layout => {layout.pos[1] += headerSz});
//get remaining-area layout
if (sweptLeft){
- xyChg = [sweptLayout.usedDims[0] - TILE_SPACING, 0];
- newDims[0] += -sweptLayout.usedDims[0] + TILE_SPACING;
+ xyChg = [sweptLayout.usedDims[0] - opts.tileSpacing, 0];
+ newDims[0] += -sweptLayout.usedDims[0] + opts.tileSpacing;
} else {
- xyChg = [0, sweptLayout.usedDims[1] - TILE_SPACING];
- newDims[1] += -sweptLayout.usedDims[1] + TILE_SPACING;
+ xyChg = [0, sweptLayout.usedDims[1] - opts.tileSpacing];
+ newDims[1] += -sweptLayout.usedDims[1] + opts.tileSpacing;
}
tempTree = new LayoutNode(new TolNode('SWEEP_REM_' + node.tolNode.name), nonLeaves);
if (nonLeaves.length > 1){
- nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, true, {subLayoutFn: sweepLeavesLayoutFn});
+ nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, false, opts, {subLayoutFn:sweepLayoutFn});
} else {
//get leftover swept-layout-area to propagate
let leftoverArea : SepSweptArea;
if (sweptLeft){
leftoverArea = new SepSweptArea( //pos is relative to the non-leaves-area
- [-sweptLayout.usedDims[0]+TILE_SPACING, sweptLayout.usedDims[1]-TILE_SPACING],
- [sweptLayout.usedDims[0]-TILE_SPACING*2, newDims[1]-sweptLayout.usedDims[1]-TILE_SPACING],
- sweptLeft, TILE_SPACING
+ [-sweptLayout.usedDims[0]+opts.tileSpacing, sweptLayout.usedDims[1]-opts.tileSpacing],
+ [sweptLayout.usedDims[0]-opts.tileSpacing*2,
+ newDims[1]-sweptLayout.usedDims[1]-opts.tileSpacing],
+ sweptLeft
);
} else {
leftoverArea = new SepSweptArea(
- [sweptLayout.usedDims[0]-TILE_SPACING, -sweptLayout.usedDims[1]+TILE_SPACING],
- [newDims[0]-sweptLayout.usedDims[0]-TILE_SPACING, sweptLayout.usedDims[1]-TILE_SPACING*2],
- sweptLeft, TILE_SPACING
+ [sweptLayout.usedDims[0]-opts.tileSpacing, -sweptLayout.usedDims[1]+opts.tileSpacing],
+ [newDims[0]-sweptLayout.usedDims[0]-opts.tileSpacing,
+ sweptLayout.usedDims[1]-opts.tileSpacing*2],
+ sweptLeft
);
}
leftoverArea.dims[0] = Math.max(0, leftoverArea.dims[0]);
leftoverArea.dims[1] = Math.max(0, leftoverArea.dims[1]);
- //call genLayout
- nonLeavesLayout = rectLayoutFn(
- tempTree, [0,0], newDims, true,
- {subLayoutFn: (n,p,d,h) => sweepLeavesLayoutFn(n,p,d,h,{sepSweptArea:leftoverArea})});
+ //generate layout
+ nonLeavesLayout = rectLayoutFn(tempTree, [0,0], newDims, false, opts,
+ {subLayoutFn: (n,p,d,h,o) => sweepLayoutFn(n,p,d,h,o,{sepSweptArea:leftoverArea})});
}
if (nonLeavesLayout == null)
return null;
@@ -383,25 +483,23 @@ let sweepLeavesLayoutFn: LayoutFn = function (node, pos, dims, hideHeader, optio
let layoutsInOldOrder = seq(node.children.length)
.map(i => children.findIndex(n => n == node.children[i]))
.map(i => layouts[i]);
- return new LayoutNode(node.tolNode, layoutsInOldOrder, pos, dims, {
- headerSz,
+ let newNode = new LayoutNode(node.tolNode, layoutsInOldOrder, pos, dims, {
+ showHeader,
usedDims: [
usingParentArea ? nonLeavesLayout.dims[0] : (sweptLeft ?
- sweptLayout.dims[0] + nonLeavesLayout.dims[0] - TILE_SPACING :
+ sweptLayout.dims[0] + nonLeavesLayout.dims[0] - opts.tileSpacing :
Math.max(sweptLayout.dims[0], nonLeavesLayout.dims[0])),
usingParentArea ? nonLeavesLayout.dims[1] + headerSz : (sweptLeft ?
Math.max(sweptLayout.dims[1], nonLeavesLayout.dims[1]) + headerSz :
- sweptLayout.dims[1] + nonLeavesLayout.dims[1] - TILE_SPACING + headerSz),
+ sweptLayout.dims[1] + nonLeavesLayout.dims[1] - opts.tileSpacing + headerSz),
],
empSpc: sweptLayout.empSpc + nonLeavesLayout.empSpc,
sepSweptArea: (usingParentArea && parentArea) ? parentArea : null,
});
+ layoutsInOldOrder.forEach(n => n.parent = newNode);
+ return newNode;
}
}
-//default layout function
-let genLayout: LayoutFn = function (node, pos, dims, hideHeader){
- return sweepLeavesLayoutFn(node, pos, dims, hideHeader);
-}
//clips values in array to within [min,max], and redistributes to compensate, returning null if unable
function limitVals(arr: number[], min: number, max: number): number[]|null {
diff --git a/src/types.ts b/src/types.ts
index cc5de25..87097a7 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -6,45 +6,3 @@ export class TolNode {
this.children = children;
}
}
-export class LayoutNode {
- //set by TileTree and LayoutFn funcs, eventually used by Tile
- tolNode: TolNode;
- children: LayoutNode[];
- pos: [number, number];
- dims: [number, number];
- headerSz: number;
- //set by layoutInfoHooks, used by LayoutFn funcs
- tileCount: number;
- //set_by/internal_to LayoutFn funcs
- usedDims: [number, number];
- empSpc: number;
- //set by LayoutFn funcs, eventually used by Tile
- sepSweptArea: SepSweptArea | null;
- //
- constructor(
- tolNode: TolNode, children: LayoutNode[], pos:[number,number]=[0,0], dims:[number,number]=[0,0],
- {headerSz=0, tileCount=0, usedDims=[0,0] as [number,number],
- empSpc=0, sepSweptArea=null as SepSweptArea|null} = {}){
- this.tolNode = tolNode;
- this.children = children;
- this.pos = pos;
- this.dims = dims;
- this.headerSz = headerSz;
- this.tileCount = tileCount;
- this.usedDims = usedDims;
- this.empSpc = empSpc;
- this.sepSweptArea = sepSweptArea;
- }
-}
-export class SepSweptArea {
- pos: [number, number];
- dims: [number, number];
- sweptLeft: boolean;
- tileSpacing: number;
- constructor(pos: [number, number], dims: [number, number], sweptLeft: boolean, tileSpacing: number){
- this.pos = pos;
- this.dims = dims;
- this.sweptLeft = sweptLeft;
- this.tileSpacing = tileSpacing;
- }
-}