Skip to content

Commit 131256b

Browse files
authored
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
1 parent 801bae1 commit 131256b

File tree

3 files changed

+320
-8
lines changed

3 files changed

+320
-8
lines changed

client/web/src/components/panels/Document.vue

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,31 @@
139139
<div class="working-colors">
140140
<SwatchPairInput />
141141
<div class="swap-and-reset">
142-
<IconButton @click="swapColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
143-
<IconButton @click="resetColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
142+
<IconButton @click="swapWorkingColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
143+
<IconButton @click="resetWorkingColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
144144
</div>
145145
</div>
146146
</LayoutCol>
147147
<LayoutCol :class="'viewport'">
148-
<div class="canvas" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp" @mousemove="canvasMouseMove" ref="canvas">
149-
<svg v-html="viewportSvg"></svg>
150-
</div>
148+
<LayoutRow :class="'bar-area'">
149+
<CanvasRuler :origin="0" :majorMarkSpacing="75" :direction="RulerDirection.Horizontal" :class="'top-ruler'" />
150+
</LayoutRow>
151+
<LayoutRow :class="'canvas-area'">
152+
<LayoutCol :class="'bar-area'">
153+
<CanvasRuler :origin="0" :majorMarkSpacing="75" :direction="RulerDirection.Vertical" />
154+
</LayoutCol>
155+
<LayoutCol :class="'canvas-area'">
156+
<div class="canvas" @mousedown="canvasMouseDown" @mouseup="canvasMouseUp" @mousemove="canvasMouseMove" ref="canvas">
157+
<svg v-html="viewportSvg"></svg>
158+
</div>
159+
</LayoutCol>
160+
<LayoutCol :class="'bar-area'">
161+
<PersistentScrollbar :direction="ScrollbarDirection.Vertical" :class="'right-scrollbar'" />
162+
</LayoutCol>
163+
</LayoutRow>
164+
<LayoutRow :class="'bar-area'">
165+
<PersistentScrollbar :direction="ScrollbarDirection.Horizontal" :class="'bottom-scrollbar'" />
166+
</LayoutRow>
151167
</LayoutCol>
152168
</LayoutRow>
153169
</LayoutCol>
@@ -184,6 +200,27 @@
184200
.viewport {
185201
flex: 1 1 100%;
186202
203+
.canvas-area {
204+
flex: 1 1 100%;
205+
}
206+
207+
.bar-area {
208+
flex: 0 0 auto;
209+
}
210+
211+
.top-ruler {
212+
padding-left: 16px;
213+
margin-right: 16px;
214+
}
215+
216+
.right-scrollbar {
217+
margin-top: -16px;
218+
}
219+
220+
.bottom-scrollbar {
221+
margin-right: 16px;
222+
}
223+
187224
.canvas {
188225
background: var(--color-1-nearblack);
189226
width: 100%;
@@ -210,6 +247,8 @@ import SwatchPairInput from "@/components/widgets/inputs/SwatchPairInput.vue";
210247
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
211248
import ShelfItemInput from "@/components/widgets/inputs/ShelfItemInput.vue";
212249
import Separator, { SeparatorDirection, SeparatorType } from "@/components/widgets/separators/Separator.vue";
250+
import PersistentScrollbar, { ScrollbarDirection } from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
251+
import CanvasRuler, { RulerDirection } from "@/components/widgets/rulers/CanvasRuler.vue";
213252
import IconButton from "@/components/widgets/buttons/IconButton.vue";
214253
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
215254
import RadioInput from "@/components/widgets/inputs/RadioInput.vue";
@@ -274,6 +313,14 @@ export default defineComponent({
274313
}
275314
todo(toolIndex);
276315
},
316+
async swapWorkingColors() {
317+
const { swap_colors } = await wasm;
318+
swap_colors();
319+
},
320+
async resetWorkingColors() {
321+
const { reset_colors } = await wasm;
322+
reset_colors();
323+
},
277324
download(filename: string, svgData: string) {
278325
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
279326
const svgUrl = URL.createObjectURL(svgBlob);
@@ -328,14 +375,16 @@ export default defineComponent({
328375
return {
329376
viewportSvg: "",
330377
activeTool: "Select",
331-
MenuDirection,
332-
SeparatorDirection,
333-
SeparatorType,
334378
modeMenuEntries,
335379
viewModeIndex: 0,
336380
snappingEnabled: true,
337381
gridEnabled: true,
338382
overlaysEnabled: true,
383+
MenuDirection,
384+
SeparatorDirection,
385+
ScrollbarDirection,
386+
RulerDirection,
387+
SeparatorType,
339388
};
340389
},
341390
components: {
@@ -344,6 +393,8 @@ export default defineComponent({
344393
SwatchPairInput,
345394
ShelfItemInput,
346395
Separator,
396+
PersistentScrollbar,
397+
CanvasRuler,
347398
IconButton,
348399
PopoverButton,
349400
RadioInput,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<template>
2+
<div class="canvas-ruler" :class="direction.toLowerCase()" ref="rulerRef">
3+
<svg :style="svgBounds">
4+
<path :d="svgPath" />
5+
</svg>
6+
</div>
7+
</template>
8+
9+
<style lang="scss">
10+
.canvas-ruler {
11+
flex: 1 1 100%;
12+
background: var(--color-5-dullgray);
13+
overflow: hidden;
14+
position: relative;
15+
16+
&.vertical {
17+
width: 16px;
18+
}
19+
20+
&.horizontal {
21+
height: 16px;
22+
}
23+
24+
svg {
25+
position: absolute;
26+
27+
path {
28+
stroke-width: 1px;
29+
stroke: var(--color-7-middlegray);
30+
}
31+
}
32+
}
33+
</style>
34+
35+
<script lang="ts">
36+
import { defineComponent, PropType } from "vue";
37+
38+
const RULER_THICKNESS = 16;
39+
40+
export enum RulerDirection {
41+
"Horizontal" = "Horizontal",
42+
"Vertical" = "Vertical",
43+
}
44+
45+
export default defineComponent({
46+
props: {
47+
direction: { type: String as PropType<RulerDirection>, default: RulerDirection.Vertical },
48+
origin: { type: Number, required: true },
49+
majorMarkSpacing: { type: Number, required: true },
50+
mediumDivisions: { type: Number, default: 5 },
51+
minorDivisions: { type: Number, default: 2 },
52+
},
53+
computed: {
54+
svgPath(): string {
55+
const isVertical = this.direction === RulerDirection.Vertical;
56+
const lineDirection = isVertical ? "H" : "V";
57+
58+
let offsetStart = this.origin % this.majorMarkSpacing;
59+
if (offsetStart < this.majorMarkSpacing) offsetStart -= this.majorMarkSpacing;
60+
61+
const divisions = this.majorMarkSpacing / this.mediumDivisions / this.minorDivisions;
62+
const majorMarksFrequency = this.mediumDivisions * this.minorDivisions;
63+
64+
let dPathAttribute = "";
65+
let i = 0;
66+
for (let location = offsetStart; location < this.rulerLength; location += divisions) {
67+
let length = RULER_THICKNESS / 4;
68+
if (i % majorMarksFrequency === 0) length = RULER_THICKNESS;
69+
else if (i % this.minorDivisions === 0) length = RULER_THICKNESS / 2;
70+
i += 1;
71+
72+
const destination = Math.round(location) + 0.5;
73+
const startPoint = isVertical ? `${RULER_THICKNESS - length},${destination}` : `${destination},${RULER_THICKNESS - length}`;
74+
dPathAttribute += `M${startPoint}${lineDirection}${RULER_THICKNESS} `;
75+
}
76+
77+
return dPathAttribute;
78+
},
79+
},
80+
methods: {
81+
handleResize() {
82+
if (!this.$refs.rulerRef) return;
83+
84+
const rulerElement = this.$refs.rulerRef as HTMLElement;
85+
const isVertical = this.direction === RulerDirection.Vertical;
86+
87+
const newLength = isVertical ? rulerElement.clientHeight : rulerElement.clientWidth;
88+
const roundedUp = (Math.floor(newLength / this.majorMarkSpacing) + 1) * this.majorMarkSpacing;
89+
90+
if (roundedUp !== this.rulerLength) {
91+
this.rulerLength = roundedUp;
92+
const thickness = `${RULER_THICKNESS}px`;
93+
const length = `${roundedUp}px`;
94+
this.svgBounds = isVertical ? { width: thickness, height: length } : { width: length, height: thickness };
95+
}
96+
},
97+
},
98+
mounted() {
99+
window.addEventListener("resize", this.handleResize);
100+
this.handleResize();
101+
},
102+
beforeUnmount() {
103+
window.removeEventListener("resize", this.handleResize);
104+
},
105+
data() {
106+
return {
107+
rulerLength: 0,
108+
svgBounds: { width: "0px", height: "0px" },
109+
RulerDirection,
110+
};
111+
},
112+
});
113+
</script>
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<template>
2+
<div class="persistent-scrollbar" :class="direction.toLowerCase()">
3+
<button class="arrow decrease"></button>
4+
<div class="scroll-track">
5+
<div class="scroll-click-area decrease" :style="[trackStart, preThumb, sides]"></div>
6+
<div class="scroll-thumb" :style="[thumbStart, thumbEnd, sides]"></div>
7+
<div class="scroll-click-area increase" :style="[postThumb, trackEnd, sides]"></div>
8+
</div>
9+
<button class="arrow increase"></button>
10+
</div>
11+
</template>
12+
13+
<style lang="scss">
14+
.persistent-scrollbar {
15+
display: flex;
16+
flex: 1 1 100%;
17+
18+
.arrow {
19+
flex: 0 0 auto;
20+
display: block;
21+
background: none;
22+
outline: none;
23+
border: none;
24+
border-style: solid;
25+
width: 0;
26+
height: 0;
27+
padding: 0;
28+
}
29+
30+
.scroll-track {
31+
flex: 1 1 100%;
32+
position: relative;
33+
34+
.scroll-thumb {
35+
position: absolute;
36+
border-radius: 4px;
37+
background: var(--color-5-dullgray);
38+
39+
&:hover {
40+
background: var(--color-6-lowergray);
41+
}
42+
}
43+
44+
.scroll-click-area {
45+
position: absolute;
46+
}
47+
}
48+
49+
&.vertical {
50+
flex-direction: column;
51+
52+
.arrow.decrease {
53+
margin: 4px 3px;
54+
border-width: 0 5px 8px 5px;
55+
border-color: transparent transparent var(--color-5-dullgray) transparent;
56+
57+
&:hover {
58+
border-color: transparent transparent var(--color-6-lowergray) transparent;
59+
}
60+
}
61+
62+
.arrow.increase {
63+
margin: 4px 3px;
64+
border-width: 8px 5px 0 5px;
65+
border-color: var(--color-5-dullgray) transparent transparent transparent;
66+
67+
&:hover {
68+
border-color: var(--color-6-lowergray) transparent transparent transparent;
69+
}
70+
}
71+
}
72+
73+
&.horizontal {
74+
flex-direction: row;
75+
76+
.arrow.decrease {
77+
margin: 3px 4px;
78+
border-width: 5px 8px 5px 0;
79+
border-color: transparent var(--color-5-dullgray) transparent transparent;
80+
81+
&:hover {
82+
border-color: transparent var(--color-6-lowergray) transparent transparent;
83+
}
84+
}
85+
86+
.arrow.increase {
87+
margin: 3px 4px;
88+
border-width: 5px 0 5px 8px;
89+
border-color: transparent transparent transparent var(--color-5-dullgray);
90+
91+
&:hover {
92+
border-color: transparent transparent transparent var(--color-6-lowergray);
93+
}
94+
}
95+
}
96+
}
97+
</style>
98+
99+
<script lang="ts">
100+
import { defineComponent, PropType } from "vue";
101+
102+
export enum ScrollbarDirection {
103+
"Horizontal" = "Horizontal",
104+
"Vertical" = "Vertical",
105+
}
106+
107+
export default defineComponent({
108+
props: {
109+
direction: { type: String as PropType<ScrollbarDirection>, default: ScrollbarDirection.Vertical },
110+
},
111+
computed: {
112+
trackStart(): { left: string } | { top: string } {
113+
return this.direction === ScrollbarDirection.Vertical ? { top: "0%" } : { left: "0%" };
114+
},
115+
preThumb(): { right: string } | { bottom: string } {
116+
const start = 25;
117+
118+
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${100 - start}%` } : { right: `${100 - start}%` };
119+
},
120+
thumbStart(): { left: string } | { top: string } {
121+
const start = 25;
122+
123+
return this.direction === ScrollbarDirection.Vertical ? { top: `${start}%` } : { left: `${start}%` };
124+
},
125+
thumbEnd(): { right: string } | { bottom: string } {
126+
const end = 25;
127+
128+
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${end}%` } : { right: `${end}%` };
129+
},
130+
postThumb(): { left: string } | { top: string } {
131+
const end = 25;
132+
133+
return this.direction === ScrollbarDirection.Vertical ? { top: `${100 - end}%` } : { left: `${100 - end}%` };
134+
},
135+
trackEnd(): { right: string } | { bottom: string } {
136+
return this.direction === ScrollbarDirection.Vertical ? { bottom: "0%" } : { right: "0%" };
137+
},
138+
sides(): { left: string; right: string } | { top: string; bottom: string } {
139+
return this.direction === ScrollbarDirection.Vertical ? { left: "0%", right: "0%" } : { top: "0%", bottom: "0%" };
140+
},
141+
},
142+
data() {
143+
return {
144+
ScrollbarDirection,
145+
};
146+
},
147+
});
148+
</script>

0 commit comments

Comments
 (0)