mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Color Input (#565)
* initial working prototype * clean up component * Fix alignment * Code review tweaks Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
38a4dfd8bc
commit
8387ffe735
6 changed files with 126 additions and 12 deletions
|
@ -15,6 +15,7 @@
|
|||
:incrementCallbackDecrease="() => updateLayout(component.widget_id, 'Decrement')"
|
||||
/>
|
||||
<TextInput v-if="component.kind === 'TextInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widget_id, value)" />
|
||||
<ColorInput v-if="component.kind === 'ColorInput'" v-bind="component.props" @update:value="(value: string) => updateLayout(component.widget_id, value)" />
|
||||
<IconButton v-if="component.kind === 'IconButton'" v-bind="component.props" :action="() => updateLayout(component.widget_id, null)" />
|
||||
<OptionalInput v-if="component.kind === 'OptionalInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widget_id, value)" />
|
||||
<RadioInput v-if="component.kind === 'RadioInput'" v-bind="component.props" @update:selectedIndex="(value: number) => updateLayout(component.widget_id, value)" />
|
||||
|
@ -41,6 +42,7 @@ import { WidgetRow } from "@/dispatcher/js-messages";
|
|||
|
||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||
import ColorInput from "@/components/widgets/inputs/ColorInput.vue";
|
||||
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
||||
import RadioInput from "@/components/widgets/inputs/RadioInput.vue";
|
||||
|
@ -70,6 +72,7 @@ export default defineComponent({
|
|||
RadioInput,
|
||||
TextLabel,
|
||||
IconLabel,
|
||||
ColorInput,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
95
frontend/src/components/widgets/inputs/ColorInput.vue
Normal file
95
frontend/src/components/widgets/inputs/ColorInput.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<LayoutRow class="color-input">
|
||||
<TextInput :value="value" :label="label" :disabled="disabled" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
||||
<Separator :type="'Related'" />
|
||||
<LayoutRow class="swatch">
|
||||
<button class="swatch-button" @click="() => menuOpen()" :style="{ background: `#${value}` }"></button>
|
||||
<FloatingMenu :type="'Popover'" :direction="'Bottom'" horizontal ref="colorFloatingMenu">
|
||||
<ColorPicker @update:color="(color) => colorPickerUpdated(color)" :color="color" />
|
||||
</FloatingMenu>
|
||||
</LayoutRow>
|
||||
</LayoutRow>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.color-input {
|
||||
.text-input input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
|
||||
.swatch-button {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
margin-top: 24px;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
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 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 },
|
||||
label: { type: String as PropType<string>, required: false },
|
||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
computed: {
|
||||
color() {
|
||||
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);
|
||||
const a = parseInt(this.value.slice(6, 8), 16);
|
||||
return { r, g, b, a: a / 255 };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
colorPickerUpdated(color: RGBA) {
|
||||
const twoDigitHex = (value: number): string => value.toString(16).padStart(2, "0");
|
||||
const alphaU8Scale = Math.floor(color.a * 255);
|
||||
const newValue = `${twoDigitHex(color.r)}${twoDigitHex(color.g)}${twoDigitHex(color.b)}${twoDigitHex(alphaU8Scale)}`;
|
||||
this.$emit("update:value", newValue);
|
||||
},
|
||||
textInputUpdated(newValue: string) {
|
||||
if ((newValue.length !== 6 && newValue.length !== 8) || !newValue.match(/[A-F,a-f,0-9]*/)) return;
|
||||
|
||||
this.$emit("update:value", newValue);
|
||||
},
|
||||
menuOpen() {
|
||||
(this.$refs.colorFloatingMenu as typeof FloatingMenu).setOpen();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
TextInput,
|
||||
ColorPicker,
|
||||
LayoutRow,
|
||||
FloatingMenu,
|
||||
Separator,
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -412,7 +412,7 @@ export function isWidgetSection(layoutRow: WidgetRow | WidgetSection): layoutRow
|
|||
return Boolean((layoutRow as WidgetSection).layout);
|
||||
}
|
||||
|
||||
export type WidgetKind = "NumberInput" | "Separator" | "IconButton" | "PopoverButton" | "OptionalInput" | "RadioInput" | "TextInput" | "TextLabel" | "IconLabel";
|
||||
export type WidgetKind = "NumberInput" | "Separator" | "IconButton" | "PopoverButton" | "OptionalInput" | "RadioInput" | "TextInput" | "TextLabel" | "IconLabel" | "ColorInput";
|
||||
|
||||
export interface Widget {
|
||||
kind: WidgetKind;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue