aboutsummaryrefslogtreecommitdiff
path: root/src/components/NonLeafTile.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/NonLeafTile.vue')
-rw-r--r--src/components/NonLeafTile.vue205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/components/NonLeafTile.vue b/src/components/NonLeafTile.vue
new file mode 100644
index 0000000..f9f2983
--- /dev/null
+++ b/src/components/NonLeafTile.vue
@@ -0,0 +1,205 @@
+<script lang="ts">
+import {defineComponent, PropType} from 'vue';
+import Tile from './Tile.vue';
+import {LayoutNode} from '../layout';
+import type {LayoutOptions} from '../layout';
+
+export default defineComponent({
+ props: {
+ layoutNode: {type: Object as PropType<LayoutNode>, required: true},
+ lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
+ uiOpts: {type: Object, required: true},
+ nonAbsPos: {type: Boolean, default: false},
+ highlight: {type: Boolean, default: false},
+ inTransition: {type: Boolean, default: false},
+ },
+ computed: {
+ showHeader(){
+ return (this.layoutNode.showHeader && !this.layoutNode.sepSweptArea) ||
+ (this.layoutNode.sepSweptArea && this.layoutNode.sepSweptArea.sweptLeft);
+ },
+ nonLeafBgColor(){
+ let colorArray = this.uiOpts.nonLeafBgColors;
+ return colorArray[this.layoutNode.depth % colorArray.length];
+ },
+ styles(): Record<string,string> {
+ let placementStyles;
+ if (!this.layoutNode.hidden){
+ placementStyles = {
+ position: 'absolute',
+ left: this.layoutNode.pos[0] + 'px',
+ top: this.layoutNode.pos[1] + 'px',
+ width: this.layoutNode.dims[0] + 'px',
+ height: this.layoutNode.dims[1] + 'px',
+ visibility: 'visible',
+ };
+ } else {
+ placementStyles = {
+ position: 'absolute',
+ left: '0',
+ top: '0',
+ width: '0',
+ height: '0',
+ visibility: 'hidden',
+ };
+ }
+ let borderR = this.uiOpts.borderRadius + 'px';
+ if (this.layoutNode.sepSweptArea != null){
+ borderR = this.layoutNode.sepSweptArea.sweptLeft ?
+ `${borderR} ${borderR} ${borderR} 0` :
+ `${borderR} 0 ${borderR} ${borderR}`;
+ }
+ return {
+ ...placementStyles,
+ // Transition related
+ transitionDuration: this.uiOpts.transitionDuration + 'ms',
+ transitionProperty: 'left, top, width, height, visibility',
+ transitionTimingFunction: 'ease-out',
+ zIndex: this.inTransition ? '1' : '0',
+ overflow: this.inTransition ? 'hidden' : 'visible',
+ // CSS variables
+ '--nonLeafBgColor': this.nonLeafBgColor,
+ '--tileSpacing': this.lytOpts.tileSpacing + 'px',
+ // Other
+ borderRadius: borderR,
+ backgroundColor: this.nonLeafBgColor,
+ boxShadow: this.inTransition ? 'none' :
+ (this.highlight ? this.uiOpts.shadowHighlight :
+ (this.layoutNode.hasFocus ? this.uiOpts.shadowFocused : this.uiOpts.shadowNormal)),
+ };
+ },
+ headerStyles(): Record<string,string> {
+ let borderR = this.uiOpts.borderRadius + 'px';
+ return {
+ height: this.lytOpts.headerSz + 'px',
+ lineHeight: this.lytOpts.headerSz + 'px',
+ fontSize: this.uiOpts.nonLeafHeaderFontSz + 'px',
+ textAlign: 'center',
+ color: this.uiOpts.nonLeafHeaderColor,
+ backgroundColor: this.uiOpts.nonLeafHeaderBgColor,
+ borderRadius: `${borderR} ${borderR} 0 0`,
+ // For ellipsis
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ };
+ },
+ sepSweptAreaStyles(): Record<string,string> {
+ let commonStyles = {
+ position: 'absolute',
+ backgroundColor: this.nonLeafBgColor,
+ boxShadow: this.inTransition ? 'none' :
+ (this.highlight ? this.uiOpts.shadowHighlight :
+ (this.layoutNode.hasFocus ? this.uiOpts.shadowFocused : this.uiOpts.shadowNormal)),
+ transitionDuration: this.uiOpts.transitionDuration + 'ms',
+ transitionProperty: 'left, top, width, height',
+ transitionTimingFunction: 'ease-out',
+ };
+ let area = this.layoutNode.sepSweptArea;
+ if (this.layoutNode.hidden || area == null){
+ return {
+ ...commonStyles,
+ visibility: 'hidden',
+ left: '0',
+ top: this.lytOpts.headerSz + 'px',
+ width: '0',
+ height: '0',
+ };
+ } else {
+ let borderR = this.uiOpts.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 ?
+ `${borderR} 0 0 ${borderR}` :
+ `${borderR} ${borderR} 0 0`,
+ };
+ }
+ },
+ },
+ methods: {
+ onMouseEnter(){
+ this.$emit('tile-mouse-enter');
+ },
+ onMouseLeave(){
+ this.$emit('tile-mouse-leave');
+ },
+ onMouseDown(){
+ this.$emit('tile-mouse-down');
+ },
+ onMouseUp(){
+ this.$emit('tile-mouse-up');
+ },
+ // Child event propagation
+ onInnerLeafClicked(data: LayoutNode){
+ this.$emit('leaf-clicked', data);
+ },
+ onInnerHeaderClicked(data: LayoutNode){
+ this.$emit('header-clicked', data);
+ },
+ onInnerLeafClickHeld(data: LayoutNode){
+ this.$emit('leaf-click-held', data);
+ },
+ onInnerHeaderClickHeld(data: LayoutNode){
+ this.$emit('header-click-held', data);
+ },
+ onInnerInfoIconClicked(data: LayoutNode){
+ this.$emit('info-icon-clicked', data);
+ },
+ },
+ name: 'non-leaf-tile', // Need this to use self in template
+ emits: [
+ 'leaf-clicked', 'header-clicked', 'leaf-click-held', 'header-click-held', 'info-icon-clicked',
+ 'tile-mouse-enter', 'tile-mouse-leave', 'tile-mouse-down', 'tile-mouse-up',
+ ],
+ components: {Tile, },
+});
+</script>
+
+<template>
+<div :style="styles">
+ <h1 v-if="showHeader" :style="headerStyles" class="hover:cursor-pointer"
+ @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"
+ @mousedown="onMouseDown" @mouseup="onMouseUp">
+ {{layoutNode.tolNode.name}}
+ </h1>
+ <div :style="sepSweptAreaStyles"
+ :class="layoutNode?.sepSweptArea?.sweptLeft ? 'hide-right-edge' : 'hide-top-edge'">
+ <h1 v-if="layoutNode?.sepSweptArea?.sweptLeft === false"
+ :style="headerStyles" class="hover:cursor-pointer"
+ @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"
+ @mousedown="onMouseDown" @mouseup="onMouseUp">
+ {{layoutNode.tolNode.name}}
+ </h1>
+ </div>
+ <tile v-for="child in layoutNode.children" :key="child.tolNode.name"
+ :layoutNode="child" :lytOpts="lytOpts" :uiOpts="uiOpts"
+ @leaf-clicked="onInnerLeafClicked" @header-clicked="onInnerHeaderClicked"
+ @leaf-click-held="onInnerLeafClickHeld" @header-click-held="onInnerHeaderClickHeld"
+ @info-icon-clicked="onInnerInfoIconClicked"/>
+</div>
+</template>
+
+<style>
+.hide-right-edge::before {
+ content: '';
+ position: absolute;
+ background-color: var(--nonLeafBgColor);
+ right: calc(0px - var(--tileSpacing));
+ bottom: 0;
+ width: var(--tileSpacing);
+ height: calc(100% + var(--tileSpacing));
+}
+.hide-top-edge::before {
+ content: '';
+ position: absolute;
+ background-color: var(--nonLeafBgColor);
+ bottom: calc(0px - var(--tileSpacing));
+ right: 0;
+ width: calc(100% + var(--tileSpacing));
+ height: var(--tileSpacing);
+}
+</style>