mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 09:58:11 +08:00
Add features to NumberInput widget; refactor widgets and tool options (#311)
Closes #310 * Add features to NumberInput widget; refactor widgets and tool options * Fix swap/reset working colors using @click instead of :action
This commit is contained in:
@@ -34,10 +34,10 @@
|
|||||||
|
|
||||||
<Separator :type="SeparatorType.Unrelated" />
|
<Separator :type="SeparatorType.Unrelated" />
|
||||||
|
|
||||||
<RadioInput v-model:index="viewModeIndex">
|
<RadioInput @update:index="viewModeChanged" v-model:index="viewModeIndex">
|
||||||
<IconButton :icon="'ViewModeNormal'" :size="24" title="View Mode: Normal" />
|
<IconButton :action="() => _" :icon="'ViewModeNormal'" :size="24" title="View Mode: Normal" />
|
||||||
<IconButton :icon="'ViewModeOutline'" :size="24" title="View Mode: Outline" />
|
<IconButton :action="() => _" :icon="'ViewModeOutline'" :size="24" title="View Mode: Outline" />
|
||||||
<IconButton :icon="'ViewModePixels'" :size="24" title="View Mode: Pixels" />
|
<IconButton :action="() => _" :icon="'ViewModePixels'" :size="24" title="View Mode: Pixels" />
|
||||||
</RadioInput>
|
</RadioInput>
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
<h3>View Mode</h3>
|
<h3>View Mode</h3>
|
||||||
@@ -46,17 +46,27 @@
|
|||||||
|
|
||||||
<Separator :type="SeparatorType.Section" />
|
<Separator :type="SeparatorType.Section" />
|
||||||
|
|
||||||
<NumberInput :callback="setRotation" :initialValue="0" :step="15" :unit="`°`" :updateOnCallback="false" ref="rotation" />
|
<NumberInput @update:value="setRotation" v-model:value="documentRotation" :step="15" :unit="`°`" ref="rotation" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" />
|
<Separator :type="SeparatorType.Section" />
|
||||||
|
|
||||||
<IconButton :icon="'ZoomIn'" :size="24" title="Zoom In" @click="this.$refs.zoom.onIncrement(1)" />
|
<IconButton :action="() => this.$refs.zoom.onIncrement(IncrementDirection.Increase)" :icon="'ZoomIn'" :size="24" title="Zoom In" />
|
||||||
<IconButton :icon="'ZoomOut'" :size="24" title="Zoom Out" @click="this.$refs.zoom.onIncrement(-1)" />
|
<IconButton :action="() => this.$refs.zoom.onIncrement(IncrementDirection.Decrease)" :icon="'ZoomOut'" :size="24" title="Zoom Out" />
|
||||||
<IconButton :icon="'ZoomReset'" :size="24" title="Zoom to 100%" @click="this.$refs.zoom.updateValue(100)" />
|
<IconButton :action="() => this.$refs.zoom.updateValue(100)" :icon="'ZoomReset'" :size="24" title="Zoom to 100%" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="SeparatorType.Related" />
|
||||||
|
|
||||||
<NumberInput :callback="setZoom" :initialValue="100" :min="0.001" :increaseMultiplier="1.25" :decreaseMultiplier="0.8" :unit="`%`" :updateOnCallback="false" ref="zoom" />
|
<NumberInput
|
||||||
|
v-model:value="documentZoom"
|
||||||
|
@update:value="setZoom"
|
||||||
|
:min="0.000001"
|
||||||
|
:max="1000000"
|
||||||
|
:step="1.25"
|
||||||
|
:stepIsMultiplier="true"
|
||||||
|
:unit="`%`"
|
||||||
|
:displayDecimalPlaces="4"
|
||||||
|
ref="zoom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow :class="'shelf-and-viewport'">
|
<LayoutRow :class="'shelf-and-viewport'">
|
||||||
@@ -97,8 +107,8 @@
|
|||||||
<div class="working-colors">
|
<div class="working-colors">
|
||||||
<SwatchPairInput />
|
<SwatchPairInput />
|
||||||
<div class="swap-and-reset">
|
<div class="swap-and-reset">
|
||||||
<IconButton @click="swapWorkingColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
|
<IconButton :action="swapWorkingColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
|
||||||
<IconButton @click="resetWorkingColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
|
<IconButton :action="resetWorkingColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
@@ -202,20 +212,23 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { makeModifiersBitfield } from "@/utilities/input";
|
import { makeModifiersBitfield } from "@/utilities/input";
|
||||||
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiveTool, ExportDocument, SetCanvasZoom, SetCanvasRotation } from "@/utilities/response-handler";
|
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, SetActiveTool, ExportDocument, SetCanvasZoom, SetCanvasRotation } from "@/utilities/response-handler";
|
||||||
|
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import SwatchPairInput from "@/components/widgets/inputs/SwatchPairInput.vue";
|
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 from "@/components/widgets/separators/Separator.vue";
|
||||||
import PersistentScrollbar, { ScrollbarDirection } from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
|
import PersistentScrollbar, { ScrollbarDirection } from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
|
||||||
import CanvasRuler, { RulerDirection } from "@/components/widgets/rulers/CanvasRuler.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";
|
||||||
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
import NumberInput, { IncrementDirection } from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
||||||
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
||||||
import ToolOptions from "@/components/widgets/options/ToolOptions.vue";
|
import ToolOptions from "@/components/widgets/options/ToolOptions.vue";
|
||||||
@@ -322,16 +335,14 @@ export default defineComponent({
|
|||||||
registerResponseHandler(ResponseType.SetCanvasZoom, (responseData: Response) => {
|
registerResponseHandler(ResponseType.SetCanvasZoom, (responseData: Response) => {
|
||||||
const updateData = responseData as SetCanvasZoom;
|
const updateData = responseData as SetCanvasZoom;
|
||||||
if (updateData) {
|
if (updateData) {
|
||||||
const zoomWidget = this.$refs.zoom as typeof NumberInput;
|
this.documentZoom = updateData.new_zoom * 100;
|
||||||
zoomWidget.setValue(updateData.new_zoom * 100);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
registerResponseHandler(ResponseType.SetCanvasRotation, (responseData: Response) => {
|
registerResponseHandler(ResponseType.SetCanvasRotation, (responseData: Response) => {
|
||||||
const updateData = responseData as SetCanvasRotation;
|
const updateData = responseData as SetCanvasRotation;
|
||||||
if (updateData) {
|
if (updateData) {
|
||||||
const rotationWidget = this.$refs.rotation as typeof NumberInput;
|
|
||||||
const newRotation = updateData.new_radians * (180 / Math.PI);
|
const newRotation = updateData.new_radians * (180 / Math.PI);
|
||||||
rotationWidget.setValue((360 + (newRotation % 360)) % 360);
|
this.documentRotation = (360 + (newRotation % 360)) % 360;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,8 +352,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
window.addEventListener("resize", () => this.viewportResize());
|
window.addEventListener("resize", () => this.viewportResize());
|
||||||
window.addEventListener("DOMContentLoaded", () => this.viewportResize());
|
window.addEventListener("DOMContentLoaded", () => this.viewportResize());
|
||||||
|
|
||||||
this.$watch("viewModeIndex", this.viewModeChanged);
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -356,6 +365,9 @@ export default defineComponent({
|
|||||||
snappingEnabled: true,
|
snappingEnabled: true,
|
||||||
gridEnabled: true,
|
gridEnabled: true,
|
||||||
overlaysEnabled: true,
|
overlaysEnabled: true,
|
||||||
|
documentRotation: 0,
|
||||||
|
documentZoom: 100,
|
||||||
|
IncrementDirection,
|
||||||
MenuDirection,
|
MenuDirection,
|
||||||
SeparatorDirection,
|
SeparatorDirection,
|
||||||
ScrollbarDirection,
|
ScrollbarDirection,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="SeparatorType.Related" />
|
||||||
|
|
||||||
<NumberInput v-model:value="opacity" :min="0" :max="100" :step="1" :unit="`%`" />
|
<NumberInput v-model:value="opacity" :min="0" :max="100" :unit="`%`" :displayDecimalPlaces="2" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="SeparatorType.Related" />
|
||||||
|
|
||||||
@@ -18,7 +18,12 @@
|
|||||||
<LayoutCol :class="'list'" @click="deselectAllLayers">
|
<LayoutCol :class="'list'" @click="deselectAllLayers">
|
||||||
<div class="layer-row" v-for="layer in layers" :key="layer.path">
|
<div class="layer-row" v-for="layer in layers" :key="layer.path">
|
||||||
<div class="layer-visibility">
|
<div class="layer-visibility">
|
||||||
<IconButton :icon="layer.visible ? 'EyeVisible' : 'EyeHidden'" @click.stop="toggleLayerVisibility(layer.path)" :size="24" :title="layer.visible ? 'Visible' : 'Hidden'" />
|
<IconButton
|
||||||
|
:action="(e) => (toggleLayerVisibility(layer.path), e.stopPropagation())"
|
||||||
|
:icon="layer.visible ? 'EyeVisible' : 'EyeHidden'"
|
||||||
|
:size="24"
|
||||||
|
:title="layer.visible ? 'Visible' : 'Hidden'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="layer"
|
class="layer"
|
||||||
@@ -111,10 +116,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { ResponseType, registerResponseHandler, Response, BlendMode, ExpandFolder, LayerPanelEntry } from "@/utilities/response-handler";
|
import { ResponseType, registerResponseHandler, Response, BlendMode, ExpandFolder, LayerPanelEntry } from "@/utilities/response-handler";
|
||||||
|
import { SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import Separator, { SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-button" :class="`size-${String(size)}`" @click="onClick">
|
<button class="icon-button" :class="`size-${String(size)}`" @click="action">
|
||||||
<IconLabel :icon="icon" />
|
<IconLabel :icon="icon" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,10 +57,10 @@ import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
action: { type: Function, required: true },
|
||||||
icon: { type: String, required: true },
|
icon: { type: String, required: true },
|
||||||
size: { type: Number, required: true },
|
size: { type: Number, required: true },
|
||||||
gapAfter: { type: Boolean, default: false },
|
gapAfter: { type: Boolean, default: false },
|
||||||
onClick: Function,
|
|
||||||
},
|
},
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popover-button">
|
<div class="popover-button">
|
||||||
<IconButton :icon="icon" :size="16" @click="clickButton" data-hover-menu-spawner />
|
<IconButton :action="handleClick" :icon="icon" :size="16" data-hover-menu-spawner />
|
||||||
<FloatingMenu :type="MenuType.Popover" :direction="MenuDirection.Bottom" ref="floatingMenu">
|
<FloatingMenu :type="MenuType.Popover" :direction="MenuDirection.Bottom" ref="floatingMenu">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
@@ -63,11 +63,14 @@ export default defineComponent({
|
|||||||
IconButton,
|
IconButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
action: { type: Function, required: false },
|
||||||
icon: { type: String, default: PopoverButtonIcon.DropdownArrow },
|
icon: { type: String, default: PopoverButtonIcon.DropdownArrow },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clickButton() {
|
handleClick() {
|
||||||
(this.$refs.floatingMenu as typeof FloatingMenu).setOpen();
|
(this.$refs.floatingMenu as typeof FloatingMenu).setOpen();
|
||||||
|
|
||||||
|
if (this.action) this.action();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -131,9 +131,7 @@ const enum ColorPickerState {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {},
|
||||||
props: {
|
props: {
|
||||||
color: {
|
color: { type: Object, required: true },
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -168,16 +166,13 @@ export default defineComponent({
|
|||||||
document.addEventListener("pointermove", this.onPointerMove);
|
document.addEventListener("pointermove", this.onPointerMove);
|
||||||
document.addEventListener("pointerup", this.onPointerUp);
|
document.addEventListener("pointerup", this.onPointerUp);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeEvents() {
|
removeEvents() {
|
||||||
document.removeEventListener("pointermove", this.onPointerMove);
|
document.removeEventListener("pointermove", this.onPointerMove);
|
||||||
document.removeEventListener("pointerup", this.onPointerUp);
|
document.removeEventListener("pointerup", this.onPointerUp);
|
||||||
},
|
},
|
||||||
|
|
||||||
getRef<T>(name: string) {
|
getRef<T>(name: string) {
|
||||||
return this.$refs[name] as T;
|
return this.$refs[name] as T;
|
||||||
},
|
},
|
||||||
|
|
||||||
onPointerDown(e: PointerEvent) {
|
onPointerDown(e: PointerEvent) {
|
||||||
if (!(e.currentTarget instanceof Element)) return;
|
if (!(e.currentTarget instanceof Element)) return;
|
||||||
const picker = e.currentTarget.getAttribute("data-picker-action");
|
const picker = e.currentTarget.getAttribute("data-picker-action");
|
||||||
@@ -200,7 +195,6 @@ export default defineComponent({
|
|||||||
this.onPointerMove(e);
|
this.onPointerMove(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onPointerMove(e: PointerEvent) {
|
onPointerMove(e: PointerEvent) {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
|
|
||||||
@@ -217,14 +211,12 @@ export default defineComponent({
|
|||||||
this.$emit("update:color", hsvToRgb(colorPicker.color));
|
this.$emit("update:color", hsvToRgb(colorPicker.color));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onPointerUp() {
|
onPointerUp() {
|
||||||
if (this.state !== ColorPickerState.Idle) {
|
if (this.state !== ColorPickerState.Idle) {
|
||||||
this.state = ColorPickerState.Idle;
|
this.state = ColorPickerState.Idle;
|
||||||
this.removeEvents();
|
this.removeEvents();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRects() {
|
updateRects() {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
|
|
||||||
@@ -249,7 +241,6 @@ export default defineComponent({
|
|||||||
colorPicker.opacity.rect.left = opacity.left;
|
colorPicker.opacity.rect.left = opacity.left;
|
||||||
colorPicker.opacity.rect.top = opacity.top;
|
colorPicker.opacity.rect.top = opacity.top;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSaturationPosition(x: number, y: number) {
|
setSaturationPosition(x: number, y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
const saturationCursor = this.getRef<HTMLDivElement>("saturationCursor");
|
const saturationCursor = this.getRef<HTMLDivElement>("saturationCursor");
|
||||||
@@ -258,7 +249,6 @@ export default defineComponent({
|
|||||||
colorPicker.color.s = saturationPosition[0] / colorPicker.saturation.rect.width;
|
colorPicker.color.s = saturationPosition[0] / colorPicker.saturation.rect.width;
|
||||||
colorPicker.color.v = (1 - saturationPosition[1] / colorPicker.saturation.rect.height) * 255;
|
colorPicker.color.v = (1 - saturationPosition[1] / colorPicker.saturation.rect.height) * 255;
|
||||||
},
|
},
|
||||||
|
|
||||||
setHuePosition(y: number) {
|
setHuePosition(y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
const hueCursor = this.getRef<HTMLDivElement>("hueCursor");
|
const hueCursor = this.getRef<HTMLDivElement>("hueCursor");
|
||||||
@@ -266,7 +256,6 @@ export default defineComponent({
|
|||||||
hueCursor.style.transform = `translateY(${huePosition}px)`;
|
hueCursor.style.transform = `translateY(${huePosition}px)`;
|
||||||
colorPicker.color.h = clamp(1 - huePosition / colorPicker.hue.rect.height);
|
colorPicker.color.h = clamp(1 - huePosition / colorPicker.hue.rect.height);
|
||||||
},
|
},
|
||||||
|
|
||||||
setOpacityPosition(y: number) {
|
setOpacityPosition(y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
const opacityCursor = this.getRef<HTMLDivElement>("opacityCursor");
|
const opacityCursor = this.getRef<HTMLDivElement>("opacityCursor");
|
||||||
@@ -274,7 +263,6 @@ export default defineComponent({
|
|||||||
opacityCursor.style.transform = `translateY(${opacityPosition}px)`;
|
opacityCursor.style.transform = `translateY(${opacityPosition}px)`;
|
||||||
colorPicker.color.a = clamp(1 - opacityPosition / colorPicker.opacity.rect.height);
|
colorPicker.color.a = clamp(1 - opacityPosition / colorPicker.opacity.rect.height);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateHue() {
|
updateHue() {
|
||||||
const { colorPicker } = this.$data._;
|
const { colorPicker } = this.$data._;
|
||||||
let color = hsvToRgb({ h: colorPicker.color.h, s: 1, v: 255, a: 1 });
|
let color = hsvToRgb({ h: colorPicker.color.h, s: 1, v: 255, a: 1 });
|
||||||
@@ -282,7 +270,6 @@ export default defineComponent({
|
|||||||
color = hsvToRgb(colorPicker.color);
|
color = hsvToRgb(colorPicker.color);
|
||||||
this.$el.style.setProperty("--opacity-picker-color", `rgb(${color.r}, ${color.g}, ${color.b})`);
|
this.$el.style.setProperty("--opacity-picker-color", `rgb(${color.r}, ${color.g}, ${color.b})`);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateColor() {
|
updateColor() {
|
||||||
if (this.state !== ColorPickerState.Idle) return;
|
if (this.state !== ColorPickerState.Idle) return;
|
||||||
const { color } = this;
|
const { color } = this;
|
||||||
|
|||||||
@@ -120,8 +120,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
import { keyboardLockApiSupported } from "@/utilities/fullscreen";
|
import { keyboardLockApiSupported } from "@/utilities/fullscreen";
|
||||||
|
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
import Separator, { SeparatorDirection, SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export default defineComponent({
|
|||||||
selectedIndex(newSelectedIndex: number) {
|
selectedIndex(newSelectedIndex: number) {
|
||||||
const entries = this.menuEntries.flat();
|
const entries = this.menuEntries.flat();
|
||||||
|
|
||||||
if (newSelectedIndex >= 0 && newSelectedIndex < entries.length) {
|
if (!Number.isNaN(newSelectedIndex) && newSelectedIndex >= 0 && newSelectedIndex < entries.length) {
|
||||||
this.activeEntry = entries[newSelectedIndex];
|
this.activeEntry = entries[newSelectedIndex];
|
||||||
} else {
|
} else {
|
||||||
this.activeEntry = { label: "-" };
|
this.activeEntry = { label: "-" };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="number-input">
|
<div class="number-input" :class="{ disabled }">
|
||||||
<button class="arrow left" @click="onIncrement(-1)"></button>
|
<input type="text" spellcheck="false" v-model="text" @change="onTextChanged()" @keydown.esc="onCancelTextChange" ref="input" :disabled="disabled" />
|
||||||
<button class="arrow right" @click="onIncrement(1)"></button>
|
<button v-if="!Number.isNaN(value)" class="arrow left" @click="onIncrement(IncrementDirection.Decrease)"></button>
|
||||||
<input type="text" spellcheck="false" v-model="text" @change="updateText($event.target.value)" /> />
|
<button v-if="!Number.isNaN(value)" class="arrow right" @click="onIncrement(IncrementDirection.Increase)"></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -17,12 +17,12 @@
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
width: calc(100% - 8px);
|
width: calc(100% - 8px);
|
||||||
margin: 0 4px;
|
line-height: 18px;
|
||||||
|
margin: 3px 4px;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 24px;
|
|
||||||
color: var(--color-e-nearwhite);
|
color: var(--color-e-nearwhite);
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -31,6 +31,17 @@
|
|||||||
&::selection {
|
&::selection {
|
||||||
background: var(--color-accent);
|
background: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
text-align: left;
|
||||||
|
width: calc(100% - 16px);
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
|
& ~ .arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:hover) .arrow {
|
&:not(:hover) .arrow {
|
||||||
@@ -89,70 +100,136 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
background: var(--color-2-mildblack);
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: var(--color-8-uppergray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export enum IncrementDirection {
|
||||||
|
Decrease = "Decrease",
|
||||||
|
Increase = "Increase",
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {},
|
||||||
props: {
|
props: {
|
||||||
initialValue: { type: Number, default: 0 },
|
value: { type: Number, required: true },
|
||||||
unit: { type: String, default: "" },
|
|
||||||
step: { type: Number, default: 1 },
|
|
||||||
displayDecimalPlaces: { type: Number, default: 3 },
|
|
||||||
increaseMultiplier: { type: Number, default: null },
|
|
||||||
decreaseMultiplier: { type: Number, default: null },
|
|
||||||
min: { type: Number, required: false },
|
min: { type: Number, required: false },
|
||||||
max: { type: Number, required: false },
|
max: { type: Number, required: false },
|
||||||
callback: { type: Function, required: false },
|
step: { type: Number, default: 1 },
|
||||||
updateOnCallback: { type: Boolean, default: true },
|
stepIsMultiplier: { type: Boolean, default: false },
|
||||||
|
isInteger: { type: Boolean, default: false },
|
||||||
|
unit: { type: String, default: "" },
|
||||||
|
unitIsHiddenWhenEditing: { type: Boolean, default: true },
|
||||||
|
displayDecimalPlaces: { type: Number, default: 3 },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: this.initialValue,
|
text: `${this.value}${this.unit}`,
|
||||||
text: this.initialValue.toString() + this.unit,
|
editing: false,
|
||||||
|
IncrementDirection,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onIncrement(direction: number) {
|
onTextFocused() {
|
||||||
if (direction === 1 && this.increaseMultiplier) this.updateValue(this.value * this.increaseMultiplier, true);
|
if (Number.isNaN(this.value)) this.text = "";
|
||||||
else if (direction === -1 && this.decreaseMultiplier) this.updateValue(this.value * this.decreaseMultiplier, true);
|
else if (this.unitIsHiddenWhenEditing) this.text = `${this.value}`;
|
||||||
else this.updateValue(this.value + this.step * direction, true);
|
else this.text = `${this.value}${this.unit}`;
|
||||||
},
|
|
||||||
|
|
||||||
updateText(newText: string) {
|
this.editing = true;
|
||||||
const newValue = parseInt(newText, 10);
|
const inputElement = this.$refs.input as HTMLInputElement;
|
||||||
this.updateValue(newValue, true);
|
// Setting the value directly is required to make `inputElement.select()` work
|
||||||
|
inputElement.value = this.text;
|
||||||
|
inputElement.select();
|
||||||
},
|
},
|
||||||
|
// Called only when `value` is changed from the <input> element via user input and committed, either with the
|
||||||
|
// enter key (via the `change` event) or when the <input> element is defocused (with the `blur` event binding)
|
||||||
|
onTextChanged() {
|
||||||
|
// The `inputElement.blur()` call at the bottom of this function causes itself to be run again, so this check skips a second run
|
||||||
|
if (!this.editing) return;
|
||||||
|
|
||||||
clampValue(newValue: number, resetOnClamp: boolean) {
|
const newValue = parseFloat(this.text);
|
||||||
if (!Number.isFinite(newValue)) return this.value;
|
this.updateValue(newValue);
|
||||||
let result = newValue;
|
|
||||||
if (Number.isFinite(this.min) && typeof this.min === "number") {
|
this.editing = false;
|
||||||
if (resetOnClamp && newValue < this.min) return this.value;
|
const inputElement = this.$refs.input as HTMLElement;
|
||||||
result = Math.max(result, this.min);
|
inputElement.blur();
|
||||||
}
|
|
||||||
if (Number.isFinite(this.max) && typeof this.max === "number") {
|
|
||||||
if (resetOnClamp && newValue > this.max) return this.value;
|
|
||||||
result = Math.min(result, this.max);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
setValue(newValue: number) {
|
onCancelTextChange() {
|
||||||
this.value = newValue;
|
this.updateValue(NaN);
|
||||||
|
|
||||||
|
this.editing = false;
|
||||||
|
const inputElement = this.$refs.input as HTMLElement;
|
||||||
|
inputElement.blur();
|
||||||
|
},
|
||||||
|
onIncrement(direction: IncrementDirection) {
|
||||||
|
if (Number.isNaN(this.value)) return;
|
||||||
|
|
||||||
|
if (this.stepIsMultiplier) {
|
||||||
|
const directionMultiplier = direction === IncrementDirection.Increase ? this.step : 1 / this.step;
|
||||||
|
this.updateValue(this.value * directionMultiplier);
|
||||||
|
} else {
|
||||||
|
const directionAddend = direction === IncrementDirection.Increase ? this.step : -this.step;
|
||||||
|
this.updateValue(this.value + directionAddend);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateValue(newValue: number) {
|
||||||
|
let sanitized = newValue;
|
||||||
|
|
||||||
|
const invalid = Number.isNaN(newValue);
|
||||||
|
if (invalid) sanitized = this.value;
|
||||||
|
|
||||||
|
if (this.isInteger) sanitized = Math.round(sanitized);
|
||||||
|
if (typeof this.min === "number" && !Number.isNaN(this.min)) sanitized = Math.max(sanitized, this.min);
|
||||||
|
if (typeof this.max === "number" && !Number.isNaN(this.max)) sanitized = Math.min(sanitized, this.max);
|
||||||
|
|
||||||
|
if (!invalid) this.$emit("update:value", sanitized);
|
||||||
|
|
||||||
const roundingPower = 10 ** this.displayDecimalPlaces;
|
const roundingPower = 10 ** this.displayDecimalPlaces;
|
||||||
this.text = `${Math.round(this.value * roundingPower) / roundingPower}${this.unit}`;
|
const displayValue = Math.round(sanitized * roundingPower) / roundingPower;
|
||||||
|
this.text = `${displayValue}${this.unit}`;
|
||||||
},
|
},
|
||||||
updateValue(inValue: number, resetOnClamp: boolean) {
|
},
|
||||||
const newValue = this.clampValue(inValue, resetOnClamp);
|
watch: {
|
||||||
|
// Called only when `value` is changed from outside this component (with v-model)
|
||||||
|
value(newValue: number) {
|
||||||
|
if (Number.isNaN(newValue)) {
|
||||||
|
this.text = "-";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.callback) this.callback(newValue);
|
let sanitized = newValue;
|
||||||
|
if (typeof this.min === "number") sanitized = Math.max(sanitized, this.min);
|
||||||
|
if (typeof this.max === "number") sanitized = Math.min(sanitized, this.max);
|
||||||
|
|
||||||
if (this.updateOnCallback) this.setValue(newValue);
|
const roundingPower = 10 ** this.displayDecimalPlaces;
|
||||||
|
const displayValue = Math.round(sanitized * roundingPower) / roundingPower;
|
||||||
|
this.text = `${displayValue}${this.unit}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
const inputElement = this.$refs.input as HTMLInputElement;
|
||||||
|
inputElement.addEventListener("focus", this.onTextFocused);
|
||||||
|
inputElement.addEventListener("blur", this.onTextChanged);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
const inputElement = this.$refs.input as HTMLInputElement;
|
||||||
|
inputElement.removeEventListener("focus", this.onTextFocused);
|
||||||
|
inputElement.removeEventListener("blur", this.onTextChanged);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="shelf-item-input" :class="{ active: active }">
|
<div class="shelf-item-input" :class="{ active: active }">
|
||||||
<IconButton :icon="icon" :size="32" />
|
<IconButton :action="() => _" :icon="icon" :size="32" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tool-options">
|
<div class="tool-options">
|
||||||
<template v-for="(option, index) in optionsMap.get(activeTool) || []" :key="index">
|
<template v-for="(option, index) in toolOptions[activeTool] || []" :key="index">
|
||||||
<IconButton v-if="option.kind === 'IconButton'" :icon="option.icon" :size="24" :title="option.title" :onClick="() => sendToolMessage(option.message)" />
|
<!-- TODO: Use `<component :is="" v-bind="attributesObject"></component>` to avoid all the separate components with `v-if` -->
|
||||||
<Separator v-if="option.kind === 'Separator'" :type="option.type" />
|
<IconButton
|
||||||
<PopoverButton v-if="option.kind === 'PopoverButton'">
|
v-if="option.kind === 'IconButton'"
|
||||||
|
:action="() => (option.message && sendToolMessage(option.message), option.callback && option.callback())"
|
||||||
|
:title="option.tooltip"
|
||||||
|
v-bind="option.props"
|
||||||
|
/>
|
||||||
|
<PopoverButton v-if="option.kind === 'PopoverButton'" :title="option.tooltip" :action="() => option.callback && option.callback()" v-bind="option.props">
|
||||||
<h3>{{ option.title }}</h3>
|
<h3>{{ option.title }}</h3>
|
||||||
<p>{{ option.placeholderText }}</p>
|
<p>{{ option.text }}</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<NumberInput v-if="option.kind === 'NumberInput'" :callback="option.callback" :initialValue="option.initial" :step="option.step" :min="option.min" :updateOnCallback="true" />
|
<NumberInput
|
||||||
|
v-if="option.kind === 'NumberInput'"
|
||||||
|
v-model:value="option.props.value"
|
||||||
|
@update:value="() => option.callback && option.callback()"
|
||||||
|
:title="option.tooltip"
|
||||||
|
v-bind="option.props"
|
||||||
|
/>
|
||||||
|
<Separator v-if="option.kind === 'Separator'" :type="option.type" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -23,43 +35,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import Separator, { SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
|
||||||
|
import { WidgetRow, SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
import Separator from "@/components/widgets/separators/Separator.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 NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
|
|
||||||
const wasm = import("@/../wasm/pkg");
|
const wasm = import("@/../wasm/pkg");
|
||||||
|
|
||||||
type ToolOptionsList = Array<ToolOptions>;
|
|
||||||
type ToolOptionsMap = Map<string, ToolOptionsList>;
|
|
||||||
type ToolOptions = IconButtonOption | SeparatorOption | PopoverButtonOption | NumberInputOption;
|
|
||||||
|
|
||||||
interface IconButtonOption {
|
|
||||||
kind: "IconButton";
|
|
||||||
icon: string;
|
|
||||||
title: string;
|
|
||||||
message?: string | object;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SeparatorOption {
|
|
||||||
kind: "Separator";
|
|
||||||
type: SeparatorType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PopoverButtonOption {
|
|
||||||
kind: "PopoverButton";
|
|
||||||
title: string;
|
|
||||||
placeholderText: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NumberInputOption {
|
|
||||||
kind: "NumberInput";
|
|
||||||
initial: number;
|
|
||||||
step: number;
|
|
||||||
min?: number;
|
|
||||||
callback?: Function;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
activeTool: { type: String },
|
activeTool: { type: String },
|
||||||
@@ -75,59 +59,75 @@ export default defineComponent({
|
|||||||
// This is a placeholder call, using the Shape tool as an example
|
// This is a placeholder call, using the Shape tool as an example
|
||||||
set_tool_options(this.$props.activeTool || "", { Shape: { shape_type: { Polygon: { vertices: newValue } } } });
|
set_tool_options(this.$props.activeTool || "", { Shape: { shape_type: { Polygon: { vertices: newValue } } } });
|
||||||
},
|
},
|
||||||
async sendToolMessage(message?: string) {
|
async sendToolMessage(message: string) {
|
||||||
if (message) {
|
const { send_tool_message } = await wasm;
|
||||||
const { send_tool_message } = await wasm;
|
send_tool_message(this.$props.activeTool || "", message);
|
||||||
send_tool_message(this.$props.activeTool || "", message);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const optionsMap: ToolOptionsMap = new Map([
|
const toolOptions: Record<string, WidgetRow> = {
|
||||||
[
|
Select: [
|
||||||
"Select",
|
{ kind: "IconButton", message: { Align: ["X", "Min"] }, tooltip: "Align Left", props: { icon: "AlignLeft", size: 24 } },
|
||||||
[
|
{ kind: "IconButton", message: { Align: ["X", "Center"] }, tooltip: "Align Horizontal Center", props: { icon: "AlignHorizontalCenter", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "AlignLeft", title: "Align Left", message: { Align: ["X", "Min"] } },
|
{ kind: "IconButton", message: { Align: ["X", "Max"] }, tooltip: "Align Right", props: { icon: "AlignRight", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "AlignHorizontalCenter", title: "Align Horizontal Center", message: { Align: ["X", "Center"] } },
|
|
||||||
{ kind: "IconButton", icon: "AlignRight", title: "Align Right", message: { Align: ["X", "Max"] } },
|
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Unrelated },
|
{ kind: "Separator", props: { type: SeparatorType.Unrelated } },
|
||||||
|
|
||||||
{ kind: "IconButton", icon: "AlignTop", title: "Align Top", message: { Align: ["Y", "Min"] } },
|
{ kind: "IconButton", message: { Align: ["Y", "Min"] }, tooltip: "Align Top", props: { icon: "AlignTop", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "AlignVerticalCenter", title: "Align Vertical Center", message: { Align: ["Y", "Center"] } },
|
{ kind: "IconButton", message: { Align: ["Y", "Center"] }, tooltip: "Align Vertical Center", props: { icon: "AlignVerticalCenter", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "AlignBottom", title: "Align Bottom", message: { Align: ["Y", "Max"] } },
|
{ kind: "IconButton", message: { Align: ["Y", "Max"] }, tooltip: "Align Bottom", props: { icon: "AlignBottom", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Related },
|
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
||||||
|
|
||||||
{ kind: "PopoverButton", title: "Align", placeholderText: "More alignment-related buttons will be here" },
|
{
|
||||||
|
kind: "PopoverButton",
|
||||||
|
popover: {
|
||||||
|
title: "Align",
|
||||||
|
text: "More alignment-related buttons will be here",
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Section },
|
{ kind: "Separator", props: { type: SeparatorType.Section } },
|
||||||
|
|
||||||
{ kind: "IconButton", icon: "FlipHorizontal", title: "Flip Horizontal", message: "FlipHorizontal" },
|
{ kind: "IconButton", message: "FlipHorizontal", tooltip: "Flip Horizontal", props: { icon: "FlipHorizontal", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "FlipVertical", title: "Flip Vertical", message: "FlipVertical" },
|
{ kind: "IconButton", message: "FlipVertical", tooltip: "Flip Vertical", props: { icon: "FlipVertical", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Related },
|
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
||||||
|
|
||||||
{ kind: "PopoverButton", title: "Flip", placeholderText: "More flip-related buttons will be here" },
|
{
|
||||||
|
kind: "PopoverButton",
|
||||||
|
popover: {
|
||||||
|
title: "Flip",
|
||||||
|
text: "More flip-related buttons will be here",
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Section },
|
{ kind: "Separator", props: { type: SeparatorType.Section } },
|
||||||
|
|
||||||
{ kind: "IconButton", icon: "BooleanUnion", title: "Boolean Union" },
|
{ kind: "IconButton", tooltip: "Boolean Union", props: { icon: "BooleanUnion", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "BooleanSubtractFront", title: "Boolean Subtract Front" },
|
{ kind: "IconButton", tooltip: "Boolean Subtract Front", props: { icon: "BooleanSubtractFront", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "BooleanSubtractBack", title: "Boolean Subtract Back" },
|
{ kind: "IconButton", tooltip: "Boolean Subtract Back", props: { icon: "BooleanSubtractBack", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "BooleanIntersect", title: "Boolean Intersect" },
|
{ kind: "IconButton", tooltip: "Boolean Intersect", props: { icon: "BooleanIntersect", size: 24 } },
|
||||||
{ kind: "IconButton", icon: "BooleanDifference", title: "Boolean Difference" },
|
{ kind: "IconButton", tooltip: "Boolean Difference", props: { icon: "BooleanDifference", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", type: SeparatorType.Related },
|
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
||||||
|
|
||||||
{ kind: "PopoverButton", title: "Boolean", placeholderText: "More boolean-related buttons will be here" },
|
{
|
||||||
],
|
kind: "PopoverButton",
|
||||||
|
popover: {
|
||||||
|
title: "Boolean",
|
||||||
|
text: "More boolean-related buttons will be here",
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
["Shape", [{ kind: "NumberInput", initial: 6, step: 1, min: 3, callback: this.setToolOptions }]],
|
Shape: [{ kind: "NumberInput", callback: this.setToolOptions, props: { value: 6, min: 3, isInteger: true } }],
|
||||||
]);
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
optionsMap,
|
toolOptions,
|
||||||
SeparatorType,
|
SeparatorType,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -70,18 +70,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
||||||
export enum SeparatorDirection {
|
|
||||||
"Horizontal" = "Horizontal",
|
|
||||||
"Vertical" = "Vertical",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SeparatorType {
|
|
||||||
"Related" = "Related",
|
|
||||||
"Unrelated" = "Unrelated",
|
|
||||||
"Section" = "Section",
|
|
||||||
"List" = "List",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
components: {},
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
export type Widgets = IconButtonWidget | SeparatorWidget | PopoverButtonWidget | NumberInputWidget;
|
||||||
|
export type WidgetRow = Array<Widgets>;
|
||||||
|
export type WidgetLayout = Array<WidgetRow>;
|
||||||
|
|
||||||
|
// Icon Button
|
||||||
|
export interface IconButtonWidget {
|
||||||
|
kind: "IconButton";
|
||||||
|
tooltip?: string;
|
||||||
|
message?: string | object;
|
||||||
|
callback?: Function;
|
||||||
|
props: IconButtonProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IconButtonProps {
|
||||||
|
// `action` is used via `IconButtonWidget.callback`
|
||||||
|
icon: string;
|
||||||
|
size: number;
|
||||||
|
gapAfter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popover Button
|
||||||
|
export interface PopoverButtonWidget {
|
||||||
|
kind: "PopoverButton";
|
||||||
|
tooltip?: string;
|
||||||
|
callback?: Function;
|
||||||
|
// popover: WidgetLayout;
|
||||||
|
popover: { title: string; text: string }; // TODO: Replace this with a `WidgetLayout` like above for arbitrary layouts
|
||||||
|
props: PopoverButtonProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopoverButtonProps {
|
||||||
|
// `action` is used via `PopoverButtonWidget.callback`
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number Input
|
||||||
|
export interface NumberInputWidget {
|
||||||
|
kind: "NumberInput";
|
||||||
|
tooltip?: string;
|
||||||
|
callback?: Function;
|
||||||
|
props: NumberInputProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberInputProps {
|
||||||
|
value: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
stepIsMultiplier?: boolean;
|
||||||
|
isInteger?: boolean;
|
||||||
|
unit?: string;
|
||||||
|
unitIsHiddenWhenEditing?: boolean;
|
||||||
|
displayDecimalPlaces?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
export interface SeparatorWidget {
|
||||||
|
kind: "Separator";
|
||||||
|
props: SeparatorProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeparatorProps {
|
||||||
|
direction?: SeparatorDirection;
|
||||||
|
type?: SeparatorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SeparatorDirection {
|
||||||
|
"Horizontal" = "Horizontal",
|
||||||
|
"Vertical" = "Vertical",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SeparatorType {
|
||||||
|
"Related" = "Related",
|
||||||
|
"Unrelated" = "Unrelated",
|
||||||
|
"Section" = "Section",
|
||||||
|
"List" = "List",
|
||||||
|
}
|
||||||
@@ -55,8 +55,11 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
import { SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
import Separator, { SeparatorType } from "@/components/widgets/separators/Separator.vue";
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
@click="handleTabClick(tabIndex)"
|
@click="handleTabClick(tabIndex)"
|
||||||
>
|
>
|
||||||
<span>{{ tabLabel }}</span>
|
<span>{{ tabLabel }}</span>
|
||||||
<IconButton :icon="'CloseX'" :size="16" v-if="tabCloseButtons" @click.stop="handleTabClose(tabIndex)" />
|
<IconButton :action="() => handleTabClose(tabIndex)" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
||||||
|
|||||||
Reference in New Issue
Block a user