diff --git a/README.md b/README.md index 36453b0..c953b45 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,14 @@ npm start - `(range start end ~step)` - `(get item key)` Gets an object's parameter with name. - `(set item key val)` Sets an object's parameter with name as value. +- `(of h ...keys)` - `(frame)` Returns a rect of the frame. - `(center)` Returns a position of the center of the frame. - `(scale rect w h)` -- `(resize ~w ~h)` +- `(resize w h)` Resizes the canvas to target w and h, returns the rect. +- `(rescale w h)` Rescales the canvas to target ratio of w and h, returns the rect. - `(crop rect)` - `(clone a b)` -- `(of h ...keys)` - `(theme variable ~el)` - `(gradient [x1 y1 x2 y2] ~colors 'black'])` - `(pixels rect fn q)` @@ -79,10 +80,9 @@ npm start - `(open path)` Imports a graphic file and resizes the frame. - `(folder ~path)` Returns the content of a folder path. - `(exit ~force)` Exits Ronin. -- `(ronin)` - `(time)` Returns timestamp in milliseconds. - `(animate ~play)` Toggles animation. -- `(js)` +- `(js)` Javascript interop. - `(test name a b)` ## Extras diff --git a/desktop/sources/scripts/library.js b/desktop/sources/scripts/library.js index e9b4b03..7bd7b3e 100644 --- a/desktop/sources/scripts/library.js +++ b/desktop/sources/scripts/library.js @@ -8,7 +8,7 @@ function Library (ronin) { this.export = (path, format = 'image/png', quality = 1.0) => { // Exports a graphic file with format. if (!path) { console.warn('Missing export path'); return path } var dataUrl = ronin.surface.el.toDataURL(format, quality) - const data = dataUrl.replace(/^data:image\/png;base64,/, '') + const data = dataUrl.replace(/^data:image\/png;base64,/, '').replace(/^data:image\/jpeg;base64,/, '') fs.writeFileSync(path, data, 'base64') return path } @@ -167,8 +167,13 @@ function Library (ronin) { }) } - this.reduce = (fn, arr, acc = 0) => { - return arr.reduce(fn, acc) + this.reduce = async (fn, arr, acc) => { + const length = arr.length + let result = acc === undefined ? subject[0] : acc + for (let i = acc === undefined ? 1 : 0; i < length; i++) { + result = await fn(result, arr[i], i, arr) + } + return result } this.len = (item) => { // Returns the length of a list. @@ -212,6 +217,12 @@ function Library (ronin) { return item[key] } + this.of = (h, ...keys) => { + return keys.reduce((acc, key) => { + return acc[key] + }, h) + } + // Frame this.frame = () => { // Returns a rect of the frame. @@ -227,12 +238,22 @@ function Library (ronin) { return { x: rect.x, y: rect.y, w: rect.w * w, h: rect.h * h } } - this.resize = async (w = 1, h = 1) => { - const rect = w <= 1 || h <= 1 ? { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } : { x: 0, y: 0, w, h } + this.resize = async (w, h, fit = true) => { // Resizes the canvas to target w and h, returns the rect. + const rect = { x: 0, y: 0, w, h } const a = document.createElement('img') const b = document.createElement('img') a.src = ronin.surface.el.toDataURL() - ronin.surface.resizeImage(a, b) + await ronin.surface.resizeImage(a, b) + ronin.surface.resize(rect, fit) + return ronin.surface.draw(b, rect) + } + + this.rescale = async (w, h) => { // Rescales the canvas to target ratio of w and h, returns the rect. + const rect = { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } + const a = document.createElement('img') + const b = document.createElement('img') + a.src = ronin.surface.el.toDataURL() + await ronin.surface.resizeImage(a, b) ronin.surface.resize(rect, true) return ronin.surface.draw(b, rect) } @@ -241,21 +262,11 @@ function Library (ronin) { return ronin.surface.crop(rect) } - // Copy/Paste - this.clone = (a, b) => { ronin.surface.clone(a, b) return [a, b] } - // TODO: Should remove (of) for (get)? - - this.of = (h, ...keys) => { - return keys.reduce((acc, key) => { - return acc[key] - }, h) - } - this.theme = (variable, el = document.documentElement) => { // ex. (theme "f_main") -> :root { --f_main: "#fff" } return getComputedStyle(el).getPropertyValue(`--${variable}`) @@ -316,20 +327,17 @@ function Library (ronin) { ronin.source.quit(force) } - // Client - this.ronin = ronin - - // Livecoding this.time = () => { // Returns timestamp in milliseconds. - return Date.now + return Date.now() } this.animate = (play = true) => { // Toggles animation. ronin.animate(play) } - // javascript interop - this.js = window + this.js = () => { // Javascript interop. + return window + } this.test = (name, a, b) => { if (`${a}` !== `${b}`) { @@ -339,4 +347,11 @@ function Library (ronin) { } return a === b } + + this.benchmark = async (fn) => { // logs time taken to execute a function + const start = Date.now() + const result = await fn() + console.log(`time taken: ${Date.now() - start}ms`) + return result + } } diff --git a/desktop/sources/scripts/lisp.js b/desktop/sources/scripts/lisp.js index 3a38a83..7459f97 100644 --- a/desktop/sources/scripts/lisp.js +++ b/desktop/sources/scripts/lisp.js @@ -33,29 +33,21 @@ function Lisp (input, lib) { }, def: function (input, context) { const identifier = input[1].value - const value = (input[2].type === TYPES.string) ? input[3] : input[2] - if (input[2].type === TYPES.string) { - // docstring - console.log(input[2].value) - } + const value = input[2].type === TYPES.string && input[3] ? input[3] : input[2] context.scope[identifier] = interpret(value, context) return value }, defn: function (input, context) { - const identifier = input[1].value - const argumentNames = (input[2].type === TYPES.string) ? input[3] : input[2] - const functionBody = (input[2].type === TYPES.string) ? input[4] : input[3] - if (input[2].type === TYPES.string) { - // docstring - console.log(input[2].value) - } - context.scope[identifier] = async function () { + const fnName = input[1].value + const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2] + const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3] + context.scope[fnName] = async function () { const lambdaArguments = arguments - const lambdaScope = argumentNames.reduce(function (acc, x, i) { + const lambdaScope = fnParams.reduce(function (acc, x, i) { acc[x.value] = lambdaArguments[i] return acc }, {}) - return interpret(functionBody, new Context(lambdaScope, context)) + return interpret(fnBody, new Context(lambdaScope, context)) } }, lambda: function (input, context) { @@ -80,7 +72,10 @@ function Lisp (input, lib) { if (input.length > 0 && input[0].value in special) { return special[input[0].value](input, context) } - const list = await Promise.all(input.map(function (x) { return interpret(x, context) })) + const list = [] + for (let i = 0; i < input.length; i++) { + list.push(await interpret(input[i], context)) + } return list[0] instanceof Function ? list[0].apply(undefined, list.slice(1)) : list } diff --git a/desktop/sources/scripts/ronin.js b/desktop/sources/scripts/ronin.js index ea59299..1e229c7 100644 --- a/desktop/sources/scripts/ronin.js +++ b/desktop/sources/scripts/ronin.js @@ -15,7 +15,6 @@ function Ronin () { this.el.id = 'ronin' this.theme = new Theme(defaultTheme) - this.source = new Source(this) this.commander = new Commander(this) this.surface = new Surface(this) diff --git a/desktop/sources/scripts/surface.js b/desktop/sources/scripts/surface.js index a1bd5eb..36ab155 100644 --- a/desktop/sources/scripts/surface.js +++ b/desktop/sources/scripts/surface.js @@ -15,6 +15,7 @@ function Surface (ronin) { this._guide.addEventListener('mousedown', ronin.commander.onMouseDown, false) this._guide.addEventListener('mousemove', ronin.commander.onMouseMove, false) this._guide.addEventListener('mouseup', ronin.commander.onMouseUp, false) + // this.context.imageSmoothingEnabled = false this.context.scale(this.ratio, this.ratio) this.guide.scale(this.ratio, this.ratio) } @@ -159,28 +160,35 @@ function Surface (ronin) { } this.resize = function (size, fit = false) { + const frame = this.getFrame() + if (frame.w === size.w && frame.h === size.h) { return } console.log('Surface', `Resize: ${size.w}x${size.h}`) this.el.width = size.w this.el.height = size.h - this.el.style.width = size.w + 'px' - this.el.style.height = size.h + 'px' + this.el.style.width = (size.w / this.ratio) + 'px' + this.el.style.height = (size.h / this.ratio) + 'px' this._guide.width = size.w this._guide.height = size.h - this._guide.style.width = size.w + 'px' - this._guide.style.height = size.h + 'px' + this._guide.style.width = (size.w / this.ratio) + 'px' + this._guide.style.height = (size.h / this.ratio) + 'px' if (fit === true) { this.fitWindow(size) } } + this.getFrame = function () { + return { x: 0, y: 0, w: this.el.width, h: this.el.height, t: 'rect' } + } + this.fitWindow = function (size) { const win = require('electron').remote.getCurrentWindow() const pad = { w: ronin.commander.isVisible === true ? 400 : 60, h: 60 } - win.setSize(size.w + pad.w, size.h + pad.h, false) + if (size.w < 10 || size.h < 10) { return } + win.setSize(Math.floor((size.w / this.ratio) + pad.w), Math.floor((size.h / this.ratio) + pad.h), true) } this.maximize = function () { - this.resize({ x: 0, y: 0, w: window.innerWidth - 60, h: window.innerHeight - 60, t: 'rect' }) + this.resize({ x: 0, y: 0, w: (window.innerWidth * this.ratio) - 60, h: (window.innerHeight * this.ratio) - 60, t: 'rect' }) } this.onResize = function () { @@ -191,10 +199,6 @@ function Surface (ronin) { ronin.log(`resize ${f.w}x${f.h}`) } - this.getFrame = function () { - return { x: 0, y: 0, w: this.el.width, h: this.el.height, t: 'rect' } - } - this.getCrop = function (rect) { const newCanvas = document.createElement('canvas') newCanvas.width = rect.w @@ -203,30 +207,34 @@ function Surface (ronin) { return newCanvas } - this.resizeImage = function (src, dst, type = 'image/jpeg', quality = 0.92) { - const tmp = new Image() - let canvas - let context - let cW = src.naturalWidth - let cH = src.naturalHeight - tmp.src = src.src - tmp.onload = function () { - canvas = document.createElement('canvas') - cW /= 2 - cH /= 2 - if (cW < src.width) { - cW = src.width + this.resizeImage = function (src, dst, type = 'image/png', quality = 1.0) { + return new Promise(resolve => { + const tmp = new Image() + let canvas + let context + let cW = src.naturalWidth + let cH = src.naturalHeight + tmp.src = src.src + // resolve() + tmp.onload = () => { + canvas = document.createElement('canvas') + cW /= 2 + cH /= 2 + if (cW < src.width) { + cW = src.width + } + if (cH < src.height) { + cH = src.height + } + canvas.width = cW + canvas.height = cH + context = canvas.getContext('2d') + context.drawImage(tmp, 0, 0, cW, cH) + dst.src = canvas.toDataURL(type, quality) + if (cW <= src.width || cH <= src.height) { return resolve() } + tmp.src = dst.src + return resolve() } - if (cH < src.height) { - cH = src.height - } - canvas.width = cW - canvas.height = cH - context = canvas.getContext('2d') - context.drawImage(tmp, 0, 0, cW, cH) - dst.src = canvas.toDataURL(type, quality) - if (cW <= src.width || cH <= src.height) { return } - tmp.src = dst.src - } + }) } } diff --git a/examples/animate.lisp b/examples/animate.lisp index b43d696..ed3eadf 100644 --- a/examples/animate.lisp +++ b/examples/animate.lisp @@ -1,12 +1,13 @@ ; animate ( + (clear) (def t (sin (div (time) 100))) - (def pos (add 200 (mul 30 t))) + (def pos (add 200 30 (mul 30 t))) (defn square (a) (rect a a a a)) (stroke (square pos) 1 "red") - (animate) - ;(animate false) to stop animation + ; set false to stop + (animate true) ) \ No newline at end of file diff --git a/examples/basics.lisp b/examples/basics.lisp new file mode 100644 index 0000000..c1832ea --- /dev/null +++ b/examples/basics.lisp @@ -0,0 +1,9 @@ +; basics +( + ; define a variable + (def a 25) + (echo a) + + ; define a function + (defn add-two (a) (add 2 a)) + (echo (add-two 4))) \ No newline at end of file diff --git a/examples/dejong.lisp b/examples/dejong.lisp index 9d19d27..9e3eac6 100644 --- a/examples/dejong.lisp +++ b/examples/dejong.lisp @@ -1,13 +1,15 @@ ; dejong attractor ( - (clear) - (defn point (x y color) (fill (circle x y 0.1) color)) + (clear) + (defn point (x y color) + (fill (rect x y 1 1) color)) + (defn _dejong (x y a b c d) (rest ((point (add 300 (mul 100 x)) (add 400 (mul 100 y)) - "rgba(255,0,0,0.5)") + "red") (add (sin (mul a y)) (mul x (cos (mul b x)))) (add (mul x (sin (mul x c))) (cos (mul d y))) )) @@ -23,5 +25,11 @@ (2 1) ) ) - (dejong 128000 1.4 -2.3 2.4 -2.1) + (benchmark (lambda () + (dejong 12800 + (random -2 2) + (random -2 2) + (random -2 2) + (random -2 2) + ))) ) diff --git a/examples/import.lisp b/examples/import.lisp new file mode 100644 index 0000000..36841c2 --- /dev/null +++ b/examples/import.lisp @@ -0,0 +1,5 @@ +( +(def a (import + "../static/crystal.jpg" + (rect 0 0 400 400))) +(echo a)) \ No newline at end of file