@solidrt/core 0.0.40 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,35 +14,36 @@
14
14
  // pass paints opaque black outside its ring, so source-over hides the base
15
15
  // entirely while plus adds only the lit pixels - black contributes nothing.
16
16
  import { render, onFrame, createSignal } from "@solidrt/core"
17
- import { createShader } from "@solidrt/core/gpu"
17
+ import { createShaderTexture, glsl } from "@solidrt/core/gpu"
18
18
 
19
19
  let SIZE = 360
20
20
 
21
21
  // Base pass: a static gradient with a soft vignette. Nothing drives it, so it
22
22
  // renders once at creation and then holds - shaders re-render on params writes.
23
- let BASE = `
24
- void main() {
25
- vec2 uv = vUV;
26
- float v = 1.0 - length(uv - 0.5) * 1.1;
27
- vec3 col = mix(vec3(0.04, 0.05, 0.14), vec3(0.15, 0.10, 0.42), uv.y);
28
- fragColor = vec4(col * v, 1.0);
29
- }
23
+ let BASE = glsl`
24
+ void main() {
25
+ vec2 uv = vUV;
26
+ float v = 1.0 - length(uv - 0.5) * 1.1;
27
+ vec3 col = mix(vec3(0.04, 0.05, 0.14), vec3(0.15, 0.10, 0.42), uv.y);
28
+ fragColor = vec4(col * v, 1.0);
29
+ }
30
30
  `
31
31
 
32
32
  // Additive pass: a breathing ring, black everywhere else.
33
- let GLOW = `
34
- void main() {
35
- vec2 uv = vUV;
36
- float r = length(uv - 0.5);
37
- float radius = 0.28 + 0.04 * sin(iTime * 2.0);
38
- float ring = smoothstep(0.06, 0.0, abs(r - radius));
39
- fragColor = vec4(vec3(1.0, 0.55, 0.15) * ring, 1.0);
40
- }
33
+ let GLOW = glsl`
34
+ uniform float uTime;
35
+ void main() {
36
+ vec2 uv = vUV;
37
+ float r = length(uv - 0.5);
38
+ float radius = 0.28 + 0.04 * sin(uTime * 2.0);
39
+ float ring = smoothstep(0.06, 0.0, abs(r - radius));
40
+ fragColor = vec4(vec3(1.0, 0.55, 0.15) * ring, 1.0);
41
+ }
41
42
  `
42
43
 
43
44
  function App() {
44
- let baseId = createShader(BASE, SIZE, SIZE)
45
- let glowId = createShader(GLOW, SIZE, SIZE, { iTime: 0 })
45
+ let baseId = createShaderTexture(BASE, SIZE, SIZE, null, { label: "base" })
46
+ let glowId = createShaderTexture(GLOW, SIZE, SIZE, { uTime: 0 }, { label: "glow" })
46
47
  let [time, setTime] = createSignal(0)
47
48
  let [mode, setMode] = createSignal<"plus" | "source-over">("plus")
48
49
  onFrame((tick) => setTime(tick / 1000))
@@ -67,7 +68,7 @@ function App() {
67
68
  width={SIZE}
68
69
  height={SIZE}
69
70
  blendMode={mode()}
70
- params={{ iTime: time() }}
71
+ params={{ uTime: time() }}
71
72
  />
72
73
  </view>
73
74
  <text fontSize={14} color="#888">blendMode "{mode()}" - click to toggle</text>
@@ -15,7 +15,7 @@ import bytes from "./logo.png" with { type: "binary" }
15
15
 
16
16
  function App() {
17
17
  let { data, width, height } = decodeImage(bytes)
18
- let id = createTexture(data, width, height)
18
+ let id = createTexture(data, width, height, { label: "logo" })
19
19
 
20
20
  return (
21
21
  <window alignItems="center" justifyContent="center">
@@ -0,0 +1,94 @@
1
+ // parseSvg turns a whole SVG *document string* into plain draw data. This is
2
+ // not HTML: there are no <rect>/<circle>/<path> JSX children to nest. You hand
3
+ // it the source text (a string you import, fetch, or inline) and get back the
4
+ // document's intrinsic size plus a flat list of draws whose keys match the
5
+ // path element's props - so rendering is a map to <d-path>, wrapped in a view
6
+ // whose `viewBox` fits the document's coordinate space into the box.
7
+ //
8
+ // The point of draws-as-data over an opaque document element: every shape is
9
+ // a real node you own. Below, the house highlights the shape under the
10
+ // pointer - exact-geometry hit testing (the path outline, not its box), with
11
+ // the recolor a per-node prop override that never re-parses the document.
12
+ // The same structure gives per-shape animation (wrap a subset in <d-view>),
13
+ // layer filtering, or interleaving your own JSX between document layers.
14
+ //
15
+ // A multi-color document keeps each shape's own fill; a monochrome icon
16
+ // using stroke/fill "currentColor" is recolored by the `color` option,
17
+ // exactly as `currentColor` would be in a browser. That is how you use an
18
+ // existing icon library (Lucide, Heroicons, Feather, Material, ...): they
19
+ // ship SVG source strings following the currentColor convention. Parse once
20
+ // per document under a memo (or at module scope for a static asset).
21
+ //
22
+ // Being vectors, the draws are resolution-independent: crisp at any drawn
23
+ // size x displayScale(). Prefer them over a raster <texture> (image.tsx)
24
+ // whenever the render size is fluid or the display DPI varies.
25
+ import { render, parseSvg, svg, createMemo, createSignal, For } from "@solidrt/core"
26
+
27
+ // Multi-color document: each shape carries its own fill. The `svg` tag returns
28
+ // the string unchanged; it exists so editors highlight the markup (like `glsl`
29
+ // for shader sources).
30
+ const HOUSE = svg`
31
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
32
+ <rect x="20" y="45" width="60" height="45" fill="#457b9d"/>
33
+ <path d="M10 50 L50 15 L90 50 Z" fill="#e63946"/>
34
+ <rect x="42" y="62" width="16" height="28" fill="#f1faee"/>
35
+ <circle cx="50" cy="35" r="6" fill="#ffd166"/>
36
+ </svg>`
37
+
38
+ // Monochrome icon (Lucide arrow-right) drawn with currentColor, recolored below.
39
+ const ARROW = svg`
40
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
41
+ stroke-linecap="round" stroke-linejoin="round">
42
+ <path d="M5 12h14"/>
43
+ <path d="M12 5l7 7-7 7"/>
44
+ </svg>`
45
+
46
+ // The payoff over the old document-element approach: each draw is its own
47
+ // node, so the shape under the pointer lights up - hit on the true outline
48
+ // (hovering the sky inside the roof triangle's box does nothing), recolor
49
+ // without a re-parse.
50
+ function InteractiveHouse() {
51
+ let doc = createMemo(() => parseSvg(HOUSE))
52
+ let [hot, setHot] = createSignal(-1)
53
+ // repaintBoundary still pays off on an interactive document: the subtree
54
+ // re-records only when a draw inside it changes (hover), never because a
55
+ // sibling elsewhere on the screen did.
56
+ return (
57
+ <view repaintBoundary width={240} height={240} viewBox={[doc().width, doc().height]}>
58
+ <For each={doc().draws}>
59
+ {(draw, i) => (
60
+ <d-path
61
+ {...draw}
62
+ color={hot() === i() ? "#ffd166" : draw.color}
63
+ onPointerEnter={() => setHot(i())}
64
+ onPointerLeave={() => setHot((v) => (v === i() ? -1 : v))}
65
+ />
66
+ )}
67
+ </For>
68
+ </view>
69
+ )
70
+ }
71
+
72
+ // The plain pattern: memoized parse, viewBox-fitted box, draws mapped to
73
+ // <d-path>, and a plain repaintBoundary (the DL-reuse tier, not "snapshot")
74
+ // so the static subtree never re-records alongside animating siblings. The
75
+ // components-package Icon is this plus theming.
76
+ function Svg(props: { src: string; size: number; color?: string }) {
77
+ let doc = createMemo(() => parseSvg(props.src, { color: props.color }))
78
+ return (
79
+ <view repaintBoundary width={props.size} height={props.size} viewBox={[doc().width, doc().height]}>
80
+ <For each={doc().draws}>{(draw) => <d-path {...draw} />}</For>
81
+ </view>
82
+ )
83
+ }
84
+
85
+ function App() {
86
+ return (
87
+ <window justifyContent="center" alignItems="center" flexDirection="row" gap={32}>
88
+ <InteractiveHouse />
89
+ <Svg size={120} src={ARROW} color="#4f8cff" />
90
+ </window>
91
+ )
92
+ }
93
+
94
+ render(() => <App />)
@@ -7,8 +7,8 @@
7
7
  //
8
8
  // This example shows only the text import itself: it reports the imported
9
9
  // file's size and first line. Shader sources are the motivating case - the
10
- // string is exactly what gpu-shader.tsx passes to createShader, moved out of
11
- // the .tsx so it can be edited as GLSL. Inlining trades update granularity for
10
+ // string is exactly what gpu-shader.tsx passes to createShaderTexture, moved
11
+ // out of the .tsx so it can be edited as GLSL. Inlining trades update granularity for
12
12
  // zero I/O, so keep big or streamable files in assets/ and read them at
13
13
  // runtime instead.
14
14
  import { render } from "@solidrt/core"
@@ -1,11 +1,13 @@
1
1
  // wave.glsl - animated scanline
2
2
  //
3
3
  // A plain fragment body in the injected-preamble dialect: no #version line, so
4
- // createShader prepends vUV / iResolution / iTime / fragColor. Living in its
5
- // own file it stays editable as GLSL instead of as a template literal.
4
+ // createShaderTexture prepends vUV / iResolution / fragColor. The time uniform
5
+ // is this file's own declaration, driven by the app through params. Living in
6
+ // its own file it stays editable as GLSL instead of as a template literal.
7
+ uniform float uTime;
6
8
  void main() {
7
9
  vec2 uv = vUV;
8
- float wave = sin(uv.x * 12.0 + iTime * 2.0) * 0.06;
10
+ float wave = sin(uv.x * 12.0 + uTime * 2.0) * 0.06;
9
11
  float d = abs(uv.y - 0.5 - wave);
10
12
  float line = smoothstep(0.05, 0.0, d);
11
13
  vec3 col = mix(vec3(0.05, 0.07, 0.12), vec3(0.2, 0.8, 1.0), line);
@@ -3,37 +3,37 @@
3
3
  // one-frame history. Here it draws a motion echo behind the orbiting square;
4
4
  // click to toggle the echo term off and compare with the plain frame.
5
5
  import { render, onFrame, createSignal } from "@solidrt/core"
6
- import { compileShader, destroyShader, linkProgram } from "@solidrt/core/gpu"
6
+ import { compileShader, destroyShader, glsl, linkProgram } from "@solidrt/core/gpu"
7
7
 
8
- let VERTEX = `#version 300 es
9
- precision highp float;
10
- out vec2 vUV;
11
- void main() {
12
- vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
13
- // uSource/uPrevious are top-left origin; flip v so the frame lands upright.
14
- vUV = vec2(p.x, 1.0 - p.y);
15
- gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
16
- }
8
+ let VERTEX = glsl`#version 300 es
9
+ precision highp float;
10
+ out vec2 vUV;
11
+ void main() {
12
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
13
+ // uSource/uPrevious are top-left origin; flip v so the frame lands upright.
14
+ vUV = vec2(p.x, 1.0 - p.y);
15
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
16
+ }
17
17
  `
18
18
 
19
- let ECHO = `
20
- uniform sampler2D uSource;
21
- uniform sampler2D uPrevious;
22
- uniform float uEcho;
23
- in vec2 vUV;
24
- void main() {
25
- vec4 cur = texture(uSource, vUV);
26
- vec4 prev = texture(uPrevious, vUV);
27
- // Brightest of the current frame and the decayed previous one: motion
28
- // leaves a one-frame ghost trailing it.
29
- fragColor = max(cur, prev * uEcho);
30
- }
19
+ let ECHO = glsl`
20
+ uniform sampler2D uSource;
21
+ uniform sampler2D uPrevious;
22
+ uniform float uEcho;
23
+ in vec2 vUV;
24
+ void main() {
25
+ vec4 cur = texture(uSource, vUV);
26
+ vec4 prev = texture(uPrevious, vUV);
27
+ // Brightest of the current frame and the decayed previous one: motion
28
+ // leaves a one-frame ghost trailing it.
29
+ fragColor = max(cur, prev * uEcho);
30
+ }
31
31
  `
32
32
 
33
33
  function App() {
34
34
  let vs = compileShader("vertex", VERTEX)
35
35
  let fs = compileShader("fragment", ECHO, { header: true })
36
- let echoProgram = linkProgram(vs, fs)
36
+ let echoProgram = linkProgram(vs, fs, { label: "echo" })
37
37
  destroyShader(vs)
38
38
  destroyShader(fs)
39
39
 
@@ -9,38 +9,40 @@
9
9
  // is an identity pass, which must be indistinguishable from no shader at all
10
10
  // (the orientation/half-pixel regression check from the plan).
11
11
  import { render, onFrame, createSignal } from "@solidrt/core"
12
- import { compileShader, destroyShader, linkProgram } from "@solidrt/core/gpu"
12
+ import { compileShader, destroyShader, glsl, linkProgram } from "@solidrt/core/gpu"
13
13
 
14
- let VERTEX = `#version 300 es
15
- precision highp float;
16
- out vec2 vUV;
17
- void main() {
18
- vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
19
- // uSource is top-left origin; flip v so the frame lands upright on the
20
- // window (the one flip of the frame path, done here in the vertex stage).
21
- vUV = vec2(p.x, 1.0 - p.y);
22
- gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
23
- }
14
+ let VERTEX = glsl`#version 300 es
15
+ precision highp float;
16
+ out vec2 vUV;
17
+ void main() {
18
+ vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
19
+ // uSource is top-left origin; flip v so the frame lands upright on the
20
+ // window (the one flip of the frame path, done here in the vertex stage).
21
+ vUV = vec2(p.x, 1.0 - p.y);
22
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
23
+ }
24
24
  `
25
25
 
26
- // { header: true } declares #version, precision, iResolution/iTime and
27
- // fragColor; uSource, vUV, and the app's own uniforms are declared here.
28
- let WARP = `
29
- uniform sampler2D uSource;
30
- uniform float uAmount;
31
- in vec2 vUV;
32
- void main() {
33
- vec2 uv = vUV;
34
- uv.x += sin(uv.y * 24.0 + iTime * 3.0) * 0.012 * uAmount;
35
- uv.y += sin(uv.x * 18.0 - iTime * 2.0) * 0.012 * uAmount;
36
- fragColor = texture(uSource, uv);
37
- }
26
+ // { header: true } declares #version, precision, iResolution and fragColor;
27
+ // uSource, vUV, and the app's own uniforms - the time included - are
28
+ // declared here.
29
+ let WARP = glsl`
30
+ uniform sampler2D uSource;
31
+ uniform float uAmount;
32
+ uniform float uTime;
33
+ in vec2 vUV;
34
+ void main() {
35
+ vec2 uv = vUV;
36
+ uv.x += sin(uv.y * 24.0 + uTime * 3.0) * 0.012 * uAmount;
37
+ uv.y += sin(uv.x * 18.0 - uTime * 2.0) * 0.012 * uAmount;
38
+ fragColor = texture(uSource, uv);
39
+ }
38
40
  `
39
41
 
40
42
  function App() {
41
43
  let vs = compileShader("vertex", VERTEX)
42
44
  let fs = compileShader("fragment", WARP, { header: true })
43
- let warp = linkProgram(vs, fs)
45
+ let warp = linkProgram(vs, fs, { label: "warp" })
44
46
  destroyShader(vs)
45
47
  destroyShader(fs)
46
48
 
@@ -50,7 +52,7 @@ function App() {
50
52
 
51
53
  return (
52
54
  <window
53
- shader={{ program: warp, params: { iTime: time(), uAmount: amount() } }}
55
+ shader={{ program: warp, params: { uTime: time(), uAmount: amount() } }}
54
56
  onPointerDown={() => setAmount(a => (a > 0 ? 0 : 1))}
55
57
  flexDirection="column"
56
58
  gap={12}
@@ -59,9 +61,9 @@ function App() {
59
61
  >
60
62
  <text fontSize={28} color="#222">Window shader</text>
61
63
  <view flexDirection="row" gap={12}>
62
- <rect w={90} h={90} radius={12} color="#0077ff" />
63
- <rect w={90} h={90} radius={12} color="#ff6a00" />
64
- <rect w={90} h={90} radius={12} color="#00c46a" />
64
+ <rect width={90} height={90} radius={12} color="#0077ff" />
65
+ <rect width={90} height={90} radius={12} color="#ff6a00" />
66
+ <rect width={90} height={90} radius={12} color="#00c46a" />
65
67
  </view>
66
68
  <text fontSize={14} color="#666">Click to toggle warp (identity at 0)</text>
67
69
  </window>
package/jsx-runtime.d.ts CHANGED
@@ -2,12 +2,18 @@ import type {
2
2
  WindowProps,
3
3
  RectProps,
4
4
  OvalProps,
5
+ LineProps,
5
6
  PathProps,
6
- SvgProps,
7
7
  ViewProps,
8
+ ViewOwnProps,
8
9
  TextProps,
9
10
  TextureProps,
10
11
  LayoutProps,
12
+ PositionProps,
13
+ GeometryProps,
14
+ OvalGeometryProps,
15
+ TextGeometryProps,
16
+ LineGeometryProps,
11
17
  Element as CoreElement,
12
18
  ElementChildrenAttribute as CoreElementChildrenAttribute
13
19
  } from "./src/types"
@@ -30,21 +36,23 @@ export namespace JSX {
30
36
  ref?: Ref<{ id: number }> | undefined
31
37
  }
32
38
 
39
+ // Layout forms compose LayoutProps and derive their geometry from the
40
+ // layout box; d-* forms compose the paint-space geometry props instead.
33
41
  interface IntrinsicElements {
34
42
  window: WindowProps & ElementRef
35
43
  view: ViewProps & ElementRef
36
44
  text: TextProps & LayoutProps & ElementRef
37
45
  rect: RectProps & LayoutProps & ElementRef
38
46
  oval: OvalProps & LayoutProps & ElementRef
47
+ line: LineProps & LayoutProps & ElementRef
39
48
  path: PathProps & LayoutProps & ElementRef
40
- svg: SvgProps & LayoutProps & ElementRef
41
49
  texture: TextureProps & LayoutProps & ElementRef
42
- "d-view": ViewProps & ElementRef
43
- "d-rect": RectProps & ElementRef
44
- "d-oval": OvalProps & ElementRef
45
- "d-path": PathProps & ElementRef
46
- "d-svg": SvgProps & ElementRef
47
- "d-texture": TextureProps & ElementRef
48
- "d-text": TextProps & ElementRef
50
+ "d-view": ViewOwnProps & ElementRef
51
+ "d-rect": RectProps & GeometryProps & ElementRef
52
+ "d-oval": OvalProps & OvalGeometryProps & ElementRef
53
+ "d-line": LineProps & LineGeometryProps & ElementRef
54
+ "d-path": PathProps & PositionProps & ElementRef
55
+ "d-texture": TextureProps & GeometryProps & ElementRef
56
+ "d-text": TextProps & TextGeometryProps & ElementRef
49
57
  }
50
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "colord": "^2.9.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@solidrt/flux-types": "0.0.40"
30
+ "@solidrt/flux-types": "0.0.42"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@solidjs/signals": "2.0.0-beta.26",
package/src/camera.ts CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { createSignal, onCleanup } from "@solidjs/signals"
10
10
  import { listCameras, open } from "flux:camera"
11
+ import type { TextureId } from "flux:gpu"
11
12
  import { on } from "srt:events"
12
13
 
13
14
  export type CameraFacing = "front" | "back" | "unknown"
@@ -41,8 +42,11 @@ let devicesAccessor: (() => CameraInfo[]) | undefined
41
42
 
42
43
  /**
43
44
  * Current camera list as a reactive accessor: re-enumerates on hotplug. Also
44
- * initializes the camera subsystem (required before hotplug events fire).
45
- * App-lifetime: there is one camera subsystem, so no cleanup is needed.
45
+ * kicks off the camera subsystem (required before hotplug events fire), which
46
+ * starts asynchronously: the list is empty until the initial device events
47
+ * arrive - moments later normally, never if the platform's capture backend is
48
+ * wedged. App-lifetime: there is one camera subsystem, so no cleanup is
49
+ * needed.
46
50
  *
47
51
  * Coverage caveat (SDL 3.4.8): only Android delivers both add and remove. On
48
52
  * Linux you get add events but not remove (removal is broken upstream); on
@@ -70,7 +74,7 @@ export function onDeviceChange(callback: (event: { added: boolean }) => void): (
70
74
  /** A live camera as reactive accessors. */
71
75
  export type CameraStream = {
72
76
  /** Texture id once the stream is up, undefined while opening; render with <texture src={...}>. */
73
- texture(): number | undefined
77
+ texture(): TextureId | undefined
74
78
  /** Actual stream size, undefined while opening. */
75
79
  width(): number | undefined
76
80
  height(): number | undefined
@@ -87,7 +91,7 @@ export type CameraStream = {
87
91
  * call open() from "flux:camera" directly.
88
92
  */
89
93
  export function createCamera(options: CameraOptions = {}): CameraStream {
90
- let [texture, setTexture] = createSignal<number | undefined>(undefined)
94
+ let [texture, setTexture] = createSignal<TextureId | undefined>(undefined)
91
95
  let [width, setWidth] = createSignal<number | undefined>(undefined)
92
96
  let [height, setHeight] = createSignal<number | undefined>(undefined)
93
97
  let [barcode, setBarcode] = createSignal<BarcodeResult | undefined>(undefined)
package/src/color.ts CHANGED
@@ -48,9 +48,21 @@ type Stop = { offset: number; color: number }
48
48
  // `pct()` length vocabulary used by layout and transformOrigin: a gradient's
49
49
  // position is naturally a fraction (like a stop offset), so 0..1 reads cleaner
50
50
  // than pct(0)..pct(100). Do not "unify" them onto pct().
51
+ //
52
+ // The optional absolute-space fields are produced by parseSvg, never by the
53
+ // factories: `units: "absolute"` switches the coordinates to the document's
54
+ // drawing space, `spread` is the SVG spreadMethod (default pad), and
55
+ // `transform` an SVG matrix(a b c d e f) sextet mapping the gradient's
56
+ // coordinates into that space (default identity).
57
+ type AbsoluteSpace = {
58
+ units?: "absolute"
59
+ spread?: "pad" | "reflect" | "repeat"
60
+ transform?: [number, number, number, number, number, number]
61
+ }
62
+
51
63
  export type Gradient =
52
- | { readonly __gradient: "linear"; x0: number; y0: number; x1: number; y1: number; stops: Stop[] }
53
- | { readonly __gradient: "radial"; cx: number; cy: number; r: number; circle: boolean; stops: Stop[] }
64
+ | ({ readonly __gradient: "linear"; x0: number; y0: number; x1: number; y1: number; stops: Stop[] } & AbsoluteSpace)
65
+ | ({ readonly __gradient: "radial"; cx: number; cy: number; r: number; circle?: boolean; stops: Stop[] } & AbsoluteSpace)
54
66
 
55
67
  /**
56
68
  * A linear gradient between two points, each given in 0..1 of the element's box