Improved helpers

This commit is contained in:
neauoire
2020-03-26 10:35:58 +09:00
parent 12bba32b5d
commit 66c436b567
12 changed files with 1057 additions and 776 deletions

View File

@@ -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 }
}
}

View File

@@ -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
View File

@@ -0,0 +1,145 @@
'use strict'
// In the real world, it didnt 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)))
}
}

View File

@@ -86,7 +86,7 @@ function Theme (client) {
this.active[key] = hex
}
this.read = (key) => {
this.get = (key) => {
return this.active[key]
}

View File

@@ -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
}

View File

@@ -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) {