From 834cb1a227da38e1fa7f2f95eff55cd104200e89 Mon Sep 17 00:00:00 2001 From: Rob Nadal Date: Tue, 21 Mar 2023 01:23:02 -0400 Subject: [PATCH] Bezier-rs: Update interactive demo site UI/UX (#1085) * wip - change demo iframe styling * fix typo * change tVariant select from radio buttons to dropdown * Added iframe styling * fix linting errors * Integrated tvariant picker as input options * Updated points in demos * Lint * Clean up CSS --------- Co-authored-by: Thomas Cheng Co-authored-by: Keavon Chambers --- libraries/bezier-rs/src/bezier/solvers.rs | 6 +- libraries/bezier-rs/src/subpath/solvers.rs | 8 +- .../src/components/BezierDemo.ts | 29 +--- .../src/components/BezierDemoPane.ts | 33 ++-- .../src/components/SubpathDemo.ts | 29 +--- .../src/components/SubpathDemoPane.ts | 33 ++-- .../src/features/bezier-features.ts | 154 +++++++++--------- .../src/features/subpath-features.ts | 78 ++++----- website/other/bezier-rs-demos/src/main.ts | 18 +- website/other/bezier-rs-demos/src/style.css | 134 ++++++++++++++- .../bezier-rs-demos/src/utils/options.ts | 18 +- .../other/bezier-rs-demos/src/utils/render.ts | 122 +++++++------- .../other/bezier-rs-demos/src/utils/types.ts | 29 ++-- .../other/bezier-rs-demos/wasm/src/subpath.rs | 4 +- .../bezier-rs-demos/wasm/src/svg_drawing.rs | 4 +- 15 files changed, 402 insertions(+), 297 deletions(-) diff --git a/libraries/bezier-rs/src/bezier/solvers.rs b/libraries/bezier-rs/src/bezier/solvers.rs index 16c06a64..e222176d 100644 --- a/libraries/bezier-rs/src/bezier/solvers.rs +++ b/libraries/bezier-rs/src/bezier/solvers.rs @@ -250,16 +250,16 @@ impl Bezier { /// The returned `t` values are with respect to the current bezier, not the provided parameter. /// If the provided curve is linear, then zero intersection points will be returned along colinear segments. /// - `error` - For intersections where the provided bezier is non-linear, `error` defines the threshold for bounding boxes to be considered an intersection point. - /// - `minimum_seperation` - The minimum difference between adjacent `t` values in sorted order + /// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order /// - pub fn intersections(&self, other: &Bezier, error: Option, minimum_seperation: Option) -> Vec { + pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec { // TODO: Consider using the `intersections_between_vectors_of_curves` helper function here // Otherwise, use bounding box to determine intersections let mut intersection_t_values = self.unfiltered_intersections(other, error); intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap()); intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| { - if !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_seperation.unwrap_or(MIN_SEPARATION_VALUE) { + if !accumulator.is_empty() && (accumulator.last().unwrap() - t).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE) { accumulator.pop(); } accumulator.push(*t); diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index f036ddf7..42a2ee36 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -20,7 +20,7 @@ impl Subpath { /// Expects the following: /// - `other`: a [Bezier] curve to check intersections against /// - `error`: an optional f64 value to provide an error bound - /// - `minimum_seperation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order. + /// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order. /// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two. /// pub fn intersections(&self, other: &Bezier, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> { @@ -50,12 +50,12 @@ impl Subpath { /// Returns a list of `t` values that correspond to the self intersection points of the subpath. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point. /// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point. - /// - `minimum_seperation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order. + /// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order. /// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two /// /// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out. /// - pub fn self_intersections(&self, error: Option, minimum_seperation: Option) -> Vec<(usize, f64)> { + pub fn self_intersections(&self, error: Option, minimum_separation: Option) -> Vec<(usize, f64)> { let mut intersections_vec = Vec::new(); let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE); // TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection @@ -64,7 +64,7 @@ impl Subpath { self.iter().enumerate().skip(i + 1).for_each(|(j, curve)| { intersections_vec.extend( curve - .intersections(&other, error, minimum_seperation) + .intersections(&other, error, minimum_separation) .iter() .filter(|&value| value > &err && (1. - value) > err) .map(|value| (j, *value)), diff --git a/website/other/bezier-rs-demos/src/components/BezierDemo.ts b/website/other/bezier-rs-demos/src/components/BezierDemo.ts index 6a600377..f3ae8f71 100644 --- a/website/other/bezier-rs-demos/src/components/BezierDemo.ts +++ b/website/other/bezier-rs-demos/src/components/BezierDemo.ts @@ -1,7 +1,7 @@ import { WasmBezier } from "@/../wasm/pkg"; import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features"; import { renderDemo } from "@/utils/render"; -import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, TVariant, Demo } from "@/utils/types"; +import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, InputOption, WasmBezierManipulatorKey, Demo } from "@/utils/types"; const SELECTABLE_RANGE = 10; @@ -20,12 +20,10 @@ class BezierDemo extends HTMLElement implements Demo { key!: BezierFeatureKey; - sliderOptions!: SliderOption[]; + inputOptions!: InputOption[]; triggerOnMouseMove!: boolean; - tVariant!: TVariant; - // Data bezier!: WasmBezier; @@ -39,33 +37,20 @@ class BezierDemo extends HTMLElement implements Demo { sliderUnits!: Record; - static get observedAttributes(): string[] { - return ["tvariant"]; - } - - attributeChangedCallback(name: string, oldValue: string, newValue: string): void { - if (name === "tvariant" && oldValue) { - this.tVariant = (newValue || "Parametric") as TVariant; - const figure = this.querySelector("figure") as HTMLElement; - this.drawDemo(figure); - } - } - async connectedCallback(): Promise { this.title = this.getAttribute("title") || ""; this.points = JSON.parse(this.getAttribute("points") || "[]"); this.key = this.getAttribute("key") as BezierFeatureKey; - this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]"); + this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]"); this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true"; - this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant; this.callback = bezierFeatures[this.key].callback as BezierCallback; const curveType = getCurveType(this.points.length); this.manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType]; this.activeIndex = undefined as number | undefined; - this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default }))); - this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))); + this.sliderData = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.default }))); + this.sliderUnits = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.unit }))); this.render(); const figure = this.querySelector("figure") as HTMLElement; @@ -79,7 +64,7 @@ class BezierDemo extends HTMLElement implements Demo { } drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void { - figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation, this.tVariant); + figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation); } onMouseDown(event: MouseEvent): void { @@ -114,7 +99,7 @@ class BezierDemo extends HTMLElement implements Demo { getSliderUnit(sliderValue: number, variable: string): string { const sliderUnit = this.sliderUnits[variable]; - return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || ""; + return (Array.isArray(sliderUnit) ? "" : sliderUnit) || ""; } } diff --git a/website/other/bezier-rs-demos/src/components/BezierDemoPane.ts b/website/other/bezier-rs-demos/src/components/BezierDemoPane.ts index 42aad5fb..139ec7ed 100644 --- a/website/other/bezier-rs-demos/src/components/BezierDemoPane.ts +++ b/website/other/bezier-rs-demos/src/components/BezierDemoPane.ts @@ -1,27 +1,27 @@ import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features"; import { renderDemoPane } from "@/utils/render"; -import { BezierCurveType, BEZIER_CURVE_TYPE, TVariant, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types"; +import { BezierCurveType, BEZIER_CURVE_TYPE, BezierDemoOptions, InputOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types"; const demoDefaults = { Linear: { points: [ - [30, 60], - [140, 120], + [55, 60], + [165, 120], ], }, Quadratic: { points: [ - [30, 50], - [140, 30], - [160, 170], + [55, 50], + [165, 30], + [185, 170], ], }, Cubic: { points: [ - [30, 30], - [60, 140], - [150, 30], - [160, 160], + [55, 30], + [85, 140], + [175, 30], + [185, 160], ], }, }; @@ -36,25 +36,19 @@ class BezierDemoPane extends HTMLElement implements DemoPane { triggerOnMouseMove!: boolean; - chooseTVariant!: boolean; - // Data demos!: BezierDemoArgs[]; id!: string; - tVariant!: TVariant; - connectedCallback(): void { - this.tVariant = "Parametric"; this.key = (this.getAttribute("name") || "") as BezierFeatureKey; this.id = `bezier/${this.key}`; this.name = bezierFeatures[this.key].name; this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]"); this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true"; - this.chooseTVariant = this.getAttribute("chooseTVariant") === "true"; // Use quadratic slider options as a default if sliders are not provided for the other curve types. - const defaultSliderOptions: SliderOption[] = this.demoOptions.Quadratic?.sliderOptions || []; + const defaultSliderOptions: InputOption[] = this.demoOptions.Quadratic?.inputOptions || []; this.demos = BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => { const givenData = this.demoOptions[curveType]; const defaultData = demoDefaults[curveType]; @@ -62,7 +56,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane { title: curveType, disabled: givenData?.disabled || false, points: givenData?.customPoints || defaultData.points, - sliderOptions: givenData?.sliderOptions || defaultSliderOptions, + inputOptions: givenData?.inputOptions || defaultSliderOptions, }; }); this.render(); @@ -77,9 +71,8 @@ class BezierDemoPane extends HTMLElement implements DemoPane { bezierDemo.setAttribute("title", demo.title); bezierDemo.setAttribute("points", JSON.stringify(demo.points)); bezierDemo.setAttribute("key", this.key); - bezierDemo.setAttribute("sliderOptions", JSON.stringify(demo.sliderOptions)); + bezierDemo.setAttribute("inputOptions", JSON.stringify(demo.inputOptions)); bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove)); - bezierDemo.setAttribute("tvariant", this.tVariant); return bezierDemo; } } diff --git a/website/other/bezier-rs-demos/src/components/SubpathDemo.ts b/website/other/bezier-rs-demos/src/components/SubpathDemo.ts index 1e6f16b6..f037cd26 100644 --- a/website/other/bezier-rs-demos/src/components/SubpathDemo.ts +++ b/website/other/bezier-rs-demos/src/components/SubpathDemo.ts @@ -2,7 +2,7 @@ import { WasmSubpath } from "@/../wasm/pkg"; import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features"; import { renderDemo } from "@/utils/render"; -import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, TVariant } from "@/utils/types"; +import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, InputOption } from "@/utils/types"; const SELECTABLE_RANGE = 10; const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"]; @@ -17,12 +17,10 @@ class SubpathDemo extends HTMLElement { closed!: boolean; - sliderOptions!: SliderOption[]; + inputOptions!: InputOption[]; triggerOnMouseMove!: boolean; - tVariant!: TVariant; - // Data subpath!: WasmSubpath; @@ -36,30 +34,17 @@ class SubpathDemo extends HTMLElement { sliderUnits!: Record; - static get observedAttributes(): string[] { - return ["tvariant"]; - } - - attributeChangedCallback(name: string, oldValue: string, newValue: string): void { - if (name === "tvariant" && oldValue) { - this.tVariant = (newValue || "Parametric") as TVariant; - const figure = this.querySelector("figure") as HTMLElement; - this.drawDemo(figure); - } - } - async connectedCallback(): Promise { this.title = this.getAttribute("title") || ""; this.triples = JSON.parse(this.getAttribute("triples") || "[]"); this.key = this.getAttribute("key") as SubpathFeatureKey; - this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]"); + this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]"); this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true"; this.closed = this.getAttribute("closed") === "true"; - this.tVariant = (this.getAttribute("tvariant") || "Parametric") as TVariant; this.callback = subpathFeatures[this.key].callback as SubpathCallback; - this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default }))); - this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))); + this.sliderData = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.default }))); + this.sliderUnits = Object.assign({}, ...this.inputOptions.map((s) => ({ [s.variable]: s.unit }))); this.render(); const figure = this.querySelector("figure") as HTMLElement; @@ -73,7 +58,7 @@ class SubpathDemo extends HTMLElement { } drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void { - figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation, this.tVariant); + figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation); } onMouseDown(event: MouseEvent): void { @@ -109,7 +94,7 @@ class SubpathDemo extends HTMLElement { getSliderUnit(sliderValue: number, variable: string): string { const sliderUnit = this.sliderUnits[variable]; - return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || ""; + return (Array.isArray(sliderUnit) ? "" : sliderUnit) || ""; } } diff --git a/website/other/bezier-rs-demos/src/components/SubpathDemoPane.ts b/website/other/bezier-rs-demos/src/components/SubpathDemoPane.ts index 2cba77f2..a60e8923 100644 --- a/website/other/bezier-rs-demos/src/components/SubpathDemoPane.ts +++ b/website/other/bezier-rs-demos/src/components/SubpathDemoPane.ts @@ -1,6 +1,6 @@ import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features"; import { renderDemoPane } from "@/utils/render"; -import { TVariant, Demo, DemoPane, SliderOption, SubpathDemoArgs } from "@/utils/types"; +import { Demo, DemoPane, InputOption, SubpathDemoArgs } from "@/utils/types"; class SubpathDemoPane extends HTMLElement implements DemoPane { // Props @@ -8,52 +8,46 @@ class SubpathDemoPane extends HTMLElement implements DemoPane { name!: string; - sliderOptions!: SliderOption[]; + inputOptions!: InputOption[]; triggerOnMouseMove!: boolean; - chooseTVariant!: boolean; - // Data demos!: SubpathDemoArgs[]; id!: string; - tVariant!: TVariant; - connectedCallback(): void { this.demos = [ { title: "Open Subpath", triples: [ - [[20, 20], undefined, [10, 90]], - [[150, 40], [60, 40], undefined], - [[175, 175], undefined, undefined], - [[100, 100], [40, 120], undefined], + [[45, 20], undefined, [35, 90]], + [[175, 40], [85, 40], undefined], + [[200, 175], undefined, undefined], + [[125, 100], [65, 120], undefined], ], closed: false, }, { title: "Closed Subpath", triples: [ - [[35, 125], undefined, [40, 40]], - [[130, 30], [120, 120], undefined], + [[60, 125], undefined, [65, 40]], + [[155, 30], [145, 120], undefined], [ - [145, 150], - [175, 90], - [70, 185], + [170, 150], + [200, 90], + [95, 185], ], ], closed: true, }, ]; - this.tVariant = "Parametric"; this.key = (this.getAttribute("name") || "") as SubpathFeatureKey; this.id = `subpath/${this.key}`; this.name = subpathFeatures[this.key].name; - this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]"); + this.inputOptions = JSON.parse(this.getAttribute("inputOptions") || "[]"); this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true"; - this.chooseTVariant = this.getAttribute("chooseTVariant") === "true"; this.render(); } @@ -68,9 +62,8 @@ class SubpathDemoPane extends HTMLElement implements DemoPane { subpathDemo.setAttribute("triples", JSON.stringify(demo.triples)); subpathDemo.setAttribute("closed", String(demo.closed)); subpathDemo.setAttribute("key", this.key); - subpathDemo.setAttribute("sliderOptions", JSON.stringify(this.sliderOptions)); + subpathDemo.setAttribute("inputOptions", JSON.stringify(this.inputOptions)); subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove)); - subpathDemo.setAttribute("tvariant", this.tVariant); return subpathDemo; } } diff --git a/website/other/bezier-rs-demos/src/features/bezier-features.ts b/website/other/bezier-rs-demos/src/features/bezier-features.ts index d5066443..be6d2c68 100644 --- a/website/other/bezier-rs-demos/src/features/bezier-features.ts +++ b/website/other/bezier-rs-demos/src/features/bezier-features.ts @@ -1,6 +1,6 @@ import { WasmBezier } from "@/../wasm/pkg"; -import { tSliderOptions, errorOptions, minimumSeparationOptions } from "@/utils/options"; -import { TVariant, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types"; +import { tSliderOptions, bezierTValueVariantOptions, errorOptions, minimumSeparationOptions } from "@/utils/options"; +import { BezierDemoOptions, WasmBezierInstance, BezierCallback, InputOption, BEZIER_T_VALUE_VARIANTS } from "@/utils/types"; const bezierFeatures = { constructor: { @@ -26,7 +26,7 @@ const bezierFeatures = { [120, 70], [160, 170], ], - sliderOptions: [ + inputOptions: [ { min: 0.01, max: 0.99, @@ -42,7 +42,7 @@ const bezierFeatures = { [120, 70], [160, 170], ], - sliderOptions: [ + inputOptions: [ { min: 0.01, max: 0.99, @@ -67,20 +67,19 @@ const bezierFeatures = { }, evaluate: { name: "Evaluate", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.evaluate(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.evaluate(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, - chooseTVariant: true, }, "lookup-table": { name: "Lookup Table", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.compute_lookup_table(options.steps), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { min: 2, max: 15, @@ -118,53 +117,53 @@ const bezierFeatures = { }, tangent: { name: "Tangent", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.tangent(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.tangent(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, - chooseTVariant: true, }, normal: { name: "Normal", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.normal(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.normal(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, - chooseTVariant: true, }, curvature: { name: "Curvature", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.curvature(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.curvature(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Linear: { disabled: true, }, Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], + }, + Cubic: { + inputOptions: [bezierTValueVariantOptions, { ...tSliderOptions, default: 0.7 }], }, }, - chooseTVariant: true, }, split: { name: "Split", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.split(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.split(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, - chooseTVariant: true, }, trim: { name: "Trim", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.trim(options.t1, options.t2, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.trim(options.t1, options.t2, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ + bezierTValueVariantOptions, { variable: "t1", min: 0, @@ -182,7 +181,6 @@ const bezierFeatures = { ], }, }, - chooseTVariant: true, }, project: { name: "Project", @@ -194,6 +192,9 @@ const bezierFeatures = { name: "Local Extrema", callback: (bezier: WasmBezierInstance, _: Record): string => bezier.local_extrema(), demoOptions: { + Linear: { + disabled: true, + }, Quadratic: { customPoints: [ [40, 40], @@ -236,7 +237,7 @@ const bezierFeatures = { callback: (bezier: WasmBezierInstance, options: Record): string => bezier.offset(options.distance), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { variable: "distance", min: -30, @@ -253,7 +254,7 @@ const bezierFeatures = { callback: (bezier: WasmBezierInstance, options: Record): string => bezier.outline(options.distance), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { variable: "distance", min: 0, @@ -270,7 +271,7 @@ const bezierFeatures = { callback: (bezier: WasmBezierInstance, options: Record): string => bezier.graduated_outline(options.start_distance, options.end_distance), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { variable: "start_distance", min: 0, @@ -302,7 +303,7 @@ const bezierFeatures = { callback: (bezier: WasmBezierInstance, options: Record): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { variable: "distance1", min: 0, @@ -338,15 +339,13 @@ const bezierFeatures = { arcs: { name: "Arcs", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.arcs(options.error, options.max_iterations, options.strategy), - demoOptions: ((): Omit => { - const sliderOptions = [ + demoOptions: ((): BezierDemoOptions => { + const inputOptions: InputOption[] = [ { variable: "strategy", - min: 0, - max: 2, - step: 1, default: 0, - unit: [": Automatic", ": FavorLargerArcs", ": FavorCorrectness"], + inputType: "dropdown", + options: ["Automatic", "FavorLargerArcs", "FavorCorrectness"], }, { variable: "error", @@ -365,13 +364,16 @@ const bezierFeatures = { ]; return { + Linear: { + disabled: true, + }, Quadratic: { customPoints: [ - [50, 50], - [85, 65], - [100, 100], + [70, 40], + [180, 50], + [160, 150], ], - sliderOptions, + inputOptions, disabled: false, }, Cubic: { @@ -381,7 +383,7 @@ const bezierFeatures = { [30, 90], [180, 160], ], - sliderOptions, + inputOptions, disabled: false, }, }; @@ -391,8 +393,8 @@ const bezierFeatures = { name: "Intersect (Line Segment)", callback: (bezier: WasmBezierInstance): string => { const line = [ - [150, 150], - [20, 20], + [45, 30], + [195, 160], ]; return bezier.intersect_line_segment(line); }, @@ -401,15 +403,15 @@ const bezierFeatures = { name: "Intersect (Quadratic)", callback: (bezier: WasmBezierInstance, options: Record): string => { const quadratic = [ - [20, 80], - [180, 10], - [90, 120], + [45, 80], + [205, 10], + [115, 120], ]; return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_separation); }, demoOptions: { Quadratic: { - sliderOptions: [errorOptions, minimumSeparationOptions], + inputOptions: [errorOptions, minimumSeparationOptions], }, }, }, @@ -417,16 +419,16 @@ const bezierFeatures = { name: "Intersect (Cubic)", callback: (bezier: WasmBezierInstance, options: Record): string => { const cubic = [ - [40, 20], - [100, 40], - [40, 120], - [175, 140], + [65, 20], + [125, 40], + [65, 120], + [200, 140], ]; return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_separation); }, demoOptions: { Quadratic: { - sliderOptions: [errorOptions, minimumSeparationOptions], + inputOptions: [errorOptions, minimumSeparationOptions], }, }, }, @@ -434,10 +436,14 @@ const bezierFeatures = { name: "Intersect (Self)", callback: (bezier: WasmBezierInstance, options: Record): string => bezier.intersect_self(options.error), demoOptions: { + Linear: { + disabled: true, + }, Quadratic: { - sliderOptions: [errorOptions], + disabled: true, }, Cubic: { + inputOptions: [errorOptions], customPoints: [ [160, 180], [170, 10], @@ -451,8 +457,8 @@ const bezierFeatures = { name: "Intersect (Rectangle)", callback: (bezier: WasmBezierInstance): string => bezier.intersect_rectangle([ - [50, 50], - [150, 150], + [75, 50], + [175, 150], ]), }, rotate: { @@ -460,7 +466,7 @@ const bezierFeatures = { callback: (bezier: WasmBezierInstance, options: Record): string => bezier.rotate(options.angle * Math.PI, 100, 100), demoOptions: { Quadratic: { - sliderOptions: [ + inputOptions: [ { variable: "angle", min: 0, @@ -475,13 +481,12 @@ const bezierFeatures = { }, "de-casteljau-points": { name: "De Casteljau Points", - callback: (bezier: WasmBezierInstance, options: Record, _: undefined, tVariant: TVariant): string => bezier.de_casteljau_points(options.t, tVariant), + callback: (bezier: WasmBezierInstance, options: Record, _: undefined): string => bezier.de_casteljau_points(options.t, BEZIER_T_VALUE_VARIANTS[options.TVariant]), demoOptions: { Quadratic: { - sliderOptions: [tSliderOptions], + inputOptions: [bezierTValueVariantOptions, tSliderOptions], }, }, - chooseTVariant: true, }, join: { name: "Join", @@ -490,21 +495,21 @@ const bezierFeatures = { let examplePoints = []; if (points.length === 2) { examplePoints = [ - [120, 155], - [40, 155], + [145, 155], + [65, 155], ]; } else if (points.length === 3) { examplePoints = [ - [40, 150], - [95, 195], - [155, 145], + [65, 150], + [120, 195], + [190, 145], ]; } else { examplePoints = [ - [140, 150], - [85, 110], - [65, 180], - [30, 140], + [165, 150], + [110, 110], + [90, 180], + [55, 140], ]; } return bezier.join(examplePoints); @@ -512,23 +517,23 @@ const bezierFeatures = { demoOptions: { Linear: { customPoints: [ - [45, 40], - [130, 90], + [70, 40], + [155, 90], ], }, Quadratic: { customPoints: [ - [153, 40], - [40, 20], - [75, 85], + [185, 40], + [65, 20], + [100, 85], ], }, Cubic: { customPoints: [ - [20, 80], - [40, 20], - [90, 100], - [130, 55], + [45, 80], + [65, 20], + [115, 100], + [155, 55], ], }, }, @@ -541,6 +546,5 @@ export type BezierFeatureOptions = { callback: BezierCallback; demoOptions?: Partial; triggerOnMouseMove?: boolean; - chooseTVariant?: boolean; }; export default bezierFeatures as Record; diff --git a/website/other/bezier-rs-demos/src/features/subpath-features.ts b/website/other/bezier-rs-demos/src/features/subpath-features.ts index f383abdd..39e0a68a 100644 --- a/website/other/bezier-rs-demos/src/features/subpath-features.ts +++ b/website/other/bezier-rs-demos/src/features/subpath-features.ts @@ -1,5 +1,5 @@ -import { tSliderOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options"; -import { TVariant, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types"; +import { tSliderOptions, subpathTValueVariantOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options"; +import { InputOption, SubpathCallback, WasmSubpathInstance, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types"; const subpathFeatures = { constructor: { @@ -8,9 +8,8 @@ const subpathFeatures = { }, insert: { name: "Insert", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.insert(options.t, tVariant), - sliderOptions: [tSliderOptions], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.insert(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, tSliderOptions], }, length: { name: "Length", @@ -18,9 +17,8 @@ const subpathFeatures = { }, evaluate: { name: "Evaluate", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.evaluate(options.t, tVariant), - sliderOptions: [tSliderOptions], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.evaluate(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, tSliderOptions], }, project: { name: "Project", @@ -30,15 +28,13 @@ const subpathFeatures = { }, tangent: { name: "Tangent", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.tangent(options.t, tVariant), - sliderOptions: [tSliderOptions], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.tangent(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, tSliderOptions], }, normal: { name: "Normal", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.normal(options.t, tVariant), - sliderOptions: [tSliderOptions], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.normal(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, tSliderOptions], }, "local-extrema": { name: "Local Extrema", @@ -57,67 +53,62 @@ const subpathFeatures = { callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.intersect_line_segment( [ - [150, 150], - [20, 20], + [80, 30], + [210, 150], ], options.error, - options.minimum_seperation + options.minimum_separation ), - sliderOptions: [intersectionErrorOptions, minimumSeparationOptions], + inputOptions: [intersectionErrorOptions, minimumSeparationOptions], }, "intersect-quadratic": { name: "Intersect (Quadratic Segment)", callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.intersect_quadratic_segment( [ - [20, 80], - [180, 10], - [90, 120], + [25, 50], + [205, 10], + [135, 180], ], options.error, - options.minimum_seperation + options.minimum_separation ), - sliderOptions: [intersectionErrorOptions, minimumSeparationOptions], + inputOptions: [intersectionErrorOptions, minimumSeparationOptions], }, "intersect-cubic": { name: "Intersect (Cubic Segment)", callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.intersect_cubic_segment( [ - [40, 20], - [100, 40], - [40, 120], - [175, 140], + [65, 20], + [125, 40], + [65, 120], + [200, 140], ], options.error, - options.minimum_seperation + options.minimum_separation ), - sliderOptions: [intersectionErrorOptions, minimumSeparationOptions], + inputOptions: [intersectionErrorOptions, minimumSeparationOptions], }, "self-intersect": { name: "Self Intersect", - callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.self_intersections(options.error, options.minimum_seperation), - sliderOptions: [intersectionErrorOptions, minimumSeparationOptions], + callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.self_intersections(options.error, options.minimum_separation), + inputOptions: [intersectionErrorOptions, minimumSeparationOptions], }, split: { name: "Split", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.split(options.t, tVariant), - sliderOptions: [tSliderOptions], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.split(options.t, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, tSliderOptions], }, trim: { name: "Trim", - callback: (subpath: WasmSubpathInstance, options: Record, _: undefined, tVariant: TVariant): string => subpath.trim(options.tVariant1, options.tVariant2, tVariant), - sliderOptions: [ - { ...tSliderOptions, default: 0.2, variable: "tVariant1" }, - { ...tSliderOptions, variable: "tVariant2" }, - ], - chooseTVariant: true, + callback: (subpath: WasmSubpathInstance, options: Record, _: undefined): string => subpath.trim(options.t1, options.t2, SUBPATH_T_VALUE_VARIANTS[options.TVariant]), + inputOptions: [subpathTValueVariantOptions, { ...tSliderOptions, default: 0.2, variable: "t1" }, { ...tSliderOptions, variable: "t2" }], }, offset: { name: "Offset", callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.offset(options.distance), - sliderOptions: [ + inputOptions: [ { variable: "distance", min: -25, @@ -130,7 +121,7 @@ const subpathFeatures = { outline: { name: "Outline", callback: (subpath: WasmSubpathInstance, options: Record): string => subpath.outline(options.distance), - sliderOptions: [ + inputOptions: [ { variable: "distance", min: 0, @@ -146,8 +137,7 @@ export type SubpathFeatureKey = keyof typeof subpathFeatures; export type SubpathFeatureOptions = { name: string; callback: SubpathCallback; - sliderOptions?: SliderOption[]; + inputOptions?: InputOption[]; triggerOnMouseMove?: boolean; - chooseTVariant?: boolean; }; export default subpathFeatures as Record; diff --git a/website/other/bezier-rs-demos/src/main.ts b/website/other/bezier-rs-demos/src/main.ts index 7bd0589a..81a54353 100644 --- a/website/other/bezier-rs-demos/src/main.ts +++ b/website/other/bezier-rs-demos/src/main.ts @@ -18,6 +18,12 @@ declare global { } window.document.title = "Bezier-rs Interactive Documentation"; +window.document.head.innerHTML += ` + + + + +`.trim(); window.customElements.define("bezier-demo", BezierDemo); window.customElements.define("bezier-demo-pane", BezierDemoPane); @@ -31,7 +37,6 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement demo.setAttribute("name", featureName); demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {})); demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove)); - demo.setAttribute("chooseTVariant", String(feature.chooseTVariant)); container?.append(demo); } @@ -40,9 +45,8 @@ function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElemen const demo = document.createElement("subpath-demo-pane"); demo.setAttribute("name", featureName); - demo.setAttribute("sliderOptions", JSON.stringify(feature.sliderOptions || [])); + demo.setAttribute("inputOptions", JSON.stringify(feature.inputOptions || [])); demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove)); - demo.setAttribute("chooseTVariant", String(feature.chooseTVariant)); container?.append(demo); } @@ -76,16 +80,16 @@ function renderExamples(): void { renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos")); } else { window.document.body.innerHTML = ` -

Bezier-rs Interactive Documentation

-

+

Bezier-rs Interactive Documentation

+

This is the interactive documentation for the Bezier-rs library. View the crate documentation for detailed function descriptions and API usage. Click and drag on the endpoints of the demo curves to visualize the various Bezier utilities and functions.

-

Beziers

+

Beziers

-

Subpaths

+

Subpaths

`.trim(); diff --git a/website/other/bezier-rs-demos/src/style.css b/website/other/bezier-rs-demos/src/style.css index 56828f63..feec1d73 100644 --- a/website/other/bezier-rs-demos/src/style.css +++ b/website/other/bezier-rs-demos/src/style.css @@ -1,10 +1,34 @@ +:root { + --color-navy: #16323f; + --color-gray: #cccccc; + --range-fill-dark: var(--color-navy); + --range-fill-light: var(--color-gray); + --range-thumb-height: 16px; +} + html, body { - font-family: Arial, sans-serif; + font-family: "Inter", sans-serif; text-align: center; background-color: white; } +.website-header { + color: var(--color-navy); + font-family: "Bona Nova", serif; +} + +.website-description { + font-weight: 500; + color: var(--color-navy); +} + +.class-header { + color: var(--color-navy); + font-family: "Bona Nova", serif; + margin-bottom: 0 +} + body > h1 { margin: 40px 0; } @@ -41,6 +65,8 @@ body > h2 { margin-top: 2em; margin-bottom: 0; padding: 0 1em; + font-family: "Bona Nova", serif; + color: var(--color-navy); } .demo-pane-header a { @@ -64,17 +90,117 @@ body > h2 { /* Demo styles */ .demo-header { - margin: 20px 0; + font-family: "Bona Nova", serif; + color: var(--color-navy); + margin-top: 10px; + margin-bottom: 5px; } .demo-figure { - width: 200px; + width: 250px; height: 200px; - margin-bottom: 20px; + margin: 10px; border: solid 1px black; } +.parent-slider-container { + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} + svg text { pointer-events: none; user-select: none; } + +/* Slider Styles */ +.slider-container { + /* width: fit-content; */ + width: 250px; + padding-bottom: 5px; +} + +.slider-label { + font-family: monospace; + display: flex; + justify-content: left; +} + +input[type="range"] { + -webkit-appearance: none; + appearance: none; + margin-right: 15px; + width: 250px; + height: 7px; + background: rgba(255, 255, 255, 0.6); + border-radius: 5px; + background: linear-gradient(var(--range-fill-dark), var(--range-fill-dark)) 0 / calc(0.5 * var(--range-thumb-height) + var(--range-ratio) * (100% - var(--range-thumb-height))) var(--range-fill-light); + background-repeat: no-repeat; + +} + +/* Input Thumb */ +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: var(--range-thumb-height); + width: var(--range-thumb-height); + border-radius: 50%; + background: var(--range-fill-dark); + box-shadow: 0 0 2px 0 #555; + transition: background .3s ease-in-out; +} + +input[type="range"]::-moz-range-thumb { + -webkit-appearance: none; + appearance: none; + height: var(--range-thumb-height); + width: var(--range-thumb-height); + border-radius: 50%; + background: var(--range-fill-dark); + box-shadow: 0 0 2px 0 #555; + transition: background .3s ease-in-out; +} + +input[type="range"]::-webkit-slider-thumb:hover { + background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), var(--range-fill-dark); +} + +input[type="range"]::-moz-range-thumb:hover { + background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), var(--range-fill-dark); +} + +/* Input Track */ +input[type=range]::-webkit-slider-runnable-track { + -webkit-appearance: none; + box-shadow: none; + border: none; + background: transparent; + background: none; +} + +input[type=range]::-moz-range-track { + -webkit-appearance: none; + appearance: none; + box-shadow: none; + border: none; + background: transparent; + background: none; + ; +} + +/* Select Styles */ +select { + font-family: monospace; +} + +.select-container { + width: 250px; + padding-bottom: 5px; + display: flex; +} + +.select-input { + margin-left: 8px; +} diff --git a/website/other/bezier-rs-demos/src/utils/options.ts b/website/other/bezier-rs-demos/src/utils/options.ts index 9cf1ccfc..ad08866f 100644 --- a/website/other/bezier-rs-demos/src/utils/options.ts +++ b/website/other/bezier-rs-demos/src/utils/options.ts @@ -1,3 +1,5 @@ +import { BEZIER_T_VALUE_VARIANTS, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types"; + export const tSliderOptions = { min: 0, max: 1, @@ -15,7 +17,7 @@ export const errorOptions = { }; export const minimumSeparationOptions = { - variable: "minimum_seperation", + variable: "minimum_separation", min: 0.001, max: 0.25, step: 0.001, @@ -29,3 +31,17 @@ export const intersectionErrorOptions = { step: 0.0025, default: 0.02, }; + +export const bezierTValueVariantOptions = { + variable: "TVariant", + default: 0, + inputType: "dropdown", + options: BEZIER_T_VALUE_VARIANTS, +}; + +export const subpathTValueVariantOptions = { + variable: "TVariant", + default: 0, + inputType: "dropdown", + options: SUBPATH_T_VALUE_VARIANTS, +}; diff --git a/website/other/bezier-rs-demos/src/utils/render.ts b/website/other/bezier-rs-demos/src/utils/render.ts index 1924b692..cfb19f5e 100644 --- a/website/other/bezier-rs-demos/src/utils/render.ts +++ b/website/other/bezier-rs-demos/src/utils/render.ts @@ -1,4 +1,4 @@ -import { TVariant, Demo, DemoPane, SliderOption } from "@/utils/types"; +import { Demo, DemoPane, InputOption } from "@/utils/types"; export function renderDemo(demo: Demo): void { const header = document.createElement("h4"); @@ -14,28 +14,72 @@ export function renderDemo(demo: Demo): void { demo.append(header); demo.append(figure); - demo.sliderOptions.forEach((sliderOption: SliderOption) => { - const sliderLabel = document.createElement("div"); - const sliderData = demo.sliderData[sliderOption.variable]; - const sliderUnit = demo.getSliderUnit(sliderData, sliderOption.variable); - sliderLabel.className = "slider-label"; - sliderLabel.innerText = `${sliderOption.variable} = ${sliderData}${sliderUnit}`; - demo.append(sliderLabel); + const parentSliderContainer = document.createElement("div"); + parentSliderContainer.className = "parent-slider-container"; - const sliderInput = document.createElement("input"); - sliderInput.className = "slider-input"; - sliderInput.type = "range"; - sliderInput.max = String(sliderOption.max); - sliderInput.min = String(sliderOption.min); - sliderInput.step = String(sliderOption.step); - sliderInput.value = String(sliderOption.default); - sliderInput.addEventListener("input", (event: Event): void => { - demo.sliderData[sliderOption.variable] = Number((event.target as HTMLInputElement).value); - sliderLabel.innerText = `${sliderOption.variable} = ${demo.sliderData[sliderOption.variable]}${sliderUnit}`; - demo.drawDemo(figure); - }); - demo.append(sliderInput); + demo.inputOptions.forEach((inputOption: InputOption) => { + const isDropdown = inputOption.inputType === "dropdown"; + + const sliderContainer = document.createElement("div"); + sliderContainer.className = isDropdown ? "select-container" : "slider-container"; + + const sliderLabel = document.createElement("div"); + const sliderData = demo.sliderData[inputOption.variable]; + const sliderUnit = demo.getSliderUnit(sliderData, inputOption.variable); + sliderLabel.className = "slider-label"; + sliderLabel.innerText = `${inputOption.variable}: ${isDropdown ? "" : sliderData}${sliderUnit}`; + sliderContainer.appendChild(sliderLabel); + + if (isDropdown) { + const selectInput = document.createElement("select"); + selectInput.className = "select-input"; + selectInput.value = String(inputOption.default); + inputOption.options?.forEach((value, idx) => { + const id = `${idx}-${value}`; + const option = document.createElement("option"); + option.value = String(idx); + option.id = id; + option.text = value; + selectInput.append(option); + }); + + selectInput.addEventListener("change", (event: Event): void => { + demo.sliderData[inputOption.variable] = Number((event.target as HTMLInputElement).value); + demo.drawDemo(figure); + }); + sliderContainer.appendChild(selectInput); + } else { + const sliderInput = document.createElement("input"); + sliderInput.className = "slider-input"; + sliderInput.type = "range"; + sliderInput.max = String(inputOption.max); + sliderInput.min = String(inputOption.min); + sliderInput.step = String(inputOption.step); + sliderInput.value = String(inputOption.default); + const range = Number(inputOption.max) - Number(inputOption.min); + + const ratio = (Number(inputOption.default) - Number(inputOption.min)) / range; + sliderInput.style.setProperty("--range-ratio", String(ratio)); + + sliderInput.addEventListener("input", (event: Event): void => { + const target = event.target as HTMLInputElement; + demo.sliderData[inputOption.variable] = Number(target.value); + const data = demo.sliderData[inputOption.variable]; + const unit = demo.getSliderUnit(demo.sliderData[inputOption.variable], inputOption.variable); + sliderLabel.innerText = `${inputOption.variable}: ${data}${unit}`; + + const ratio = (Number(target.value) - Number(inputOption.min)) / range; + sliderInput.style.setProperty("--range-ratio", String(ratio)); + + demo.drawDemo(figure); + }); + sliderContainer.appendChild(sliderInput); + } + + parentSliderContainer.append(sliderContainer); }); + + demo.append(parentSliderContainer); } export function renderDemoPane(demoPane: DemoPane): void { @@ -55,30 +99,6 @@ export function renderDemoPane(demoPane: DemoPane): void { container.append(header); } - const tVariantContainer = document.createElement("div"); - tVariantContainer.className = "t-variant-choice"; - - const tVariantLabel = document.createElement("strong"); - tVariantLabel.innerText = "TValue Variant:"; - tVariantContainer.append(tVariantLabel); - - const radioInputs = ["Parametric", "Euclidean"].map((tVariant) => { - const id = `${demoPane.id}-${tVariant}`; - const radioInput = document.createElement("input"); - radioInput.type = "radio"; - radioInput.id = id; - radioInput.value = tVariant; - radioInput.name = `TVariant - ${demoPane.id}`; - radioInput.checked = tVariant === "Parametric"; - tVariantContainer.append(radioInput); - - const label = document.createElement("label"); - label.htmlFor = id; - label.innerText = tVariant; - tVariantContainer.append(label); - return radioInput; - }); - const demoRow = document.createElement("div"); demoRow.className = "demo-row"; @@ -87,21 +107,9 @@ export function renderDemoPane(demoPane: DemoPane): void { return; } const demoComponent = demoPane.buildDemo(demo); - - radioInputs.forEach((radioInput: HTMLElement) => { - radioInput.addEventListener("input", (event: Event): void => { - demoPane.tVariant = (event.target as HTMLInputElement).value as TVariant; - demoComponent.setAttribute("tvariant", demoPane.tVariant); - }); - }); demoRow.append(demoComponent); }); container.append(demoRow); - - if (demoPane.chooseTVariant) { - container.append(tVariantContainer); - } - demoPane.append(container); } diff --git a/website/other/bezier-rs-demos/src/utils/types.ts b/website/other/bezier-rs-demos/src/utils/types.ts index 76d61064..b883fb15 100644 --- a/website/other/bezier-rs-demos/src/utils/types.ts +++ b/website/other/bezier-rs-demos/src/utils/types.ts @@ -11,26 +11,26 @@ export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_ou export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const; export type BezierCurveType = typeof BEZIER_CURVE_TYPE[number]; -export type TVariant = "Euclidean" | "Parametric"; - -export type BezierCallback = (bezier: WasmBezierInstance, options: Record, mouseLocation?: [number, number], tVariant?: TVariant) => string; -export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record, mouseLocation?: [number, number], tVariant?: TVariant) => string; +export type BezierCallback = (bezier: WasmBezierInstance, options: Record, mouseLocation?: [number, number]) => string; +export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record, mouseLocation?: [number, number]) => string; export type BezierDemoOptions = { [key in BezierCurveType]: { disabled?: boolean; - sliderOptions?: SliderOption[]; + inputOptions?: InputOption[]; customPoints?: number[][]; }; }; -export type SliderOption = { - min: number; - max: number; - step: number; - default: number; +export type InputOption = { variable: string; + min?: number; + max?: number; + step?: number; + default?: number; unit?: string | string[]; + inputType?: "slider" | "dropdown"; + options?: string[]; }; export function getCurveType(numPoints: number): BezierCurveType { @@ -61,7 +61,7 @@ export interface DemoArgs { export interface BezierDemoArgs extends DemoArgs { points: number[][]; - sliderOptions: SliderOption[]; + inputOptions: InputOption[]; } export interface SubpathDemoArgs extends DemoArgs { @@ -70,7 +70,7 @@ export interface SubpathDemoArgs extends DemoArgs { } export interface Demo extends HTMLElement { - sliderOptions: SliderOption[]; + inputOptions: InputOption[]; sliderData: Record; sliderUnits: Record; @@ -85,7 +85,8 @@ export interface DemoPane extends HTMLElement { name: string; demos: DemoArgs[]; id: string; - chooseTVariant: boolean; - tVariant: TVariant; buildDemo(demo: DemoArgs): Demo; } + +export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const; +export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const; diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index ab6970e7..4c176914 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -23,8 +23,8 @@ const SCALE_UNIT_VECTOR_FACTOR: f64 = 50.; fn parse_t_variant(t_variant: &String, t: f64) -> SubpathTValue { match t_variant.as_str() { - "Parametric" => SubpathTValue::GlobalParametric(t), - "Euclidean" => SubpathTValue::GlobalEuclidean(t), + "GlobalParametric" => SubpathTValue::GlobalParametric(t), + "GlobalEuclidean" => SubpathTValue::GlobalEuclidean(t), _ => panic!("Unexpected TValue string: '{}'", t_variant), } } diff --git a/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs b/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs index e7abdc12..f2e25877 100644 --- a/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs +++ b/website/other/bezier-rs-demos/wasm/src/svg_drawing.rs @@ -1,7 +1,7 @@ use glam::DVec2; // SVG drawing constants -pub const SVG_OPEN_TAG: &str = r#""#; +pub const SVG_OPEN_TAG: &str = r#""#; pub const SVG_CLOSE_TAG: &str = ""; // Stylistic constants @@ -30,7 +30,7 @@ pub fn wrap_svg_tag(contents: String) -> String { /// Helper function to create an SVG text entity. pub fn draw_text(text: String, x_pos: f64, y_pos: f64, fill: &str) -> String { - format!(r#"{text}"#) + format!(r#"{text}"#) } /// Helper function to create an SVG circle entity.