Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Created November 28, 2019 13:36
Show Gist options
  • Save mattdesl/b7c9665b142810f506bf9e4b6c68660f to your computer and use it in GitHub Desktop.
Save mattdesl/b7c9665b142810f506bf9e4b6c68660f to your computer and use it in GitHub Desktop.

Speculative Design for a new creative coding toolkit

This is just some rough ideas & brainstorming around a new toolkit for creative coding, taking inspiration from design tools like Figma and Sketch.

A design is built from "Components", where each component is one script. Components can expose attributes (like line color), and also expose UI. Components can nest other components, so you can create modular designs and re-use individual components easily on future projects. Ideally, components could be pulled from a community-driven library and added into your designs ad-hoc.

In theory the editor could also allow editing component scripts on the fly, so you can pull in a component and easily remix its code.

Some examples of what this could be used for:

  • Generative art, design, and motion graphics
  • Data visualization
  • VJing
  • Pen plotter art
  • Interactive media artworks
  • And more...

How it Works

The current prototype is built on Svelte, which is also the compiler/language used in the .txl files. Components would just be modules on npmjs.com that expose a main .txl file (i.e. like a .svelte file).

<script>
import LinearGradient from '@mattdesl/components/LinearGradient.txl';
import NoiseLines from '@mattdesl/components/NoiseLines.txl';
</script>
<LinearGradient />
<NoiseLines />
<script>
import { Props, Color, Slider, Checkbox } from "txl";
// These props can be passed in via parent components
export let angle = 0;
export let length = 1;
export let colorA = '#F80BFF';
export let colorB = '#E8FF44';
export let reverse = false;
// The render function gets called each frame with the state
// of the application, like timeline playhead
function render ({ playhead, context, width, height }) {
const norm = [ Math.cos(angle), Math.sin(angle) ];
const hypot = Math.sqrt(width * width + height * height);
const lenHalf = length * hypot / 2;
const a = [ width / 2 + norm[0] * lenHalf, height / 2 + norm[1] * lenHalf ];
const b = [ width / 2 - norm[0] * lenHalf, height / 2 - norm[1] * lenHalf ];
const gradient = context.createLinearGradient(a[0], a[1], b[0], b[1]);
const t = reverse ? 1 : 0;
gradient.addColorStop(t, colorA);
gradient.addColorStop(1 - t, colorB);
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
}
</script>
<!-- Components can have their own UI and sliders -->
<Props>
<Slider label='Angle' bind:value={angle} min={-Math.PI * 2} max={Math.PI * 2} step={0.01} />
<Slider label='Length' bind:value={length} min={0} max={2} step={0.01} />
<Color label='Color A' bind:value={colorA} />
<Color label='Color B' bind:value={colorB} />
<Checkbox label='Reverse' bind:checked={reverse} />
</Props>
<script>
import { Props, Color, Slider, Checkbox } from "txl";
import Random from 'txl/random';
import { lerp } from 'txl/math';
export let length = 10;
export let thickness = 1;
export let frequency = 0.5;
export let color = '#000000';
export let count = 50;
export let margin = 0;
function render ({ playhead, context, width, height }) {
for (let y = 0; y < count; y++) {
for (let x = 0; x < count; x++) {
const u = count <= 1 ? 0.5 : (x / (count - 1));
const v = count <= 1 ? 0.5 : (y / (count - 1));
const nu = u * 2 - 1;
const nv = v * 2 - 1;
const n = Random.loopNoise2D(nu * frequency, nv * frequency, playhead, 1);
const px = lerp(margin, width - margin, u);
const py = lerp(margin, height - margin, v);
const angle = n * 2 * Math.PI;
context.strokeStyle = color;
drawLine(context, px, py, angle, length, thickness);
}
}
}
function drawLine (context, x, y, angle, len, thick) {
context.lineWidth = thick;
context.beginPath();
const halfLen = len / 2;
const [ nx, ny ] = [ Math.cos(angle), Math.sin(angle) ];
const p0 = [ x + nx * halfLen, y + ny * halfLen ];
const p1 = [ x - nx * halfLen, y - ny * halfLen ];
const path = [ p0, p1 ];
context.beginPath();
path.forEach(p => context.lineTo(p[0], p[1]));
context.stroke();
}
</script>
<Props>
<Color label='Color' bind:value={color} />
<Slider label='Count' bind:value={count} min={0} max={50} step={1} />
<Slider label='Length' bind:value={length} min={0} max={50} step={0.01} />
<Slider label='Thickness' bind:value={thickness} min={0.001} max={5} step={0.01} />
<Slider label='Margin' bind:value={margin} min={0} max={50} step={0.1} />
<Slider label='Frequency' bind:value={frequency} min={0} max={1} step={0.001} />
</Props>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment