aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Tile.vue96
-rw-r--r--src/components/TileTree.vue3
-rw-r--r--src/lib.ts27
3 files changed, 76 insertions, 50 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 9e4be03..411ba76 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -2,6 +2,22 @@
import {defineComponent, PropType} from 'vue';
import {LayoutNode} from '../lib';
+// Configurable settings (integer values specify pixels)
+let options = {
+ borderRadius: 5,
+ shadowNormal: '0 0 2px black',
+ shadowWithHover: '0 0 1px 2px greenyellow',
+ // For leaf tiles
+ leafHeaderX: 4,
+ leafHeaderY: 2,
+ leafHeaderColor: '#fafaf9',
+ expandableLeafHeaderColor: 'greenyellow', //yellow, greenyellow, turquoise,
+ // For non-leaf tile-groups
+ nonLeafBgColors: ['#44403c', '#57534e'], //tiles at depth N use the Nth color, repeating from the start as needed
+ nonLeafHeaderColor: '#fafaf9',
+ nonLeafHeaderBgColor: '#1c1917',
+};
+
// Component holds a tree-node structure representing a tile or tile-group to be rendered
export default defineComponent({
name: 'tile', // Need this to use self in template
@@ -15,14 +31,7 @@ export default defineComponent({
},
data(){
return {
- borderRadius: '5px',
- leafHeaderHorzSpc: 4,
- leafHeaderVertSpc: 2,
- leafHeaderColor: '#fafaf9',
- expandableLeafHeaderColor: 'greenyellow', //yellow, greenyellow, turquoise,
- nonLeafBgColor: '#44403c',
- nonLeafHeaderColor: '#fafaf9',
- nonLeafHeaderBgColor: '#78716c',
+ options: options,
// Used during transitions and to emulate/show an apparently-joined div
zIdx: 0,
overflow: this.isRoot ? 'hidden' : 'visible',
@@ -39,6 +48,10 @@ export default defineComponent({
return (this.layoutNode.showHeader && !this.layoutNode.sepSweptArea) ||
(this.layoutNode.sepSweptArea && this.layoutNode.sepSweptArea.sweptLeft);
},
+ nonLeafBgColor(){
+ let colorArray = this.options.nonLeafBgColors;
+ return colorArray[this.layoutNode.depth % colorArray.length];
+ },
tileStyles(): Record<string,string> {
return {
// Places div using layoutNode, with centering if root
@@ -57,7 +70,9 @@ export default defineComponent({
transitionTimingFunction: 'ease-out',
// CSS variables
'--nonLeafBgColor': this.nonLeafBgColor,
- '--expandableLeafHeaderColor': this.expandableLeafHeaderColor,
+ '--tileSpacing': this.tileSpacing + 'px',
+ '--shadowNormal': this.options.shadowNormal,
+ '--shadowWithHover': this.options.shadowWithHover,
};
},
leafStyles(): Record<string,string> {
@@ -66,18 +81,18 @@ export default defineComponent({
height: '100%',
backgroundImage: 'url(\'/img/' + this.layoutNode.tolNode.name.replaceAll('\'', '\\\'') + '.png\')',
backgroundSize: 'cover',
- borderRadius: this.borderRadius,
+ borderRadius: this.options.borderRadius + 'px',
};
},
leafHeaderStyles(): Record<string,string> {
return {
position: 'absolute',
- left: this.leafHeaderHorzSpc + 'px',
- top: this.leafHeaderVertSpc + 'px',
- maxWidth: (this.layoutNode.dims[0] - this.leafHeaderHorzSpc*2) + 'px',
+ left: this.options.leafHeaderX + 'px',
+ top: this.options.leafHeaderY + 'px',
+ maxWidth: (this.layoutNode.dims[0] - this.options.leafHeaderX * 2) + 'px',
height: this.headerSz + 'px',
lineHeight: this.headerSz + 'px',
- color: this.isExpandable ? this.expandableLeafHeaderColor : this.leafHeaderColor,
+ color: this.isExpandable ? this.options.expandableLeafHeaderColor : this.options.leafHeaderColor,
// For ellipsis
overflow: 'hidden',
textOverflow: 'ellipsis',
@@ -89,24 +104,25 @@ export default defineComponent({
width: '100%',
height: '100%',
backgroundColor: this.nonLeafBgColor,
- outline: this.isRoot ? '' : 'black solid 1px',
- borderRadius: this.borderRadius,
+ borderRadius: this.options.borderRadius + 'px',
};
if (this.layoutNode.sepSweptArea != null){
+ let r = this.options.borderRadius + 'px';
temp = this.layoutNode.sepSweptArea.sweptLeft ?
- {...temp, borderRadius: `${this.borderRadius} ${this.borderRadius} ${this.borderRadius} 0`} :
- {...temp, borderRadius: `${this.borderRadius} 0 ${this.borderRadius} ${this.borderRadius}`};
+ {...temp, borderRadius: `${r} ${r} ${r} 0`} :
+ {...temp, borderRadius: `${r} 0 ${r} ${r}`};
}
return temp;
},
nonLeafHeaderStyles(): Record<string,string> {
+ let r = this.options.borderRadius + 'px';
return {
height: this.headerSz + 'px',
lineHeight: this.headerSz + 'px',
textAlign: 'center',
- color: this.nonLeafHeaderColor,
- backgroundColor: this.nonLeafHeaderBgColor,
- borderRadius: `${this.borderRadius} ${this.borderRadius} 0 0`,
+ color: this.options.nonLeafHeaderColor,
+ backgroundColor: this.options.nonLeafHeaderBgColor,
+ borderRadius: `${r} ${r} 0 0`,
// For ellipsis
overflow: 'hidden',
textOverflow: 'ellipsis',
@@ -117,7 +133,6 @@ export default defineComponent({
let commonStyles = {
position: 'absolute',
backgroundColor: this.nonLeafBgColor,
- outline: 'black solid 1px',
transitionDuration: this.transitionDuration + 'ms',
transitionProperty: 'left, top, width, height',
transitionTimingFunction: 'ease-out',
@@ -133,15 +148,14 @@ export default defineComponent({
height: '0',
};
} else {
+ let r = this.options.borderRadius + 'px';
return {
...commonStyles,
left: area.pos[0] + 'px',
top: area.pos[1] + 'px',
width: area.dims[0] + 'px',
height: area.dims[1] + 'px',
- borderRadius: area.sweptLeft ?
- `${this.borderRadius} 0 0 ${this.borderRadius}` :
- `${this.borderRadius} ${this.borderRadius} 0 0`,
+ borderRadius: area.sweptLeft ? `${r} 0 0 ${r}` : `${r} ${r} 0 0`,
};
}
},
@@ -174,16 +188,16 @@ export default defineComponent({
<template>
<div :style="tileStyles">
<div v-if="isLeaf" :style="leafStyles"
- :class="isExpandable ? ['hover:cursor-pointer', 'shadow-on-hover'] : ''" @click="onLeafClick">
- <div :style="{borderRadius: this.borderRadius}" class="upper-scrim"/>
+ :class="isExpandable ? ['hover:cursor-pointer', 'shadow-with-hover'] : 'shadow-normal'" @click="onLeafClick">
+ <div :style="{borderRadius: options.borderRadius + 'px'}" class="scrim-upper-half"/>
<div :style="leafHeaderStyles">{{layoutNode.tolNode.name}}</div>
</div>
- <div v-else :style="nonLeafStyles">
+ <div v-else :style="nonLeafStyles" class="shadow-normal">
<div v-if="showHeader" :style="nonLeafHeaderStyles" class="hover:cursor-pointer" @click="onHeaderClick">
{{layoutNode.tolNode.name}}
</div>
<div :style="sepSweptAreaStyles"
- :class="layoutNode?.sepSweptArea?.sweptLeft ? 'hide-right-edge' : 'hide-top-edge'">
+ :class="[layoutNode?.sepSweptArea?.sweptLeft ? 'hide-right-edge' : 'hide-top-edge', 'shadow-normal']">
<div v-if="layoutNode?.sepSweptArea?.sweptLeft === false"
:style="nonLeafHeaderStyles" class="hover:cursor-pointer" @click="onHeaderClick">
{{layoutNode.tolNode.name}}
@@ -201,28 +215,34 @@ export default defineComponent({
content: '';
position: absolute;
background-color: var(--nonLeafBgColor);
- right: -1px;
+ right: calc(0px - var(--tileSpacing));
bottom: 0;
- width: 1px;
- height: 101%;
+ width: var(--tileSpacing);
+ height: calc(100% + var(--tileSpacing));
}
.hide-top-edge::before {
content: '';
position: absolute;
background-color: var(--nonLeafBgColor);
- bottom: -1px;
+ bottom: calc(0px - var(--tileSpacing));
right: 0;
- width: 101%;
- height: 1px;
+ width: calc(100% + var(--tileSpacing));
+ height: var(--tileSpacing);
}
-.upper-scrim {
+.scrim-upper-half {
position: absolute;
top: 0;
height: 50%;
width: 100%;
background-image: linear-gradient(to top, rgba(0,0,0,0), rgba(0,0,0,0.4));
}
-.shadow-on-hover:hover {
- box-shadow: 0 0 1px 2px var(--expandableLeafHeaderColor);
+.shadow-with-hover:hover {
+ box-shadow: var(--shadowWithHover);
+}
+.shadow-with-hover {
+ box-shadow: var(--shadowNormal);
+}
+.shadow-normal {
+ box-shadow: var(--shadowNormal);
}
</style>
diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue
index f03d24d..f0d8f07 100644
--- a/src/components/TileTree.vue
+++ b/src/components/TileTree.vue
@@ -18,9 +18,8 @@ function preprocessTol(node: any): any {
}
const tol: TolNode = preprocessTol(tolRaw);
-// Configurable settings
+// Configurable settings (integer values specify pixels)
let layoutOptions: LayoutOptions = {
- // Integer values specify pixels
tileSpacing: 8,
headerSz: 20,
minTileSz: 50,
diff --git a/src/lib.ts b/src/lib.ts
index 5af96b0..16f1e2e 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -25,11 +25,13 @@ export class LayoutTree {
this.options = options;
}
// Used by constructor to initialise the LayoutNode tree
- initHelper(tolNode: TolNode, depth: number): LayoutNode {
- if (depth == 0){
- return new LayoutNode(tolNode, []);
+ initHelper(tolNode: TolNode, depthLeft: number, atDepth: number = 0): LayoutNode {
+ if (depthLeft == 0){
+ let node = new LayoutNode(tolNode, []);
+ node.depth = atDepth;
+ return node;
} else {
- let children = tolNode.children.map((n: TolNode) => this.initHelper(n, depth-1));
+ let children = tolNode.children.map((n: TolNode) => this.initHelper(n, depthLeft-1, atDepth+1));
let node = new LayoutNode(tolNode, children);
children.forEach(n => n.parent = node);
return node;
@@ -54,7 +56,10 @@ export class LayoutTree {
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);
+ node.children.forEach(n => {
+ n.parent = node;
+ n.depth = node.depth + 1;
+ });
this.updateDCounts(node, node.children.length-1);
// Try layout
let success = this.tryLayout(pos, dims);
@@ -121,10 +126,11 @@ export class LayoutNode {
dims: [number, number];
showHeader: boolean;
sepSweptArea: SepSweptArea | null;
- // Used for layout heuristics
+ // Used for layout heuristics and info display
dCount: number; // Number of descendant leaf nodes
+ depth: number; // Number of ancestor nodes
empSpc: number; // Amount of unused space (in pixels)
- // Creates object with given fields ('parent' is generally initialised later, 'dCount' is computed)
+ // Creates object with given fields ('parent' are 'depth' are generally initialised later, 'dCount' is computed)
constructor(
tolNode: TolNode, children: LayoutNode[], pos=[0,0] as [number,number], dims=[0,0] as [number,number],
{showHeader=false, sepSweptArea=null as SepSweptArea|null, empSpc=0} = {}){
@@ -136,6 +142,7 @@ export class LayoutNode {
this.showHeader = showHeader;
this.sepSweptArea = sepSweptArea;
this.dCount = children.length == 0 ? 1 : arraySum(children.map(n => n.dCount));
+ this.depth = 0;
this.empSpc = empSpc;
}
}
@@ -240,7 +247,7 @@ let sqrLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts){
(usedNumCols * usedNumRows - numChildren) * (usedTileSz - opts.tileSpacing)**2 +
arraySum(childLayouts.map(lyt => lyt.empSpc));
let newNode = new LayoutNode(node.tolNode, childLayouts, pos, usedDims, {showHeader, empSpc});
- childLayouts.forEach(n => n.parent = newNode);
+ childLayouts.forEach(n => {n.parent = newNode; n.depth = node.depth;});
return newNode;
}
// Lays out nodes as rows of rectangles, deferring to sqrLayoutFn() or oneSqrLayoutFn() for simpler cases
@@ -409,7 +416,7 @@ let rectLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts, ownOpt
// Create layout
let usedDims: [number,number] = [dims[0] - usedEmpRight, dims[1] - usedEmpBottom];
let newNode = new LayoutNode(node.tolNode, usedChildLyts, pos, usedDims, {showHeader, empSpc: lowestEmpSpc});
- usedChildLyts.forEach(n => n.parent = newNode);
+ usedChildLyts.forEach(n => {n.parent = newNode; n.depth = node.depth});
return newNode;
}
// Lays out nodes by pushing leaves to one side, and using rectLayoutFn() for the non-leaves
@@ -650,7 +657,7 @@ let sweepLayoutFn: LayoutFn = function (node, pos, dims, showHeader, opts, ownOp
let empSpc = (!usingParentArea ? leavesLyt.empSpc : 0) + nonLeavesLyt.empSpc;
let newNode = new LayoutNode(node.tolNode, layoutsInOldOrder, pos, usedDims,
{showHeader, empSpc, sepSweptArea: usingParentArea ? parentArea! : null});
- layoutsInOldOrder.forEach(n => n.parent = newNode);
+ layoutsInOldOrder.forEach(n => {n.parent = newNode; n.depth = node.depth;});
return newNode;
}