mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
All shapes now have a Fill in the properties panel; color inputs are now optional (#583)
* Add aditional stroke properties * Make the colour input optional * Fix fmt * Apply code review changes * Code review nitpicks * Fix recursion Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
e4b2cb2f53
commit
ac6f4ad325
7 changed files with 157 additions and 70 deletions
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open || type === 'Dialog'" ref="floatingMenu">
|
||||
<div class="tail" v-if="type === 'Popover'"></div>
|
||||
<div class="tail" v-if="type === 'Popover'" ref="tail"></div>
|
||||
<div class="floating-menu-container" ref="floatingMenuContainer">
|
||||
<LayoutCol class="floating-menu-content" data-floating-menu-content :scrollableY="scrollableY" ref="floatingMenuContent" :style="floatingMenuContentStyle">
|
||||
<slot></slot>
|
||||
|
@ -201,51 +201,74 @@ export default defineComponent({
|
|||
open: false,
|
||||
pointerStillDown: false,
|
||||
containerResizeObserver,
|
||||
workspaceBounds: new DOMRect(),
|
||||
floatingMenuBounds: new DOMRect(),
|
||||
floatingMenuContentBounds: new DOMRect(),
|
||||
};
|
||||
},
|
||||
// Gets the client bounds of the elements and apply relevant styles to them
|
||||
// TODO: Use the Vue :style attribute more whilst not causing recursive updates
|
||||
updated() {
|
||||
const workspace = document.querySelector("[data-workspace]");
|
||||
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
|
||||
const floatingMenuContentComponent = this.$refs.floatingMenuContent as typeof LayoutCol;
|
||||
const floatingMenuContent = floatingMenuContentComponent && (floatingMenuContentComponent.$el as HTMLElement);
|
||||
const workspace = document.querySelector("[data-workspace]");
|
||||
const floatingMenu = this.$refs.floatingMenu as HTMLElement;
|
||||
|
||||
if (!floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !workspace) return;
|
||||
if (!workspace || !floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !floatingMenu) return;
|
||||
|
||||
const workspaceBounds = workspace.getBoundingClientRect();
|
||||
const floatingMenuBounds = floatingMenuContent.getBoundingClientRect();
|
||||
this.workspaceBounds = workspace.getBoundingClientRect();
|
||||
this.floatingMenuBounds = floatingMenu.getBoundingClientRect();
|
||||
this.floatingMenuContentBounds = floatingMenuContent.getBoundingClientRect();
|
||||
|
||||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
const tailOffset = this.type === "Popover" ? 10 : 0;
|
||||
if (this.direction === "Bottom") floatingMenuContent.style.top = `${tailOffset + this.floatingMenuBounds.top}px`;
|
||||
if (this.direction === "Top") floatingMenuContent.style.bottom = `${tailOffset + this.floatingMenuBounds.bottom}px`;
|
||||
if (this.direction === "Right") floatingMenuContent.style.left = `${tailOffset + this.floatingMenuBounds.left}px`;
|
||||
if (this.direction === "Left") floatingMenuContent.style.right = `${tailOffset + this.floatingMenuBounds.right}px`;
|
||||
|
||||
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
|
||||
const tail = this.$refs.tail as HTMLElement;
|
||||
if (tail) {
|
||||
if (this.direction === "Bottom") tail.style.top = `${this.floatingMenuBounds.top}px`;
|
||||
if (this.direction === "Top") tail.style.bottom = `${this.floatingMenuBounds.bottom}px`;
|
||||
if (this.direction === "Right") tail.style.left = `${this.floatingMenuBounds.left}px`;
|
||||
if (this.direction === "Left") tail.style.right = `${this.floatingMenuBounds.right}px`;
|
||||
}
|
||||
|
||||
type Edge = "Top" | "Bottom" | "Left" | "Right";
|
||||
let zeroedBorderDirection1: Edge | undefined;
|
||||
let zeroedBorderDirection2: Edge | undefined;
|
||||
let zeroedBorderVertical: Edge | undefined;
|
||||
let zeroedBorderHorizontal: Edge | undefined;
|
||||
|
||||
if (this.direction === "Top" || this.direction === "Bottom") {
|
||||
zeroedBorderDirection1 = this.direction === "Top" ? "Bottom" : "Top";
|
||||
zeroedBorderVertical = this.direction === "Top" ? "Bottom" : "Top";
|
||||
|
||||
if (floatingMenuBounds.left - this.windowEdgeMargin <= workspaceBounds.left) {
|
||||
if (this.floatingMenuContentBounds.left - this.windowEdgeMargin <= this.workspaceBounds.left) {
|
||||
floatingMenuContent.style.left = `${this.windowEdgeMargin}px`;
|
||||
if (workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderDirection2 = "Left";
|
||||
if (this.workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderHorizontal = "Left";
|
||||
}
|
||||
if (floatingMenuBounds.right + this.windowEdgeMargin >= workspaceBounds.right) {
|
||||
if (this.floatingMenuContentBounds.right + this.windowEdgeMargin >= this.workspaceBounds.right) {
|
||||
floatingMenuContent.style.right = `${this.windowEdgeMargin}px`;
|
||||
if (workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderDirection2 = "Right";
|
||||
if (this.workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderHorizontal = "Right";
|
||||
}
|
||||
}
|
||||
if (this.direction === "Left" || this.direction === "Right") {
|
||||
zeroedBorderDirection2 = this.direction === "Left" ? "Right" : "Left";
|
||||
zeroedBorderHorizontal = this.direction === "Left" ? "Right" : "Left";
|
||||
|
||||
if (floatingMenuBounds.top - this.windowEdgeMargin <= workspaceBounds.top) {
|
||||
if (this.floatingMenuContentBounds.top - this.windowEdgeMargin <= this.workspaceBounds.top) {
|
||||
floatingMenuContent.style.top = `${this.windowEdgeMargin}px`;
|
||||
if (workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderDirection1 = "Top";
|
||||
if (this.workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderVertical = "Top";
|
||||
}
|
||||
if (floatingMenuBounds.bottom + this.windowEdgeMargin >= workspaceBounds.bottom) {
|
||||
if (this.floatingMenuContentBounds.bottom + this.windowEdgeMargin >= this.workspaceBounds.bottom) {
|
||||
floatingMenuContent.style.bottom = `${this.windowEdgeMargin}px`;
|
||||
if (workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderDirection1 = "Bottom";
|
||||
if (this.workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderVertical = "Bottom";
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rounded corner from where the tail perfectly meets the corner
|
||||
if (this.type === "Popover" && this.windowEdgeMargin === 6 && zeroedBorderDirection1 && zeroedBorderDirection2) {
|
||||
switch (`${zeroedBorderDirection1}${zeroedBorderDirection2}`) {
|
||||
// Remove the rounded corner from the content where the tail perfectly meets the corner
|
||||
if (this.type === "Popover" && this.windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {
|
||||
switch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) {
|
||||
case "TopLeft":
|
||||
floatingMenuContent.style.borderTopLeftRadius = "0";
|
||||
break;
|
||||
|
@ -375,6 +398,7 @@ export default defineComponent({
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Switching from open to closed
|
||||
if (!newState && oldState) {
|
||||
window.removeEventListener("pointermove", this.pointerMoveHandler);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<LayoutRow class="color-input">
|
||||
<TextInput :value="displayValue" :label="label" :disabled="disabled" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
||||
<OptionalInput :icon="'CloseX'" :checked="!!value" @update:checked="(val) => updateEnabled(val)"></OptionalInput>
|
||||
<TextInput :value="displayValue" :label="label" :disabled="disabled || !value" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
||||
<Separator :type="'Related'" />
|
||||
<LayoutRow class="swatch">
|
||||
<button class="swatch-button" @click="() => menuOpen()" :style="`--swatch-color: #${value}`"></button>
|
||||
<button class="swatch-button" :class="{ 'disabled-swatch': !value }" :style="`--swatch-color: #${value}`" @click="() => menuOpen()"></button>
|
||||
<FloatingMenu :type="'Popover'" :direction="'Bottom'" horizontal ref="colorFloatingMenu">
|
||||
<ColorPicker @update:color="(color) => colorPickerUpdated(color)" :color="color" />
|
||||
</FloatingMenu>
|
||||
|
@ -44,6 +45,17 @@
|
|||
height: 100%;
|
||||
background: var(--swatch-color);
|
||||
}
|
||||
|
||||
&.disabled-swatch::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 4px solid red;
|
||||
width: 33px;
|
||||
left: 22px;
|
||||
top: -4px;
|
||||
transform: rotate(135deg);
|
||||
transform-origin: 0% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
|
@ -63,18 +75,21 @@ import { RGBA } from "@/dispatcher/js-messages";
|
|||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import ColorPicker from "@/components/widgets/floating-menus/ColorPicker.vue";
|
||||
import FloatingMenu from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
||||
import TextInput from "@/components/widgets/inputs/TextInput.vue";
|
||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||
|
||||
export default defineComponent({
|
||||
emits: ["update:value"],
|
||||
props: {
|
||||
value: { type: String as PropType<string>, required: true },
|
||||
value: { type: String as PropType<string | undefined>, required: true },
|
||||
label: { type: String as PropType<string>, required: false },
|
||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
computed: {
|
||||
color() {
|
||||
if (!this.value) return { r: 0, g: 0, b: 0, a: 1 };
|
||||
|
||||
const r = parseInt(this.value.slice(0, 2), 16);
|
||||
const g = parseInt(this.value.slice(2, 4), 16);
|
||||
const b = parseInt(this.value.slice(4, 6), 16);
|
||||
|
@ -82,6 +97,8 @@ export default defineComponent({
|
|||
return { r, g, b, a: a / 255 };
|
||||
},
|
||||
displayValue() {
|
||||
if (!this.value) return "";
|
||||
|
||||
const value = this.value.toLowerCase();
|
||||
const shortenedIfOpaque = value.slice(-2) === "ff" ? value.slice(0, 6) : value;
|
||||
return `#${shortenedIfOpaque}`;
|
||||
|
@ -106,15 +123,23 @@ export default defineComponent({
|
|||
.map((byte) => `${byte}${byte}`)
|
||||
.concat("ff")
|
||||
.join("");
|
||||
} else if (match.length === 6) sanitized = `${match}ff`;
|
||||
else if (match.length === 8) sanitized = match;
|
||||
else return;
|
||||
} else if (match.length === 6) {
|
||||
sanitized = `${match}ff`;
|
||||
} else if (match.length === 8) {
|
||||
sanitized = match;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit("update:value", sanitized);
|
||||
},
|
||||
menuOpen() {
|
||||
(this.$refs.colorFloatingMenu as typeof FloatingMenu).setOpen();
|
||||
},
|
||||
updateEnabled(value: boolean) {
|
||||
if (value) this.$emit("update:value", "000000");
|
||||
else this.$emit("update:value", undefined);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
TextInput,
|
||||
|
@ -122,6 +147,7 @@ export default defineComponent({
|
|||
LayoutRow,
|
||||
FloatingMenu,
|
||||
Separator,
|
||||
OptionalInput,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
<style lang="scss">
|
||||
.optional-input {
|
||||
flex-grow: 0;
|
||||
|
||||
label {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue