Merge pull request #1 from hundredrabbits/master

sync
This commit is contained in:
Nikolaus Gradwohl 2019-07-18 05:06:53 +02:00 committed by GitHub
commit 77d1aea227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 755 additions and 357 deletions

View File

@ -1,19 +1,90 @@
# Ronin
_All I wanted, was a way of resizing photos._
_"All I wanted, was a quick way of resizing a few photos.."_
Ronin is a LISP repl to create generative graphics currently under development. You can follow the daily progress on [Mastodon](https://merveilles.town/@neauoire/). Until we have documented the library, you can find a list of available functions [here](https://github.com/hundredrabbits/Ronin/blob/master/desktop/sources/scripts/library.js).
Ronin is a [LISP](https://en.wikipedia.org/wiki/Lisp_(programming_language)) repl to create generative graphics currently under development. You can follow the daily progress on [Mastodon](https://merveilles.town/@neauoire/).
<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/>
## Install & Run
## Electron Build
You can download [builds](https://hundredrabbits.itch.io/ronin) for **OSX, Windows and Linux**, or if you wish to build it yourself, follow these steps:
```
cd desktop
git clone https://github.com/hundredrabbits/Ronin.git
cd Ronin/desktop/
npm install
npm start
```
<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/>
## Library
- `(import path rect)` Imports a graphic file with format.
- `(export path ~format ~quality)` Exports a graphic file with format.
- `(pos x y ~t)` Returns a position shape.
- `(size w h ~t)` Returns a size shape.
- `(rect x y w h ~t)` Returns a rect shape.
- `(circle x y r ~t)` Returns a circle shape.
- `(line a b ~t)` Returns a line shape.
- `(text x y g s ~f ~t)` Returns a text shape.
- `(svg d ~t)` Returns a svg shape.
- `(stroke ~shape)` Strokes a shape.
- `(fill ~rect)` Fills a shape.
- `(clear ~rect)` Clears a rect.
- `(add ...args)` Adds values.
- `(sub ...args)` Subtracts values.
- `(mul ...args)` Multiplies values.
- `(div ...args)` Divides values.
- `(mod a b)` Returns the modulo of a and b.
- `(clamp val min max)` Clamps a value between min and max.
- `(step val step)`
- `(min)`
- `(max)`
- `(ceil)`
- `(floor)`
- `(sin)`
- `(cos)`
- `(PI)`
- `(TWO_PI)`
- `(random ...args)`
- `(gt a b)` Returns true if a is greater than b, else false.
- `(lt a b)` Returns true if a is less than b, else false.
- `(eq a b)` Returns true if a is equal to b, else false.
- `(and a b ...rest)` Returns true if all conditions are true.
- `(or a b ...rest)` Returns true if at least one condition is true.
- `(map fn arr)`
- `(filter fn arr)`
- `(reduce fn arr ~acc)`
- `(len item)` Returns the length of a list.
- `(first arr)` Returns the first item of a list.
- `(last arr)` Returns the last
- `(rest [_ ...arr])`
- `(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.
- `(frame)` Returns a rect of the frame.
- `(center)` Returns a position of the center of the frame.
- `(scale rect w h)`
- `(resize ~w ~h)`
- `(crop rect)`
- `(clone a b)`
- `(of h ...keys)`
- `(theme variable ~el)`
- `(gradient [x1 y1 x2 y2] ~colors 'black'])`
- `(pixels rect fn q)`
- `(saturation pixel ~q)`
- `(contrast pixel ~q)`
- `(echo ...args)`
- `(str ...args)`
- `(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)`
- `(test name a b)`
## Extras
- This application supports the [Ecosystem Theme](https://github.com/hundredrabbits/Themes).

View File

@ -40,6 +40,7 @@
ronin.controller.addRole('default', 'Edit', 'paste')
ronin.controller.addRole('default', 'Edit', 'delete')
ronin.controller.addRole('default', 'Edit', 'selectall')
ronin.controller.add("default","Edit","Re-Indent",() => { ronin.commander.reindent() },"CmdOrCtrl+Shift+I")
ronin.controller.add("default","View","Zoom In",() => { ronin.modZoom(0.25) },"CmdOrCtrl+=")
ronin.controller.add("default","View","Zoom Out",() => { ronin.modZoom(-0.25) },"CmdOrCtrl+-")
ronin.controller.add("default","View","Zoom Reset",() => { ronin.modZoom(1,true) },"CmdOrCtrl+0")

View File

@ -5,12 +5,12 @@ body { margin:0px; padding:0px; overflow:hidden; font-family:"input_mono_regular
#ronin { height: calc(100vh - 60px); width:calc(100vw - 60px); -webkit-app-region: drag; padding: 30px;overflow: hidden; }
#ronin #wrapper { overflow: hidden; position: relative; }
#ronin #wrapper #commander { z-index: 9000;position: relative;width: 310px;height: calc(100vh - 60px);-webkit-app-region: no-drag;padding-right: 30px;transition: margin-left 250ms;}
#ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 80px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px}
#ronin #wrapper #commander div#status { position: absolute; bottom: 0px; }
#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 div#status { position: absolute; bottom: 0px; text-transform: lowercase;}
#ronin.hidden #wrapper #commander { margin-left:-331px; }
#ronin canvas#surface,#ronin canvas#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%; left:340px; transition: left 250ms}
#ronin canvas#guide { background:none; }
#ronin canvas#surface { border-radius: 2px }
#ronin.hidden canvas#surface, #ronin.hidden canvas#guide { left:0px; }
#ronin.hidden canvas#surface, #ronin.hidden canvas#guide { left:10px; }

View File

@ -4,18 +4,28 @@ function Commander (ronin) {
this._input = document.createElement('textarea')
this._status = document.createElement('div')
this._status.id = 'status'
this._log = document.createElement('div')
this._log.id = 'log'
this._source = document.createElement('div')
this._source.id = 'source'
this._docs = document.createElement('div')
this._docs.id = 'help'
this.isVisible = true
this.install = function (host) {
this.el.appendChild(this._input)
this._status.appendChild(this._log)
this._status.appendChild(this._source)
this._status.appendChild(this._docs)
this.el.appendChild(this._status)
host.appendChild(this.el)
this._input.addEventListener('input', this.onInput)
this._input.addEventListener('click', this.onClick)
this.docs.install()
}
this.start = function () {
this._status.textContent = 'Idle. (zoom 100%)'
this.setStatus('Ready.')
this._input.focus()
this.run()
this.hide()
@ -33,21 +43,59 @@ function Commander (ronin) {
this.run()
}
this.reindent = function () {
let val = this._input.value.replace(/\n/g, '').replace(/ +(?= )/g, '').replace(/\( \(/g, '((').replace(/\) \)/g, '))').trim()
let depth = 0
for (let i = 0; i < val.length; i++) {
const c = val.charAt(i)
if (c === '(') { depth++ } else if (c === ')') { depth-- }
if (c === ';') {
const indent = '\n' + (' '.repeat(depth))
val = val.insert(indent, i)
i += indent.length
}
if (c === '(') {
const indent = '\n' + (' '.repeat(depth - 1))
val = val.insert(indent, i)
i += indent.length
}
}
this._input.value = val.trim()
}
this.setStatus = function (msg) {
if (!msg) { return }
this._status.textContent = `${(msg + '').substr(0, 40)}`
// Logs
if (msg && msg !== this._log.textContent) {
this._log.textContent = `${msg}`
console.log(msg)
}
// Source
const _source = `${ronin.source} ${this._input.value.split('\n').length} lines`
if (_source !== this._source.textContent) {
this._source.textContent = _source
}
// Docs
const _docs = this.docs.print(this.getLastfn())
if (_docs !== this._docs.textContent) {
this._docs.textContent = `${_docs}`
}
}
this.update = function () {
}
this.onInput = function () {
this.onInput = () => {
this.setStatus()
}
this.getQuery = function () {
this.onClick = () => {
this.setStatus()
}
this.getLastfn = function () {
const pos = this._input.value.substr(0, this._input.selectionStart).lastIndexOf('(')
return this._input.value.substr(pos).split(' ')[0].replace(/\(/g, '').replace(/\)/g, '').trim()
}
// Mouse
@ -62,7 +110,6 @@ function Commander (ronin) {
this.mouseRect.a.x = e.offsetX
this.mouseRect.a.y = e.offsetY
this.mouseRect.t = 'pos'
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.capture()
this.show()
}
@ -73,7 +120,6 @@ function Commander (ronin) {
this.mouseRect.h = e.offsetY - this.mouseRect.y
this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit()
}
}
@ -85,7 +131,6 @@ function Commander (ronin) {
this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY
this.mouseRect.t = ''
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit()
this._input.focus()
ronin.surface.clearGuide()
@ -162,4 +207,44 @@ function Commander (ronin) {
this.hide()
}
}
// Docs micro-module
this.docs = {
dict: {},
load: function () {
const fs = require('fs')
const path = require('path')
const p = path.join(__dirname, 'scripts/', 'library.js')
if (!fs.existsSync(p)) { console.warn('Docs', 'File does not exist: ' + p); return }
const lines = fs.readFileSync(p, 'utf8').split('\n').filter((line) => { return line.substr(0, 7) === ' this.' })
return lines.map((line) => { return line.trim().substr(5).trim() })
},
install: function (payload = this.load()) {
for (const id in payload) {
const name = payload[id].substr(0, payload[id].indexOf(' = '))
const parent = payload[id].substr(payload[id].indexOf(' = ')).match(/\(([^)]+)\)/)
const params = parent ? parent[1].split(',').map((word) => { return word.indexOf(' = ') > -1 ? '~' + (word.split(' = ')[0]).trim() : word.trim() }) : []
const note = payload[id].indexOf('// ') > -1 ? payload[id].split('//')[1].trim() : ''
this.dict[name] = { note, params }
if (params.length < 1) { console.warn('Docs', 'Missing params for ' + name) }
if (note === '') { console.warn('Docs', 'Missing note for ' + name) }
}
console.log('Docs', `Loaded ${Object.keys(this.dict).length} functions.`)
console.log(this.toMarkdown())
},
toMarkdown: function () {
return Object.keys(this.dict).reduce((acc, item, key) => {
const example = `${item} ${this.dict[item].params.reduce((acc, item) => {
return `${acc}${item} `
}, '').trim()}`
return `${acc}- \`(${example.trim()})\` ${this.dict[item].note}\n`
}, '')
},
print: function (name) {
return this.dict[name] ? `(${name} ${this.dict[name].params.reduce((acc, item) => { return `${acc}${item} ` }, '').trim()})` : ''
}
}
String.prototype.insert = function (s, i) { return [this.slice(0, i), `${s}`, this.slice(i)].join('') }
}

View File

@ -1,269 +1,88 @@
function Library (ronin) {
this.open = async (path) => {
return ronin.surface.open(path)
}
this.export = (path, type = 'image/png', quality = 1.0) => {
if (!path) { console.warn('Missing export path'); return path }
var dataUrl = ronin.surface.el.toDataURL(type, quality)
const data = dataUrl.replace(/^data:image\/png;base64,/, '')
fs.writeFileSync(path, data, 'base64')
return path
}
this.draw = async (path, rect) => {
this.import = async (path, rect) => { // Imports a graphic file with format.
const img = new Image()
img.src = path
return ronin.surface.draw(img, rect)
}
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 }
const a = document.createElement('img')
const b = document.createElement('img')
a.src = ronin.surface.el.toDataURL()
ronin.surface.resizeImage(a, b)
ronin.surface.resize(rect, true)
return ronin.surface.draw(b, rect)
}
this.crop = async (rect) => {
return ronin.surface.crop(rect)
}
this.folder = (path = ronin.source.path) => {
return fs.existsSync(path) ? fs.readdirSync(path) : []
}
this.exit = () => {
ronin.source.quit()
}
// Logic
this.gt = (a, b) => {
return a > b
}
this.lt = (a, b) => {
return a < b
}
this.eq = (a, b) => {
return a === b
}
this.and = (a, b, ...rest) => {
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (!args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
this.or = (a, b, ...rest) => {
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
// Arrays
this.map = async (fn, arr) => {
return Promise.all(arr.map(fn))
}
this._filter = (fn, arr) => {
return arr.filter(fn)
}
this.filter = (fn, arr) => {
const list = Array.from(arr)
return Promise.all(list.map((element, index) => fn(element, index, list)))
.then(result => {
return list.filter((_, index) => {
return result[index]
})
})
}
this.reduce = (fn, arr, acc = 0) => {
return arr.reduce(fn, acc)
}
this.len = (item) => {
return item.length
}
this.first = (arr) => {
return arr[0]
}
this.last = (arr) => {
return arr[arr.length - 1]
}
this.rest = ([_, ...arr]) => {
return arr
}
this.range = (start, end, step = 1) => {
let arr = []
if (step > 0) {
for (let i = start; i <= end; i += step) {
arr.push(i)
}
} else {
for (let i = start; i >= end; i += step) {
arr.push(i)
}
}
return arr
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,/, '')
fs.writeFileSync(path, data, 'base64')
return path
}
// Shapes
this.pos = (x, y, t = 'pos') => {
this.pos = (x, y, t = 'pos') => { // Returns a position shape.
return { x, y, t }
}
this.size = (w, h, t = 'size') => {
this.size = (w, h, t = 'size') => { // Returns a size shape.
return { w, h, t }
}
this.rect = (x, y, w, h, t = 'rect') => {
this.rect = (x, y, w, h, t = 'rect') => { // Returns a rect shape.
return { x, y, w, h, t }
}
this.circle = (x, y, r, t = 'circle') => {
this.circle = (x, y, r, t = 'circle') => { // Returns a circle shape.
return { x, y, r, t }
}
this.line = (a, b, t = 'line') => {
this.line = (a, b, t = 'line') => { // Returns a line shape.
return { a, b, t }
}
this.text = (x, y, g, s, f = 'Arial', t = 'text') => {
this.text = (x, y, g, s, f = 'Arial', t = 'text') => { // Returns a text shape.
return { x, y, g, s, f, t }
}
this.svg = (d, t = 'svg') => {
this.svg = (d, t = 'svg') => { // Returns a svg shape.
return { d, t }
}
// Helpers
// Actions
this.frame = () => {
return ronin.surface.getFrame()
}
this.center = () => {
const rect = this.frame()
return this.pos(rect.w / 2, rect.h / 2)
}
this.scale = (rect, w, h) => {
return { x: rect.x, y: rect.y, w: rect.w * w, h: rect.h * h }
}
// Copy/Paste
this.clone = (a, b) => {
ronin.surface.clone(a, b)
return [a, b]
}
this.stroke = (shape = this.frame(), thickness, color) => {
this.stroke = (shape = this.frame(), thickness, color) => { // Strokes a shape.
ronin.surface.stroke(shape, thickness, color)
return shape
}
this.fill = (rect = this.frame(), color) => {
this.fill = (rect = this.frame(), color) => { // Fills a shape.
ronin.surface.fill(rect, color)
return rect
}
this.clear = (rect = this.frame()) => {
this.clear = (rect = this.frame()) => { // Clears a rect.
ronin.surface.clear(rect)
return rect
}
this.get = (item, key) => {
return item[key]
}
this.set = (item, key, val) => {
item[key] = val
return item[key]
}
// 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}`)
}
// Pixels
this.pixels = (rect, fn, q) => {
const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h)
for (let i = 0, loop = img.data.length; i < loop; i += 4) {
const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] }
const processed = fn(pixel, q)
img.data[i] = processed[0]
img.data[i + 1] = processed[1]
img.data[i + 2] = processed[2]
img.data[i + 3] = processed[3]
}
ronin.surface.context.putImageData(img, 0, 0)
return rect
}
this.saturation = (pixel, q = 1) => {
const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a]
}
this.contrast = (pixel, q = 1) => {
const intercept = 128 * (1 - q)
return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a]
}
// Math
this.add = (...args) => {
this.add = (...args) => { // Adds values.
return args.reduce((sum, val) => sum + val)
}
this.sub = (...args) => {
this.sub = (...args) => { // Subtracts values.
return args.reduce((sum, val) => sum - val)
}
this.mul = (...args) => {
this.mul = (...args) => { // Multiplies values.
return args.reduce((sum, val) => sum * val)
}
this.div = (...args) => {
this.div = (...args) => { // Divides values.
return args.reduce((sum, val) => sum / val)
}
this.mod = (a, b) => {
this.mod = (a, b) => { // Returns the modulo of a and b.
return a % b
}
this.clamp = (val, min, max) => {
this.clamp = (val, min, max) => { // Clamps a value between min and max.
return Math.min(max, Math.max(min, val))
}
@ -298,7 +117,183 @@ function Library (ronin) {
return Math.random()
}
// Generics
// Logic
this.gt = (a, b) => { // Returns true if a is greater than b, else false.
return a > b
}
this.lt = (a, b) => { // Returns true if a is less than b, else false.
return a < b
}
this.eq = (a, b) => { // Returns true if a is equal to b, else false.
return a === b
}
this.and = (a, b, ...rest) => { // Returns true if all conditions are true.
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (!args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
this.or = (a, b, ...rest) => { // Returns true if at least one condition is true.
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
// Arrays
this.map = async (fn, arr) => {
return Promise.all(arr.map(fn))
}
this.filter = (fn, arr) => {
const list = Array.from(arr)
return Promise.all(list.map((element, index) => fn(element, index, list)))
.then(result => {
return list.filter((_, index) => {
return result[index]
})
})
}
this.reduce = (fn, arr, acc = 0) => {
return arr.reduce(fn, acc)
}
this.len = (item) => { // Returns the length of a list.
return item.length
}
this.first = (arr) => { // Returns the first item of a list.
return arr[0]
}
this.last = (arr) => { // Returns the last
return arr[arr.length - 1]
}
this.rest = ([_, ...arr]) => {
return arr
}
this.range = (start, end, step = 1) => {
let arr = []
if (step > 0) {
for (let i = start; i <= end; i += step) {
arr.push(i)
}
} else {
for (let i = start; i >= end; i += step) {
arr.push(i)
}
}
return arr
}
// Objects
this.get = (item, key) => { // Gets an object's parameter with name.
return item[key]
}
this.set = (item, key, val) => { // Sets an object's parameter with name as value.
item[key] = val
return item[key]
}
// Frame
this.frame = () => { // Returns a rect of the frame.
return ronin.surface.getFrame()
}
this.center = () => { // Returns a position of the center of the frame.
const rect = this.frame()
return this.pos(rect.w / 2, rect.h / 2)
}
this.scale = (rect, w, h) => {
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 }
const a = document.createElement('img')
const b = document.createElement('img')
a.src = ronin.surface.el.toDataURL()
ronin.surface.resizeImage(a, b)
ronin.surface.resize(rect, true)
return ronin.surface.draw(b, rect)
}
this.crop = async (rect) => {
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}`)
}
// Gradients
this.gradient = ([x1, y1, x2, y2], colors = ['white', 'black']) => {
return ronin.surface.linearGradient(x1, y1, x2, y2, colors)
}
// Pixels
this.pixels = (rect, fn, q) => {
const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h)
for (let i = 0, loop = img.data.length; i < loop; i += 4) {
const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] }
const processed = fn(pixel, q)
img.data[i] = processed[0]
img.data[i + 1] = processed[1]
img.data[i + 2] = processed[2]
img.data[i + 3] = processed[3]
}
ronin.surface.context.putImageData(img, 0, 0)
return rect
}
this.saturation = (pixel, q = 1) => {
const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a]
}
this.contrast = (pixel, q = 1) => {
const intercept = 128 * (1 - q)
return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a]
}
// Misc
this.echo = (...args) => {
ronin.log(args)
@ -309,26 +304,39 @@ function Library (ronin) {
return args.reduce((acc, val) => { return acc + val }, '')
}
this.open = async (path) => { // Imports a graphic file and resizes the frame.
return ronin.surface.open(path)
}
this.folder = (path = ronin.source.path) => { // Returns the content of a folder path.
return fs.existsSync(path) ? fs.readdirSync(path) : []
}
this.exit = (force = false) => { // Exits Ronin.
ronin.source.quit(force)
}
// Client
this.ronin = ronin
// Livecoding
this.time = () => { // Returns timestamp in milliseconds.
return Date.now
}
this.animate = (play = true) => { // Toggles animation.
ronin.animate(play)
}
// javascript interop
this.js = window
this.test = (name, a, b) => {
if (Array.isArray(a)) {
// TODO: make testing more solid
a = a.toString()
b = b.toString()
}
if (a !== b) {
if (`${a}` !== `${b}`) {
console.warn('failed ' + name, a, b)
} else {
console.log('passed ' + name, a)
}
return a === b
}
// Livecoding
this.time = Date.now
// javascript interop
this.js = window
// Client
this.ronin = ronin
}

View File

@ -20,9 +20,8 @@ function Lisp (input, lib) {
const special = {
include: (input, context) => {
const p = input[1].value
if (!fs.existsSync(p)) { console.warn('Source', p); return [] }
const file = fs.readFileSync(p, { encoding: 'utf-8' })
if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Source', input[1].value); return [] }
const file = fs.readFileSync(input[1].value, { encoding: 'utf-8' })
return interpret(this.parse(file), context)
},
let: function (input, context) {

View File

@ -52,7 +52,6 @@ function Ronin () {
}
this.log = function (...msg) {
console.log(...msg)
this.commander.setStatus(msg.reduce((acc, val) => { return acc + val + ' ' }, ''))
}

View File

@ -15,6 +15,7 @@ function Source (ronin) {
console.log('Source', 'Make a new file..')
this.path = null
ronin.surface.clear()
ronin.log(`New file.`)
}
this.open = function () {
@ -57,6 +58,7 @@ function Source (ronin) {
if (quitAfter === true) {
app.exit()
}
ronin.log(`Writing file.`)
}
this.read = function (loc = this.path) {
@ -65,6 +67,7 @@ function Source (ronin) {
console.log('Source', 'Reading ' + loc)
this.path = loc
this.load(fs.readFileSync(this.path, 'utf8'))
ronin.log(`Reading file.`)
}
this.run = function () {
@ -75,8 +78,8 @@ function Source (ronin) {
ronin.commander._input.value = data
}
this.quit = function () {
if (this.hasChanges() === true) {
this.quit = function (force = false) {
if (this.hasChanges() === true && force === false) {
this.verify()
} else {
app.exit()
@ -144,7 +147,7 @@ function Source (ronin) {
}
this.toString = function () {
return this.path ? this.name() : 'unsaved'
return this.path ? this.name() + '.lisp' : 'unsaved'
}
function isDifferent (a, b) {

View File

@ -61,6 +61,15 @@ function Surface (ronin) {
context.closePath()
}
this.linearGradient = function (x1, y1, x2, y2, colors, context = this.context) {
const gradient = context.createLinearGradient(x1, y1, x2, y2)
const step = 1 / (colors.length - 1)
colors.forEach((color, i) => {
gradient.addColorStop(i * step, color)
})
return gradient
}
// Tracers
this.trace = function (shape, context) {

159
documentation.md Normal file
View File

@ -0,0 +1,159 @@
# Functions
## IO
`(open path)`
`(export path type quality)`
`(draw path rect)`
`(resize width height)`
`(crop rect)`
`(folder path)`
`(exit)`
## Logic
`(gt a b)` check if `a` is greater than `b`
`(lt a b)` check if `a` is lower than `b`
`(eq a b)` check if `a` is equal to `b`
`(and a b <c d...>)` returns true if all conditions are true
`(or a b <cd...>)` returns true if at least one condition is true
## Arrays
`(map function array)`
`(filter function array)`
`(reduce function array accumulator)`
`(len array)`
`(first array)`
`(last array)`
`(rest array)`
`(range start end step)`
## Shapes
`(pos x y)`
`(size w h)`
`(rect x y w h t)`
`(circle x y r)`
`(line start end)`
`(text x y g string font)`
`(svg data)`
## Helpers
`(frame)`
`(center)`
`(scale rect width height)`
## Copy/Paste
`(clone start end)` clone start `rect` into end `rect`
`(stroke shape thickness color)`
`(fill shape color)`
`(clear shape)`
## Objects
`(get item key <keys>)`
`(set item key val)`
## Colors
`(theme variable)`
`(gradient (x1,y1,x2,y2) colors)`
`(pixels rect function q)`
`(saturation pixel q)`
`(contrast pixel q)`
## Math
`(add ...values)`
`(sub...values)`
`(mul ...values)`
`(div ...values)`
`(mod a b)`
`(clamp value min max)`
`(step value step)`
`(min a b)`
`(max a b)`
`(ceil value)`
`(floor value)`
`(sin a)`
`(cos a)`
`PI, TWO_PI`
`(random)`
`(random start end)`
`(random max)`
## Generics
`(echo args)`
`(str args)`
`(test name value expectedValue)`
## Livecoding
`(time)` returns timestamp in milliseconds
`(animate)` start animation
`(animate false)` stop animation
## Javascript interop
`js`
## Client
`ronin`

View File

@ -1,13 +1,12 @@
; animate
(
(def start (get ronin "animate"))
(def t (sin (div (time) 100)))
(def pos (add 200 (mul 30 t)))
(defn square (a) (rect a a a a))
(stroke (square pos) 1 "red")
(start)
(animate)
;(animate false) to stop animation
)

29
examples/g_spiral1.lisp Normal file
View File

@ -0,0 +1,29 @@
; animated recusive spiral
; by @local_guru
(
(def start (get ronin "animate"))
(clear)
(defn rec
(v)
(if (gt v 0)
((stroke
(circle
(add 300
(mul (cos (add (div v 17) (div (time) 2000)))
(div v 2)
)
)
(add 300
(mul (sin (div v 11))
(div v 2)
)
)
(div v 2))
1 "rgba(255,255,255,0.1")
(rec (sub v 0.3))
)
)
)
(start)
(rec 300)
)

View File

@ -17,7 +17,7 @@
; Draw photo
(draw
(import
"../static/crystal.jpg"
(rect 0 0 400 400))

16
examples/gradient.lisp Normal file
View File

@ -0,0 +1,16 @@
(
; gradients
(clear)
(fill
(svg "M405,15 L405,15 L150,150 L195,90 L240,135 L120,195 L75,90 L135,165 L120,225 L90,240 L60,210 L90,150 L255,180 L285,180 L285,165 ")
(gradient
(0 -50 600 175)
("red" "orange" "blue" "green")))
(stroke
(svg "M255,60 L255,60 L135,180 L75,60 L195,210 L120,225 L105,225 L165,255 L225,195 L255,135 L285,150") 1
(gradient
(50 0 180 0)
("black" "white" "blue" "green")))
)

View File

@ -1,36 +1,57 @@
; guides file
((clear)
(stroke (frame) 1 "red")
; guides
(
(clear)
(stroke
(frame) 1 "red")
(stroke
(line
(pos 0 0)
(pos (of (frame) "w") (of (frame) "h")))
1 "red")
(pos
(of
(frame) "w")
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos (of (frame) "w") 0)
(pos 0 (of (frame) "h")))
1 "red")
(pos
(of
(frame) "w") 0)
(pos 0
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos (div (of (frame) "w") 2) 0)
(pos (div (of (frame) "w") 2) (of (frame) "h")))
1 "red")
(pos
(div
(of
(frame) "w") 2) 0)
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos 0 (div (of (frame) "h") 2))
(pos (div (of (frame) "w") 2) (of (frame) "h")))
1 "#72dec2")
(pos 0
(div
(of
(frame) "h") 2))
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "#72dec2")
(stroke
(line
(pos (div (of (frame) "w") 2) 0)
(pos (of (frame) "w") (div (of (frame) "h") 2)))
1 "#72dec2")
)
(pos
(div
(of
(frame) "w") 2) 0)
(pos
(of
(frame) "w")
(div
(of
(frame) "h") 2))) 1 "#72dec2"))

4
examples/include.lisp Normal file
View File

@ -0,0 +1,4 @@
; include
(
(include "../examples/recursive.lisp")
(echo line-color))

View File

@ -1,12 +1,7 @@
; pixels
(
(clear)
(draw
"../../PREVIEW.jpg"
(import "../../PREVIEW.jpg"
(frame))
(pixels
(rect 0 0 500 500)
saturation
0.5)
)
(rect 0 0 500 500) saturation 0.5))

View File

@ -1,21 +1,17 @@
; random
(
(clear)
(defn place
(rec)
(if (gt rec 0)
(if
(gt rec 0)
(
(draw "../static/crystal.jpg"
(import "../static/crystal.jpg"
(rect
(random 200)
(random 200)
(random 200)
(random 200)))
(place (sub rec 1))
))
)
(place 30)
)
(place
(sub rec 1)))))
(place 30))

View File

@ -1,17 +1,16 @@
; recursive
(
(clear)
(def line-color "red")
(defn rec
(v)
(if (gt v 0)
((stroke (circle
(mul 5 v)
(mul 5 v)
(mul 5 v)) 1 line-color)
(rec (sub v 5))))
)
(rec 100)
)
(if
(gt v 0)
(
(stroke
(circle
(mul 5 v)
(mul 5 v)
(mul 5 v)) 1 "red")
(rec
(sub v 5)))))
(rec 100))

View File

@ -1,7 +1,5 @@
; resize
(
(clear)
(open "../../PREVIEW.jpg")
(resize 0.5 0.5)
)
(resize 0.5 0.5))

View File

@ -1,4 +0,0 @@
(
(include "../examples/recursive.lisp")
(echo line-color)
)

View File

@ -1,5 +1,8 @@
((fill
(svg "M255,60 L255,60 L135,180 L75,60 L195,210 L120,225 L105,225 L165,255 L225,195 L255,135 L285,150") "white")
(stroke
(svg "M405,15 L405,15 L150,150 L195,90 L240,135 L120,195 L75,90 L135,165 L120,225 L90,240 L60,210 L90,150 L255,180 L285,180 L285,165 ") "pink"))
(
(clear)
; ronin path
(stroke
(svg "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 ") 2 "white")
; outline
(stroke
(svg "M15,15 L15,15 L285,15 L285,285 L15,285 Z") 1 "#555"))

View File

@ -1,30 +1,38 @@
((clear)
(def col
(lambda
(i)
(of
((theme "f_high")
(theme "f_med")
(theme "f_low")
(theme "f_inv")
(theme "b_high")
(theme "b_med")
(theme "b_low")
(theme "b_inv"))
(mod i 8))))
(def rec
(lambda
(v i)
(if (gt v 0)
((fill
(circle
(add
(div (of (frame) "w") 1.6)
(mul 1.5 v))
(mul 10 v)
(mul v (div v 5)))
(col i))
(rec
(sub v 3)
(add i 1))))))
(rec 40 0))
; theme
(
(clear)
(def col
(lambda
(i)
(of
(
(theme "f_high")
(theme "f_med")
(theme "f_low")
(theme "f_inv")
(theme "b_high")
(theme "b_med")
(theme "b_low")
(theme "b_inv"))
(mod i 8))))
(def rec
(lambda
(v i)
(if
(gt v 0)
(
(fill
(circle
(add
(div
(of
(frame) "w") 1.6)
(mul 1.5 v))
(mul 10 v)
(mul v
(div v 5)))
(col i))
(rec
(sub v 3)
(add i 1))))))
(rec 40 0))