ronin/scripts/client.js
neauoire 8742548eaa *
2020-03-24 16:42:53 +09:00

246 lines
7.0 KiB
JavaScript

'use strict'
/* global Acels */
/* global Theme */
/* global Source */
/* global Commander */
/* global Surface */
/* global Library */
/* global Lisp */
/* global Image */
/* global requestAnimationFrame */
function Client () {
this.el = document.createElement('div')
this.el.id = 'ronin'
this.acels = new Acels(this)
this.theme = new Theme(this)
this.source = new Source(this)
this.commander = new Commander(this)
this.surface = new Surface(this)
this.library = new Library(this)
this.lisp = new Lisp(this.library)
this.bindings = {}
this.install = function (host = document.body) {
this._wrapper = document.createElement('div')
this._wrapper.id = 'wrapper'
this.commander.install(this._wrapper)
this.surface.install(this._wrapper)
this.el.appendChild(this._wrapper)
host.appendChild(this.el)
this.theme.install(host)
this.acels.install(host)
window.addEventListener('dragover', this.onDrag)
window.addEventListener('drop', this.onDrop)
this.acels.set('∷', 'Toggle Menubar', 'Tab', () => { this.acels.toggle() })
this.acels.set('∷', 'Open Theme', 'CmdOrCtrl+Shift+O', () => { this.theme.open() })
this.acels.set('∷', 'Reset Theme', 'CmdOrCtrl+Backspace', () => { this.theme.reset() })
this.acels.set('File', 'New', 'CmdOrCtrl+N', () => { this.source.new(); this.surface.clear(); this.commander.clear() })
this.acels.set('File', 'Save', 'CmdOrCtrl+S', () => { this.source.write('ronin', 'lisp', this.commander._input.value, 'text/plain') })
this.acels.set('File', 'Export Image', 'CmdOrCtrl+E', () => { this.source.write('ronin', 'png', this.surface.el.toDataURL('image/png', 1.0), 'image/png') })
this.acels.set('File', 'Open', 'CmdOrCtrl+O', () => { this.source.open('lisp', this.whenOpen) })
this.acels.add('Edit', 'undo')
this.acels.add('Edit', 'redo')
this.acels.add('Edit', 'cut')
this.acels.add('Edit', 'copy')
this.acels.add('Edit', 'paste')
this.acels.add('Edit', 'selectAll')
this.acels.set('View', 'Toggle Guides', 'CmdOrCtrl+Shift+H', () => { this.surface.toggleGuides() })
this.acels.set('View', 'Toggle Commander', 'CmdOrCtrl+K', () => { this.commander.toggle() })
this.acels.set('View', 'Expand Commander', 'CmdOrCtrl+Shift+K', () => { this.commander.toggle(true) })
this.acels.set('Project', 'Run', 'CmdOrCtrl+R', () => { this.commander.run() })
this.acels.set('Project', 'Reload Run', 'CmdOrCtrl+Shift+R', () => { this.source.revert(); this.commander.run() })
this.acels.set('Project', 'Re-Indent', 'CmdOrCtrl+Shift+I', () => { this.commander.reindent() })
this.acels.set('Project', 'Clean', 'Escape', () => { this.commander.cleanup() })
this.acels.route(this)
}
this.start = function () {
console.log('Ronin', 'Starting..')
console.info(`${this.acels}`)
this.theme.start()
this.acels.start()
this.source.start()
this.commander.start()
this.surface.start()
this.loop()
}
this.whenOpen = (file, res) => {
console.log(file, res)
this.commander.load(res)
this.commander.show()
}
this.loop = () => {
if (this.bindings.animate && typeof this.bindings.animate === 'function') {
this.bindings.animate()
}
requestAnimationFrame(() => this.loop())
}
this.log = (...msg) => {
this.commander.setStatus(msg.reduce((acc, val) => {
return acc + JSON.stringify(val).replace(/"/g, '').trim() + ' '
}, ''))
}
this.bind = (event, fn) => {
this.bindings[event] = fn
}
// Cursor
this.mouseOrigin = null
this.onMouseDown = (e, id = 'mouse-down') => {
const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
this.mouseOrigin = pos
const shape = this.mouseShape(pos, id)
if (this.bindings[id]) {
this.bindings[id](shape)
}
this.commander.capture()
this.surface.clearGuide()
this.surface.drawGuide(shape)
}
this.onKeyPress = (e, id = 'key-press') => {
if (this.bindings[id]) {
this.bindings[id](e)
}
}
this.onKeyDown = (e, id = 'key-down') => {
if (this.bindings[id]) {
this.bindings[id](e)
}
}
this.onKeyUp = (e, id = 'key-up') => {
if (this.bindings[id]) {
this.bindings[id](e)
}
}
this.onMouseMove = (e, id = 'mouse-move') => {
const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
const shape = this.mouseShape(pos, id)
if (this.bindings[id]) {
this.bindings[id](shape)
}
if (this.mouseOrigin) {
this.commander.commit(shape, false, e.which !== 1)
this.surface.clearGuide()
this.surface.drawGuide(shape)
}
}
this.onMouseUp = (e, id = 'mouse-up') => {
const pos = { x: e.offsetX * this.surface.ratio, y: e.offsetY * this.surface.ratio }
const shape = this.mouseShape(pos, id)
if (this.bindings[id]) {
this.bindings[id](shape)
}
if (this.mouseOrigin) {
this.commander.commit(shape, true, e.which !== 1)
}
this.mouseOrigin = null
this.surface.clearGuide()
}
this.onMouseOver = (e) => {
this.mouseOrigin = null
}
this.onMouseOut = (e) => {
this.mouseOrigin = null
}
this.onDrag = (e) => {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
this.onDrop = (e) => {
e.preventDefault()
e.stopPropagation()
const file = e.dataTransfer.files[0]
if (file.name.indexOf('.lisp') > -1) {
this.source.read(file, this.whenOpen)
this.log('Loaded ' + file.name)
}
if (file.type === 'image/jpeg' || file.type === 'image/png') {
const img = new Image()
img.onload = () => {
this.cache.set(file.name, img)
this.commander.injectPath(file.name)
this.log('Loaded ' + file.name)
}
img.src = URL.createObjectURL(file)
}
}
this.cache = {
data: {},
set: (key, content) => {
this.log((this.cache.data[key] ? 'Updated ' : 'Stored ') + key)
this.cache.data[key] = content
},
get: (key) => {
return this.cache.data[key]
}
}
this.mouseShape = (position, type) => {
if (!this.mouseOrigin) { return }
const x = position.x
const y = position.y
const xy = x + ' ' + y
const pos = { x, y }
const line = {
a: { x: this.mouseOrigin.x, y: this.mouseOrigin.y },
b: { x: pos.x, y: pos.y }
}
const size = { w: line.a.x ? pos.x - line.a.x : 0, h: line.a.y ? pos.y - line.a.y : 0 }
const rect = {
x: line.a.x,
y: line.a.y,
w: size.w,
h: size.h
}
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 a = Math.atan2(pos.y - line.a.y, pos.x - line.a.x).toFixed(2)
const circle = {
cx: line.a.x,
cy: line.a.y,
r: d
}
const arc = {
cx: line.a.x,
cy: line.a.y,
r: d,
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 }
}
}