Moved everything to /desktop

This commit is contained in:
Devine Lu Linvega
2019-07-13 09:07:41 +09:00
parent 371c255022
commit b7120ab8a9
40 changed files with 90 additions and 328 deletions

BIN
desktop/icon.icns Normal file

Binary file not shown.

BIN
desktop/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

4
desktop/icon.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg class="vector" width="300px" height="300px" xmlns="http://www.w3.org/2000/svg" baseProfile="full" version="1.1" style="width: 300px; height: 300px; stroke: rgb(0, 0, 0); stroke-width: 0; fill: none; stroke-linecap: square;">
<rect width="300" height="300" style="fill:rgb(0,0,0)" />
<path d="M60,60 l120,0 a60,60 0 0,1 60,60 a-60,60 0 0,1 -60,60 l-120,0 M180,180 a60,60 0 0,1 60,60" stroke='white' stroke-width='15'></path>
</svg>

After

Width:  |  Height:  |  Size: 439 B

76
desktop/main.js Normal file
View File

@@ -0,0 +1,76 @@
const { app, BrowserWindow, webFrame, Menu } = require('electron')
const path = require('path')
const url = require('url')
const shell = require('electron').shell
let isShown = true
app.win = null
app.on('ready', () => {
app.win = new BrowserWindow({
width: 660,
height: 390,
minWidth: 320,
minHeight: 320,
backgroundColor: '#000',
icon: path.join(__dirname, { darwin: 'icon.icns', linux: 'icon.png', win32: 'icon.ico' }[process.platform] || 'icon.ico'),
resizable: true,
frame: process.platform !== 'darwin',
skipTaskbar: process.platform === 'darwin',
autoHideMenuBar: process.platform === 'darwin',
webPreferences: { zoomFactor: 1.0, nodeIntegration: true, backgroundThrottling: false }
})
app.win.loadURL(`file://${__dirname}/sources/index.html`)
app.inspect()
app.win.on('closed', () => {
win = null
app.quit()
})
app.win.on('hide', function () {
isShown = false
})
app.win.on('show', function () {
isShown = true
})
app.on('window-all-closed', () => {
app.quit()
})
app.on('activate', () => {
if (app.win === null) {
createWindow()
} else {
app.win.show()
}
})
})
app.inspect = function () {
app.win.toggleDevTools()
}
app.toggleFullscreen = function () {
app.win.setFullScreen(!app.win.isFullScreen())
}
app.toggleVisible = function () {
if (process.platform === 'win32') {
if (!app.win.isMinimized()) { app.win.minimize() } else { app.win.restore() }
} else {
if (isShown && !app.win.isFullScreen()) { app.win.hide() } else { app.win.show() }
}
}
app.injectMenu = function (menu) {
try {
Menu.setApplicationMenu(Menu.buildFromTemplate(menu))
} catch (err) {
console.warn('Cannot inject menu.')
}
}

23
desktop/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "Ronin",
"version": "0.1.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"clean" : "rm -r ~/Desktop/Ronin-darwin-x64/ ; rm -r ~/Desktop/Ronin-linux-x64/ ; rm -r ~/Desktop/Ronin-win32-x64/ ; echo 'cleaned build location'",
"build_osx" : "electron-packager . Ronin --platform=darwin --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.icns ; echo 'Built for OSX'",
"build_linux" : "electron-packager . Ronin --platform=linux --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.ico ; echo 'Built for LINUX'",
"build_win" : "electron-packager . Ronin --platform=win32 --arch=x64 --out ~/Desktop/ --overwrite --icon=icon.ico ; echo 'Built for WIN'",
"build" : "npm run clean ; npm run build_osx ; npm run build_linux ; npm run build_win",
"push_osx" : "~/butler push ~/Desktop/Ronin-darwin-x64/ hundredrabbits/ronin:osx-64",
"push_linux" : "~/butler push ~/Desktop/Ronin-linux-x64/ hundredrabbits/ronin:linux-64",
"push_win" : "~/butler push ~/Desktop/Ronin-win32-x64/ hundredrabbits/ronin:windows-64",
"push_theme" : "~/butler push ~/Github/HundredRabbits/Themes/themes/ hundredrabbits/ronin:themes",
"push_status" : "~/butler status hundredrabbits/ronin",
"push" : "npm run build ; npm run push_theme ; npm run push_osx ; npm run push_linux ; npm run push_win ; npm run clean ; npm run push_status"
},
"devDependencies": {
"electron": "^5.0.6",
"electron-packager": "^13.1.1"
}
}

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<head>
<meta charset='UTF-8'>
<script type="text/javascript" src="scripts/theme.js"></script>
<script type="text/javascript" src="scripts/controller.js"></script>
<script type="text/javascript" src="scripts/units/color.js"></script>
<script type="text/javascript" src="scripts/core/module.js"></script>
<script type="text/javascript" src="scripts/core/method.js"></script>
<script type="text/javascript" src="scripts/modules/frame.js"></script>
<script type="text/javascript" src="scripts/modules/brush.js"></script>
<script type="text/javascript" src="scripts/modules/line.js"></script>
<script type="text/javascript" src="scripts/modules/io.js"></script>
<script type="text/javascript" src="scripts/modules/path.js"></script>
<script type="text/javascript" src="scripts/modules/filter.js"></script>
<script type="text/javascript" src="scripts/modules/type.js"></script>
<script type="text/javascript" src="scripts/core/layer.js"></script>
<script type="text/javascript" src="scripts/layers/guide.js"></script>
<script type="text/javascript" src="scripts/core/cursor.js"></script>
<script type="text/javascript" src="scripts/core/swatch.js"></script>
<script type="text/javascript" src="scripts/core/docs.js"></script>
<script type="text/javascript" src="scripts/core/port.js"></script>
<script type="text/javascript" src="scripts/core/query.js"></script>
<script type="text/javascript" src="scripts/core/keyboard.js"></script>
<script type="text/javascript" src="scripts/core/commander.js"></script>
<script type="text/javascript" src="scripts/ronin.js"></script>
<link rel="stylesheet" type="text/css" href="links/reset.css"/>
<link rel="stylesheet" type="text/css" href="links/fonts.css"/>
<link rel="stylesheet" type="text/css" href="links/main.css"/>
<title>Ronin</title>
</head>
<body>
<script type="text/javascript">
const {dialog,app} = require('electron').remote;
const fs = require('fs');
const app_path = app.getAppPath();
var ronin = new Ronin();
ronin.controller = new Controller();
ronin.controller.add("default","*","About",() => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Dotgrid'); },"CmdOrCtrl+,");
ronin.controller.add("default","*","Fullscreen",() => { app.toggleFullscreen(); },"CmdOrCtrl+Enter");
ronin.controller.add("default","*","Hide",() => { app.toggleVisible(); },"CmdOrCtrl+H");
ronin.controller.add("default","*","Inspect",() => { app.inspect(); },"CmdOrCtrl+.");
ronin.controller.add("default","*","Reset",() => { dotgrid.reset(); dotgrid.theme.reset(); },"CmdOrCtrl+Backspace");
ronin.controller.add("default","*","Quit",() => { app.exit(); },"CmdOrCtrl+Q");
ronin.controller.addRole('default', 'Edit', 'undo')
ronin.controller.addRole('default', 'Edit', 'redo')
ronin.controller.addRole('default', 'Edit', 'cut')
ronin.controller.addRole('default', 'Edit', 'copy')
ronin.controller.addRole('default', 'Edit', 'paste')
ronin.controller.addRole('default', 'Edit', 'delete')
ronin.controller.addRole('default', 'Edit', 'selectall')
ronin.controller.commit();
ronin.install(document.body);
window.addEventListener('load', () => { ronin.start(); })
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
body { margin:0px; padding:0px; overflow:hidden; font-family:"input_mono_medium",courier,monospace; background:000; -webkit-app-region: drag; -webkit-user-select: none;}
*:focus {outline: none; }
yu { display:block; }
:root { --background: "#222"; --f_high: "#fff";--f_med: "#777";--f_low: "#444";--f_inv: "#000";--b_high: "#000";--b_med: "#affec7";--b_low: "#000";--b_inv: "#affec7"; }
#cursor { z-index:899; position: absolute; }
#guide { z-index:810;position: absolute; transition: opacity 250ms; opacity: 0}
#above { z-index:800; position: absolute; }
#below { z-index:799; position: absolute; }
#ronin { background:var(--b_low); height: 100vh; width:100vw; }
#commander { z-index: 9000; top: 15px; position: absolute; transition: all 150ms; left: 15px; width: 300px; height: calc(100vh - 60px); border-radius: 3px; padding: 15px;}
#commander textarea { background: none; width: 100%; height: calc(100% - 45px); resize: none; font-size: 12px;color: white; }
#commander.hidden { top:-40px; }
#commander.visible { top:0px; }
surface { display: block; background:var(--background); position: absolute; top:0px; transition: all 100ms; }
surface .layer { border-radius: 4px; overflow: hidden; width:100%; height:100%;}

View File

@@ -0,0 +1 @@
* { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:collapse;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}

View File

@@ -0,0 +1,72 @@
'use strict'
function Controller () {
const fs = require('fs')
const { dialog, app } = require('electron').remote
this.menu = { default: {} }
this.mode = 'default'
this.app = require('electron').remote.app
this.start = function () {
}
this.add = function (mode, cat, label, fn, accelerator) {
if (!this.menu[mode]) { this.menu[mode] = {} }
if (!this.menu[mode][cat]) { this.menu[mode][cat] = {} }
this.menu[mode][cat][label] = { fn: fn, accelerator: accelerator }
}
this.addRole = function (mode, cat, label) {
if (!this.menu[mode]) { this.menu[mode] = {} }
if (!this.menu[mode][cat]) { this.menu[mode][cat] = {} }
this.menu[mode][cat][label] = { role: label }
}
this.clearCat = function (mode, cat) {
if (this.menu[mode]) { this.menu[mode][cat] = {} }
}
this.set = function (mode = 'default') {
this.mode = mode
this.commit()
}
this.format = function () {
const f = []
const m = this.menu[this.mode]
for (const cat in m) {
const submenu = []
for (const name in m[cat]) {
const option = m[cat][name]
if (option.role) {
submenu.push({ role: option.role })
} else {
submenu.push({ label: name, accelerator: option.accelerator, click: option.fn })
}
}
f.push({ label: cat, submenu: submenu })
}
return f
}
this.commit = function () {
this.app.injectMenu(this.format())
}
this.accelerator_for_key = function (key, menu) {
const acc = { basic: null, ctrl: null }
for (cat in menu) {
const options = menu[cat]
for (const id in options.submenu) {
const option = options.submenu[id]; if (option.role) { continue }
acc.basic = (option.accelerator.toLowerCase() === key.toLowerCase()) ? option.label.toUpperCase().replace('TOGGLE ', '').substr(0, 8).trim() : acc.basic
acc.ctrl = (option.accelerator.toLowerCase() === ('CmdOrCtrl+' + key).toLowerCase()) ? option.label.toUpperCase().replace('TOGGLE ', '').substr(0, 8).trim() : acc.ctrl
}
}
return acc
}
}
module.exports = new Controller()

View File

@@ -0,0 +1,24 @@
function Commander () {
this.el = document.createElement('yu')
this.el.id = 'commander'
this.input_el = document.createElement('textarea')
this.input_el.value = `
(scale 0.5 0.5
(resize 150 150
(crop
(rect 0 0 300 300))))`.trim()
this.install = function () {
this.el.appendChild(this.input_el)
ronin.el.appendChild(this.el)
this.input_el.focus()
}
this.update = function () {
}
this.getQuery = function () {
}
}

View File

@@ -0,0 +1,137 @@
function Cursor (rune) {
this.line = { origin: null, from: null, to: null, destination: null }
this.is_down = false
this.query = null
this.mode = 'vertex'
this.size = 2
this.pos = { x: 0, y: 0 }
this.target = null
this.mouse_pos = function (e) {
var pos = { x: e.clientX, y: e.clientY }
pos.x = ((pos.x / ronin.frame.width) / ronin.frame.zoom.scale) * ronin.frame.width
pos.y = ((pos.y / ronin.frame.height) / ronin.frame.zoom.scale) * ronin.frame.height
pos.x -= (ronin.frame.zoom.offset.x / ronin.frame.zoom.scale)
pos.y -= (ronin.frame.zoom.offset.y / ronin.frame.zoom.scale)
return pos
}
this.mouse_down = function (e) {
e.preventDefault()
var pos = ronin.cursor.mouse_pos(e)
ronin.commander.blur()
ronin.cursor.line.origin = { x: pos.x, y: pos.y }
ronin.cursor.line.from = { x: pos.x, y: pos.y }
// Save original query
ronin.cursor.query = ronin.commander.input_el.value
if (ronin.commander.active_module()) { /* DO NOTHING */ } else if (e.shiftKey) { /* DO NOTHING */ } else if (e.altKey && e.shiftKey) { ronin.brush.methods.pick.run(pos) } else if (e.altKey) { ronin.brush.erase(ronin.cursor.line) } else { ronin.brush.stroke(ronin.cursor.line) }
if (e.shiftKey) { ronin.cursor.mode = 'rect' }
if (e.altKey) { ronin.cursor.mode = 'arc_to' }
if (e.ctrlKey) { ronin.cursor.mode = 'cc_arc_to' }
}
this.mouse_move = function (e) {
e.preventDefault()
var pos = ronin.cursor.mouse_pos(e)
ronin.cursor.pos = pos
if (!ronin.cursor.line.from) { return }
ronin.cursor.line.to = { x: pos.x, y: pos.y }
if (ronin.commander.active_module()) { ronin.cursor.inject_query() } else if (e.altKey && e.shiftKey) { ronin.brush.methods.pick.run(pos) } else if (e.shiftKey) { ronin.cursor.drag(ronin.cursor.line) } else if (e.altKey) { ronin.brush.erase(ronin.cursor.line) } else { ronin.brush.stroke(ronin.cursor.line) }
ronin.cursor.line.from = { x: pos.x, y: pos.y }
}
this.mouse_up = function (e) {
e.preventDefault()
var pos = ronin.cursor.mouse_pos(e)
ronin.cursor.line.destination = { x: pos.x, y: pos.y }
ronin.cursor.inject_query()
ronin.cursor.is_down = false
ronin.cursor.line = {}
ronin.cursor.mode = 'vertex'
ronin.cursor.query = ronin.commander.input_el.value
ronin.brush.absolute_thickness = 0
}
this.mouse_alt = function (e) {
console.log(e)
}
this.drag = function (line) {
var offset = make_offset(line.from, line.to)
var data = ronin.cursor.target.select()
ronin.cursor.target.clear()
ronin.cursor.target.context().putImageData(data, offset.x * -2, offset.y * -2)
}
this.swap_layer = function () {
this.target = this.target.name == 'above' ? ronin.layers.below : ronin.layers.above
ronin.commander.update()
}
this.select_layer = function (layer) {
this.target = layer
ronin.commander.update()
}
function make_offset (a, b) {
return { x: a.x - b.x, y: a.y - b.y }
}
this.inject_query = function () {
if (ronin.cursor.query && ronin.cursor.query.indexOf('$') < 0) { return }
var a = ronin.cursor.line.origin
var b = ronin.cursor.line.destination ? ronin.cursor.line.destination : ronin.cursor.line.from
var str = '<error>'
if (ronin.cursor.mode == 'vertex') {
str = b.x + ',' + b.y
} else if (ronin.cursor.mode == 'rect') {
var offset = a.x + ',' + a.y
var rect = (b.x - a.x) + 'x' + (b.y - a.y)
str = offset + '|' + rect
} else if (ronin.cursor.mode == 'arc_to') {
str = '@>' + b.x + ',' + b.y
} else if (ronin.cursor.mode == 'cc_arc_to') {
str = '@<' + b.x + ',' + b.y
}
//
var i = ronin.cursor.query ? ronin.cursor.query.indexOf('$') : ''
var i1 = ronin.cursor.query ? ronin.cursor.query.substr(i, 2) : ''
var e1 = ronin.cursor.query ? ronin.cursor.query.substr(i - 1, 2) : ''
if (e1 == '#$') {
var r = parseInt((b.x / ronin.frame.width) * 255)
var g = 255 - parseInt((b.x / ronin.frame.width) * 255)
var b = parseInt((b.y / ronin.frame.height) * 255)
var str = new Color().rgb_to_hex([r, g, b])
ronin.commander.inject(ronin.cursor.query.replace('#$', str + ' '))
} else if (i1 == '$+') {
ronin.commander.inject(ronin.cursor.query.replace('$+', str + '&$+'))
} else if (ronin.cursor.query) {
ronin.commander.inject(ronin.cursor.query.replace('$', str))
}
}
}

View File

@@ -0,0 +1,61 @@
function Docs () {
this.export = function () {
var html = ''
html += this.print_intro()
html += '## Cursor\n'
html += 'Include `$` in a query and click on the canvas to inject the cursor position in the query.\n'
html += '- `$ click` inject a **Pos**.\n'
html += '- `$+ click` inject a **Pos**, and append `$+` for multiple positions.\n'
html += '- `$ shift click` inject a **Rect**.\n'
html += '- `#$ click` inject a **Color**.\n'
html += '- `x` swap **Color** with secondary.\n\n'
html += '- `z` draw under render.\n\n'
html += '## Modules\n'
html += this.print_modules(ronin.modules)
html += this.print_license()
fs.writeFile('/Users/VillaMoirai/Github/HundredRabbits/Ronin/README.md', html, (err) => {
if (err) { alert('An error ocurred creating the file ' + err.message) }
})
return html
}
this.print_intro = function () {
html = '# Ronin\n'
html += 'Ronin is a graphic design tool, to paint, resize and export graphics.\n\n'
html += "<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/>\n\n"
return html
}
this.print_modules = function (modules) {
var html = ''
for (module_name in modules) {
var module = modules[module_name]
html += '## ' + module_name + '\n\n'
html += module.docs + '\n\n'
html += this.print_methods(module.methods) + '\n'
}
return html + '\n'
}
this.print_methods = function (methods) {
var html = '### Methods\n'
for (method_name in methods) {
var method = methods[method_name]
html += '- `' + method_name + ':' + method.params + '` ' + method.info + '\n'
}
return html
}
this.print_license = function () {
html = '## License\n'
html += 'See the [LICENSE](LICENSE.md) file for license rights and limitations.\n'
return html
}
}

View File

@@ -0,0 +1,19 @@
function Keyboard () {
this.is_down = {}
this.key_up = function (e) {
}
this.key_down = function (e) {
ronin.keyboard.is_down[e.key] = true
if (ronin.commander.is_focused() && e.key == 'Enter') {
e.preventDefault()
ronin.commander.validate()
}
if (ronin.commander.is_focused()) {
}
}
}

View File

@@ -0,0 +1,61 @@
function Layer (name) {
this.name = name
this.el = document.createElement('canvas')
this.el.id = name
this.el.className = 'layer'
this.install = function () {
ronin.frame.el.appendChild(this.el)
}
this.update = function (zoom = { scale: 1, offset: { x: 0, y: 0 } }) {
}
this.context = function () {
return this.el.getContext('2d')
}
this.resize_to = function (size) {
console.log(`Resized ${this.name}`)
this.el.width = ronin.frame.width * 2
this.el.height = ronin.frame.height * 2
this.update()
}
this.select = function (x = 0, y = 0, width = ronin.frame.width, height = ronin.frame.width) {
return this.context().getImageData(x, y, width * 2, height * 2)
}
this.to_base64 = function (format = 'png', quality = 0.9) {
return format == 'png' ? this.el.toDataURL('image/png') : this.el.toDataURL('image/jpeg', 0.9)
}
this.to_img = function () {
var img = new Image()
img.src = this.to_base64()
return img
}
this.clear = function () {
this.context().clearRect(0, 0, this.el.width * 2, this.el.height * 2)
}
this.fill = function (color = 'red') {
var ctx = this.context()
ctx.beginPath()
ctx.globalCompositeOperation = 'source-over'
ctx.moveTo(0, 0)
ctx.lineTo(this.el.width, 0)
ctx.lineTo(this.el.width, this.el.height)
ctx.lineTo(0, this.el.height)
ctx.lineTo(0, 0)
ctx.fillStyle = color
ctx.fill()
ctx.closePath()
}
this.zoom = function (zoom = { scale: 1, offset: { x: 0, y: 0 } }) {
this.update(zoom)
}
}

View File

@@ -0,0 +1,10 @@
function Method (name, params, info = 'Missing documentation', f) {
this.name = name
this.params = params
this.info = info
this.run = f
this.docs = function () {
return '<b>' + this.params + '</b> <i>' + this.info + '</i>'
}
}

View File

@@ -0,0 +1,6 @@
function Module (name, docs = 'Missing documentation.') {
this.name = name
this.methods = {}
this.docs = docs
}

View File

@@ -0,0 +1,20 @@
function Port (host, name, input, output, value, max, docs) {
this.host = host
this.name = name
this.input = input
this.output = output
this.value = value
this.max = max
this.docs = docs
this.write = function (value) {
this.value = value
var target = this.host.routes[this.name]
if (!this.output) { return }
if (!target) { console.log('No output for', this.name); return }
this.host.ports[target].write(this.value)
}
}

View File

@@ -0,0 +1,104 @@
function Query (query_str = '') {
// Strip trailing variables
query_str = query_str.indexOf('&$+') > -1 ? query_str.replace('&$+', '').trim() : query_str
this.string = query_str
this.module = query_str.split(' ')[0]
var parts = query_str.split(' ').splice(1)
this.raw = parts.join(' ')
this.methods = {}
this.routes = {}
this.last = query_str.indexOf(' ') > -1 ? query_str.split(' ')[query_str.split(' ').length - 1] : query_str
this.last_char = query_str.trim().substr(query_str.trim().length - 1, 1)
for (part_id in parts) {
var part = parts[part_id]
// Method
if (part.indexOf(':') > -1) {
var key = part.indexOf(':') > -1 ? part.split(':')[0] : 'any'
var value = part.indexOf(':') > -1 ? part.split(':')[1] : part
this.methods[key] = parse_parameters(value)
}
// Port
else if (part.indexOf('->') > -1) {
var key = part.indexOf('->') > -1 ? part.split('->')[0] : 'any'
var value = part.indexOf('->') > -1 ? part.split('->')[1] : part
this.routes[key] = value
}
}
function parse_parameters (param_str) {
// Modifier
if (param_str.indexOf('>>') > -1) {
return parse_modifier(param_str)
} else {
if (param_str.indexOf('&') > -1) {
return parse_sequence(param_str)
} else {
return parse_unit(param_str)
}
}
return param_str
}
function parse_modifier (mod_str) {
var h = {}
var parts = mod_str.split('>>')
if (parts[0].indexOf('&') > -1) {
h.from = parse_sequence(parts[0])
} else {
h.from = parse_unit(parts[0])
}
if (parts[1].indexOf('&') > -1) {
h.to = parse_sequence(parts[1])
} else {
h.to = parse_unit(parts[1])
}
return h
}
function parse_sequence (seq_str) {
var a = []
var parts = seq_str.split('&')
for (part_id in parts) {
var part = parts[part_id]
a.push(parse_unit(part))
}
return a
}
function parse_unit (unit_str) {
// Arc
if (unit_str.indexOf('@') == 0) {
unit_str = unit_str.substr(1, unit_str.length - 1)
var clockwise = unit_str.indexOf('>') == 0
unit_str = unit_str.substr(1, unit_str.length - 1)
return { x: parseInt(unit_str.split(',')[0]), y: parseInt(unit_str.split(',')[1]), clockwise: clockwise }
}
if (unit_str.indexOf('.') > -1 && unit_str.indexOf('/') > -1) {
return unit_str
}
// Rect
if (unit_str.indexOf('|') > -1 && unit_str.indexOf(',') > -1 && unit_str.indexOf('x') > -1) {
return Object.assign(parse_unit(unit_str.split('|')[0]), parse_unit(unit_str.split('|')[1]))
}
// Pos
if (unit_str.indexOf(',') > -1) {
return { x: parseInt(unit_str.split(',')[0]), y: parseInt(unit_str.split(',')[1]) }
}
// Size
if (unit_str.indexOf('x') > -1 && !isNaN(unit_str.split('x')[0]) && !isNaN(unit_str.split('x')[1])) {
return { width: parseInt(unit_str.split('x')[0]), height: parseInt(unit_str.split('x')[1]) }
}
// Float
if (unit_str.indexOf('.') > -1) {
return parseFloat(unit_str)
}
// Any
return unit_str
}
}

View File

@@ -0,0 +1,20 @@
function Swatch () {
this.index = 0
this.colors = []
this.start = function () {
this.update()
}
this.update = function () {
this.colors = [ronin.theme.active.f_high, ronin.theme.active.f_med, ronin.theme.active.f_low]
}
this.swap = function () {
this.index += 1
}
this.color = function (offset = 0) {
return this.colors[(this.index + offset) % this.colors.length]
}
}

View File

@@ -0,0 +1,216 @@
function Guide () {
Layer.call(this)
this.el.id = 'guide'
this.inspect = true
this.update = function () {
this.clear()
var units = this.find_units()
if (this.inspect) {
this.draw_inspector()
}
if (ronin.commander.input_el.value == '~') {
this.toggle_color_picker(true)
}
// Brush mirrors
for (id in ronin.brush.pointers) {
var pointer = ronin.brush.pointers[id]
if (!pointer.options.mirror) { continue }
units.push({ x1: pointer.options.mirror.x, y1: 0, x2: pointer.options.mirror.x, y2: ronin.frame.height })
}
if (units.length == 0) { return }
for (i in units) {
this.draw(units[i])
}
}
this.toggle = function () {
this.el.style.opacity = this.el.style.opacity == 0 ? 1 : 0
}
this.toggle_color_picker = function (show) {
if (!show) { return }
var originalData = ronin.cursor.target.context().getImageData(0, 0, ronin.frame.width * 2, ronin.frame.height * 2)
var data = originalData.data
for (var i = 0; i < data.length; i += 4) {
var x = i % (ronin.frame.width * 8)
var y = i / (ronin.frame.width * 32)
data[i] = x / 32
data[i + 1] = 255 - (x / 32)
data[i + 2] = y
data[i + 3] = 255
}
ronin.layers.guide.context().putImageData(originalData, 0, 0)
}
this.draw = function (u = null) {
if (u && u.x1 != null && u.y1 != null && u.x2 != null && u.y2 != null) {
this.draw_line({ x: u.x1, y: u.y1 }, { x: u.x2, y: u.y2 })
}
if (u && u.x && u.y) {
this.draw_pos(u)
}
if (u && u.width && u.height) {
this.draw_rect(u)
this.draw_pos({ x: u.x + (u.width / 2), y: u.y + (u.height / 2) })
}
}
this.draw_rect = function (u) {
var ctx = this.context()
u.x = !u.x ? 0 : u.x
u.y = !u.y ? 0 : u.y
var offset = { x: u.x * 2, y: u.y * 2 }
var rect = { width: u.width * 2, height: u.height * 2 }
// Outline
ctx.beginPath()
ctx.globalCompositeOperation = 'source-over'
ctx.moveTo(offset.x, offset.y)
ctx.lineTo(offset.x + rect.width, offset.y)
ctx.lineTo(offset.x + rect.width, offset.y + rect.height)
ctx.lineTo(offset.x, offset.y + rect.height)
ctx.lineTo(offset.x, offset.y)
ctx.lineCap = 'round'
ctx.lineWidth = 2
ctx.strokeStyle = '#000'
ctx.stroke()
ctx.closePath()
}
this.draw_line = function (u1, u2, color = '#000') {
var ctx = this.context()
ctx.beginPath()
ctx.globalCompositeOperation = 'source-over'
ctx.moveTo(u1.x * 2, u1.y * 2)
ctx.lineTo(u2.x * 2, u2.y * 2)
ctx.lineCap = 'round'
ctx.lineWidth = 0.5
ctx.strokeStyle = color
ctx.stroke()
ctx.closePath()
}
this.draw_pos = function (u) {
var ctx = this.context()
var offset = 2
var radius = 5
var pos = { x: u.x * 2, y: u.y * 2 }
ctx.beginPath()
ctx.globalCompositeOperation = 'source-over'
ctx.moveTo(pos.x + offset, pos.y)
ctx.lineTo(pos.x + radius, pos.y)
ctx.moveTo(pos.x, pos.y + offset)
ctx.lineTo(pos.x, pos.y + radius)
ctx.moveTo(pos.x - offset, pos.y)
ctx.lineTo(pos.x - radius, pos.y)
ctx.moveTo(pos.x, pos.y - offset)
ctx.lineTo(pos.x, pos.y - radius)
ctx.lineCap = 'round'
ctx.lineWidth = 4
ctx.strokeStyle = '#000'
ctx.stroke()
ctx.lineWidth = 2
ctx.strokeStyle = '#fff'
ctx.stroke()
ctx.closePath()
}
this.find_units = function (q = ronin.commander.getQuery()) {
var a = []
// for (method_id in q.methods) {
// var params = q.methods[method_id]
// if (params.from && params.to) {
// a = a.concat(params.from)
// a = a.concat(params.to)
// } else {
// a = a.concat(params)
// }
// }
return a
}
this.draw_inspector = function () {
var color = 'black'
// Center
this.draw_line({ x: 0, y: ronin.frame.height / 2 }, { x: 10, y: ronin.frame.height / 2 }, color)
this.draw_line({ x: ronin.frame.width - 10, y: ronin.frame.height / 2 }, { x: ronin.frame.width, y: ronin.frame.height / 2 }, color)
this.draw_line({ x: (ronin.frame.width / 2) - 10, y: ronin.frame.height / 2 }, { x: (ronin.frame.width / 2) + 10, y: ronin.frame.height / 2 }, color)
this.draw_line({ x: ronin.frame.width / 2, y: 0 }, { x: ronin.frame.width / 2, y: 10 }, color)
this.draw_line({ x: ronin.frame.width / 2, y: ronin.frame.height - 10 }, { x: ronin.frame.width / 2, y: ronin.frame.height }, color)
this.draw_line({ x: ronin.frame.width / 2, y: (ronin.frame.height / 2) - 10 }, { x: ronin.frame.width / 2, y: (ronin.frame.height / 2) + 10 }, color)
var ctx = this.context()
var w = ronin.frame.width * 2
var h = ronin.frame.height * 2
var angle = 45
ctx.translate(w / 2, h / 2)
ctx.rotate(angle * Math.PI / 180)
this.line(ctx, -w, 0, w, 0, color)
this.line(ctx, w * 0.4, -h, w * 0.4, h, color)
this.line(ctx, -w * 0.4, -h, -w * 0.4, h, color)
this.line(ctx, -w, h * 0.25, w, h * 0.25, color)
this.line(ctx, -w, -h * 0.25, w, -h * 0.25, color)
this.line(ctx, w * 0.1, 0, w * 0.1, h, color)
this.line(ctx, -w * 0.1, 0, -w * 0.1, -h, color)
this.circle(ctx, w * 0.4, -h * 0.25, w * 0.05, 1, 1.5, color)
this.circle(ctx, -w * 0.4, h * 0.25, w * 0.05, 0, 0.5, color)
ctx.font = '5px Arial'
ctx.fillStyle = color
ctx.fillText(angle + "'", (w * 0.4) + 10, 10)
ctx.font = '5px Arial'
ctx.fillStyle = color
ctx.fillText(angle + "'", (-w * 0.4) - 20, -10)
ctx.rotate(-angle * Math.PI / 180)
ctx.translate(-w / 2, -h / 2)
}
this.line = function (context, x1, x2, y1, y2, color) {
context.beginPath()
context.moveTo(x1, x2)
context.lineTo(y1, y2)
context.lineCap = 'round'
context.lineWidth = 0.5
context.strokeStyle = color
context.stroke()
context.closePath()
}
this.circle = function (context, x, y, r, c1, c2, color) {
context.beginPath()
context.arc(x, y, r, c1 * Math.PI, c2 * Math.PI)
context.lineCap = 'round'
context.lineWidth = 0.5
context.strokeStyle = color
context.stroke()
context.closePath()
}
}

View File

@@ -0,0 +1,107 @@
function Brush () {
Module.call(this, 'brush')
this.speed = 0
this.swatch = new Swatch()
this.pointers = [
new Pointer({ offset: { x: 0, y: 0 } })
]
this.methods.add = new Method('add', 'x,y&mirror_x,mirror_y', 'Add a new pointer to the brush', function (q) {
var offset = q.length ? q[0] : q
var mirror = q.length ? q[1] : null
ronin.brush.pointers.push(new Pointer({ offset: offset, mirror: mirror }))
})
this.methods.remove = new Method('remove', '', 'Remove last pointer', function (q) {
ronin.brush.pointers.pop()
})
this.methods.pick = new Method('pick', 'x,y', "Set brush color to a position's pixel.", function (q) {
var pixel = (ronin.commander.input_el.value == '~' ? ronin.guide : ronin.cursor.target).context().getImageData(q.x * 2, q.y * 2, 1, 1).data
var c = new Color().rgb_to_hex(pixel)
var color = new Color(c)
ronin.cursor.color = color.hex
})
this.methods.set_color = new Method('set_color', '#ff0033', 'Set color', function (q) {
ronin.cursor.color = q
})
this.absolute_thickness = 0
this.thickness = function (line) {
var ratio = clamp(1 - (ronin.brush.speed / 20), 0, 1)
var t = ronin.cursor.size * ratio
this.absolute_thickness = t > this.absolute_thickness ? this.absolute_thickness + 0.25 : this.absolute_thickness - 0.25
return this.absolute_thickness
}
this.stroke = function (line) {
this.speed = distance_between(line.from, line.to)
for (pointer_id in this.pointers) {
this.pointers[pointer_id].stroke(line)
}
}
this.erase = function (line) {
this.speed = distance_between(line.from, line.to)
for (pointer_id in this.pointers) {
this.pointers[pointer_id].stroke(line, true)
}
}
this.pick = function (line) {
if (!line.to) {
line.to = line.from
}
var pixel = ronin.cursor.target.context().getImageData(line.to.x * 2, line.to.y * 2, 1, 1).data
}
this.mod_size = function (mod) {
ronin.cursor.size = clamp(parseInt(ronin.cursor.size) + mod, 1, 100)
}
function clamp (v, min, max) {
return v < min ? min : v > max ? max : v
}
function distance_between (a, b) {
return a && b ? Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)) : 0
}
}
function Pointer (options) {
this.options = options
this.stroke = function (line, erase = false) {
var ctx = ronin.cursor.target.context()
if (this.options.mirror) {
line.from.x = (this.options.mirror.x * 2) - line.from.x
line.to.x = (this.options.mirror.x * 2) - line.to.x
}
if (!line.to) {
line.to = line.from
}
ctx.beginPath()
ctx.globalCompositeOperation = erase ? 'destination-out' : 'source-over'
ctx.moveTo((line.from.x * 2) + this.options.offset.x, (line.from.y * 2) + this.options.offset.y)
ctx.lineTo((line.to.x * 2) + this.options.offset.x, (line.to.y * 2) + this.options.offset.y)
ctx.lineCap = 'round'
ctx.lineWidth = ronin.brush.thickness(line)
ctx.strokeStyle = ronin.brush.swatch.color()
ctx.stroke()
ctx.closePath()
}
function clamp (v, min, max) {
return v < min ? min : v > max ? max : v
}
}

View File

@@ -0,0 +1,60 @@
function Filter () {
Module.call(this, 'filter', 'Pixel filter')
this.methods.balance = new Method('balance', '#ff0033', 'Filter color balance.', function (q) {
var color = new Color(q).floats()
var originalData = ronin.cursor.target.context().getImageData(0, 0, ronin.frame.width * 2, ronin.frame.height * 2)
var data = originalData.data
for (var i = 0; i < data.length; i += 4) {
data[i] = data[i] * (color.r + 0.5)
data[i + 1] = data[i + 1] * (color.g + 0.5)
data[i + 2] = data[i + 2] * (color.b + 0.5)
}
ronin.cursor.target.context().putImageData(originalData, 0, 0)
})
this.methods.saturation = new Method('saturation', '#ff00333', 'Filter color saturation.', function (q) {
var color = new Color(q).floats()
var originalData = ronin.cursor.target.context().getImageData(0, 0, ronin.frame.width * 2, ronin.frame.height * 2)
var data = originalData.data
for (var i = 0; i < data.length; i += 4) {
var r = data[i]
var g = data[i + 1]
var b = data[i + 2]
var v = color.r * r + color.g * g + color.b * b
data[i] = data[i + 1] = data[i + 2] = v
}
ronin.cursor.target.context().putImageData(originalData, 0, 0)
})
this.preview = function (q) {
if (!q.methods.saturation) { return }
ronin.preview.clear()
// var color = new Color(q).floats();
var x = q.methods.saturation.x / ronin.frame.width
var originalData = ronin.cursor.target.context().getImageData(0, 0, ronin.frame.width * 2, ronin.frame.height * 2)
var data = originalData.data
for (var i = 0; i < data.length; i += 4) {
var r = data[i]
var g = data[i + 1]
var b = data[i + 2]
var v = (r + g + b) / 3
data[i] = (r * x) + (v * (1 - x))
data[i + 1] = (g * x) + (v * (1 - x))
data[i + 2] = (b * x) + (v * (1 - x))
}
ronin.preview.context().putImageData(originalData, 0, 0)
}
}

View File

@@ -0,0 +1,90 @@
function Frame () {
Module.call(this, 'frame', 'Manager for the canvas size')
this.el = document.createElement('surface')
this.background = 'pink'
this.install = function () {
ronin.el.appendChild(this.el)
}
this.methods.new = new Method('new', 'WxH', 'New Canvas', function (q) {
ronin.layers.above.clear()
ronin.layers.below.clear()
ronin.frame.resize_to({ width: 900, height: 540 })
})
this.width = 400
this.height = 400
this.zoom = { scale: 1, offset: { x: 0, y: 0 } }
this.methods.set_background = new Method('set_background', 'WxH', 'Resize canvas to size.', function (q) {
ronin.frame.background = q
ronin.frame.el.style.backgroundColor = q
})
this.methods.resize = new Method('resize', 'WxH', 'Resize canvas to size.', function (q) {
var data = ronin.cursor.target.select(0, 0, ronin.frame.width, ronin.frame.height)
ronin.cursor.target.clear()
ronin.frame.resize_to(q)
ronin.cursor.target.context().putImageData(data, 0, 0)
})
this.methods.rescale = new Method('rescale', '0.5', 'Rescale canvas to float.', function (p) {
var new_size = { width: ronin.frame.width * p, height: ronin.frame.height * p }
ronin.cursor.target.context().drawImage(ronin.cursor.target.to_img(), 0, 0, new_size.width * 2, new_size.height * 2)
setTimeout(function () { ronin.frame.methods.resize.run(new_size) }, 500)
})
this.methods.crop = new Method('crop', 'X,Y|WxH', 'Crop canvas to rect.', function (p) {
var data = ronin.cursor.target.select(0, 0, p.width * 2, p.height * 2)
ronin.cursor.target.clear()
ronin.frame.resize_to(p)
setTimeout(function () { ronin.cursor.target.context().putImageData(data, p.x * -2, p.y * -2) }, 500)
})
this.methods.clear = new Method('clear', '', 'Erase entire canvas', function (q) {
ronin.cursor.target.clear()
})
this.methods.fill = new Method('fill', '#f00', 'Fill entire canvas with color', function (q) {
ronin.cursor.target.fill(q || ronin.cursor.color)
})
this.methods.inspect = new Method('inspect', '', 'View canvas details', function (q) {
ronin.guide.inspect = !ronin.guide.inspect
ronin.guide.draw()
})
this.methods.zoom = new Method('zoom', '', 'Zoom canvas', function (q) {
if (ronin.frame.zoom.scale == parseInt(q)) { return }
ronin.frame.zoom.scale = parseInt(q)
ronin.frame.el.style.width = `${ronin.frame.width * ronin.frame.zoom.scale}px`
ronin.frame.el.style.height = `${ronin.frame.height * ronin.frame.zoom.scale}px`
ronin.frame.zoom.offset.x = ronin.frame.zoom.scale == 1 ? 0 : ((-ronin.cursor.pos.x * ronin.frame.zoom.scale) + (ronin.frame.width / 2))
ronin.frame.zoom.offset.y = ronin.frame.zoom.scale == 1 ? 0 : ((-ronin.cursor.pos.y * ronin.frame.zoom.scale) + (ronin.frame.height / 2))
// Normalize
if (ronin.frame.zoom.offset.x > 0) { ronin.frame.zoom.offset.x = 0 }
if (ronin.frame.zoom.offset.y > 0) { ronin.frame.zoom.offset.y = 0 }
ronin.frame.el.style.top = `${ronin.frame.zoom.offset.y}px`
ronin.frame.el.style.left = `${ronin.frame.zoom.offset.x}px`
})
this.resize_to = function (size) {
this.el.style.width = `${size.width}px`
this.el.style.height = `${size.height}px`
ronin.frame.width = size.width
ronin.frame.height = size.height
const { dialog, app } = require('electron').remote
var win = require('electron').remote.getCurrentWindow()
win.setSize(size.width, size.height)
ronin.layers.above.resize_to(size)
ronin.layers.below.resize_to(size)
ronin.guide.resize_to(size)
}
}

View File

@@ -0,0 +1,131 @@
function IO () {
Module.call(this, 'io', 'File import/export tools.')
this.image = null
this.methods.open = new Method('open', 'browser', 'Press enter to open the file browser.', function (q) {
var filepath = q ? [q] : dialog.showOpenDialog({ properties: ['openFile'] })
if (!filepath) { console.log('Nothing to load'); return }
fs.readFile(filepath[0], 'utf-8', (err, data) => {
if (err) { alert('An error ocurred reading the file :' + err.message); return }
var img = new Image()
img.src = filepath[0]
img.onload = function () {
var width = parseInt(img.naturalWidth * 0.5)
var height = parseInt(img.naturalHeight * 0.5)
ronin.frame.resize_to({ width: width, height: height })
ronin.io.draw_image(ronin.cursor.target.context(), img, { x: 0, y: 0, width: width, height: height })
}
})
})
this.methods.load = new Method('load', 'browser', 'Press enter to open the file browser.', function (q) {
var filepath = q ? [q] : dialog.showOpenDialog({ properties: ['openFile'] })
if (!filepath) { console.log('Nothing to load'); return }
fs.readFile(filepath[0], 'utf-8', (err, data) => {
if (err) { alert('An error ocurred reading the file :' + err.message); return }
var img = new Image()
img.src = filepath[0]
img.onload = function () {
ronin.io.image = img
ronin.commander.inject('io draw:20,20|100x100')
}
})
})
this.methods.render = new Method('render', 'png', 'Export canvas.', function (q) {
var ext = 'png'
var fs = require('fs')
var data = ronin.io.render().to_base64(ext).replace(/^data:image\/\w+;base64,/, '')
var buf = new Buffer(data, 'base64')
dialog.showSaveDialog((fileName) => {
if (fileName === undefined) { return }
fs.writeFile(fileName + '.' + ext, buf)
})
})
this.methods.export = new Method('render', 'jpg', 'Export canvas.', function (q) {
var ext = 'jpg'
var fs = require('fs')
var data = ronin.io.render(ronin.frame.background).to_base64(ext).replace(/^data:image\/\w+;base64,/, '')
var buf = new Buffer(data, 'base64')
dialog.showSaveDialog((fileName) => {
if (fileName === undefined) { return }
fs.writeFile(fileName + '.' + ext, buf)
})
})
this.methods.draw = new Method('draw', 'X,Y|WxH', 'Draw the loaded image pixels.', function (q) {
if (!ronin.io.image) { return }
ronin.io.draw_image(ronin.cursor.target.context(), ronin.io.image, ronin.commander.query().methods.draw)
ronin.io.image = null
})
// this.preview = function(q)
// {
// ronin.preview.clear();
// if(ronin.commander.query().methods.draw && this.image){
// this.draw_image(ronin.preview.context(),this.image,ronin.commander.query().methods.draw);
// }
// }
this.render = function (fill = null) {
var export_layer = new Layer()
export_layer.el.width = ronin.frame.width * 2
export_layer.el.height = ronin.frame.height * 2
if (fill) {
export_layer.fill(fill)
}
export_layer.context().drawImage(ronin.layers.below.el, 0, 0)
export_layer.context().drawImage(ronin.layers.above.el, 0, 0)
return export_layer
}
this.draw_image = function (ctx, img, params) {
var width = parseInt(img.naturalWidth * 0.5)
var height = parseInt(img.naturalHeight * 0.5)
var scale = params.width > params.height ? (params.width / width) * 2 : (params.height / height) * 2
ctx.drawImage(img, params.x * 2, params.y * 2, width * scale, height * scale)
}
}
window.addEventListener('dragover', function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
})
window.addEventListener('drop', function (e) {
e.stopPropagation()
e.preventDefault()
var files = e.dataTransfer.files
var file = files[0]
var path = file.path ? file.path : file.name
if (path.substr(-4, 4) == '.thm') { return }
if (file.type && !file.type.match(/image.*/)) { console.log('Not image', file.type); return false }
var reader = new FileReader()
reader.onload = function (event) {
var img = new Image()
img.src = event.target.result
ronin.io.image = img
ronin.commander.inject('io draw:20,20|100x100')
}
reader.readAsDataURL(file)
})

View File

@@ -0,0 +1,69 @@
function Line () {
Module.call(this, 'line', 'Drawing lines. Tween expects something in the `$&$>>$&$` format.')
this.ports.step = new Port(this, 'step', false, true, 0, 100, 'The tween line index.')
this.ports.thickness = new Port(this, 'thickness', true, true, 1, 100, 'The tween line thickness.')
this.methods.stroke = new Method('stroke', 'x1,y1&x2,y2', 'Stroke positions.', function (q) {
ronin.line.stroke_multi(q)
})
this.methods.tween = new Method('tween', 'tween:$&$>>$&$ step->thickness', 'Stroke lines between strokes.', function (q) {
var from = q.from
var to = q.to
ronin.line.ports.step.write(0)
while (ronin.line.ports.step.value < ronin.line.ports.step.max) {
var progress = ronin.line.ports.step.value / parseFloat(ronin.line.ports.step.max)
var new_positions = tween_positions(from, to, progress)
ronin.line.stroke_multi(new_positions)
ronin.line.ports.step.write(ronin.line.ports.step.value + 1)
}
})
this.preview = function (q) {
// TODO
// console.log(q);
}
this.stroke_multi = function (coordinates) {
var from = coordinates[0]
for (pos_id in coordinates) {
var pos = coordinates[pos_id]
ronin.line.stroke(from, pos)
from = pos
}
}
this.stroke = function (from, to, ctx = ronin.cursor.target.context()) {
ctx.beginPath()
ctx.globalCompositeOperation = 'source-over'
ctx.moveTo((from.x * 2), (from.y * 2))
ctx.lineTo((to.x * 2), (to.y * 2))
ctx.lineCap = 'round'
ctx.lineWidth = clamp(ronin.line.ports.thickness.value, 1, 200) * 0.1
ctx.strokeStyle = '#000'
ctx.stroke()
ctx.closePath()
}
function clamp (v, min, max) {
return v < min ? min : v > max ? max : v
}
function tween_positions (froms, tos, progress) {
var a = []
for (pos_id in froms) {
var from = froms[pos_id]
var to = tos[pos_id]
a.push(tween_position(from, to, progress))
}
return a
}
function tween_position (from, to, progress) {
var offset = { x: to.x - from.x, y: to.y - from.y }
return { x: from.x + offset.x * progress, y: from.y + offset.y * progress }
}
}

View File

@@ -0,0 +1,27 @@
function Magnet () {
Module.call(this, 'magnet', 'Cursor magnetisation settings, changes are reflected on the grid layer.')
this.size = 0
this.step = 4
this.methods.lock = new Method('lock', '10', 'Magnetize cursor', function (q) {
var size = parseInt(q)
if (size < 5) { this.unlock(); return }
ronin.magnet.size = size
ronin.grid.draw(size, ronin.magnet.step)
})
this.methods.unlock = new Method('unlock', '', 'Release cursor', function (q) {
ronin.magnet.size = 0
ronin.grid.clear()
})
this.filter = function (pos) {
if (this.size < 5) {
return pos
}
var s = this.size
return { x: parseInt(pos.x / s) * s, y: parseInt(pos.y / s) * s }
}
}

View File

@@ -0,0 +1,90 @@
function Path () {
Module.call(this, 'path', 'Trace lines and draw shapes.')
this.settings = { cap: 'square' }
this.methods.stroke = new Method('stroke', 'x,y&', '', function (q) {
ronin.preview.clear()
var path = ronin.path.create_path(q)
var ctx = ronin.cursor.target.context()
ctx.beginPath()
ctx.lineCap = ronin.path.settings.cap
ctx.lineWidth = ronin.cursor.size
ctx.strokeStyle = ronin.cursor.color
ctx.stroke(new Path2D(path))
ctx.closePath()
})
this.methods.fill = new Method('fill', 'x,y&', '', function (q) {
ronin.preview.clear()
var path = ronin.path.create_path(q)
var ctx = ronin.cursor.target.context()
ctx.beginPath()
ctx.lineCap = ronin.path.settings.cap
ctx.lineWidth = ronin.cursor.size
ctx.fillStyle = ronin.cursor.color
ctx.fill(new Path2D(path))
ctx.closePath()
})
this.methods.svg = new Method('svg', 'M0,0 L100,100', '', function (q) {
var path = ronin.commander.query().raw.replace('svg:', '').trim()
var ctx = ronin.cursor.target.context()
ctx.beginPath()
ctx.lineCap = ronin.path.settings.cap
ctx.lineWidth = ronin.cursor.size
ctx.strokeStyle = ronin.cursor.color
ctx.stroke(new Path2D(path))
ctx.closePath()
})
this.preview = function (q) {
if (!q.methods.stroke) { return }
ronin.preview.clear()
var path = ronin.path.create_path(q.methods.stroke)
var ctx = ronin.preview.context()
ctx.beginPath()
ctx.lineCap = ronin.path.settings.cap
ctx.lineWidth = ronin.cursor.size
ctx.strokeStyle = ronin.cursor.color
ctx.stroke(new Path2D(path))
ctx.closePath()
}
this.create_path = function (q_array) {
var svg_path = ''
var prev = { x: 0, y: 0 }
for (q_id in q_array) {
var coord = q_array[q_id]
if (!coord.x || !coord.y) { continue }
coord.x *= 2; coord.y *= 2
if (svg_path == '') {
var origin = { x: coord.x, y: coord.y }
svg_path += 'M' + (coord.x) + ',' + (coord.y) + ' '
} else if (coord.clockwise == true) {
var offset = make_offset(coord, prev)
svg_path += 'a' + (offset.x) + ',' + (offset.y) + ' 0 0,1 ' + (offset.x) + ',' + (offset.y)
} else if (coord.clockwise == false) {
var offset = make_offset(coord, prev)
svg_path += 'a' + (offset.x) + ',' + (offset.y) + ' 0 0,0 ' + (offset.x) + ',' + (offset.y)
} else {
var offset = make_offset(coord, prev)
svg_path += 'l' + (offset.x) + ',' + (offset.y) + ' '
}
prev = coord
}
return svg_path
}
function make_offset (a, b) {
return { x: a.x - b.x, y: a.y - b.y }
}
}

View File

@@ -0,0 +1,28 @@
function Type () {
Module.call(this, 'type')
this.settings = { color: '#000000', font: 'Gotham Light', anchor: 'center' }
this.methods.write = new Method('write', 'text&x,y|WxH', 'Draw text', function (q) {
var rect = q[1]
var size = rect.height * 2
ronin.preview.clear()
ronin.cursor.target.context().textAlign = ronin.type.settings.anchor
ronin.cursor.target.context().font = size + 'px ' + ronin.type.settings.font.replace('+', ' ')
ronin.cursor.target.context().fillText(q[0].replace('+', ' '), rect.x * 2, (rect.y * 2) + size)
})
this.preview = function (q) {
if (!q.methods.write || !q.methods.write[1]) { return }
var rect = q.methods.write[1]
var size = rect.height * 2
ronin.preview.clear()
ronin.preview.context().textAlign = this.settings.anchor
ronin.preview.context().font = size + 'px ' + this.settings.font.replace('+', ' ')
ronin.preview.context().fillText(q.methods.write[0].replace('+', ' '), rect.x * 2, (rect.y * 2) + size)
}
}

View File

@@ -0,0 +1,110 @@
function Ronin () {
const defaultTheme = {
background: '#eee',
f_high: '#000',
f_med: '#999',
f_low: '#ccc',
f_inv: '#000',
b_high: '#000',
b_med: '#888',
b_low: '#aaa',
b_inv: '#ffb545'
}
this.el = document.createElement('yu')
this.el.id = 'ronin'
this.theme = new Theme(defaultTheme)
this.keyboard = new Keyboard()
this.commander = new Commander()
this.cursor = new Cursor()
this.docs = new Docs()
this.guide = new Guide()
this.above = new Layer('above')
this.below = new Layer('below')
this.io = new IO()
this.brush = new Brush()
this.frame = new Frame()
this.path = new Path()
this.filter = new Filter()
this.type = new Type()
this.layers = {
guide: this.guide,
above: this.above,
below: this.below,
cursor: this.cursor,
guide: this.guide
}
this.modules = {
brush: this.brush,
frame: this.frame,
io: this.io,
path: this.path,
filter: this.filter,
type: this.type
}
this.install = function (host) {
this.brush.swatch.start()
document.body.appendChild(this.el)
this.frame.width = window.innerWidth
this.frame.height = window.innerHeight
this.commander.install()
this.frame.install()
this.cursor.target = this.layers.above
// this.guide.install();
this.above.install()
this.below.install()
this.guide.install()
this.guide.update()
this.theme.install(host, () => { this.update() })
}
this.start = function () {
this.theme.start()
window.addEventListener('dragover', ronin.io.drag_over)
window.addEventListener('drop', ronin.io.drop)
ronin.frame.el.addEventListener('mousedown', ronin.cursor.mouse_down)
ronin.frame.el.addEventListener('mousemove', ronin.cursor.mouse_move)
ronin.frame.el.addEventListener('mouseup', ronin.cursor.mouse_up)
ronin.frame.el.addEventListener('contextmenu', ronin.cursor.mouse_alt)
window.addEventListener('keydown', ronin.keyboard.key_down)
window.addEventListener('keyup', ronin.keyboard.key_up)
ronin.commander.input_el.addEventListener('input', ronin.commander.on_input)
console.log('Ronin', 'Started')
this.above.update()
this.below.update()
this.guide.update()
this.commander.update()
this.load()
}
this.reset = function () {
this.theme.reset()
}
this.update = function () {
}
this.load = function (content = this.default()) {
}
this.default = function () {
return 'select_layer:below ; fill:#fff ; select_layer:above ; add_cursor:1,1 ; add_cursor:-1,-1'
}
}

View File

@@ -0,0 +1,127 @@
'use strict'
function Theme (_default) {
const themer = this
this.active = _default
this.el = document.createElement('style')
this.el.type = 'text/css'
this.install = function (host = document.body, callback) {
host.appendChild(this.el)
this.callback = callback
}
this.start = function () {
console.log('Theme', 'Starting..')
if (isJson(localStorage.theme)) {
const storage = JSON.parse(localStorage.theme)
if (validate(storage)) {
console.log('Theme', 'Loading localStorage..')
this.load(storage)
return
}
}
this.load(_default)
}
this.load = function (data) {
const theme = parse(data)
if (!validate(theme)) { console.warn('Theme', 'Not a theme', theme); return }
console.log('Theme', `Loaded theme!`)
this.el.innerHTML = `:root { --background: ${theme.background}; --f_high: ${theme.f_high}; --f_med: ${theme.f_med}; --f_low: ${theme.f_low}; --f_inv: ${theme.f_inv}; --b_high: ${theme.b_high}; --b_med: ${theme.b_med}; --b_low: ${theme.b_low}; --b_inv: ${theme.b_inv}; }`
localStorage.setItem('theme', JSON.stringify(theme))
this.active = theme
if (this.callback) {
this.callback()
}
}
this.reset = function () {
this.load(_default)
}
function parse (any) {
if (any && any.background) { return any } else if (any && any.data) { return any.data } else if (any && isJson(any)) { return JSON.parse(any) } else if (any && isHtml(any)) { return extract(any) }
return null
}
// Drag
this.drag = function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
this.drop = function (e) {
e.preventDefault()
e.stopPropagation()
const file = e.dataTransfer.files[0]
if (!file || !file.name) { console.warn('Theme', 'Unnamed file.'); return }
if (file.name.indexOf('.thm') < 0 && file.name.indexOf('.svg') < 0) { console.warn('Theme', 'Skipped, not a theme'); return }
const reader = new FileReader()
reader.onload = function (e) {
themer.load(e.target.result)
}
reader.readAsText(file)
}
this.open = function () {
const fs = require('fs')
const { dialog, app } = require('electron').remote
let paths = dialog.showOpenDialog(app.win, { properties: ['openFile'], filters: [{ name: 'Themes', extensions: ['svg'] }] })
if (!paths) { console.log('Nothing to load'); return }
fs.readFile(paths[0], 'utf8', function (err, data) {
if (err) throw err
themer.load(data)
})
}
window.addEventListener('dragover', this.drag)
window.addEventListener('drop', this.drop)
// Helpers
function validate (json) {
if (!json) { return false }
if (!json.background) { return false }
if (!json.f_high) { return false }
if (!json.f_med) { return false }
if (!json.f_low) { return false }
if (!json.f_inv) { return false }
if (!json.b_high) { return false }
if (!json.b_med) { return false }
if (!json.b_low) { return false }
if (!json.b_inv) { return false }
return true
}
function extract (text) {
const svg = new DOMParser().parseFromString(text, 'text/xml')
try {
return {
'background': svg.getElementById('background').getAttribute('fill'),
'f_high': svg.getElementById('f_high').getAttribute('fill'),
'f_med': svg.getElementById('f_med').getAttribute('fill'),
'f_low': svg.getElementById('f_low').getAttribute('fill'),
'f_inv': svg.getElementById('f_inv').getAttribute('fill'),
'b_high': svg.getElementById('b_high').getAttribute('fill'),
'b_med': svg.getElementById('b_med').getAttribute('fill'),
'b_low': svg.getElementById('b_low').getAttribute('fill'),
'b_inv': svg.getElementById('b_inv').getAttribute('fill')
}
} catch (err) {
console.warn('Theme', 'Incomplete SVG Theme', err)
}
}
function isJson (text) {
try { JSON.parse(text); return true } catch (error) { return false }
}
function isHtml (text) {
try { new DOMParser().parseFromString(text, 'text/xml'); return true } catch (error) { return false }
}
}

View File

@@ -0,0 +1,54 @@
function Color (hex = '#000000', rgb) {
if (rgb) {
this.rgb = rgb
this.hex = '#' + ('0' + parseInt(rgb.r, 10).toString(16)).slice(-2) + ('0' + parseInt(rgb.g, 10).toString(16)).slice(-2) + ('0' + parseInt(rgb.b, 10).toString(16)).slice(-2)
} else {
this.hex = hex
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this.hex)
this.rgb = { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) }
}
this.r = this.rgb.r
this.g = this.rgb.g
this.b = this.rgb.b
this.average = parseInt((this.r + this.g + this.b) / 3)
this.invert = { r: 255 - this.rgb.r, g: 255 - this.rgb.g, b: 255 - this.rgb.b }
this.rgba = function () {
return 'rgba(' + this.rgb().r + ',' + this.rgb().g + ',' + this.rgb().b + ',1)'
}
this.floats = function () {
var rgb = this.rgb
return { r: rgb.r / 255, g: rgb.g / 255, b: rgb.b / 255 }
}
this.render = function () {
return this.hex
}
this.rgb_to_hex = function (rgb) {
return '#' + ('0' + parseInt(rgb[0], 10).toString(16)).slice(-2) + ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) + ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2)
}
this.brightness = function () {
return this.rgb() ? (this.rgb().r + this.rgb().g + this.rgb().b) / 3 : 0
}
this.style = function () {
return this.brightness() > 150 ? 'bright' : 'dark'
}
this.tween = function (target, value) {
var c1 = this.floats()
var c2 = target.floats()
var r = ((255 * c1.r) * value) + ((255 * c2.r) * (1 - value))
var g = ((255 * c1.g) * value) + ((255 * c2.g) * (1 - value))
var b = ((255 * c1.b) * value) + ((255 * c2.b) * (1 - value))
var rgb = [parseInt(r), parseInt(g), parseInt(b)]
var hex = new Color().rgb_to_hex(rgb)
return hex
}
}