Add (nonfunctional) rulers and scrollbars to the viewport (#279)
* Add nonfunctional rulers and scrollbars to viewport * Switch from DOM divs to SVG lines * Switch from SVG lines to a single SVG path * Change variable names
This commit is contained in:
parent
f5bb235ae0
commit
b650658810
|
|
@ -139,15 +139,31 @@
|
||||||
<div class="working-colors">
|
<div class="working-colors">
|
||||||
<SwatchPairInput />
|
<SwatchPairInput />
|
||||||
<div class="swap-and-reset">
|
<div class="swap-and-reset">
|
||||||
<IconButton @click="swapColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
|
<IconButton @click="swapWorkingColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
|
||||||
<IconButton @click="resetColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
|
<IconButton @click="resetWorkingColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol :class="'viewport'">
|
<LayoutCol :class="'viewport'">
|
||||||
<div class="canvas" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp" @mousemove="canvasMouseMove" ref="canvas">
|
<LayoutRow :class="'bar-area'">
|
||||||
<svg v-html="viewportSvg"></svg>
|
<CanvasRuler :origin="0" :majorMarkSpacing="75" :direction="RulerDirection.Horizontal" :class="'top-ruler'" />
|
||||||
</div>
|
</LayoutRow>
|
||||||
|
<LayoutRow :class="'canvas-area'">
|
||||||
|
<LayoutCol :class="'bar-area'">
|
||||||
|
<CanvasRuler :origin="0" :majorMarkSpacing="75" :direction="RulerDirection.Vertical" />
|
||||||
|
</LayoutCol>
|
||||||
|
<LayoutCol :class="'canvas-area'">
|
||||||
|
<div class="canvas" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp" @mousemove="canvasMouseMove" ref="canvas">
|
||||||
|
<svg v-html="viewportSvg"></svg>
|
||||||
|
</div>
|
||||||
|
</LayoutCol>
|
||||||
|
<LayoutCol :class="'bar-area'">
|
||||||
|
<PersistentScrollbar :direction="ScrollbarDirection.Vertical" :class="'right-scrollbar'" />
|
||||||
|
</LayoutCol>
|
||||||
|
</LayoutRow>
|
||||||
|
<LayoutRow :class="'bar-area'">
|
||||||
|
<PersistentScrollbar :direction="ScrollbarDirection.Horizontal" :class="'bottom-scrollbar'" />
|
||||||
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|
@ -184,6 +200,27 @@
|
||||||
.viewport {
|
.viewport {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
||||||
|
.canvas-area {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-area {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-ruler {
|
||||||
|
padding-left: 16px;
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-scrollbar {
|
||||||
|
margin-top: -16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-scrollbar {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.canvas {
|
.canvas {
|
||||||
background: var(--color-1-nearblack);
|
background: var(--color-1-nearblack);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -210,6 +247,8 @@ import SwatchPairInput from "@/components/widgets/inputs/SwatchPairInput.vue";
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
import ShelfItemInput from "@/components/widgets/inputs/ShelfItemInput.vue";
|
import ShelfItemInput from "@/components/widgets/inputs/ShelfItemInput.vue";
|
||||||
import Separator, { SeparatorDirection, SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
import Separator, { SeparatorDirection, SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
||||||
|
import PersistentScrollbar, { ScrollbarDirection } from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
|
||||||
|
import CanvasRuler, { RulerDirection } from "@/components/widgets/rulers/CanvasRuler.vue";
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import RadioInput from "@/components/widgets/inputs/RadioInput.vue";
|
import RadioInput from "@/components/widgets/inputs/RadioInput.vue";
|
||||||
|
|
@ -274,6 +313,14 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
todo(toolIndex);
|
todo(toolIndex);
|
||||||
},
|
},
|
||||||
|
async swapWorkingColors() {
|
||||||
|
const { swap_colors } = await wasm;
|
||||||
|
swap_colors();
|
||||||
|
},
|
||||||
|
async resetWorkingColors() {
|
||||||
|
const { reset_colors } = await wasm;
|
||||||
|
reset_colors();
|
||||||
|
},
|
||||||
download(filename: string, svgData: string) {
|
download(filename: string, svgData: string) {
|
||||||
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
||||||
const svgUrl = URL.createObjectURL(svgBlob);
|
const svgUrl = URL.createObjectURL(svgBlob);
|
||||||
|
|
@ -328,14 +375,16 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
viewportSvg: "",
|
viewportSvg: "",
|
||||||
activeTool: "Select",
|
activeTool: "Select",
|
||||||
MenuDirection,
|
|
||||||
SeparatorDirection,
|
|
||||||
SeparatorType,
|
|
||||||
modeMenuEntries,
|
modeMenuEntries,
|
||||||
viewModeIndex: 0,
|
viewModeIndex: 0,
|
||||||
snappingEnabled: true,
|
snappingEnabled: true,
|
||||||
gridEnabled: true,
|
gridEnabled: true,
|
||||||
overlaysEnabled: true,
|
overlaysEnabled: true,
|
||||||
|
MenuDirection,
|
||||||
|
SeparatorDirection,
|
||||||
|
ScrollbarDirection,
|
||||||
|
RulerDirection,
|
||||||
|
SeparatorType,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -344,6 +393,8 @@ export default defineComponent({
|
||||||
SwatchPairInput,
|
SwatchPairInput,
|
||||||
ShelfItemInput,
|
ShelfItemInput,
|
||||||
Separator,
|
Separator,
|
||||||
|
PersistentScrollbar,
|
||||||
|
CanvasRuler,
|
||||||
IconButton,
|
IconButton,
|
||||||
PopoverButton,
|
PopoverButton,
|
||||||
RadioInput,
|
RadioInput,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<div class="canvas-ruler" :class="direction.toLowerCase()" ref="rulerRef">
|
||||||
|
<svg :style="svgBounds">
|
||||||
|
<path :d="svgPath" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.canvas-ruler {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
background: var(--color-5-dullgray);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
path {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: var(--color-7-middlegray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
const RULER_THICKNESS = 16;
|
||||||
|
|
||||||
|
export enum RulerDirection {
|
||||||
|
"Horizontal" = "Horizontal",
|
||||||
|
"Vertical" = "Vertical",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
direction: { type: String as PropType<RulerDirection>, default: RulerDirection.Vertical },
|
||||||
|
origin: { type: Number, required: true },
|
||||||
|
majorMarkSpacing: { type: Number, required: true },
|
||||||
|
mediumDivisions: { type: Number, default: 5 },
|
||||||
|
minorDivisions: { type: Number, default: 2 },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
svgPath(): string {
|
||||||
|
const isVertical = this.direction === RulerDirection.Vertical;
|
||||||
|
const lineDirection = isVertical ? "H" : "V";
|
||||||
|
|
||||||
|
let offsetStart = this.origin % this.majorMarkSpacing;
|
||||||
|
if (offsetStart < this.majorMarkSpacing) offsetStart -= this.majorMarkSpacing;
|
||||||
|
|
||||||
|
const divisions = this.majorMarkSpacing / this.mediumDivisions / this.minorDivisions;
|
||||||
|
const majorMarksFrequency = this.mediumDivisions * this.minorDivisions;
|
||||||
|
|
||||||
|
let dPathAttribute = "";
|
||||||
|
let i = 0;
|
||||||
|
for (let location = offsetStart; location < this.rulerLength; location += divisions) {
|
||||||
|
let length = RULER_THICKNESS / 4;
|
||||||
|
if (i % majorMarksFrequency === 0) length = RULER_THICKNESS;
|
||||||
|
else if (i % this.minorDivisions === 0) length = RULER_THICKNESS / 2;
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
const destination = Math.round(location) + 0.5;
|
||||||
|
const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;
|
||||||
|
dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dPathAttribute;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleResize() {
|
||||||
|
if (!this.$refs.rulerRef) return;
|
||||||
|
|
||||||
|
const rulerElement = this.$refs.rulerRef as HTMLElement;
|
||||||
|
const isVertical = this.direction === RulerDirection.Vertical;
|
||||||
|
|
||||||
|
const newLength = isVertical ? rulerElement.clientHeight : rulerElement.clientWidth;
|
||||||
|
const roundedUp = (Math.floor(newLength / this.majorMarkSpacing) + 1) * this.majorMarkSpacing;
|
||||||
|
|
||||||
|
if (roundedUp !== this.rulerLength) {
|
||||||
|
this.rulerLength = roundedUp;
|
||||||
|
const thickness = `${RULER_THICKNESS}px`;
|
||||||
|
const length = `${roundedUp}px`;
|
||||||
|
this.svgBounds = isVertical ? { width: thickness, height: length } : { width: length, height: thickness };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener("resize", this.handleResize);
|
||||||
|
this.handleResize();
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener("resize", this.handleResize);
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rulerLength: 0,
|
||||||
|
svgBounds: { width: "0px", height: "0px" },
|
||||||
|
RulerDirection,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
<template>
|
||||||
|
<div class="persistent-scrollbar" :class="direction.toLowerCase()">
|
||||||
|
<button class="arrow decrease"></button>
|
||||||
|
<div class="scroll-track">
|
||||||
|
<div class="scroll-click-area decrease" :style="[trackStart, preThumb, sides]"></div>
|
||||||
|
<div class="scroll-thumb" :style="[thumbStart, thumbEnd, sides]"></div>
|
||||||
|
<div class="scroll-click-area increase" :style="[postThumb, trackEnd, sides]"></div>
|
||||||
|
</div>
|
||||||
|
<button class="arrow increase"></button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.persistent-scrollbar {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: block;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
border-style: solid;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-track {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.scroll-thumb {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--color-5-dullgray);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-6-lowergray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-click-area {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.arrow.decrease {
|
||||||
|
margin: 4px 3px;
|
||||||
|
border-width: 0 5px 8px 5px;
|
||||||
|
border-color: transparent transparent var(--color-5-dullgray) transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent transparent var(--color-6-lowergray) transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow.increase {
|
||||||
|
margin: 4px 3px;
|
||||||
|
border-width: 8px 5px 0 5px;
|
||||||
|
border-color: var(--color-5-dullgray) transparent transparent transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-6-lowergray) transparent transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.arrow.decrease {
|
||||||
|
margin: 3px 4px;
|
||||||
|
border-width: 5px 8px 5px 0;
|
||||||
|
border-color: transparent var(--color-5-dullgray) transparent transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent var(--color-6-lowergray) transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow.increase {
|
||||||
|
margin: 3px 4px;
|
||||||
|
border-width: 5px 0 5px 8px;
|
||||||
|
border-color: transparent transparent transparent var(--color-5-dullgray);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent transparent transparent var(--color-6-lowergray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
export enum ScrollbarDirection {
|
||||||
|
"Horizontal" = "Horizontal",
|
||||||
|
"Vertical" = "Vertical",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
direction: { type: String as PropType<ScrollbarDirection>, default: ScrollbarDirection.Vertical },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
trackStart(): { left: string } | { top: string } {
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { top: "0%" } : { left: "0%" };
|
||||||
|
},
|
||||||
|
preThumb(): { right: string } | { bottom: string } {
|
||||||
|
const start = 25;
|
||||||
|
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${100 - start}%` } : { right: `${100 - start}%` };
|
||||||
|
},
|
||||||
|
thumbStart(): { left: string } | { top: string } {
|
||||||
|
const start = 25;
|
||||||
|
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { top: `${start}%` } : { left: `${start}%` };
|
||||||
|
},
|
||||||
|
thumbEnd(): { right: string } | { bottom: string } {
|
||||||
|
const end = 25;
|
||||||
|
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${end}%` } : { right: `${end}%` };
|
||||||
|
},
|
||||||
|
postThumb(): { left: string } | { top: string } {
|
||||||
|
const end = 25;
|
||||||
|
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { top: `${100 - end}%` } : { left: `${100 - end}%` };
|
||||||
|
},
|
||||||
|
trackEnd(): { right: string } | { bottom: string } {
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { bottom: "0%" } : { right: "0%" };
|
||||||
|
},
|
||||||
|
sides(): { left: string; right: string } | { top: string; bottom: string } {
|
||||||
|
return this.direction === ScrollbarDirection.Vertical ? { left: "0%", right: "0%" } : { top: "0%", bottom: "0%" };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
ScrollbarDirection,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue