dreemgl
Version:
DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes
377 lines (334 loc) • 12 kB
JavaScript
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others.
Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.*/
define.class('./jsviewer', function(require, baseclass, $ui$, textbox, label){
this.readonly = false
var enumchange = this.enumchange
this.init = function(){
}
this.format_options = {
force_newlines_array:false,
force_newlines_object:false
}
// process inserts with matching parens
this.processInsert = function(lo, hi, text){
var cdelta = 0
if(this.error_state) return [text, 0]
if(this.textbuf.charCodeAt(lo) === 9){
cdelta += 1
}
if(text == '"'){
if(this.textbuf.charCodeAt(lo) == 34) text = '', cdelta += 1
else text +='"', cdelta -= 1
}
else if(text == "'"){
if(this.textbuf.charCodeAt(lo) == 39) text = '', cdelta += 1
else text +="'", cdelta -= 1
}
else if(text == ')'){
if(this.textbuf.charCodeAt(lo) == 41) text = '', cdelta += 1
}
else if(text == ']'){
if(this.textbuf.charCodeAt(lo) == 93) text = '', cdelta += 1
}
else if(text == '}'){
// do a forward scan
if(this.textbuf.charCodeAt(lo) === 10 && this.textbuf.charCodeAt(lo+1) === 9 && this.textbuf.charCodeAt(lo+2) == 125){
text = '', cdelta = 0
}
else if(this.textbuf.charCodeAt(lo) == 125) text = '', cdelta += 1
}
else if(text == '('){
cdelta -= 1
text += ')'
}
else if(text == '['){
cdelta -= 1
text += ']'
}
else if(text == '{'){
if(lo != hi){
// do something special
}
cdelta -= 1
text += '}'
}
else if(text == '\n'){ // autoindent code
var i = hi
var state = 0
var indent = 0
var split = 0
while(this.textbuf.charCodeAt(i) !== 9 && i >= 0){
i--
}
while(this.textbuf.charCodeAt(i) === 9){
i--
indent++
}
text += Array(indent + 1).join('\t')
}
return [text, cdelta]
}
// some patching up
this.stripNextOnBackspace = function(pos){
return false
//ch = this.textbuf.charCodeAt(pos)
//if(ch == 91 && this.textbuf.charCodeAt(pos+1) == 93) return true
//if(ch == 123 && this.textbuf.charCodeAt(pos+1) == 125) return true
//if(ch == 40 && this.textbuf.charCodeAt(pos+1) == 41) return true
//return false
}
this.update_force = function(){
this.change_timeout = undefined
baseclass.cursorsChanged.call(this)
this.redraw()
}
// the change event
this.change_id = 0
this.change_timeout = 0
this.textChanged = function(){
baseclass.textChanged.call(this, true)
this.worker.postMessage({change_id:++this.change_id, format_options:this.format_options, source:this._value})
if(this.change_timeout) return
this.change_timeout = this.setTimeout(this.update_force, 30)
}
this.cursorsChanged = function(){
if(!this.change_timeout){
baseclass.cursorsChanged.call(this)
if(this.format_dirty){
this.change = "cursor"
this.worker.postMessage({change_id:++this.change_id, format_options:this.format_options, source:this._value})
}
}
//this.change_timeout = this.setTimeout(this.update_force, 30)
}
// we can skip tabs
this.atMoveLeft = function(pos){
if(this.textbuf.charCodeAt(pos) === 10) return pos - 1
return pos
}
this.atMoveRight = function(pos){
if(this.textbuf.charCodeAt(pos) === 9) return pos + 1
return pos
}
// alright lets make a worker that parses and reserializes
var worker = define.class('$system/rpc/worker', function(require){
var Parser = require('$system/parse/onejsparser')
var JSFormatter = require('$system/parse/jsformatter')
this.onmessage = function(msg){
// lets start a parse!
try{
var ast = Parser.parse(msg.source)
}
catch(e){
this.postMessage({error:e.message, pos:e.pos})
return
}
// ok now we need to reserialize from ast
var buf = {
out:vec4.array(msg.source.length + 100),
charCodeAt: function(i){return this.out.array[i*4]},
char_count:0,
walk_id:0
};
// lets reserialize output
var out = buf.out
JSFormatter.walk(ast, buf, msg.format_options, function(text, padding, l1, l2, l3, node){
if(text === '\n'){
this.last_is_newline = true
return
}
if(text === '\t' && this.last_is_newline){
text = '\n'
}
this.last_is_newline = false
out.ensureSize(out.length + text.length)
var o = out.length
var first = text.charCodeAt(0)
if(first !== 32 && first !== 9 && first !== 10) buf.walk_id++
for(var i = 0; i < text.length; i++){
var v = o * 4 + i * 4
out.array[v] = text.charCodeAt(i)
out.array[v + 1] = ((padding||0) + this.actual_indent*65536)*-1
if(l1 < 0) out.array[v + 2] = l1
else out.array[v + 2] = 65536 * (l1||0) + 256 * (l2||0) + (l3||0)
out.array[v + 3] = buf.walk_id + 65536*this.actual_line
}
out.length += text.length
buf.char_count += text.length;
})
this.postMessage({length:buf.out.length, change_id: msg.change_id, array:buf.out.array}, [buf.out.array.buffer])
}
})
this.oninit = function(prev){
this.worker = prev && prev.worker || worker()
// if we get source back yay
this.worker.onmessage = function(msg){
var mesh = this.shaders.typeface.mesh
if(this.change_timeout){
this.clearTimeout(this.change_timeout)
this.update_force()
}
//return
var err = this.find('error')
if(msg.error){
var rect = mesh.cursorRect(msg.pos)
err.x = rect.x
err.y = rect.y + rect.h + 4
err.text = '^'+msg.error
err.visible = true
this.error_state = true
return
}
else{
this.error_state = false
if(err._visible) err.visible = false
}
if(msg.change_id !== this.change_id) return // toss it, its too late.
var dt = Date.now()
var start = 0
var data_new = msg.array
var data_old = mesh.array
var len_new = msg.length
var len_old = mesh.length / 6
for(;start < len_new && start < len_old; start++){
var off_old = start * 10 * 6
var off_new = start * 4
if(data_new[off_new] !== data_old[off_old + 6]) break
if(data_new[off_new+1] !== data_old[off_old + 7]) break
//if(data_new[off_new+2] !== data_old[off_old + 8]) break
//if(data_new[off_new+3] !== data_old[off_old + 9]) break
// copy data over
data_old[off_old + 7] = data_old[off_old + 17] = data_old[off_old + 27] =
data_old[off_old + 37] = data_old[off_old + 47] = data_old[off_old + 57] = data_new[off_new+1]
data_old[off_old + 8] = data_old[off_old + 18] = data_old[off_old + 28] =
data_old[off_old + 38] = data_old[off_old + 48] = data_old[off_old + 58] = data_new[off_new+2]
data_old[off_old + 9] = data_old[off_old + 19] = data_old[off_old + 29] =
data_old[off_old + 39] = data_old[off_old + 49] = data_old[off_old + 59] = data_new[off_new+3]
}
var end_old = len_old - 1, end_new = len_new - 1
for(;end_old > start && end_new > start; end_old--, end_new--){
var off_old = end_old * 10 * 6
var off_new = end_new * 4
//console.log(start, end_old, data_new[off_new], data_old[off_old+6],data_new[off_new] !== data_old[off_old + 6])
if(data_new[off_new] !== data_old[off_old + 6]) break
if(data_new[off_new+1] !== data_old[off_old + 7]) break
//if(data_new[off_new+2] !== data_old[off_old + 8]) break
//if(data_new[off_new+3] !== data_old[off_old + 9]) break
data_old[off_old + 7] = data_old[off_old + 17] = data_old[off_old + 27] =
data_old[off_old + 37] = data_old[off_old + 47] = data_old[off_old + 57] = data_new[off_new+1]
data_old[off_old + 8] = data_old[off_old + 18] = data_old[off_old + 28] =
data_old[off_old + 38] = data_old[off_old + 48] = data_old[off_old + 58] = data_new[off_new+2]
data_old[off_old + 9] = data_old[off_old + 19] = data_old[off_old + 29] =
data_old[off_old + 39] = data_old[off_old + 49] = data_old[off_old + 59] = data_new[off_new+3]
}
//mesh.clean = false
var cursor_now = this.cursorset.list[0].start
var new_range = end_new - start
var old_range = end_old - start
if(old_range < new_range && this.change === 'delete') return this.format_dirty = true
if(old_range > new_range && this.change === 'keypress') return this.format_dirty = true
// do the cursor move magic
var deleted_whitespace = true
if(this.change === 'delete'){
var undo_data = this.undo_stack[this.undo_stack.length - 1].data
if(undo_data) for(var i = 0; i < undo_data.length; i++){
var char = undo_data.array[i*4]
if(char !== 32 && char !== 9 && char !== 10) deleted_whitespace = false
}
}
// dont autoreformat immediately when deleting characters, only with whitespace
if(new_range < old_range && this.change === 'delete' && start < cursor_now && !deleted_whitespace) return this.format_dirty = true
if(this.change === 'undoredo')return this.format_dirty = true
// if we insert a newline or do a delete use the marker
if(new_range !== old_range){
if(this.change === 'keypress' && this.change_keypress === '\n'|| this.change === 'delete' && deleted_whitespace){
// use the tag
var nextto = mesh.tagAt(cursor_now,3)
for(var t = start; t < len_new; t++){
if(nextto == data_new[t*4+3]){
cursor_now = t//this.cursorset.list[0].moveToOffset(t)
break
}
}
}
else if(this.change === 'delete'){
//console.log("BE HERE")
//this.cursorset.list[0].moveToOffset(cursor_now - 1)
}
else if(this.change === 'keypress'){
// stick to the character
var char_at = data_new[cursor_now*4]
//if(char_at === 9){ // we are typing in a tab
// this.cursorset.list[0].moveToOffset(end_new+1)
//}
//else
if(char_at !== 44){
var nextto = mesh.tagAt(cursor_now - 1,0)
var fd = 0
for(var t = cursor_now - 1; t < len_new; t++){
if(nextto == data_new[t*4+0]){
fd = 1
}
else if(fd){ // move the cursor
cursor_now = t//this.cursorset.list[0].moveToOffset(t)
break
}
}
}
}
this.cursorset.update()
// create the undo entry
if(new_range){
this.undo_group--
this.addUndoInsert(start, end_old+1)
this.addUndoDelete(start, end_new+1)
this.undo_group++
}
}
else{
this.cursorset.update()
}
// this replaces the textbuffer
mesh.setLength(start)
var buf = {struct:1, start:start, array:data_new, length:len_new}
mesh.add(buf)
// lets figure out the linenumbers between start and end_new
if(new_range > old_range){
var min = Infinity, max = -Infinity
for(var i = start; i < end_new; i++){
var line = Math.floor(data_new[i*4+3]/65536)
if(line<min)min = line
if(line>max)max = line
}
}
this.line_start = min
this.line_end = max
this._line_anim = 1.0
this.line_anim = 0.
this.cursorset.list[0].moveToOffset(cursor_now)
//var rect = mesh.cursorRect(start)
//err.x = rect.x
//err.y = rect.y + rect.h + 4
//err.text = 'WOOPWOOP'
//err.visible = true
//console.log(mesh.tagAt(start, 0))
//console.log(mesh.tagAt(start, 1), data_new[start*4+1])
this.format_dirty = false
mesh.clean = false
this.redraw()
}.bind(this)
}
this.render = function(){
return label({position:'absolute',name:'error',bgcolor:'red',fgcolor:'white',borderradius:1, visible:false})
}
// Basic usage
var jseditor = this.constructor
this.constructor.examples = {
Usage: function(){
return [jseditor({bgcolor:"#000040", padding:vec4(14), source: "console.log(\"Hello world!\");"})]
}
}
})