Improved helpers
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
/* global Commander */
|
||||
/* global Surface */
|
||||
/* global Library */
|
||||
/* global Lisp */
|
||||
/* global Lain */
|
||||
/* global Image */
|
||||
/* global requestAnimationFrame */
|
||||
|
||||
@@ -21,7 +21,7 @@ function Client () {
|
||||
this.commander = new Commander(this)
|
||||
this.surface = new Surface(this)
|
||||
this.library = new Library(this)
|
||||
this.lisp = new Lisp(this.library)
|
||||
this.lain = new Lain(this.library)
|
||||
|
||||
this.bindings = {}
|
||||
|
||||
@@ -226,6 +226,7 @@ function Client () {
|
||||
}
|
||||
const wh = rect.w + ' ' + rect.h
|
||||
const d = Math.sqrt(((line.a.x - line.b.x) * (line.a.x - line.b.x)) + ((line.a.y - line.b.y) * (line.a.y - line.b.y))).toFixed(2)
|
||||
const r = d
|
||||
const a = Math.atan2(pos.y - line.a.y, pos.x - line.a.x).toFixed(2)
|
||||
const circle = {
|
||||
cx: line.a.x,
|
||||
@@ -239,6 +240,6 @@ function Client () {
|
||||
sa: 0,
|
||||
ea: a
|
||||
}
|
||||
return { x, y, xy, wh, d, a, line, rect, pos, size, circle, arc, type, 'is-down': type !== 'mouse-up' ? true : null }
|
||||
return { x, y, xy, wh, d, r, a, line, rect, pos, size, circle, arc, type, 'is-down': type !== 'mouse-up' ? true : null }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,21 +30,23 @@ function Commander (client) {
|
||||
this._input.onkeydown = (e) => {
|
||||
if (e.keyCode === 9 || e.which === 9) { e.preventDefault(); this.inject(' ') }
|
||||
}
|
||||
client.surface.maximize()
|
||||
}
|
||||
|
||||
this.start = function () {
|
||||
this.setStatus('Ready.')
|
||||
this.load(this.splash)
|
||||
this.show()
|
||||
this._input.value = this.splash
|
||||
setTimeout(() => { this.run() }, 1000)
|
||||
this.setStatus('Ready.')
|
||||
}
|
||||
|
||||
this.run = (txt = this._input.value) => {
|
||||
if (this._input.value.indexOf('$') > -1) { txt = this.clean(txt) }
|
||||
client.bindings = {}
|
||||
if (this._input.value.trim() === '') {
|
||||
client.surface.maximize()
|
||||
|
||||
}
|
||||
client.lisp.run(txt)
|
||||
client.lain.run(`(${txt})`)
|
||||
this.feedback()
|
||||
}
|
||||
|
||||
@@ -180,7 +182,7 @@ function Commander (client) {
|
||||
if (word === 'line') { return `(line ${shape.a.x} ${shape.a.y} ${shape.b.x} ${shape.b.y})` }
|
||||
if (word === 'circle') { return `(circle ${shape.cx} ${shape.cy} ${shape.r})` }
|
||||
if (word === 'arc') { return `(arc ${shape.cx} ${shape.cy} ${shape.r} ${shape.sa} ${shape.ea})` }
|
||||
if (word === 'x' || word === 'y' || word === 'xy' || word === 'wh' || word === 'a') { return `${shape}` }
|
||||
if (word === 'x' || word === 'y' || word === 'xy' || word === 'wh' || word === 'a' || word === 'r') { return `${shape}` }
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -240,7 +242,7 @@ function Commander (client) {
|
||||
const name = this.getCurrentFunction()
|
||||
const fn = client.library[name]
|
||||
if (!fn) { return }
|
||||
const fnString = fn.toString().replace('async ', '')
|
||||
const fnString = fn.toString()
|
||||
if (fnString.indexOf(') => {') < 0) { return }
|
||||
const fnParams = fnString.split(') => {')[0].substr(1).split(',').reduce((acc, item) => { return `${acc}${item.indexOf('=') > -1 ? '~' + item.split('=')[0].trim() : item} ` }, '').trim()
|
||||
return `(${(name + ' ' + fnParams).trim()})`
|
||||
@@ -248,17 +250,14 @@ function Commander (client) {
|
||||
|
||||
// Splash
|
||||
|
||||
this.splash = `
|
||||
; Ronin v2.40
|
||||
(clear)
|
||||
this.splash = `; Ronin v2.50
|
||||
|
||||
(def logo-path "M60,60 L195,60 A45,45 0 0,1 240,105 A45,45 0 0,1 195,150 L60,150 M195,150 A45,45 0 0,1 240,195 L240,240 ")
|
||||
(def pos-x
|
||||
(mul frame:c 0.25))
|
||||
(def pos-y
|
||||
(sub frame:m 150))
|
||||
;
|
||||
(clear)
|
||||
(resize 600 600)
|
||||
(stroke
|
||||
(svg pos-x pos-y logo-path) theme:f_high 5)
|
||||
`
|
||||
(svg 140 140 logo-path) "black" 7)`
|
||||
|
||||
function insert (str, add, i) {
|
||||
return [str.slice(0, i), `${add}`, str.slice(i)].join('')
|
||||
|
||||
145
scripts/lib/lain.js
Normal file
145
scripts/lib/lain.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict'
|
||||
|
||||
// In the real world, it didn’t matter if I was there or not.
|
||||
// When I realized that, I was no longer afraid of losing my body.
|
||||
|
||||
function Lain (lib = {}) {
|
||||
const TYPES = { identifier: 0, number: 1, string: 2, bool: 3, symbol: 4 }
|
||||
|
||||
const Context = function (scope, parent) {
|
||||
this.scope = scope
|
||||
this.parent = parent
|
||||
this.get = function (identifier) {
|
||||
if (identifier in this.scope) {
|
||||
return this.scope[identifier]
|
||||
} else if (this.parent !== undefined) {
|
||||
return this.parent.get(identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const special = {
|
||||
let: function (input, context) {
|
||||
const letContext = input[1].reduce(function (acc, x) {
|
||||
acc.scope[x[0].value] = interpret(x[1], context)
|
||||
return acc
|
||||
}, new Context({}, context))
|
||||
return interpret(input[2], letContext)
|
||||
},
|
||||
def: function (input, context) {
|
||||
if (input.length !== 3) { console.warn('Lain', 'Invalid definition.'); return }
|
||||
const identifier = input[1].host ? input[1].host : input[1].value
|
||||
if (input[1].host) {
|
||||
if (!context.scope[identifier]) { context.scope[identifier] = {} }
|
||||
context.scope[identifier][input[1].value] = interpret(input[2], context)
|
||||
return context.scope[identifier][input[1].value]
|
||||
}
|
||||
context.scope[identifier] = interpret(input[2], context)
|
||||
return context.scope[identifier]
|
||||
},
|
||||
defn: function (input, context) {
|
||||
const identifier = input[1].value
|
||||
if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) }
|
||||
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[identifier] = function () {
|
||||
const lambdaArguments = arguments
|
||||
const lambdaScope = fnParams.reduce(function (acc, x, i) {
|
||||
acc[x.value] = lambdaArguments[i]
|
||||
return acc
|
||||
}, {})
|
||||
return interpret(fnBody, new Context(lambdaScope, context))
|
||||
}
|
||||
},
|
||||
λ: function (input, context) {
|
||||
return function () {
|
||||
const lambdaArguments = arguments
|
||||
const lambdaScope = input[1].reduce(function (acc, x, i) {
|
||||
acc[x.value] = lambdaArguments[i]
|
||||
return acc
|
||||
}, {})
|
||||
return interpret(input[2], new Context(lambdaScope, context))
|
||||
}
|
||||
},
|
||||
if: function (input, context) {
|
||||
return interpret(input[1], context) ? interpret(input[2], context) : input[3] ? interpret(input[3], context) : []
|
||||
}
|
||||
}
|
||||
|
||||
const interpretList = function (input, context) {
|
||||
if (input.length > 0 && input[0].value in special) {
|
||||
return special[input[0].value](input, context)
|
||||
}
|
||||
const list = []
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
if (input[i].type === TYPES.symbol) {
|
||||
if (input[i].host) {
|
||||
const host = context.get(input[i].host)
|
||||
if (host) {
|
||||
list.push(host[input[i].value])
|
||||
}
|
||||
} else {
|
||||
list.push(obj => obj[input[i].value])
|
||||
}
|
||||
} else {
|
||||
list.push(interpret(input[i], context))
|
||||
}
|
||||
}
|
||||
return list[0] instanceof Function ? list[0].apply(undefined, list.slice(1)) : list
|
||||
}
|
||||
|
||||
const interpret = function (input, context) {
|
||||
if (!input) { console.warn('Lain', context.scope); return null }
|
||||
if (context === undefined) {
|
||||
return interpret(input, new Context(lib))
|
||||
} else if (input instanceof Array) {
|
||||
return interpretList(input, context)
|
||||
} else if (input.type === TYPES.identifier) {
|
||||
return context.get(input.value)
|
||||
} else if (input.type === TYPES.number || input.type === TYPES.symbol || input.type === TYPES.string || input.type === TYPES.bool) {
|
||||
return input.value
|
||||
}
|
||||
}
|
||||
|
||||
const categorize = function (input) {
|
||||
if (!isNaN(parseFloat(input))) {
|
||||
return { type: TYPES.number, value: parseFloat(input) }
|
||||
} else if (input[0] === '"' && input.slice(-1) === '"') {
|
||||
return { type: TYPES.string, value: input.slice(1, -1) }
|
||||
} else if (input[0] === ':') {
|
||||
return { type: TYPES.symbol, value: input.slice(1) }
|
||||
} else if (input.indexOf(':') > 0) {
|
||||
return { type: TYPES.symbol, host: input.split(':')[0], value: input.split(':')[1] }
|
||||
} else if (input === 'true' || input === 'false') {
|
||||
return { type: TYPES.bool, value: input === 'true' }
|
||||
} else {
|
||||
return { type: TYPES.identifier, value: input }
|
||||
}
|
||||
}
|
||||
|
||||
const parenthesize = function (input, list) {
|
||||
if (list === undefined) { return parenthesize(input, []) }
|
||||
const token = input.shift()
|
||||
if (token === undefined) {
|
||||
return list.pop()
|
||||
} else if (token === '(') {
|
||||
list.push(parenthesize(input, []))
|
||||
return parenthesize(input, list)
|
||||
} else if (token === ')') {
|
||||
return list
|
||||
} else {
|
||||
return parenthesize(input, list.concat(categorize(token)))
|
||||
}
|
||||
}
|
||||
|
||||
const tokenize = function (input) {
|
||||
const i = input.replace(/^[\s]*;.*\n?/gm, '').split('"')
|
||||
return i.map(function (x, i) {
|
||||
return i % 2 === 0 ? x.replace(/\(/g, ' ( ').replace(/\)/g, ' ) ') : x.replace(/ /g, '!ws!')
|
||||
}).join('"').trim().split(/\s+/).map(function (x) { return x.replace(/!ws!/g, ' ') })
|
||||
}
|
||||
|
||||
this.run = (input) => {
|
||||
return interpret(parenthesize(tokenize(input)))
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ function Theme (client) {
|
||||
this.active[key] = hex
|
||||
}
|
||||
|
||||
this.read = (key) => {
|
||||
this.get = (key) => {
|
||||
return this.active[key]
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,23 @@
|
||||
|
||||
function Library (client) {
|
||||
// IO
|
||||
this.open = async (name, scale = 1) => { // Import a graphic and scale canvas to fit.
|
||||
this.open = (name, scale = 1) => { // Import a graphic and scale canvas to fit.
|
||||
const img = client.cache.get(name)
|
||||
if (!img) { client.log('No data for ' + name); return }
|
||||
const rect = this.rect(0, 0, img.width * scale, img.height * scale)
|
||||
await this.resize(rect.w, rect.h).then(this.import(name, rect))
|
||||
this.resize(rect.w, rect.h)
|
||||
this.import(name, rect)
|
||||
return rect
|
||||
}
|
||||
|
||||
this.import = async (name, shape, alpha = 1) => { // Imports a graphic file with format.
|
||||
this.import = (name, shape, alpha = 1) => { // Imports a graphic file with format.
|
||||
const img = client.cache.get(name)
|
||||
if (!img) { client.log('No data for ' + name); return }
|
||||
client.surface.draw(img, shape, alpha)
|
||||
return shape || this.rect(0, 0, img.width, img.height)
|
||||
}
|
||||
|
||||
this.export = async (name = 'ronin', type = 'image/png', quality = 1.0) => { // Exports a graphic file with format.
|
||||
this.export = (name = 'ronin', type = 'image/png', quality = 1.0) => { // Exports a graphic file with format.
|
||||
const ext = type === 'image/png' ? name + '.png' : name + '.jpg'
|
||||
client.source.write(name, ext, client.surface.el.toDataURL(type, 1.0), type)
|
||||
}
|
||||
@@ -79,36 +80,36 @@ function Library (client) {
|
||||
|
||||
// Frame
|
||||
|
||||
this.resize = async (w = client.surface.bounds().w, h = client.surface.bounds().h, fit = true) => { // Resizes the canvas to target w and h, returns the rect.
|
||||
this.resize = (w = client.surface.bounds().w, h = client.surface.bounds().h, fit = true) => { // Resizes the canvas to target w and h, returns the rect.
|
||||
if (w === this['get-frame']().w && h === this['get-frame']().h) { return }
|
||||
const rect = { x: 0, y: 0, w, h }
|
||||
const a = document.createElement('img')
|
||||
const b = document.createElement('img')
|
||||
a.src = client.surface.el.toDataURL()
|
||||
await client.surface.resizeImage(a, b)
|
||||
client.surface.resizeImage(a, b)
|
||||
client.surface.resize(rect, fit)
|
||||
return client.surface.draw(b, rect)
|
||||
}
|
||||
|
||||
this.rescale = async (w = 1, h) => { // Rescales the canvas to target ratio of w and h, returns the rect.
|
||||
this.rescale = (w = 1, h) => { // Rescales the canvas to target ratio of w and h, returns the rect.
|
||||
const rect = { x: 0, y: 0, w: this['get-frame']().w * w, h: this['get-frame']().h * (h || w) }
|
||||
const a = document.createElement('img')
|
||||
const b = document.createElement('img')
|
||||
a.src = client.surface.el.toDataURL()
|
||||
await client.surface.resizeImage(a, b)
|
||||
client.surface.resizeImage(a, b)
|
||||
client.surface.resize(rect, true)
|
||||
return client.surface.draw(b, rect)
|
||||
}
|
||||
|
||||
this.crop = async (rect = this['get-frame']()) => { // Crop canvas to rect.
|
||||
this.crop = (rect = this['get-frame']()) => { // Crop canvas to rect.
|
||||
return client.surface.crop(rect)
|
||||
}
|
||||
|
||||
this.copy = async (rect = this['get-frame']()) => { // Copy a section of the canvas.
|
||||
this.copy = (rect = this['get-frame']()) => { // Copy a section of the canvas.
|
||||
return client.surface.copy(rect)
|
||||
}
|
||||
|
||||
this.paste = async (copy, rect = this['get-frame']()) => { // Paste a section of the canvas.
|
||||
this.paste = (copy, rect = this['get-frame']()) => { // Paste a section of the canvas.
|
||||
return client.surface.paste(copy, rect)
|
||||
}
|
||||
|
||||
@@ -123,6 +124,7 @@ function Library (client) {
|
||||
}
|
||||
|
||||
this.view = (a, b) => { // View a part of the canvas.
|
||||
if (!a || !b) { return }
|
||||
this.guide({ a: { x: a.x, y: a.y }, b: { x: b.x, y: b.y } })
|
||||
this.guide(a)
|
||||
this.guide(b)
|
||||
@@ -142,8 +144,8 @@ function Library (client) {
|
||||
return this.color(this.floor(sum[0] / count), this.floor(sum[1] / count), this.floor(sum[2] / count))
|
||||
}
|
||||
|
||||
this.orient = async (deg = 0) => { // Orient canvas with angle in degrees.
|
||||
const copy = await this.copy()
|
||||
this.orient = (deg = 0) => { // Orient canvas with angle in degrees.
|
||||
const copy = this.copy()
|
||||
const frame = this['get-frame']()
|
||||
const mode = Math.floor(deg / 90) % 4
|
||||
const offset = { x: [0, 0, -frame.w, -frame.w], y: [0, -frame.h, -frame.h, 0] }
|
||||
@@ -157,8 +159,8 @@ function Library (client) {
|
||||
}
|
||||
|
||||
this.mirror = { // Mirror canvas, methods: `x`, `y`.
|
||||
x: async (j = 0) => {
|
||||
const copy = await this.copy()
|
||||
x: (j = 0) => {
|
||||
const copy = this.copy()
|
||||
const frame = this['get-frame']()
|
||||
client.surface.context.save()
|
||||
client.surface.context.translate(frame.w, 0)
|
||||
@@ -166,8 +168,8 @@ function Library (client) {
|
||||
client.surface.context.drawImage(copy, 0, 0)
|
||||
client.surface.context.restore()
|
||||
},
|
||||
y: async (j = 0) => {
|
||||
const copy = await this.copy()
|
||||
y: (j = 0) => {
|
||||
const copy = this.copy()
|
||||
const frame = this['get-frame']()
|
||||
client.surface.context.save()
|
||||
client.surface.context.translate(0, frame.h)
|
||||
@@ -240,12 +242,12 @@ function Library (client) {
|
||||
|
||||
// Pixels
|
||||
|
||||
this.pixels = async (fn, q = 1, rect = this['get-frame']()) => {
|
||||
this.pixels = (fn, q = 1, rect = this['get-frame']()) => {
|
||||
if (!fn) { console.warn('Unknown function'); return rect }
|
||||
const img = client.surface.context.getImageData(rect.x, rect.y, rect.w, rect.h)
|
||||
for (let i = 0, loop = img.data.length; i < loop; i += 4) {
|
||||
const pixel = [img.data[i], img.data[i + 1], img.data[i + 2], img.data[i + 3]]
|
||||
const processed = await fn(pixel, q)
|
||||
const processed = fn(pixel, q)
|
||||
img.data[i] = this.clamp(parseInt(processed[0]), 0, 255)
|
||||
img.data[i + 1] = this.clamp(parseInt(processed[1]), 0, 255)
|
||||
img.data[i + 2] = this.clamp(parseInt(processed[2]), 0, 255)
|
||||
@@ -412,10 +414,10 @@ function Library (client) {
|
||||
|
||||
// Arrays
|
||||
|
||||
this.each = async (arr, fn) => { // Run a function for each element in a list.
|
||||
this.each = (arr, fn) => { // Run a function for each element in a list.
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const arg = arr[i]
|
||||
await fn(arg)
|
||||
fn(arg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,11 +435,11 @@ function Library (client) {
|
||||
})
|
||||
}
|
||||
|
||||
this.reduce = async (arr, fn, acc) => {
|
||||
this.reduce = (arr, fn, acc) => {
|
||||
const length = arr.length
|
||||
let result = acc === undefined ? arr[0] : acc
|
||||
for (let i = acc === undefined ? 1 : 0; i < length; i++) {
|
||||
result = await fn(result, arr[i], i, arr)
|
||||
result = fn(result, arr[i], i, arr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -589,9 +591,9 @@ function Library (client) {
|
||||
return a === b
|
||||
}
|
||||
|
||||
this.benchmark = async (fn) => { // Logs time taken to execute a function.
|
||||
this.benchmark = (fn) => { // Logs time taken to execute a function.
|
||||
const start = Date.now()
|
||||
const result = await fn()
|
||||
const result = fn()
|
||||
console.log(`time taken: ${Date.now() - start}ms`)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -174,24 +174,21 @@ function Surface (client) {
|
||||
// IO
|
||||
|
||||
this.draw = function (img, shape = this.getFrame(), alpha = 1) {
|
||||
return new Promise(resolve => {
|
||||
this.context.globalAlpha = alpha
|
||||
if (isLine(shape)) {
|
||||
this.context.drawImage(img, shape.a.x, shape.a.y, shape.b.x - shape.a.x, shape.b.y - shape.a.y)
|
||||
} else if (isRect(shape)) {
|
||||
const fit = fitRect({ w: img.width, h: img.height }, { w: shape.w, h: shape.h })
|
||||
this.context.drawImage(img, shape.x, shape.y, fit.w, fit.h)
|
||||
} else if (isCircle(shape)) {
|
||||
const side = Math.sqrt(Math.pow(shape.r, 2) / 2)
|
||||
const rect = { x: shape.cx - (side), y: shape.cy - (side), w: side * 2, h: side * 2 }
|
||||
const fit = fitRect({ w: img.width, h: img.height }, { w: rect.w, h: rect.h })
|
||||
this.context.drawImage(img, rect.x, rect.y, fit.w, fit.h)
|
||||
} else {
|
||||
this.context.drawImage(img, shape.x, shape.y, img.width, img.height)
|
||||
}
|
||||
this.context.globalAlpha = 1
|
||||
resolve()
|
||||
})
|
||||
this.context.globalAlpha = alpha
|
||||
if (isLine(shape)) {
|
||||
this.context.drawImage(img, shape.a.x, shape.a.y, shape.b.x - shape.a.x, shape.b.y - shape.a.y)
|
||||
} else if (isRect(shape)) {
|
||||
const fit = fitRect({ w: img.width, h: img.height }, { w: shape.w, h: shape.h })
|
||||
this.context.drawImage(img, shape.x, shape.y, fit.w, fit.h)
|
||||
} else if (isCircle(shape)) {
|
||||
const side = Math.sqrt(Math.pow(shape.r, 2) / 2)
|
||||
const rect = { x: shape.cx - (side), y: shape.cy - (side), w: side * 2, h: side * 2 }
|
||||
const fit = fitRect({ w: img.width, h: img.height }, { w: rect.w, h: rect.h })
|
||||
this.context.drawImage(img, rect.x, rect.y, fit.w, fit.h)
|
||||
} else {
|
||||
this.context.drawImage(img, shape.x, shape.y, img.width, img.height)
|
||||
}
|
||||
this.context.globalAlpha = 1
|
||||
}
|
||||
|
||||
this.crop = function (rect) {
|
||||
|
||||
Reference in New Issue
Block a user