Progress toward new controls

This commit is contained in:
neauoire 2019-11-03 13:36:58 -05:00
parent 9a7149edb3
commit ec3fee9ec2
15 changed files with 399 additions and 307 deletions

View File

@ -1,6 +1,6 @@
'use strict'
function Cursor () {
function Cursor (dotgrid) {
this.pos = { x: 0, y: 0 }
this.translation = null
this.operation = null
@ -96,4 +96,6 @@ function Cursor () {
}
function isEqual (a, b) { return a.x === b.x && a.y === b.y }
function clamp (v, min, max) { return v < min ? min : v > max ? max : v }
function step (v, s) { return Math.round(v / s) * s }
}

View File

@ -1,23 +1,28 @@
'use strict'
function Dotgrid () {
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'
}
/* global Acels */
/* global Theme */
/* global Source */
/* global History */
/* global Manager */
/* global Renderer */
/* global Tool */
/* global Interface */
/* global Picker */
/* global Cursor */
/* global webFrame */
/* global FileReader */
function Dotgrid () {
// ISU
this.install = function (host) {
console.info('Dotgrid', 'Installing..')
this.theme = new Theme(defaultTheme)
this.acels = new Acels()
this.theme = new Theme()
this.history = new History()
this.source = new Source(this)
@ -27,47 +32,86 @@ function Dotgrid () {
this.interface = new Interface(this)
this.picker = new Picker(this)
this.cursor = new Cursor(this)
this.listener = new Listener(this)
host.appendChild(this.renderer.el)
// Add events
document.addEventListener('mousedown', (e) => { this.cursor.down(e) }, false)
document.addEventListener('mousemove', (e) => { this.cursor.move(e) }, false)
document.addEventListener('contextmenu', (e) => { this.cursor.alt(e) }, false)
document.addEventListener('mouseup', (e) => { this.cursor.up(e) }, false)
document.addEventListener('copy', (e) => { this.copy(e) }, false)
document.addEventListener('cut', (e) => { this.cut(e) }, false)
document.addEventListener('paste', (e) => { this.paste(e) }, false)
window.addEventListener('resize', (e) => { this.onResize() }, false)
window.addEventListener('dragover', (e) => { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy' })
window.addEventListener('drop', this.drag)
this.acels.set('File', 'New', 'CmdOrCtrl+N', () => { this.source.new() })
this.acels.set('File', 'Save', 'CmdOrCtrl+S', () => { this.source.save('export.grid', this.commander._input.value, 'text/plain') })
this.acels.set('File', 'Export Image', 'CmdOrCtrl+E', () => { this.source.download('export.png', this.surface.el.toDataURL('image/png', 1.0), 'image/png') })
this.acels.set('File', 'Open', 'CmdOrCtrl+O', () => { this.source.open('grid', this.whenOpen) })
this.acels.set('File', 'Revert', 'CmdOrCtrl+W', () => { this.source.revert() })
this.acels.set('History', 'Undo', 'CmdOrCtrl+Z', () => { this.history.undo() })
this.acels.set('History', 'Redo', 'CmdOrCtrl+Shift+Z', () => { this.history.redo() })
this.acels.set('Stroke', 'Line', 'A', () => { this.tool.cast('line') })
this.acels.set('Stroke', 'Arc', 'S', () => { this.tool.cast('arc_c') })
this.acels.set('Stroke', 'Arc Rev', 'D', () => { this.tool.cast('arc_r') })
this.acels.set('Stroke', 'Bezier', 'F', () => { this.tool.cast('bezier') })
this.acels.set('Stroke', 'Close', 'Z', () => { this.tool.cast('close') })
this.acels.set('Stroke', 'Arc(full)', 'T', () => { this.tool.cast('arc_c_full') })
this.acels.set('Stroke', 'Arc Rev(full)', 'Y', () => { this.tool.cast('arc_r_full') })
this.acels.set('Stroke', 'Clear Selection', 'Escape', () => { this.tool.clear() })
this.acels.set('Effect', 'Linecap', 'Q', () => { this.tool.toggle('linecap') })
this.acels.set('Effect', 'Linejoin', 'W', () => { this.tool.toggle('linejoin') })
this.acels.set('Effect', 'Mirror', 'E', () => { this.tool.toggle('mirror') })
this.acels.set('Effect', 'Fill', 'R', () => { this.tool.toggle('fill') })
this.acels.set('Effect', 'Thicker', '}', () => { this.tool.toggle('thickness', 1) })
this.acels.set('Effect', 'Thinner', '{', () => { this.tool.toggle('thickness', -1) })
this.acels.set('Effect', 'Thicker +5', ']', () => { this.tool.toggle('thickness', 5) })
this.acels.set('Effect', 'Thinner -5', '[', () => { this.tool.toggle('thickness', -5) })
this.acels.set('Manual', 'Add Point', 'Enter', () => { this.tool.addVertex(this.cursor.pos); this.renderer.update() })
this.acels.set('Manual', 'Move Up', 'Up', () => { this.cursor.pos.y -= 15; this.renderer.update() })
this.acels.set('Manual', 'Move Right', 'Right', () => { this.cursor.pos.x += 15; this.renderer.update() })
this.acels.set('Manual', 'Move Down', 'Down', () => { this.cursor.pos.y += 15; this.renderer.update() })
this.acels.set('Manual', 'Move Left', 'Left', () => { this.cursor.pos.x -= 15; this.renderer.update() })
this.acels.set('Manual', 'Remove Point', 'Shift+Backspace', () => { this.tool.removeSegmentsAt(this.cursor.pos) })
this.acels.set('Manual', 'Remove Segment', 'Backspace', () => { this.tool.removeSegment() })
this.acels.set('Layers', 'Foreground', 'CmdOrCtrl+1', () => { this.tool.selectLayer(0) })
this.acels.set('Layers', 'Middleground', 'CmdOrCtrl+2', () => { this.tool.selectLayer(1) })
this.acels.set('Layers', 'Background', 'CmdOrCtrl+3', () => { this.tool.selectLayer(2) })
this.acels.set('Layers', 'Merge Layers', 'CmdOrCtrl+M', () => { this.tool.merge() })
this.acels.set('View', 'Color Picker', 'G', () => { this.picker.start() })
this.acels.set('View', 'Toggle Grid', 'H', () => { this.renderer.toggle() })
this.acels.install(window)
this.manager.install()
this.interface.install(host)
this.theme.install(host, () => { this.update() })
}
this.start = function () {
console.info('Dotgrid', 'Starting..')
this.start = () => {
console.log('Ronin', 'Starting..')
console.info(`${this.acels}`)
this.theme.start()
this.tool.start()
this.renderer.start()
this.interface.start()
// Add events
document.addEventListener('mousedown', function (e) { dotgrid.cursor.down(e) }, false)
document.addEventListener('mousemove', function (e) { dotgrid.cursor.move(e) }, false)
document.addEventListener('contextmenu', function (e) { dotgrid.cursor.alt(e) }, false)
document.addEventListener('mouseup', function (e) { dotgrid.cursor.up(e) }, false)
document.addEventListener('copy', function (e) { dotgrid.copy(e) }, false)
document.addEventListener('cut', function (e) { dotgrid.cut(e) }, false)
document.addEventListener('paste', function (e) { dotgrid.paste(e) }, false)
window.addEventListener('resize', function (e) { dotgrid.onResize() }, false)
window.addEventListener('dragover', function (e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer.dropEffect = 'copy' })
window.addEventListener('drop', dotgrid.drag)
this.source.new()
this.onResize()
setTimeout(() => { document.body.className += ' ready' }, 250)
}
this.update = function () {
this.update = () => {
this.manager.update()
this.interface.update()
this.renderer.update()
}
this.clear = function () {
this.clear = () => {
this.history.clear()
this.tool.reset()
this.reset()
@ -75,7 +119,7 @@ function Dotgrid () {
this.interface.update(true)
}
this.reset = function () {
this.reset = () => {
this.tool.clear()
this.update()
}
@ -103,7 +147,7 @@ function Dotgrid () {
// Resize Tools
this.fitSize = function () {
this.fitSize = () => {
if (this.requireResize() === false) { return }
console.log('Dotgrid', `Will resize to: ${printSize(this.getRequiredSize())}`)
this.setWindowSize(this.getRequiredSize())
@ -118,47 +162,47 @@ function Dotgrid () {
this.update()
}
this.getPadding = function () {
this.getPadding = () => {
return { x: 60, y: 90 }
}
this.getWindowSize = function () {
this.getWindowSize = () => {
return { width: window.innerWidth, height: window.innerHeight }
}
this.getProjectSize = function () {
this.getProjectSize = () => {
return this.tool.settings.size
}
this.getPaddedSize = function () {
this.getPaddedSize = () => {
const rect = this.getWindowSize()
const pad = this.getPadding()
return { width: step(rect.width - pad.x, 15), height: step(rect.height - pad.y, 15) }
}
this.getRequiredSize = function () {
this.getRequiredSize = () => {
const rect = this.getProjectSize()
const pad = this.getPadding()
return { width: step(rect.width, 15) + pad.x, height: step(rect.height, 15) + pad.y }
}
this.requireResize = function () {
this.requireResize = () => {
const _window = this.getWindowSize()
const _required = this.getRequiredSize()
const offset = sizeOffset(_window, _required)
if (offset.width !== 0 || offset.height !== 0) {
console.log(`Dotgrid`, `Require ${printSize(_required)}, but window is ${printSize(_window)}(${printSize(offset)})`)
console.log('Dotgrid', `Require ${printSize(_required)}, but window is ${printSize(_window)}(${printSize(offset)})`)
return true
}
return false
}
this.onResize = function () {
this.onResize = () => {
const _project = this.getProjectSize()
const _padded = this.getPaddedSize()
const offset = sizeOffset(_padded, _project)
if (offset.width !== 0 || offset.height !== 0) {
console.log(`Dotgrid`, `Resize project to ${printSize(_padded)}`)
console.log('Dotgrid', `Resize project to ${printSize(_padded)}`)
this.tool.settings.size = _padded
}
this.update()
@ -179,39 +223,39 @@ function Dotgrid () {
reader.onload = function (e) {
const data = e.target && e.target.result ? e.target.result : ''
dotgrid.source.load(filename, data)
dotgrid.fitSize()
this.source.load(filename, data)
this.fitSize()
}
reader.readAsText(file)
}
this.copy = function (e) {
dotgrid.renderer.update()
this.renderer.update()
if (e.target !== this.picker.input) {
e.clipboardData.setData('text/source', dotgrid.tool.export(dotgrid.tool.layer()))
e.clipboardData.setData('text/plain', dotgrid.tool.path())
e.clipboardData.setData('text/html', dotgrid.manager.el.outerHTML)
e.clipboardData.setData('text/svg+xml', dotgrid.manager.el.outerHTML)
e.clipboardData.setData('text/source', this.tool.export(this.tool.layer()))
e.clipboardData.setData('text/plain', this.tool.path())
e.clipboardData.setData('text/html', this.manager.el.outerHTML)
e.clipboardData.setData('text/svg+xml', this.manager.el.outerHTML)
e.preventDefault()
}
dotgrid.renderer.update()
this.renderer.update()
}
this.cut = function (e) {
dotgrid.renderer.update()
this.renderer.update()
if (e.target !== this.picker.input) {
e.clipboardData.setData('text/source', dotgrid.tool.export(dotgrid.tool.layer()))
e.clipboardData.setData('text/plain', dotgrid.tool.export(dotgrid.tool.layer()))
e.clipboardData.setData('text/html', dotgrid.manager.el.outerHTML)
e.clipboardData.setData('text/svg+xml', dotgrid.manager.el.outerHTML)
dotgrid.tool.layers[dotgrid.tool.index] = []
e.clipboardData.setData('text/source', this.tool.export(this.tool.layer()))
e.clipboardData.setData('text/plain', this.tool.export(this.tool.layer()))
e.clipboardData.setData('text/html', this.manager.el.outerHTML)
e.clipboardData.setData('text/svg+xml', this.manager.el.outerHTML)
this.tool.layers[this.tool.index] = []
e.preventDefault()
}
dotgrid.renderer.update()
this.renderer.update()
}
this.paste = function (e) {
@ -219,12 +263,12 @@ function Dotgrid () {
let data = e.clipboardData.getData('text/source')
if (isJson(data)) {
data = JSON.parse(data.trim())
dotgrid.tool.import(data)
this.tool.import(data)
}
e.preventDefault()
}
dotgrid.renderer.update()
this.renderer.update()
}
}

View File

@ -38,9 +38,9 @@ function Generator (layer, style) {
for (const id in vertices) {
if (skip > 0) { skip -= 1; continue }
let vertex = vertices[parseInt(id)]
let next = vertices[parseInt(id) + 1]
let afterNext = vertices[parseInt(id) + 2]
const vertex = vertices[parseInt(id)]
const next = vertices[parseInt(id) + 1]
const afterNext = vertices[parseInt(id) + 2]
if (parseInt(id) === 0 && !prev || parseInt(id) === 0 && prev && (prev.x !== vertex.x || prev.y !== vertex.y)) {
html += `M${vertex.x},${vertex.y} `
@ -49,16 +49,16 @@ function Generator (layer, style) {
if (type === 'line') {
html += this._line(vertex)
} else if (type === 'arc_c') {
let clock = mirror > 0 && mirror < 3 ? '0,0' : '0,1'
const clock = mirror > 0 && mirror < 3 ? '0,0' : '0,1'
html += this._arc(vertex, next, clock)
} else if (type === 'arc_r') {
let clock = mirror > 0 && mirror < 3 ? '0,1' : '0,0'
const clock = mirror > 0 && mirror < 3 ? '0,1' : '0,0'
html += this._arc(vertex, next, clock)
} else if (type === 'arc_c_full') {
let clock = mirror > 0 ? '1,0' : '1,1'
const clock = mirror > 0 ? '1,0' : '1,1'
html += this._arc(vertex, next, clock)
} else if (type === 'arc_r_full') {
let clock = mirror > 0 ? '1,1' : '1,0'
const clock = mirror > 0 ? '1,1' : '1,0'
html += this._arc(vertex, next, clock)
} else if (type === 'bezier') {
html += this._bezier(next, afterNext)

View File

@ -16,7 +16,7 @@ function Interface (dotgrid) {
this.start = function (host) {
let html = ''
let options = {
const options = {
cast: {
line: { key: 'A', icon: 'M60,60 L240,240' },
arc_c: { key: 'S', icon: 'M60,60 A180,180 0 0,1 240,240' },
@ -57,7 +57,7 @@ function Interface (dotgrid) {
onmouseover="dotgrid.interface.over('${type}','${name}')"
viewBox="0 0 300 300"
class="icon ${type}">
<path id="${name}_path" class="icon_path" d="${tool.icon}"/>${name === 'depth' ? `<path class="icon_path inactive" d=""/>` : ''}
<path id="${name}_path" class="icon_path" d="${tool.icon}"/>${name === 'depth' ? '<path class="icon_path inactive" d=""/>' : ''}
<rect ar="${name}" width="300" height="300" opacity="0">
<title>${name.capitalize()}${tool.key ? '(' + tool.key + ')' : ''}</title>
</rect>
@ -89,7 +89,7 @@ function Interface (dotgrid) {
this.down = function (type, name, event) {
if (!dotgrid.tool[type]) { console.warn(`Unknown option(type): ${type}.${name}`, dotgrid.tool); return }
const mod = event.button == 2 ? -1 : 1;
const mod = event.button === 2 ? -1 : 1
dotgrid.tool[type](name, mod)
this.update(true)
dotgrid.renderer.update(true)
@ -101,7 +101,7 @@ function Interface (dotgrid) {
if (this.prev_operation === dotgrid.cursor.operation && force === false) { return }
let multiVertices = null
let segments = dotgrid.tool.layer()
const segments = dotgrid.tool.layer()
const sumSegments = dotgrid.tool.length()
for (const i in segments) {

View File

@ -0,0 +1,115 @@
'use strict'
function Acels () {
this.all = {}
this.install = (host = window) => {
host.addEventListener('keydown', this.onKeyDown, false)
host.addEventListener('keyup', this.onKeyUp, false)
}
this.set = (cat, name, accelerator, downfn, upfn) => {
if (this.all[accelerator]) { console.warn('Acels', `Trying to overwrite ${this.all[accelerator].name}, with ${name}.`) }
this.all[accelerator] = { cat, name, downfn, upfn, accelerator }
}
this.get = (accelerator) => {
return this.all[accelerator]
}
this.sort = () => {
const h = {}
for (const item of Object.values(this.all)) {
if (!h[item.cat]) { h[item.cat] = [] }
h[item.cat].push(item)
}
return h
}
this.convert = (event) => {
const accelerator = event.key.substr(0, 1).toUpperCase() + event.key.substr(1)
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
return `CmdOrCtrl+Shift+${accelerator}`
}
if (event.shiftKey) {
return `Shift+${accelerator}`
}
if (event.ctrlKey || event.metaKey) {
return `CmdOrCtrl+${accelerator}`
}
return accelerator
}
this.onKeyDown = (e) => {
const target = this.get(this.convert(e))
if (!target || !target.downfn) { return }
target.downfn()
e.preventDefault()
}
this.onKeyUp = (e) => {
const target = this.get(this.convert(e))
if (!target || !target.upfn) { return }
target.upfn()
e.preventDefault()
}
this.toMarkdown = () => {
const cats = this.sort()
let text = ''
for (const cat in cats) {
text += `\n### ${cat}\n\n`
for (const item of cats[cat]) {
text += `- \`${item.accelerator}\`: ${item.info}\n`
}
}
return text.trim()
}
this.toString = () => {
const cats = this.sort()
let text = ''
for (const cat in cats) {
for (const item of cats[cat]) {
text += `${cat}: ${item.name} | ${item.accelerator}\n`
}
}
return text.trim()
}
// Electron specifics
this.inject = (name = 'Untitled') => {
const app = require('electron').remote.app
const injection = []
injection.push({
label: name,
submenu: [
{ label: 'About', click: () => { require('electron').shell.openExternal('https://github.com/hundredrabbits/' + name) } },
{ label: 'Download Themes', click: () => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Themes') } },
{ label: 'Fullscreen', accelerator: 'CmdOrCtrl+Enter', click: () => { app.toggleFullscreen() } },
{ label: 'Hide', accelerator: 'CmdOrCtrl+H', click: () => { app.toggleVisible() } },
{ label: 'Toggle Menubar', accelerator: 'Alt+H', click: () => { app.toggleMenubar() } },
{ label: 'Inspect', accelerator: 'CmdOrCtrl+.', click: () => { app.inspect() } },
{ label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: () => { app.exit() } }
]
})
const sorted = this.sort()
for (const cat of Object.keys(sorted)) {
const submenu = []
for (const option of sorted[cat]) {
if (option.role) {
submenu.push({ role: option.role })
} else if (option.type) {
submenu.push({ type: option.type })
} else {
submenu.push({ label: option.name, accelerator: option.accelerator, click: option.downfn })
}
}
injection.push({ label: cat, submenu: submenu })
}
app.injectMenu(injection)
}
}

View File

@ -1,72 +0,0 @@
'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,69 @@
'use strict'
/* global FileReader */
/* global MouseEvent */
function Source () {
this.cache = {}
this.install = () => {
}
this.start = () => {
this.new()
}
this.new = () => {
console.log('Source', 'New file..')
this.cache = {}
}
this.open = (ext, callback) => {
console.log('Source', 'Open file..')
const input = document.createElement('input')
input.type = 'file'
input.onchange = (e) => {
const file = e.target.files[0]
if (file.name.indexOf(ext) < 0) { console.warn('Source', 'File is not ' + ext); return }
this.cache = file
this.load(this.cache, callback)
}
input.click()
}
this.save = (name, content, type = 'text/plain', callback) => {
this.saveAs(name, content, type, callback)
}
this.saveAs = (name, content, type = 'text/plain', callback) => {
console.log('Source', 'Save new file..')
this.download(name, content, type, callback)
}
this.revert = () => {
}
// I/O
this.load = (file, callback) => {
const reader = new FileReader()
reader.onload = (event) => {
const res = event.target.result
callback(res)
}
reader.readAsText(file, 'UTF-8')
}
this.download = (name, content, type) => {
console.info('Source', `Downloading ${name}(${type})`)
const link = document.createElement('a')
link.setAttribute('download', name)
if (type === 'image/png' || type === 'image/jpeg') {
link.setAttribute('href', content)
} else {
link.setAttribute('href', 'data:' + type + ';charset=utf-8,' + encodeURIComponent(content))
}
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
}
}

View File

@ -1,19 +1,25 @@
'use strict'
function Theme (_default) {
/* global localStorage */
/* global FileReader */
/* global DOMParser */
function Theme () {
const themer = this
this.active = _default
this.default = { 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.active = {}
this.el = document.createElement('style')
this.el.type = 'text/css'
this.install = function (host = document.body, callback) {
this.install = (host = document.body, callback) => {
host.appendChild(this.el)
this.callback = callback
}
this.start = function () {
this.start = () => {
this.active = this.default
console.log('Theme', 'Starting..')
if (isJson(localStorage.theme)) {
const storage = JSON.parse(localStorage.theme)
@ -23,13 +29,13 @@ function Theme (_default) {
return
}
}
this.load(_default)
this.load(this.active)
}
this.load = function (data) {
this.load = (data) => {
const theme = parse(data)
if (!validate(theme)) { console.warn('Theme', 'Not a theme', theme); return }
console.log('Theme', `Loaded theme!`)
if (!validate(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
@ -38,8 +44,12 @@ function Theme (_default) {
}
}
this.reset = function () {
this.load(_default)
this.reset = () => {
this.load(this.default)
}
this.get = (key) => {
return this.active[key]
}
function parse (any) {
@ -49,18 +59,18 @@ function Theme (_default) {
// Drag
this.drag = function (e) {
this.drag = (e) => {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
}
this.drop = function (e) {
this.drop = (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 }
if (file.name.indexOf('.thm') < 0 && file.name.indexOf('.svg') < 0) { return }
const reader = new FileReader()
reader.onload = function (e) {
themer.load(e.target.result)
@ -68,10 +78,10 @@ function Theme (_default) {
reader.readAsText(file)
}
this.open = function () {
this.open = () => {
const fs = require('fs')
const { dialog, app } = require('electron').remote
let paths = dialog.showOpenDialog(app.win, { properties: ['openFile'], filters: [{ name: 'Themes', extensions: ['svg'] }] })
const 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
@ -102,15 +112,15 @@ function Theme (_default) {
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')
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)

View File

@ -1,69 +0,0 @@
'use strict'
// Dotgrid UDP Listener
// Ex: 1a0156(6 characters 0-9a-z)
// 0 layer[0-2]
// 1 type[lcrd*]
// 2 from[0-z][0-z]
// 4 to[0-z][0-z]
const dgram = require('dgram')
function Listener (dotgrid) {
this.server = dgram.createSocket('udp4')
function base36 (c) {
return clamp(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y'].indexOf(c.toLowerCase()), 0, 36) * 15 + 15
}
function clear () {
dotgrid.tool.erase()
}
function operate (data) {
if (!data) { return }
if (!dotgrid.tool.layers[data.layer]) { return }
dotgrid.tool.addSegment(data.type, [data.from, data.to], data.layer)
}
function draw () {
dotgrid.renderer.update(true)
}
function parse (msg) {
if (msg === '' || msg === '*') {
return draw()
}
if (['0', '1', '2'].indexOf(msg) > -1) {
return clear()
}
const layer = parseInt(msg.substr(0, 1))
const type = { 'l': 'line', 'c': 'arc_c', 'r': 'arc_r', '*': 'draw' }[msg.substr(1, 1).toLowerCase()]
const from = { x: base36(msg.substr(2, 1)), y: base36(msg.substr(3, 1)) }
const to = { x: base36(msg.substr(4, 1)), y: base36(msg.substr(5, 1)) }
return { layer: layer, type: type, from: from, to: to }
}
function clamp (v, min, max) {
return v < min ? min : v > max ? max : v
}
// Server
this.server.on('message', (msg, rinfo) => {
operate(parse(`${msg}`))
})
this.server.on('listening', () => {
const address = this.server.address()
console.log(`Server listening for UDP:\n ${address.address}:${address.port}`)
})
this.server.on('error', (err) => {
console.log(`Server error:\n ${err.stack}`)
server.close()
})
this.server.bind(49161)
}

View File

@ -30,7 +30,7 @@ function Manager (dotgrid) {
for (const id in this.layers) {
let style = styles[id]
let path = paths[id]
let layer = this.layers[id]
const layer = this.layers[id]
// Easter Egg
if (dotgrid.tool.settings.crest === true) {
style = styles[0]
@ -49,9 +49,9 @@ function Manager (dotgrid) {
}
this.svg64 = function () {
let xml = new XMLSerializer().serializeToString(this.el)
let svg64 = btoa(xml)
let b64Start = 'data:image/svg+xml;base64,'
const xml = new XMLSerializer().serializeToString(this.el)
const svg64 = btoa(xml)
const b64Start = 'data:image/svg+xml;base64,'
return b64Start + svg64
}
@ -60,9 +60,9 @@ function Manager (dotgrid) {
this.toPNG = function (size = dotgrid.tool.settings.size, callback) {
this.update()
let image64 = this.svg64()
let img = new Image()
let canvas = document.createElement('canvas')
const image64 = this.svg64()
const img = new Image()
const canvas = document.createElement('canvas')
canvas.width = (size.width) * 2
canvas.height = (size.height) * 2
img.onload = function () {

View File

@ -18,6 +18,9 @@ function Picker (dotgrid) {
this.input.setAttribute('placeholder', `${dotgrid.tool.style().color.replace('#', '').trim()}`)
this.input.setAttribute('maxlength', 6)
this.input.addEventListener('keydown', this.onKeyDown, false)
this.input.addEventListener('keyup', this.onKeyUp, false)
dotgrid.interface.el.className = 'picker'
this.input.focus()
this.input.value = ''
@ -27,7 +30,7 @@ function Picker (dotgrid) {
this.update = function () {
if (!this.isActive) { return }
if (!is_color(this.input.value)) { return }
if (!isColor(this.input.value)) { return }
const hex = `#${this.input.value}`
@ -50,7 +53,7 @@ function Picker (dotgrid) {
}
this.validate = function () {
if (!is_color(this.input.value)) { return }
if (!isColor(this.input.value)) { return }
const hex = `#${this.input.value}`
@ -60,28 +63,7 @@ function Picker (dotgrid) {
this.stop()
}
this.listen = function (e, is_down = false) {
if (is_down && !isColorChar(e.key)) {
e.preventDefault()
return
}
if (e.key === 'Enter') {
this.validate()
e.preventDefault()
return
}
if (e.key === 'Escape') {
this.stop()
e.preventDefault()
return
}
this.update()
}
function is_color (val) {
function isColor (val) {
if (val.length !== 3 && val.length !== 6) {
return false
}
@ -90,11 +72,22 @@ function Picker (dotgrid) {
return re.test(val)
}
function isColorChar (val) {
const re = /[0-9A-Fa-f]/g
return re.test(val)
this.onKeyDown = (e) => {
if (e.key === 'Enter') {
this.validate()
e.preventDefault()
return
}
if (e.key === 'Escape') {
this.stop()
e.preventDefault()
return
}
e.stopPropagation()
}
this.input.onkeydown = function (event) { dotgrid.picker.listen(event, true) }
this.input.onkeyup = function (event) { dotgrid.picker.listen(event) }
this.onKeyUp = (e) => {
e.stopPropagation()
this.update()
}
}

View File

@ -19,7 +19,7 @@ function Renderer (dotgrid) {
this.update = function (force = false) {
this.resize()
dotgrid.manager.update()
let render = new Image()
const render = new Image()
render.onload = () => {
this.draw(render)
}
@ -106,7 +106,7 @@ function Renderer (dotgrid) {
for (let x = markers.w - 1; x >= 0; x--) {
for (let y = markers.h - 1; y >= 0; y--) {
let isStep = x % 4 === 0 && y % 4 === 0
const isStep = x % 4 === 0 && y % 4 === 0
// Don't draw margins
if (x === 0 || y === 0) { continue }
this.drawMarker({
@ -129,13 +129,13 @@ function Renderer (dotgrid) {
}
this.drawPreview = function () {
let operation = dotgrid.cursor.operation && dotgrid.cursor.operation.cast ? dotgrid.cursor.operation.cast : null
const operation = dotgrid.cursor.operation && dotgrid.cursor.operation.cast ? dotgrid.cursor.operation.cast : null
if (!dotgrid.tool.canCast(operation)) { return }
if (operation === 'close') { return }
let path = new Generator([{ vertices: dotgrid.tool.vertices, type: operation }]).toString({ x: 0, y: 0 }, 2)
let style = {
const path = new Generator([{ vertices: dotgrid.tool.vertices, type: operation }]).toString({ x: 0, y: 0 }, 2)
const style = {
color: dotgrid.theme.active.f_med,
thickness: 2,
strokeLinecap: 'round',
@ -201,7 +201,7 @@ function Renderer (dotgrid) {
}
this.drawPath = function (path, style) {
let p = new Path2D(path)
const p = new Path2D(path)
this.context.strokeStyle = style.color
this.context.lineWidth = style.thickness * this.scale

View File

@ -65,7 +65,7 @@ function Source (dotgrid) {
const link = document.createElement('a')
link.setAttribute('href', base64)
link.setAttribute('download', name)
link.dispatchEvent(new MouseEvent(`click`, { bubbles: true, cancelable: true, view: window }))
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }))
}
function isJson (text) { try { JSON.parse(text); return true } catch (error) { return false } }

View File

@ -102,9 +102,9 @@ function Tool (dotgrid) {
this.removeSegmentsAt = function (pos) {
for (const segmentId in this.layer()) {
let segment = this.layer()[segmentId]
const segment = this.layer()[segmentId]
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
if (Math.abs(pos.x) === Math.abs(vertex.x) && Math.abs(pos.y) === Math.abs(vertex.y)) {
segment.vertices.splice(vertexId, 1)
}
@ -119,11 +119,11 @@ function Tool (dotgrid) {
}
this.selectSegmentAt = function (pos, source = this.layer()) {
let target_segment = null
const target_segment = null
for (const segmentId in source) {
let segment = source[segmentId]
const segment = source[segmentId]
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
if (vertex.x === Math.abs(pos.x) && vertex.y === Math.abs(pos.y)) {
return segment
}
@ -140,9 +140,9 @@ function Tool (dotgrid) {
this.vertexAt = function (pos) {
for (const segmentId in this.layer()) {
let segment = this.layer()[segmentId]
const segment = this.layer()[segmentId]
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
if (vertex.x === Math.abs(pos.x) && vertex.y === Math.abs(pos.y)) {
return vertex
}
@ -152,7 +152,7 @@ function Tool (dotgrid) {
}
this.addSegment = function (type, vertices, index = this.index) {
let append_target = this.canAppend({ type: type, vertices: vertices }, index)
const append_target = this.canAppend({ type: type, vertices: vertices }, index)
if (append_target) {
this.layer(index)[append_target].vertices = this.layer(index)[append_target].vertices.concat(vertices)
} else {
@ -179,11 +179,11 @@ function Tool (dotgrid) {
this.toggle = function (type, mod = 1) {
if (type === 'linecap') {
let a = ['butt', 'square', 'round']
const a = ['butt', 'square', 'round']
this.i.linecap += mod
this.style().strokeLinecap = a[this.i.linecap % a.length]
} else if (type === 'linejoin') {
let a = ['miter', 'round', 'bevel']
const a = ['miter', 'round', 'bevel']
this.i.linejoin += mod
this.style().strokeLinejoin = a[this.i.linejoin % a.length]
} else if (type === 'fill') {
@ -221,7 +221,7 @@ function Tool (dotgrid) {
this.canAppend = function (content, index = this.index) {
for (const id in this.layer(index)) {
let stroke = this.layer(index)[id]
const stroke = this.layer(index)[id]
if (stroke.type !== content.type) { continue }
if (!stroke.vertices) { continue }
if (!stroke.vertices[stroke.vertices.length - 1]) { continue }
@ -236,7 +236,7 @@ function Tool (dotgrid) {
if (!type) { return false }
// Cannot cast close twice
if (type === 'close') {
let prev = this.layer()[this.layer().length - 1]
const prev = this.layer()[this.layer().length - 1]
if (!prev || prev.type === 'close') {
return false
}
@ -250,9 +250,9 @@ function Tool (dotgrid) {
}
this.paths = function () {
let l1 = new Generator(dotgrid.tool.layers[0], dotgrid.tool.styles[0]).toString({ x: 0, y: 0 }, 1)
let l2 = new Generator(dotgrid.tool.layers[1], dotgrid.tool.styles[1]).toString({ x: 0, y: 0 }, 1)
let l3 = new Generator(dotgrid.tool.layers[2], dotgrid.tool.styles[2]).toString({ x: 0, y: 0 }, 1)
const l1 = new Generator(dotgrid.tool.layers[0], dotgrid.tool.styles[0]).toString({ x: 0, y: 0 }, 1)
const l2 = new Generator(dotgrid.tool.layers[1], dotgrid.tool.styles[1]).toString({ x: 0, y: 0 }, 1)
const l3 = new Generator(dotgrid.tool.layers[2], dotgrid.tool.styles[2]).toString({ x: 0, y: 0 }, 1)
return [l1, l2, l3]
}
@ -263,9 +263,9 @@ function Tool (dotgrid) {
this.translate = function (a, b) {
for (const segmentId in this.layer()) {
let segment = this.layer()[segmentId]
const segment = this.layer()[segmentId]
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
if (vertex.x === Math.abs(a.x) && vertex.y === Math.abs(a.y)) {
segment.vertices[vertexId] = { x: Math.abs(b.x), y: Math.abs(b.y) }
}
@ -283,7 +283,7 @@ function Tool (dotgrid) {
if (!segment) { return }
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
}
@ -295,9 +295,9 @@ function Tool (dotgrid) {
this.translateLayer = function (a, b) {
const offset = { x: a.x - b.x, y: a.y - b.y }
for (const segmentId in this.layer()) {
let segment = this.layer()[segmentId]
const segment = this.layer()[segmentId]
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
}
}
@ -313,7 +313,7 @@ function Tool (dotgrid) {
if (!segment) { return }
for (const vertexId in segment.vertices) {
let vertex = segment.vertices[vertexId]
const vertex = segment.vertices[vertexId]
segment.vertices[vertexId] = { x: vertex.x - offset.x, y: vertex.y - offset.y }
}
this.layer().push(segment)

View File

@ -19,6 +19,7 @@
<meta property="og:description" content="A minimalist vector graphics tool." />
<meta property="og:site_name" content="Dotgrid" />
<!-- Generics -->
<script type="text/javascript" src="desktop/sources/scripts/lib/acels.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/lib/theme.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/lib/history.js"></script>
<!-- Dotgrid -->
@ -30,9 +31,7 @@
<script type="text/javascript" src="desktop/sources/scripts/tool.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/generator.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/picker.js"></script>
<script type="text/javascript" src="desktop/sources/scripts//source.js"></script>
<!-- Web Specific -->
<script type="text/javascript" src="web/events.js"></script>
<script type="text/javascript" src="desktop/sources/scripts/source.js"></script>
<!-- Styles -->
<link rel="stylesheet" type="text/css" href="desktop/sources/links/reset.css"/>
<link rel="stylesheet" type="text/css" href="desktop/sources/links/fonts.css"/>
@ -42,14 +41,15 @@
</head>
<body class='web'>
<script>
'use strict';
function Listener(){}
const dialog = null;
'use strict'
const dotgrid = new Dotgrid()
dotgrid.install(document.body);
window.addEventListener('load', () => { dotgrid.start(); })
dotgrid.install(document.body)
window.addEventListener('load', () => {
dotgrid.start()
})
</script>
</body>
</html>