Git Product home page Git Product logo

cycle-canvas's Introduction

cycle-canvas npm version Build Status

A canvas driver for Cycle.js. Great for games or art.

Currently highly experimental. Expect major breaking changes.

Installation

$ npm install cycle-canvas --save

Example

import {run} from '@cycle/rxjs-run';
import {makeCanvasDriver, rect, text} from 'cycle-canvas';
import {Observable} from 'rxjs/Observable';

function main () {
  return {
    Canvas: Observable.just(
      rect({
        x: 10,
        y: 10,

        width: 160,
        height: 100,

        draw: [
          {fill: 'purple'}
        ],

        children: [
          text({
            x: 15,
            y: 25,

            value: 'Hello World!',

            font: '18pt Arial',

            draw: [
              {fill: 'white'}
            ]
          })
        ]
      })
    )
  };
}

const drivers = {
  Canvas: makeCanvasDriver(null, {width: 800, height: 600})
};

run(main, drivers);

Looks like this:

img

Also check out the flappy bird example.

You can find the source for flappy bird here.

API

Creating a canvas driver

Using event streams of the canvas element

Drawing shapes and text

Transformations

Creating a canvas driver

makeCanvasDriver(selector, canvasSize = null)

A factory for the canvas driver function.

Receives a selector which should resolve to a canvas to which the driver function will attach.

If the selector does not resolve to a canvas, a new canvas element will be added at the bottom of the document and the driver will attach to that canvas.

The input to this driver is a stream of drawing instructions and transformations as detailed below.

Arguments

  • selector: string a css selector to use in order to find a canvas to attach the driver to.
  • canvasSize: {width: integer, height: integer} an object that denotes the size to set for the attached canvas. If null, the driver attaches to its canvas without altering its size.

Using event streams of the canvas element

sources.Canvas.events(eventName)

Canvas driver exposes a source object with an events method, which works similarly to the events method of the DOM driver.

Example:

import {run} from '@cycle/rxjs-run';
import {makeCanvasDriver, rect, text} from 'cycle-canvas';
import 'rxjs'

function main (sources) {
  const canvasClick$ = sources.Canvas.events('click')
  const counter$ = canvasClick$.startWith(0).scan(counter => counter + 1)
  return {
    Canvas: counter$.map(counter =>
      rect({
        children: [
          text({
            x: 15,
            y: 25,
            value: `Canvas was clicked ${counter} times`,
            font: '18pt Arial',
            draw: [
              {fill: 'black'}
            ]
          })
        ]
      })
    )
  };
}

const drivers = {
  Canvas: makeCanvasDriver(null, {width: 800, height: 600})
};

run(main, drivers);

Drawing shapes and text

rect(params = {})

Draws a rectangle given an object containing drawing parameters.

params {}:

  • x: number The x axis for the starting point.
  • y: number The y axis for the starting point.
  • width: number The rectangles width.
  • heigh: number The rectangles height.
  • draw: array List of drawing operation objects.
    • fill: string The color or style to use inside the rectangle. Default is black #000.
    • stroke: string The color or style to use as the stroke style. Default is black #000.
    • clear: boolean Sets all pixels in the rectangle to transparent.
  • children: array List of child drawing shapes or text. This property is optional.

Example:

rect({
	x: 10,
	y: 10,
	width: 100,
	height: 100,
	draw: [
		{fill: 'purple'}
	],
	children: [
		rect({
			x: 20,
			y: 20,
			width: 50,
			height: 50,
			draw: [
				{fill: 'blue'}
			]
		})
	]
})

line(params = {})

Draws line(s) given an object containing drawing parameters.

params {}:

  • x: number The x axis for the starting point.
  • y: number The y axis for the starting point.
  • style: object The style properties.
    • lineWidth: number The width of the line. Default is 1.
    • lineCap: string The end point of the line. Default is butt. Possible values are butt, round and square.
    • lineJoin: string The type of corner created when two lines meet. Default is miter. Possible values are miter, round and bevel.
    • strokeStyle: string The color or style to use as the stroke style. Default is black #000.
    • lineDash: array A list of numbers that specifies the line dash pattern.
  • points: array List of point objects that specify the x/y coordinates for each point.
  • children: array List of child drawing shapes or text. This property is optional.

Example:

line({
	x: 10,
	y: 10,
	style: {
		lineWidth: 2,
		lineCap: 'square',
		strokeStyle: '#CCCCCC'
	},
	points: [
		{x: 10, y: 10},
		{x: 10, y: 20},
		{x: 20, y: 10},
		{x: 10, y: 10}
	]
})

arc(params = {})

Draws an arc given an object containing drawing parameters.

params {}:

  • x: number The x coordinate of the arc's center.
  • y: number The y coordinate of the arc's center.
  • radius: number The arc's radius.
  • startAngle: number The angle at which the arc starts, measured clockwise from the positive x axis and expressed in radians.
  • endAngle: number The angle at which the arc ends, measured clockwise from the positive x axis and expressed in radians.
  • anticlockwise An optional Boolean which, if true, causes the arc to be drawn counter-clockwise between the two angles. By default it is drawn clockwise.
  • draw: array List of drawing operation objects.
    • fill: string The color or style to use inside the arc.
    • stroke: string The color or style to use as the stroke style.
  • children: array List of child drawing shapes or text. This property is optional.

Example:

arc({
  x: 50,
  y: 50,
  radius: 50,
  startAngle: 0,
  endAngle: 2 * Math.PI,
  false,
  draw: [{fill: 'red'}, {stroke: 'black'}]
})

polygon(params = {})

Draws line(s) given an object containing drawing parameters.

params {}:

  • points: array List of point objects that specify the x/y coordinates for each point of the polygon. Using less than 3 points is a terrible way to draw a line.
  • draw: array List of drawing operation objects.
    • fill: string The color or style to use inside the polygon. If not present, the polygon will not be filled.
    • stroke: string The color or style to use as the stroke style. If not present, the polygon will not have an outline.
  • children: array List of child drawing shapes or text. This property is optional.

Example:

polygon({
	points: [
		{x: 10, y: 0},
		{x: 0, y: 10},
		{x: 0, y: 30},
		{x: 30, y: 30},
		{x: 30, y: 10} // a house shaped polygon
	],
	draw: {
		stroke: '#000',
		fill: '#ccc'
	},
})

text(options = {})

Draws text given an object containing drawing parameters.

params {}:

  • x: number The x axis for the starting point.
  • y: number The y axis for the starting point.
  • value: string The text to draw.
  • font: string The text style. Uses same syntax as the CSS font property.
  • draw: array List of drawing operations objects.
    • fill: string The color or style to fill the text. Default is black #000.
    • stroke: string The color or style to use as the stroke style. Default is black #000.
  • children: array List of child drawing shapes or text. This property is optional.

Example:

text({
	x: 10,
	y: 10,
	value: 'Hello World!',
	font: '18pt Arial',
	draw: [
		{fill: 'white'}
	]
})

image(params = {})

Draws an image given an object containing drawing parameters.

params {}:

  • x: number The x axis for the starting point.
  • y: number The y axis for the starting point.
  • src: CanvasImageSource The image to draw.
  • width: number The width to scale the image to. This property is optional.
  • height: number The height to scale the image to. This property is optional.
  • sx: number The x axis of the source image. This property is optional.
  • sy: number The y axis of the source image. This property is optional.
  • sWidth: number The width of the source image. This property is optional.
  • sHeight: number The height of the source image. This property is optional.

Example:

image({
	x: 10,
	y: 10,
  src: document.querySelector('img')
})

Transformations

Transformations are added as a list to the transformations attribute to drawing shapes and text.

translate: {x: number, y: number}

Moves the canvas origin to a different point.

Example:

	rect({
		transformations: [
      {translate: {x: 10, y: 10}}
    ],
		x: 100,
		y: 100,
		width: 150,
		height: 150,
		draw: [
			{fill: 'purple'}
		]
	})

rotate: number

Rotate the canvas around the current origin.

Example:

	rect({
		transformations: [
		  {rotate: (20*Math.PI/180)}
    ],
		x: 10,
		y: 10,
		width: 150,
		height: 150,
		draw: [
			{fill: 'purple'}
		]
	})

scale: {x: number, y: number}

Scales the drawing bigger or smaller.

Example:

	rect({
		transformations: [
		  {scale: {x: 2, y: 2}},
    ],
		x: 10,
		y: 10,
		width: 150,
		height: 150,
		draw: [
			{fill: 'purple'}
		]
	})

Combining transformations

Example:

Rotate around the point (100, 100) and draw a 50x50px box centered there:

	rect({
		transformations: [
      {translate: {x: 100, y: 100}},
      {rotate: (20*Math.PI/180)}
    ],
		x: -25, // At this point, {x: 0, y: 0} is a point on position {x: 100, y: 100} of the canvas
		y: -25,
		width: 50,
		height: 50,
		draw: [
			{fill: 'purple'}
		]
	})

cycle-canvas's People

Contributors

donaldpipowitch avatar eoogbe avatar grozen avatar pete-otaqui avatar raquelxmoss avatar schempy avatar vfeskov avatar widdershin avatar zestree avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cycle-canvas's Issues

Decide how to proceed with transformations

I added this as a comment on @schempy's PR last week, but it was after it was closed and I think people missed it (#4 (comment)).

Here it is again:

I ran into a problem when trying to update the flappy bird example app to use translate and rotate. The problem is that the way these transformations are performed in a canvas is reminiscent of how these things have been done in computer graphics since the 90s (I would guess). That means there's a hidden state in the canvas with all the transformations done so far. You can see it in the example for rotate in MDN: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations#A_rotate_example

There you'll see that, to rotate the square around the center where it's going to be drawn, you have to:

  • translate to the middle of where you'll draw the square (offsets + half the width/height)
  • rotate the angle you want
  • translate back (just -1 * the numbers of the first step)
  • draw the square

I dislike this as it creates a cognitive overhead where you have to mentally do the transformations in your head to figure out if they are correct (specially when they are not, as there's a good chance the thing you wanted to draw is going to be off screen and it's harder to figure out which step is the problem).

Some options to approach this problem that I can think of:

  1. just expose what the canvas api implements, and let the user of the driver deal with transformations and states;
  2. expose the 'low level' api for now, and build abstractions on top of it (for example a rotateAround that does that rotate around a specified coordiante)
  3. come up with a way to delay applying the transformations, and then process them all in one go so they 'do the right thing'
  4. read up and see if anyone figure out a way to do these type of transformations in a functional way.

How it could like for 1:

group([
  saveState(),
  translate({x: 55, y: 55}),
  rotate({angle: 0.5}),
  translate({x: -55, y: -55}),
  rect({x: 50, y: 50, width: 10, height: 10, draw: [{fill: 'black'}]),
  restoreState()
])

How it could like for 2:

rotateAround(
  {x: 55, y: 55, angle: 0.5},
  [
    rect({x: 50, y: 50, width: 10, height: 10, draw: [{fill: 'black'}])
  ]
)

I'm not sure how it could look like for 3 or 4. One thing that I believe is that these approaches are harder to do having the transformations being attributes to regular draw elements (they would have to be a list to allow the process demonstrated here).

Thoughts?

makeCanvasDriver does not work without its second argument

While this is not clearly documented in any way (which is a thing that should also be fixed, possibly while fixing this), it seems very sensible that makeCanvasDriver's second argument should be optional, assuming you supply its first argument.

The reasoning behind this is that the first argument causes the driver to attach to an existing canvas, which already has its own width and height, making specifying them redundant if not counterproductive.

What happens presently is that the builder method throws a type error as it assumes the second argument has a value and tries to access its properties.

Error when using xs.of instead of Observale.of

@Widdershin

TypeError: xstream_1.default.fromObservable(sinks[name]).subscribe is not a function.

The code in main.js:

import { run } from '@cycle/run';
import { makeCanvasDriver, rect, text } from 'cycle-canvas';
import xs, { stream } from 'xstream';

function main(sources) {
const obj = {
Canvas: xs.of(
rect({
x: 10, y: 10, width: 160, height: 100,
draw: [{ fill: 'purple' }],
children: [
text({x: 15, y: 25, value: 'Hello World!', font: '18pt Arial', draw: [{ fill: 'white' }]})
]
})
)
};
return obj
}
const drivers = {
Canvas: makeCanvasDriver(null, { width: 800, height: 600 })
};
run(main, drivers);

Add support for images

Often in game development you want to be able to use external images. Canvas supports drawing images, so we can as well.

Add more examples

Build some cool little canvas examples to show how to use this library:

Off the top of my head, it would be cool to have some retro games like Pong or Asteroids.

Anyone got any other ideas?

Allow usage with dynamically created canvas

From Gitter (source):

I'd like to create a small drawing app with Cycle and create a canvas like this as a VDOM. I'd like to use cycle-canvas, but it looks like I need to create a canvas beforehand or let cycle-canvas create on for me, but then I don't have full control of it. I guess I'd need a way to add the canvas element to the sink in order to create a canvas dynamically...? (And is there a better way to get the element as an observable than this?)

Dicontinued? Advice on canvas development?

Hi! I am looking into a cycle.js solution for canvas. However, this package indicates being highly experimental and hasn't been updated for years. Has someone collected experience with it on production or is there another solution out there? If not, do you think that this indicates that cycle.js/RFP isn't appropriate for working with canvas and what better approaches/paradigms do you suggest? I really like cycle.js's mental model and would like to know the reason of its lack of adoption when working with html canvas. Any advices?

Thank you!

Release v0.1.0

Here is a list of what I think we need to do to release v0.1.0:

package.json

  • Replace all instances of "cycle-hot-reloading-example" with "cycle-canvas"
  • Set version to v0.1.0
  • Everything in dependencies should be in devDependencies. (we don't actually use any of it in the driver, just examples)
  • Add some scripts to compile our es6 to es5 prior to release. These will hook into the npm publish step
   "scripts": {
     "start": "babel-node ./examples/flappy-bird/server.js",
     "test": "mocha --compilers js:babel-register",
     "bundle": "browserify ./examples/flappy-bird/index.js -t babelify -t uglifyify -o bundle.js",
+    "precompile-lib": "rm -rf lib/ && mkdir -p lib",
+    "compile-lib": "babel src -d lib",
+    "prepublish": "npm run compile-lib"
   },
  • Change the main file to point at the compiled es5 code (src/canvas-driver.js -> lib/canvas-driver.js)
  • Add a files array with lib/ so that it gets included in the compiled package
+   "files": [
+     "lib/"
+   ],

After all that is done we need to test that the module can be installed locally.

  • Run npm link in the cycle-canvas directory
  • Copy one of the example projects to a directory outside the cycle-canvas directory
  • Run npm link cycle-canvas in the copied example directory
  • If you ls node_modules/cycle-canvas, you should see the cycle-canvas code, including the lib directory
  • Change the example to import from cycle-canvas instead of the relative path.
  • You should be able to run the example and it should import cycle-canvas successfully

After we've tested that we can install our module locally we're almost ready for release.

  • Push all changes up and check that travis passes
  • git tag v0.1.0
  • git push origin master --tags
  • npm adduser (you should only have to do this the first time and you'll need an npm account)
  • npm publish (exciting!)
  • try npm install cycle-canvas in the copied example project
  • celebrate! ๐ŸŽ‰
  • tell the world!

I think that's everything, but there's a good chance I've missed something. Give it a go and let me know how you get on!

Feelings about eslint?

Finally getting back on the bandwagon here, I was all rearing to write some code. However, I realized that while in most of my recent endeavors I've been using ESLint for hints and consistency, this is not the sort of thing I feel comfortable subjecting the project to without consulting others.

So, how do we feel about ESLint? Would it be all right to incorporate it as a development dependency?

Document API

Before we release v0.1 it would be great to add some documentation to the README around the various options that are currently available.

@schempy @apretto do either of you feel like taking a crack at this? I probably won't get to it this week (currently traveling, will only be home for a day before I have more traveling to do). I'm more than happy to help though.

I think this is the last thing we need to do before our first release.

Elements hierarchy meaning

What element hierarchy is meant to represent?

In other words, how

rect({children: [
  rect({
    x: 10, y: 10, width: 160, height: 100,
    children: [
      text({x: 15, y: 25, ...})
    ]
  })
]})

is different from

rect({children: [
  rect({
    x: 10, y: 10, width: 160, height: 100,
  })
  text({x: 15, y: 25, ...})
]})

In DOM tree there is a particular meaning of node hierarchies described by "block model".
What it represents here?

  1. Some overlay rules?
  2. Grouping for transformations?
  3. User-level classification stuff?
  4. Nothing but reserved for future?

Add support for polygons

I propose to add the polygon function to the driver:

I'll start an implementation for it, but any input is welcome.

The signature I'm considering will look something like this:

polygon({
  transformations: [],
  points: [{x: 10, y: 10}, {x: 20, y: 10}, {x: 15, y: 17}],
  draw: [
    {fill: 'purple'}
  ]
}); // draw a purple triangle

Also, perhaps we should consider removing the x and y parameter from the line function, and have that point being the first one in the list of points, just like I propose here in the polygon.

Thoughts on this last idea?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.