mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-08 16:58:12 +08:00
Bezier-rs: Convert from canvas to svg for constructors (#776)
* Convert constructor to use svg * Convert the through_points functions to svg Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
8dce2144c4
commit
e9cd792635
@@ -1,7 +1,5 @@
|
||||
import { WasmBezier } from "@/../wasm/pkg";
|
||||
|
||||
import { COLORS, drawBezier, drawPoint, getContextFromCanvas, getPointSizeByIndex } from "@/utils/drawing";
|
||||
import { BezierCallback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
|
||||
import { Callback, BezierPoint, BezierStyleConfig, Point, WasmBezierManipulatorKey, WasmBezierInstance } from "@/utils/types";
|
||||
|
||||
// Offset to increase selectable range, used to make points easier to grab
|
||||
const FUDGE_FACTOR = 3;
|
||||
@@ -24,13 +22,13 @@ class BezierDrawing {
|
||||
|
||||
bezier: WasmBezierInstance;
|
||||
|
||||
callback: BezierCallback;
|
||||
callback: Callback;
|
||||
|
||||
options: Record<string, number>;
|
||||
|
||||
createThroughPoints: boolean;
|
||||
|
||||
constructor(bezier: WasmBezierInstance, callback: BezierCallback, options: Record<string, number>, createThroughPoints = false) {
|
||||
constructor(bezier: WasmBezierInstance, callback: Callback, options: Record<string, number>, createThroughPoints = false) {
|
||||
this.bezier = bezier;
|
||||
this.callback = callback;
|
||||
this.options = options;
|
||||
@@ -120,22 +118,13 @@ class BezierDrawing {
|
||||
|
||||
// For the create through points cases, we store a bezier where the handle is actually the point that the curve should pass through
|
||||
// This is so that we can re-use the drag and drop logic, while simply drawing the desired bezier instead
|
||||
const actualBezierPointLength = JSON.parse(this.bezier.get_points()).length;
|
||||
let pointsToDraw = this.points;
|
||||
const pointsToDraw = this.points;
|
||||
|
||||
let styleConfig: Partial<BezierStyleConfig> = {
|
||||
handleLineStrokeColor: COLORS.INTERACTIVE.STROKE_2,
|
||||
};
|
||||
let dragIndex = this.dragIndex;
|
||||
if (this.createThroughPoints) {
|
||||
let bezierThroughPoints;
|
||||
const pointList = this.points.map((p) => [p.x, p.y]);
|
||||
if (actualBezierPointLength === 3) {
|
||||
bezierThroughPoints = WasmBezier.quadratic_through_points(pointList, this.options.t);
|
||||
} else {
|
||||
bezierThroughPoints = WasmBezier.cubic_through_points(pointList, this.options.t, this.options["midpoint separation"]);
|
||||
}
|
||||
pointsToDraw = JSON.parse(bezierThroughPoints.get_points());
|
||||
if (this.dragIndex === 1) {
|
||||
// Do not propagate dragIndex when the the non-endpoint is moved
|
||||
dragIndex = null;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div>
|
||||
<h4 class="example-header">{{ title }}</h4>
|
||||
<figure @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove" class="example-figure" v-html="bezierSVG"></figure>
|
||||
<div v-for="(slider, index) in sliderOptions" :key="index">
|
||||
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
|
||||
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { WasmBezier } from "@/../wasm/pkg";
|
||||
import { getConstructorKey, getCurveType } from "@/utils/helpers";
|
||||
import { BezierCallback, BezierCurveType, WasmBezierManipulatorKey, SliderOption } from "@/utils/types";
|
||||
|
||||
const SELECTABLE_RANGE = 10;
|
||||
|
||||
// Given the number of points in the curve, map the index of a point to the correct manipulator key
|
||||
const MANIPULATOR_KEYS_FROM_BEZIER_TYPE: { [key in BezierCurveType]: WasmBezierManipulatorKey[] } = {
|
||||
[BezierCurveType.Linear]: ["set_start", "set_end"],
|
||||
[BezierCurveType.Quadratic]: ["set_start", "set_handle_start", "set_end"],
|
||||
[BezierCurveType.Cubic]: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
title: String,
|
||||
points: {
|
||||
type: Array as PropType<Array<Array<number>>>,
|
||||
required: true,
|
||||
mutable: true,
|
||||
},
|
||||
callback: {
|
||||
type: Function as PropType<BezierCallback>,
|
||||
required: true,
|
||||
},
|
||||
sliderOptions: {
|
||||
type: Object as PropType<Array<SliderOption>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const curveType = getCurveType(this.points.length);
|
||||
const manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
|
||||
|
||||
const bezier = WasmBezier[getConstructorKey(curveType)](this.points);
|
||||
const sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
|
||||
|
||||
return {
|
||||
bezier,
|
||||
bezierSVG: this.callback(bezier, sliderData),
|
||||
manipulatorKeys,
|
||||
activeIndex: undefined as number | undefined,
|
||||
mutablePoints: JSON.parse(JSON.stringify(this.points)),
|
||||
sliderData,
|
||||
sliderUnits: Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onMouseDown(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
|
||||
const point = this.mutablePoints[pointIndex];
|
||||
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
|
||||
this.activeIndex = pointIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
onMouseUp() {
|
||||
this.activeIndex = undefined;
|
||||
},
|
||||
onMouseMove(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
if (this.activeIndex !== undefined) {
|
||||
this.bezier[this.manipulatorKeys[this.activeIndex]](mx, my);
|
||||
this.mutablePoints[this.activeIndex] = [mx, my];
|
||||
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
||||
}
|
||||
},
|
||||
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
|
||||
},
|
||||
watch: {
|
||||
sliderData: {
|
||||
handler() {
|
||||
this.bezierSVG = this.callback(this.bezier, this.sliderData);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.example-figure {
|
||||
border: solid 1px black;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="example-pane-header">{{ name }}</h3>
|
||||
<div class="example-row">
|
||||
<div v-for="(example, index) in examples" :key="index">
|
||||
<BezierExample v-if="!example.disabled" :title="example.title" :points="example.points" :callback="callback" :sliderOptions="example.sliderOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { BezierCallback, BezierCurveType, ExampleOptions } from "@/utils/types";
|
||||
|
||||
import BezierExample from "@/components/BezierExample.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: String,
|
||||
callback: {
|
||||
type: Function as PropType<BezierCallback>,
|
||||
required: true,
|
||||
},
|
||||
exampleOptions: {
|
||||
type: Object as PropType<ExampleOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const exampleDefaults = {
|
||||
[BezierCurveType.Linear]: {
|
||||
points: [
|
||||
[30, 60],
|
||||
[140, 120],
|
||||
],
|
||||
},
|
||||
[BezierCurveType.Quadratic]: {
|
||||
points: [
|
||||
[30, 50],
|
||||
[140, 30],
|
||||
[160, 170],
|
||||
],
|
||||
},
|
||||
[BezierCurveType.Cubic]: {
|
||||
points: [
|
||||
[30, 30],
|
||||
[60, 140],
|
||||
[150, 30],
|
||||
[160, 160],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
examples: Object.values(BezierCurveType).map((curveType) => {
|
||||
const givenData = this.exampleOptions[curveType];
|
||||
const defaultData = exampleDefaults[curveType];
|
||||
return {
|
||||
title: curveType,
|
||||
disabled: givenData?.disabled || false,
|
||||
points: givenData?.customPoints || defaultData.points,
|
||||
sliderOptions: givenData?.sliderOptions || [],
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
components: {
|
||||
BezierExample,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.example-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.example-pane-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,7 @@
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import BezierDrawing from "@/components/BezierDrawing";
|
||||
import { BezierCallback, WasmBezierInstance } from "@/utils/types";
|
||||
import { Callback, WasmBezierInstance } from "@/utils/types";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -19,7 +19,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
callback: {
|
||||
type: Function as PropType<BezierCallback>,
|
||||
type: Function as PropType<Callback>,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user