Skip to content

Commit

Permalink
ColorRamp WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
newcat committed Feb 25, 2024
1 parent 07b776c commit f550ec6
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 45 deletions.
44 changes: 30 additions & 14 deletions src/graph/nodes/colors/ColorRampNode.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
export {};
/*
TODO:
import { defineNode, NodeBuilder } from "@baklavajs/core";
import { markRaw } from "vue";
import { NodeInterface, defineNode } from "baklavajs";
import { ColorArrayInterface, ColorSingleInterface, SliderInterface } from "../../interfaces";
import { Color } from "../../colors";
import ColorRampOption from "../../options/ColorRampOption.vue";

export const ColorRampNode2 = new NodeBuilder("Color Ramp")
.addInputInterface("Factor", "SliderOption", 0, { type: "number", min: 0, max: 1 })
.addOption("Color Ramp", "ColorRampOption", () => [
{ color: [0, 0, 0], position: 0 },
{ color: [255, 255, 255], position: 1 },
])
.addOutputInterface("Color Band", { type: "color_array" })
.addOutputInterface("Single Color", { type: "color_single" })
.build();
*/
export interface ColorRampStop {
id: string;
position: number;
color: Color;
}

export interface ColorRampValue {
mode: "RGB" | "HSV" | "HSL";
stops: ColorRampStop[];
}

export const ColorRampNode = defineNode({
type: "Color Ramp",
inputs: {
colorRamp: () =>
new NodeInterface<ColorRampValue>("Color Ramp", { mode: "RGB", stops: [] })
.setPort(false)
.setComponent(markRaw(ColorRampOption)),
factor: () => new SliderInterface("Factor", 0.5, 0, 1),
},
outputs: {
colorRamp: () => new ColorArrayInterface("Color Ramp"),
value: () => new ColorSingleInterface("Value").setComponent(null),
},
});
26 changes: 6 additions & 20 deletions src/graph/options/ColorOption.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,21 @@
<div class="color-option">
<div class="__name">{{ intf.name }}</div>
<div class="__color">
<color-picker :model-value="color" @update:model-value="setColor"></color-picker>
<color-picker v-model="modelValue"></color-picker>
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { NodeInterface } from "@baklavajs/core";
import ColorPicker from "./ColorPicker.vue";
import { fromChroma, Color, chroma, toChroma } from "../colors";
import { Color } from "../colors";
const props = defineProps({
intf: { type: Object as () => NodeInterface, required: true },
modelValue: { type: Array as unknown as () => Color, required: true },
});
const modelValue = defineModel<Color>({ required: true });
const emit = defineEmits(["update:modelValue"]);
const color = computed(() => {
if (!props.modelValue) {
return "#000000";
} else {
return toChroma(props.modelValue).css();
}
});
function setColor(color: string) {
emit("update:modelValue", fromChroma(chroma(color)));
}
defineProps<{
intf: NodeInterface;
}>();
</script>

<style lang="scss" scoped>
Expand Down
26 changes: 15 additions & 11 deletions src/graph/options/ColorPicker.vue
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
<template>
<div ref="el" class="color-picker" @click.self="open = true" :style="{ backgroundColor: modelValue }">
<div ref="el" class="color-picker" @click.self="open = true" :style="{ backgroundColor: hexColor }">
<transition name="slide-fade">
<cp-chrome
class="color-picker-overlay"
v-show="open"
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event.hex)"
:model-value="hexColor"
class="color-picker-overlay"
:disable-alpha="true"
@update:model-value="setColor"
></cp-chrome>
</transition>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Chrome as CpChrome } from "@ckpack/vue-color";
import { computed, ref } from "vue";
import { Chrome as CpChrome, Payload } from "@ckpack/vue-color";
import { onClickOutside } from "@vueuse/core";
import { Color, toChroma } from "../colors";
defineProps({
modelValue: { type: String, default: "#000000" },
});
const emit = defineEmits(["update:modelValue"]);
const modelValue = defineModel<Color>({ default: [0, 0, 0] });
const el = ref<HTMLElement | null>(null);
const open = ref(false);
const hexColor = computed(() => (modelValue.value ? toChroma(modelValue.value).css() : "#000000"));
function setColor(color: Payload) {
modelValue.value = [color.rgba.r as number, color.rgba.g as number, color.rgba.b as number];
}
onClickOutside(el, () => {
open.value = false;
});
Expand Down
132 changes: 132 additions & 0 deletions src/graph/options/ColorRampOption.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<div>
<div class="flex gap-2">
<Button icon="mdi mdi-plus" text rounded @click="addStop" />
<Button icon="mdi mdi-minus" text rounded @click="removeStop" />
<Button icon="mdi mdi-dots-vertical" text rounded />
<Dropdown v-model="modelValue.mode" :options="modes" placeholder="Mode" />
</div>
<div class="color-ramp" ref="colorRampEl">
<template v-for="stop in modelValue.stops" :key="stop.id">
<div class="color-stop-indicator" :style="{ left: `${Math.round(100 * stop.position)}%` }"></div>
<div
class="color-stop-handle"
:class="{ '--selected': selectedStopId === stop.id }"
:style="{ left: `${Math.round(100 * stop.position)}%` }"
@mousedown="onHandleMousedown(stop)"
></div>
</template>
</div>
<div class="flex gap-2 h-4">
<ColorPicker class="w-full" v-if="selectedStop" v-model="selectedStop.color" />
</div>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import { v4 as uuidv4 } from "uuid";
import Button from "primevue/button";
import Dropdown from "primevue/dropdown";
import ColorPicker from "./ColorPicker.vue";
import type { ColorRampStop, ColorRampValue } from "../nodes/colors/ColorRampNode";
const modes = ["RGB", "HSV", "HSL"];
const modelValue = defineModel<ColorRampValue>({ required: true });
const colorRampEl = ref<HTMLElement>();
const selectedStopId = ref("");
const selectedStop = computed(() => modelValue.value.stops.find((s) => s.id === selectedStopId.value));
const cssLinearGradient = computed(() => {
if (modelValue.value.stops.length === 0) {
return "black";
} else if (modelValue.value.stops.length === 1) {
return `rgb(${modelValue.value.stops[0].color[0]}, ${modelValue.value.stops[0].color[1]}, ${modelValue.value.stops[0].color[2]})`;
}
const args: string[] = ["to right"];
for (const stop of modelValue.value.stops.toSorted((a, b) => a.position - b.position)) {
args.push(`rgb(${stop.color[0]}, ${stop.color[1]}, ${stop.color[2]}) ${Math.round(100 * stop.position)}%`);
}
return `linear-gradient(${args.join(", ")})`;
});
function addStop() {
modelValue.value.stops.push({
id: uuidv4(),
color: [255 * Math.random(), 255 * Math.random(), 255 * Math.random()],
position: Math.random(),
});
}
function removeStop() {
modelValue.value.stops = modelValue.value.stops.filter((s) => s.id !== selectedStopId.value);
}
function onHandleMousedown(stop: ColorRampStop) {
selectedStopId.value = stop.id;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
}
function onMouseUp() {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
}
function onMouseMove(event: MouseEvent) {
if (!colorRampEl.value) {
return;
}
const rect = colorRampEl.value.getBoundingClientRect();
const stop = modelValue.value.stops.find((s) => s.id === selectedStopId.value);
if (stop) {
stop.position = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
}
}
</script>

<style scoped>
.color-ramp {
position: relative;
width: 100%;
height: 1.75rem;
background: v-bind(cssLinearGradient);
border-radius: var(--baklava-control-border-radius);
margin-top: 1rem;
margin-bottom: 1rem;
}
.color-stop-handle {
position: absolute;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
top: 50%;
background: white;
border: 1px solid gray;
transform: translate(-50%, -50%);
cursor: pointer;
box-shadow: 0 0 0.25rem 0 rgba(0, 0, 0, 0.25);
}
.color-stop-handle:hover {
box-shadow: 0 0 0.5rem 0 rgba(0, 0, 0, 0.5);
}
.color-stop-handle.--selected {
background: var(--baklava-control-color-primary);
}
.color-stop-indicator {
position: absolute;
height: 1.75rem;
top: 0;
transform: translateX(-0.5px);
border-left: 1px dashed #0008;
}
</style>

0 comments on commit f550ec6

Please sign in to comment.