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

@ -29,23 +29,29 @@ Project Clean Escape
Example
; clear screen
(clear)
; draw red square
(stroke
(rect 30 30 100 100) "red" 2)
; download result
(export)
Helpers
Ronin helpers are keywords that facilitates adding coordinates from the canvas into your script. The currently supported helpers are `$rect`, `$pos`, `$circle`, `$line`, `$drag` and `$view`. Holding right-click while using a $helper will run the script as the mouse is injecting coordinates into the script. Paste the following script, and trace a shape in the canvas:
Ronin helpers are keywords that facilitates adding coordinates from the canvas into your script. The currently supported helpers are $rect, $pos, $line, $circle & $arc. Holding right-click while using a $helper will run the script as the mouse is injecting coordinates into the script. Paste the following script, and trace a shape in the canvas:
; draw a red circle
(fill $circle "red")
Additional helpers are also available to change parts of a shape, these are as follow: $x, $y, $xy, $wh, $a & $r. Paste the following script, and change the position and radius of a circle:
(clear)
(fill
(circle $xy $r) "red")
Extra helpers are available for various transformations, these are as follow: $drag, $view, $poly, $move & $rotate. Paste the following script, and draw the vertices of a line, press escape to stop:
(clear)
(stroke
$poly "red")
Import/Export
To save an image in memory, open an image file with Ronin, or drag an image file on the window. You will then be able to import it by using the file image's name. If the image file is `preview.png`, you can import it as follow:

View File

@ -10,12 +10,13 @@
<link rel="stylesheet" type="text/css" href="./links/theme.css"/>
<script type="text/javascript" src="./scripts/lib/acels.js"></script>
<script type="text/javascript" src="./scripts/lib/lisp.js"></script>
<script type="text/javascript" src="./scripts/lib/lain.js"></script>
<script type="text/javascript" src="./scripts/lib/source.js"></script>
<script type="text/javascript" src="./scripts/lib/theme.js"></script>
<script type="text/javascript" src="./scripts/client.js"></script>
<script type="text/javascript" src="./scripts/commander.js"></script>
<script type="text/javascript" src="./scripts/lain(old).js"></script>
<script type="text/javascript" src="./scripts/library.js"></script>
<script type="text/javascript" src="./scripts/surface.js"></script>

View File

@ -1,80 +1,78 @@
(clear)
;
(open $path)
(open $path 0.75)
; picker to elementary
(def unit 40)
(def unit 10)
(def f
(get-frame))
(def pos-row-1
(sub f:h
(mul unit 4)))
(sub f:h 80))
(def pos-row-2
(sub f:h
(mul unit 2)))
(sub f:h 45))
; color picker
(def color-1
(pick
(guide
(rect $xy unit unit))))
(rect 846 220 unit unit))))
(def color-2
(pick
(guide
(rect $xy unit unit))))
(rect 584 364 unit unit))))
(def color-3
(pick
(guide
(rect $xy unit unit))))
(rect 70 538 unit unit))))
(def color-4
(pick
(guide
(rect $xy unit unit))))
(rect 468 650 unit unit))))
(def color-5
(pick
(guide
(rect $xy unit unit))))
(rect 254 246 unit unit))))
(def color-6
(pick
(guide
(rect $xy unit unit))))
(rect 190 502 unit unit))))
(def color-7
(pick
(guide
(rect $xy unit unit))))
(rect 1084 446 unit unit))))
(def color-8
(pick
(guide
(rect $xy unit unit))))
(rect 1068 730 unit unit))))
; display
(fill
(circle
(mul unit 2) pos-row-1 unit) color-1)
(mul 20 2) pos-row-1 18) color-1)
(fill
(circle
(mul unit 4) pos-row-1 unit) color-2)
(mul 20 4) pos-row-1 18) color-2)
(fill
(circle
(mul unit 6) pos-row-1 unit) color-3)
(mul 20 6) pos-row-1 18) color-3)
(fill
(circle
(mul unit 8) pos-row-1 unit) color-4)
(mul 20 8) pos-row-1 18) color-4)
(fill
(circle
(mul unit 3) pos-row-2 unit) color-5)
(mul 20 3) pos-row-2 18) color-5)
(fill
(circle
(mul unit 5) pos-row-2 unit) color-6)
(mul 20 5) pos-row-2 18) color-6)
(fill
(circle
(mul unit 7) pos-row-2 unit) color-7)
(mul 20 7) pos-row-2 18) color-7)
(fill
(circle
(mul unit 9) pos-row-2 unit) color-8)
(mul 20 9) pos-row-2 18) color-8)
;
(def res
(add color-1:hex ":" color-2:hex ":" color-3:hex ":" color-4:hex ":" color-5:hex ":" color-6:hex ":" color-7:hex ":" color-8:hex))

1499
index.html

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ body { margin:0px; padding:0px; overflow:hidden; font-family:"input_mono_regular
#ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 105px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px}
#ronin #wrapper #commander #status { position: absolute; bottom: 0px; line-height: 15px; height: 30px; overflow: hidden; width: calc(100% - 75px); padding-left:45px;}
#ronin #wrapper #commander #status #run { display: block; width: 26px; height: 26px; position: absolute; top: 0px; border-radius: 15px; left:0px; cursor: pointer; border:2px solid #fff; transition: background-color 250ms, border-color 250ms}
#ronin #wrapper #commander #status #run:hover { background: none }
#ronin.expand #wrapper #commander { width:100%; }
#ronin #surface, #ronin #guide { position: absolute; top:0px; -webkit-user-select: none;-webkit-app-region: no-drag; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><circle cx='10' cy='10' r='1' fill='%23555'></circle></svg>"); background-size: 10px 10px; background-position: -4px -4px; width:100%; height:100%; transition: left 250ms, opacity 250ms; opacity: 1; }

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