;
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
-
+
+
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
+
- 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.