Skip to content

Color Input #565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions editor/src/document/properties_panel_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::layer_panel::LayerDataTypeDiscriminant;
use crate::document::properties_panel_message::TransformOp;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::{
IconLabel, LayoutRow, NumberInput, PopoverButton, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
};
use crate::message_prelude::*;

Expand Down Expand Up @@ -424,9 +424,9 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: color.rgba_hex(),
on_update: WidgetCallback::new(|text_input: &TextInput| {
on_update: WidgetCallback::new(|text_input: &ColorInput| {
if let Some(color) = Color::from_rgba_str(&text_input.value).or(Color::from_rgb_str(&text_input.value)) {
let new_fill = Fill::Solid(color);
PropertiesPanelMessage::ModifyFill { fill: new_fill }.into()
Expand Down Expand Up @@ -455,9 +455,9 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: gradient_1.positions[0].1.rgba_hex(),
on_update: WidgetCallback::new(move |text_input: &TextInput| {
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
if let Some(color) = Color::from_rgba_str(&text_input.value).or(Color::from_rgb_str(&text_input.value)) {
let mut new_gradient = (*gradient_1).clone();
new_gradient.positions[0].1 = color;
Expand All @@ -483,9 +483,9 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: gradient_2.positions[1].1.rgba_hex(),
on_update: WidgetCallback::new(move |text_input: &TextInput| {
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
if let Some(color) = Color::from_rgba_str(&text_input.value).or(Color::from_rgb_str(&text_input.value)) {
let mut new_gradient = (*gradient_2).clone();
new_gradient.positions[1].1 = color;
Expand Down Expand Up @@ -524,9 +524,9 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextInput(TextInput {
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: stroke.color().rgba_hex(),
on_update: WidgetCallback::new(move |text_input: &TextInput| {
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
PropertiesPanelMessage::ModifyStroke {
color: text_input.value.clone(),
weight: weight as f64,
Expand Down
10 changes: 8 additions & 2 deletions editor/src/layout/layout_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,23 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
responses.push_back(callback_message);
}
Widget::RadioInput(radio_input) => {
let update_value = value.as_u64().expect("OptionalInput update was not of type: u64");
let update_value = value.as_u64().expect("RadioInput update was not of type: u64");
radio_input.selected_index = update_value as u32;
let callback_message = (radio_input.entries[update_value as usize].on_update.callback)(&());
responses.push_back(callback_message);
}
Widget::TextInput(text_input) => {
let update_value = value.as_str().expect("OptionalInput update was not of type: string");
let update_value = value.as_str().expect("TextInput update was not of type: string");
text_input.value = update_value.into();
let callback_message = (text_input.on_update.callback)(text_input);
responses.push_back(callback_message);
}
Widget::ColorInput(color_input) => {
let update_value = value.as_str().expect("ColorInput update was not of type: string");
color_input.value = update_value.into();
let callback_message = (color_input.on_update.callback)(color_input);
responses.push_back(callback_message);
}
Widget::TextLabel(_) => {}
};
self.send_layout(layout_target, responses);
Expand Down
10 changes: 10 additions & 0 deletions editor/src/layout/widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ impl<T> Default for WidgetCallback<T> {
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Widget {
ColorInput(ColorInput),
IconButton(IconButton),
IconLabel(IconLabel),
NumberInput(NumberInput),
Expand Down Expand Up @@ -199,6 +200,15 @@ pub struct TextInput {
pub on_update: WidgetCallback<TextInput>,
}

#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorInput {
pub value: String,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<ColorInput>,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub enum NumberInputIncrementBehavior {
Add,
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/widgets/WidgetRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)" />
Expand All @@ -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";
Expand Down Expand Up @@ -70,6 +72,7 @@ export default defineComponent({
RadioInput,
TextLabel,
IconLabel,
ColorInput,
},
});
</script>
Expand Down
95 changes: 95 additions & 0 deletions frontend/src/components/widgets/inputs/ColorInput.vue
Original file line number Diff line number Diff line change
@@ -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>
2 changes: 1 addition & 1 deletion frontend/src/dispatcher/js-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down