forked from edemaine/svgtiler-gui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gui.coffee
330 lines (291 loc) · 10.8 KB
/
gui.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
sidebarScale = 2
defaultMapping = '''
- <symbol viewBox="0 0 20 10"><rect width="20" height="10" fill="purple"/></symbol>
| <symbol viewBox="0 0 10 20"><rect width="10" height="20" fill="purple"/></symbol>
+ <symbol viewBox="0 0 10 10"><rect width="10" height="10" fill="green"/></symbol>
X <symbol viewBox="-10 -10 20 20" width="auto" height="auto"><circle r="10" fill="red"/></symbol>
<symbol viewBox="0 0 10 10" width="10" height="10"></symbol>
'''
mappings = new svgtiler.Mappings()
tiles = {}
selectedTile = null
board = null
id = (i) -> document.getElementById i
text = (t) -> document.createTextNode t
element = (tag, className, children) ->
el = document.createElement tag
el.className = className if className?
el.appendChild child for child in children if children?
el
TODO = -> alert "Sorry, this feature isn't supported yet."
formatKey = (key) ->
key.replace(/ /g, '␣') or '(empty)'
load = (filename, filedata) ->
input = svgtiler.Input.recognize filename, filedata
if input instanceof svgtiler.Mapping
addMapping input
addMapping = (mapping) ->
mappings.push mapping
id('mappings').appendChild div = element 'div', 'mapping', [
text mapping.filename + " "
del = element 'i', 'fas fa-times delete'
]
del.addEventListener 'click', -> removeMapping(mapping)
if mapping.function?
div.setAttribute 'title', 'function-defined mapping'
else
div.setAttribute 'title', 'symbols: ' +
(formatKey(key) for key of mapping.map).join ', '
addTile key for key of mapping.map
id('key').style.visibility = 'visible'
board?.update()
removeMapping = (mapping) ->
index = mappings.maps.indexOf mapping
return unless index > -1
mappings.maps.splice(index, 1)
id('mappings').removeChild id('mappings').children[index]
# removeTile key for key of mapping.map
id('key').style.visibility = if mappings.maps.length then 'visible' else 'hidden'
board?.update()
addTile = (key) ->
return if key of tiles
symbol = mappings.lookup(key) ? svgtiler.unrecognizedSymbol
if symbol.use?
symbol = symbol.use new svgtiler.Context [[symbol]], 0, 0
symbolSvg = symbol.xml.documentElement.cloneNode true
symbolSvg.setAttribute 'id', symbolId = 't' + symbol.id()
svg = document.createElementNS svgtiler.SVGNS, 'svg'
#svg.setAttribute 'xmlns:xlink', svgtiler.XLINKNS
svg.setAttribute 'width', symbol.width * sidebarScale
svg.setAttribute 'height', symbol.height * sidebarScale
svg.appendChild symbolSvg
svg.setAttributeNS svgtiler.SVGNS, 'viewBox',
"0 0 #{symbol.viewBox[2]} #{symbol.viewBox[3]}"
if symbolSvg.tagName == 'symbol'
use = document.createElementNS svgtiler.SVGNS, 'use'
use.setAttribute 'href', '#' + symbolId
svg.appendChild use
tiles[key] = tile = element 'div', 'tile', [
element 'div', null, [
element 'code', null, [text formatKey key]
del = element 'i', 'fas fa-times delete'
]
svg
]
del.addEventListener 'click', (e) ->
removeTile(key)
e.stopPropagation()
tile.addEventListener 'click', ->
tiles[selectedTile]?.classList.remove 'selected'
this.classList.add 'selected'
selectedTile = key
id('tiles').appendChild tile
removeTile = (key) ->
return unless key of tiles
selectedTile = null if selectedTile == key
tile = tiles[key]
delete tiles[key]
id('tiles').removeChild tile
class Board
@minSize: 3
constructor: (height=Board.minSize, width=Board.minSize, @emptyTile=' ') ->
@board = (@emptyTile for c in [0...width] for r in [0...height])
@listeners = []
@update()
addListener: (func) -> @listeners.push func
height: -> @board.length
width: -> @board[0].length
set: (r, c, key) -> @board[r][c] = key if @board[r]?[c]?
get: (r, c) -> @board[r]?[c] ? @emptyTile
resize: (dh, dw, dr, dc, skip) ->
@board = for r in [0...@height() + dh]
for c in [0...@width() + dw]
continue if skip? and skip r, c
oldR = r - (dr?(r, c) ? dr)
oldC = c - (dc?(r, c) ? dc)
@get oldR, oldC
@update()
addRow: (row, copy) ->
console.log 'addRow', row
@resize 1, 0,
((r, c) -> r > row), 0, # dr, dc
((r, c) -> not copy and r == coord) # skip
addColumn: (col, copy) ->
@resize 0, 1,
0, ((r, c) -> c > col), # dr, dc
((r, c) -> not copy and c == coord) # skip
removeRow: (row) ->
@resize -1, 0,
((r, c) -> -(r >= row)), 0 # dr, dc
removeColumn: (col) ->
@resize 0, -1,
0, ((r, c) -> -(c >= col)) # dr, dc
emptyRow: (r) ->
for cell in @board[r] when cell != emptyTile
return false
true
emptyCol: (c) ->
for row in @board when row[c] != emptyTile
return false
true
autosize: ->
TODO() # TODO: update this to use [r][c] instead of [x][y]
## Left
for x in [0...@nx()]
break unless @emptyX x
if x == 0 ## no blanks: grow
@resize 1, 0, 1, 0
else
x -= 1 ## leave one blank on side
x = Math.min x, @nx() - Board.minSize ## size at least Board.minSize
if x > 0
@resize -x, 0, -x, 0
## Right
for x in [@nx()-1..0]
break unless @emptyX x
if x == @nx()-1 ## no blanks: grow
@resize 1, 0, 0, 0
else
x += 2 ## leave one blank
x = Math.max x, Board.minSize ## size at least Board.minSize
if x < @nx()
@resize x - @nx(), 0, 0, 0
## Top
for y in [0...@ny()]
break unless @emptyY y
if y == 0 ## no blanks: grow
@resize 0, 1, 0, 1
else
y -= 1 ## leave one blank on side
y = Math.min y, @ny() - Board.minSize ## size at least Board.minSize
if y > 0
@resize 0, -y, 0, -y
## Bottom
for y in [@ny()-1..0]
break unless @emptyY y
if y == @ny()-1 ## no blanks: grow
@resize 0, 1, 0, 0
else
y += 2 ## leave one blank
y = Math.max y, Board.minSize ## size at least Board.minSize
if y < @ny()
@resize 0, y - @ny(), 0, 0
toDrawing: ->
d = new svgtiler.Drawing()
d.data = (key for key in row for row in @board)
d
update: ->
# @autosize()
lis(this) for lis in @listeners
class BoardView
@defaultScale: 2
@addRowKey: '__add_row__'
@addColKey: '__add_col__'
@removeRowKey: '__remove_row__'
@removeColKey: '__remove_col__'
@emptySpecialKey: '__empty__'
constructor: (@board, @boardDiv) ->
@board.addListener(@redraw)
@scale = BoardView.defaultScale
uiMapping = new svgtiler.Mapping()
uiMapping.load (key) =>
return unless @isSpecialKey key
return '' if key == BoardView.emptySpecialKey
isRow = key in [BoardView.addRowKey, BoardView.removeRowKey]
isAdd = key in [BoardView.addRowKey, BoardView.addColKey]
"""
<symbol viewBox='0 0 20 20' #{if isRow then 'height' else 'width'}='auto'>
<text class='fas' x='10' y='10' dominant-baseline='middle' text-anchor='middle' fill='#{if isAdd then 'green' else 'red'}'>
#{if isAdd then '' else ''}
</text>
</symbol>
"""
@mappings = new svgtiler.Mappings [uiMapping, mappings]
@redraw()
isSpecialKey: (key) -> /^__\w+__$/i.test key
preprocessDrawing: (drawing) ->
height = drawing.data.length
width = drawing.data[0].length
for row in drawing.data
row.push BoardView.addRowKey
row.push if height > 1 then BoardView.removeRowKey else BoardView.emptySpecialKey
drawing.data.push(BoardView.addColKey for i in [0...width])
if width > 1
drawing.data.push(BoardView.removeColKey for i in [0...width])
else
drawing.data.push(BoardView.emptySpecialKey for i in [0...width])
drawing.data[height].push(BoardView.emptySpecialKey, BoardView.emptySpecialKey)
drawing.data[height+1].push(BoardView.emptySpecialKey, BoardView.emptySpecialKey)
return
postprocessSymbols: (svg) ->
# Add bounding box to every non-special symbol
for elt in svg.children when elt.tagName == 'symbol' and
elt.viewBox? and
not @isSpecialKey elt.id
bbox = document.createElementNS svgtiler.SVGNS, 'rect'
# quite hacky, probably fails horribly when the symbol has overflow
bbox.setAttribute 'x', elt.viewBox.baseVal.x
bbox.setAttribute 'y', elt.viewBox.baseVal.y
bbox.setAttribute 'width', elt.viewBox.baseVal.width
bbox.setAttribute 'height', elt.viewBox.baseVal.height
bbox.setAttribute 'fill', 'transparent'
bbox.setAttribute 'stroke', 'black'
bbox.setAttribute 'stroke-width', '0.1'
elt.appendChild bbox
redraw: =>
drawing = @board.toDrawing()
@preprocessDrawing drawing
console.log drawing.data
svg = drawing.renderSVGDOM(@mappings).documentElement
@boardDiv.removeChild @boardDiv.firstChild while @boardDiv.firstChild
@boardDiv.appendChild(svg)
svg.removeAttribute 'preserveAspectRatio'
svg.setAttribute 'width', svg.getAttribute('width') * @scale
svg.setAttribute 'height', svg.getAttribute('height') * @scale
@postprocessSymbols(svg)
# Add click listeners
for elt in svg.children when elt.tagName == 'use'
row = elt.getAttribute 'data-r'
col = elt.getAttribute 'data-c'
do (row, col) =>
href = elt.getAttribute('href') ? elt.getAttribute('xlink:href')
key = href.slice(1)
if 0 <= row < @board.height() and 0 <= col < @board.width()
tryPaint = =>
# console.log(row, col)
if selectedTile? and @board.get(row, col) != selectedTile
@board.set row, col, selectedTile
@board.update()
elt.addEventListener 'mousedown', tryPaint
elt.addEventListener 'mousemove', (e) =>
tryPaint() if e.buttons & 1 # lowest bit = left button
else if key == BoardView.addRowKey
elt.addEventListener 'click', => @board.addRow row, true
else if key == BoardView.addColKey
elt.addEventListener 'click', => @board.addColumn col, true
else if key == BoardView.removeRowKey
elt.addEventListener 'click', => @board.removeRow row
else if key == BoardView.removeColKey
elt.addEventListener 'click', => @board.removeColumn col
# serializer = new XMLSerializer()
# console.log serializer.serializeToString svg
return
window.onload = ->
load 'default.txt', defaultMapping
board = new Board 10, 10
boardView = new BoardView(board, id('board'))
id('load').addEventListener 'click', ->
id('file').click()
id('file').addEventListener 'input', ->
return unless id('file').files.length
file = id('file').files[0]
reader = new FileReader()
reader.onload = -> load file.name, reader.result
reader.readAsText file, encoding: svgtiler.Input.encoding
id('file').value = null;
id('keyForm').addEventListener 'submit', (e) ->
e.preventDefault()
addTile id('key').value
id('key').value = ''
id('save').addEventListener 'click', TODO
id('border').addEventListener 'click', TODO