mattdesl / canvas-sketch

[beta] A framework for making generative artwork in JavaScript and the browser.
MIT License
5.01k stars 394 forks source link

GUI/HUD Feature #20

Open mattdesl opened 5 years ago

mattdesl commented 5 years ago

I'm working in a feature/hud branch to test out the idea of a built-in GUI system for canvas-sketch. It would be like dat.gui but a bit more opinionated, a lot more declarative, and will integrate easily with canvas-sketch as well as features like exporting/importing serialized data (for example, making prints parameters reproducible).

Here is an example, no fancy styling yet:

screen shot 2018-10-01 at 5 19 13 pm

And a video in this tweet.

It would be built with Preact to keep the canvas-sketch library small.

Syntax

I don't want to introduce a lot of new API concepts to users, and I want it to be declarative and in line with the rest of the ethos of pure render functions. My current thought is to have something like this:

const canvasSketch = require('canvas-sketch');

const settings = {
  dimensions: [ 640, 640 ],
  params: {
    background: 'pink',
    time: {
      value: 0.5,
      min: 0,
      max: 1,
      step: 0.001
    },
    number: 0.25,
    text: 'some text',
    download: ({ exportFrame }) => exportFrame()
  }
};

canvasSketch(() => {
  return ({ params }) => {
    const { background, radius } = params;
    console.log('Current background:', background); // e.g. #ff0000
    console.log('Current radius:', radius); // e.g. 0.523
  };
}, settings);

Motivation

It won't be all that different than dat.gui, but:

Features

Should support:

Questions

nkint commented 5 years ago

👏 nice! I love the concept to have a minimal gui serializable as the save-seed-and-commit works.

Is this a can of worms I don't even want to get into? 😅

kellymilligan commented 5 years ago

Love this idea @mattdesl! I've tried to use ControlKit in my environment before with loads of issues, this would be a real game changer.

guidoschmidt commented 5 years ago

Your approach looks promising, great work 🎉. I love the idea of the fuzzy searching, folders would be definitively nice, too.

To have custom change handlers, something like the following would be convenient from my point of view:

background: {
  value: 'pink',
  onChange: newValue => { /* custom code that should run on change */ }
}

Also I was wondering if it would be possible to have interconnected parameter changes (e.g. change the background color which then will effect another parameter), ... just popped into my head as a maybe nice feature for generative art 🤔.

👍 +1 for persisting localStorage, which @kellymilligan suggested

mattdesl commented 5 years ago

Thanks guys!

Some more questions:

Possible answers for each...

Serialize

If you specify { serialize: true } in your params, then each time you export a frame, it will also export a [filename].params.json to be associated with that artwork. This means serialize is a reserved word you can't use in your own UI. In the case of animations, it exports a single JSON for a sequence of PNG frames.

To deserialize, you can drag and drop a JSON file onto the page...? Or maybe also specify a --param-file in the CLI to override default params. Maybe there should also be some sort of programmatic way of doing it?

Reacting to onChange and button events

One tricky thing is that most of the time when you react to changes and button presses, you will need to be in the scope of your sketch. What is the syntax for that? Take for example:

const { createRandom } = require('canvas-sketch-util/math');

const settings = {
  params: {
    seed: 0
  }
};

canvasSketch(({ params }) => {
  const random = createRandom(params.seed);

  // how to do random.setSeed(newSeed) with param change?

  return ({ context }) => {
    // render...
  };
}, settings);

Folder Syntax

I'm not sure any clean way to tackle this, really. I could use ES7 decorators but I'd rather not introduce non-standard syntax. One way is just looking for { type: 'folder' } and if found, all keys that are not reserved (keys like open, visible can't be used for example) will be made into UI elements:

const settings = {
  params: {
    circle: {
      type: 'folder',
      background: 'red',
      lineWidth: { min: 0, max: 10, value: 3, step: 0.1 }
    }
  }
};

canvasSketch(({ params }) => {
  console.log(params.circle.lineWidth) // 3
}, settings);

Special Properties

Some properties like dimensions or duration you will just want to 'expose' but not have to wire up yourself manually. In the case of dimensions it could be nice to have canvas-sketch populate a drop-down with paper size presets and so forth. In that case, maybe using special type keys.

const settings = {
  params: {
    // Expose a dimension selector
    dimensions: { type: 'dimensions' },
    duration: {
      // Expose a duration slider
      type: 'duration',
      // In case you wanted to give a specific constraint to the loop duration
      min: 1, 
      max: 10
    } 
  }
};

canvasSketch(mySketch, settings);
dmnsgn commented 5 years ago

I see you are using your own flavour of localStorage in the examples/util/controls.js. Have you ever tried dat.GUI's gui.remember(settings.params); which allegedly saves the current state to localStorage?

mattdesl commented 5 years ago

Yeah! I ended up doing a few things differently:

terkelg commented 5 years ago

I would like to help with this one. I've been thinking about building a lightweight HUD library for a while as a separate module but It can be nice to make sure it works great with canvas-sketch – we can design the API based on the needs of canvas-sketch

onetwothreebutter commented 5 years ago

I'm trying to add a control panel/settings to an app I've built using the canvas-sketch and modulating orb thing @mattdesl built for his Frontend Masters course. However, it seems like canvas-sketch may be preventing my mouse events from reaching my form fields. And I've never been able to right-click to inspect the page, even before adding my control panel... is canvas-sketch preventing event bubbling somehow? If this is too off-topic, I can start a new issue or ask this question in a different venue!

mattdesl commented 5 years ago

Hey @onetwothreebutter do you mind starting a new issue and also providing some code samples? It shouldn’t capture mouse events by default.

RafalWilinski commented 4 years ago

I've managed to add https://github.com/dataarts/dat.gui with minimal amount of effort.

  1. npm install dat.gui --save

  2. Instantiate GUI

    import * as dat from 'dat.gui';
    const gui = new dat.GUI();  
  3. Extend settings Object with something like params

    const settings = {
    suffix: Random.getSeed(),
    dimensions: 'A4',
    orientation: 'portrait',
    pixelsPerInch: 300,
    params: {
    myControlledVar: 10
    },
  4. Add params to be controlled by datGui

    gui.add(settings.params, 'myControlledVar', 1, 50);
  5. Reference it inside render function returned by sketch:

    const sketch = (props) => {
    
    return (props) => {
    const { myControlledVar } = settings.params;
    ...
  6. Profit

Screenshot 2019-11-09 at 13 09 30

mattdesl commented 4 years ago

Here's an example showing basically the same thing, but also re-rendering the frame when a GUI parameter changes (fairly useful for static sketches).

https://gist.github.com/mattdesl/04ceca544e637ce1da4d2cf5200d71af