diff --git a/scripts/base.js b/scripts/base.js index 7ce0be5..6e96964 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -17,7 +17,7 @@ Reflect = {}; Symbol = {}; URIError = {}; -Object.complete_assign = function(target, source) +Object.mixin = function(target, source) { if (typeof source === 'undefined') return target; @@ -150,7 +150,7 @@ Object.copy = function(proto, ...objs) { var c = Object.create(proto); for (var obj of objs) - Object.complete_assign(c, obj); + Object.mixin(c, obj); return c; } @@ -160,15 +160,17 @@ Object.defHidden = function(obj, prop) Object.defineProperty(obj, prop, {enumerable:false, writable:true}); } -Object.hide = function(obj,prop) +Object.hide = function(obj,...props) { - var p = Object.getOwnPropertyDescriptor(obj,prop); - if (!p) { - Log.warn(`No property of name ${prop}.`); - return; + for (var prop of props) { + var p = Object.getOwnPropertyDescriptor(obj,prop); + if (!p) { + Log.warn(`No property of name ${prop}.`); + return; + } + p.enumerable = false; + Object.defineProperty(obj, prop, p); } - p.enumerable = false; - Object.defineProperty(obj, prop, p); } Object.defineProperty(Object.prototype, 'obscure', { diff --git a/scripts/components.js b/scripts/components.js index 94640eb..9b43305 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -30,17 +30,17 @@ component.sprite = Object.copy(component, { layer:0, enabled:true, path: "", + rect: {s0:0, s1: 1, t0: 0, t1: 1}, toString() { return "sprite"; }, make(go) { var nsprite = Object.create(this); nsprite.gameobject = go; Object.assign(nsprite, make_sprite(go.body)); - Object.complete_assign(nsprite, component.sprite.impl); + Object.mixin(nsprite, component.sprite.impl); + Object.hide(nsprite, 'gameobject', 'id'); for (var p in component.sprite.impl) - if (typeof this[p] !== 'undefined') { - Log.warn(`setting ${p}`); + if (typeof this[p] !== 'undefined') nsprite[p] = this[p]; - } return nsprite; }, }); @@ -55,7 +55,6 @@ component.sprite.impl = { hide() { this.enabled = false; }, show() { this.enabled = true; }, asset(str) { this.path = str; }, - rect: {s0:0, s1: 1, t0: 0, t1: 1}, get enabled() { return cmd(114,this.id); }, set enabled(x) { cmd(20,this.id,x); }, set color(x) { cmd(96,this.id,x); }, @@ -705,8 +704,8 @@ component.circle2d = Object.copy(collider2d, { var circle = Object.create(this); circle.gameobject = go; Object.assign(circle, make_circle2d(go.body)); - Object.complete_assign(circle,this.impl); - + Object.mixin(circle,this.impl); + Object.hide(circle, 'gameobject', 'id', 'shape', 'scale'); return circle; }, }); diff --git a/scripts/diff.js b/scripts/diff.js index dd522d7..fec7c09 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -88,6 +88,45 @@ function positive_diff(from, to) var diff = {}; } +function vdiff(from,to) +{ + if (typeof from === 'number') { + var a = Number.prec(from); + return a === to ? undefined : a; + } + + if (typeof from === 'object') { + var ret = {}; + Object.keys(from).forEach(function(k) { + var diff = vdiff(from[k], to[k]); + if (diff) ret[k] = diff; + }); + return ret.empty ? undefined : ret; + } +} + +function gdiff(from, to) { + var obj = {}; + + Object.entries(from).forEach(function([k,v]) { + if (typeof v === 'function') return; + if (!Object.isAccessor(from, k)) { + obj[k] = v; + return; + } + + var diff = vdiff(v, to[k]); + if (diff) { + if (Array.isArray(v)) + obj[k] = Object.values(diff); + else + obj[k] = diff; + } + }); + + return obj; +}; + function diff(from, to) { var obj = {}; @@ -102,10 +141,5 @@ function diff(from, to) { } } - for (var e in from) { - if (!to.hasOwnProperty(e)) - obj[e] = "DELETE"; - } - return obj; }; diff --git a/scripts/editor.js b/scripts/editor.js index 8ead122..dd927dd 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -402,7 +402,7 @@ var editor = { lvlchain.forEach(function(x,i) { lvlcolor = colormap.sample(lvlcolorsample); var lvlstr = x.toString(); - if (x.dirty) + if (x._ed.dirty) lvlstr += "*"; if (i === lvlchain.length-1) lvlstr += "[this]"; GUI.text(lvlstr, [0, ypos], 1, lvlcolor); @@ -421,12 +421,12 @@ var editor = { this.selectlist.forEach(function(x) { var sname = x.__proto__.toString(); - if (!x.level_obj().empty) - x.dirty = true; + if (!x.json_obj().empty) + x._ed.dirty = true; else - x.dirty = false; + x._ed.dirty = false; - if (x.dirty) sname += "*"; + if (x._ed.dirty) sname += "*"; GUI.text(sname, world2screen(x.worldpos()).add([0, 16]), 1, lvlcolor); GUI.text(x.worldpos().map(function(x) { return Math.round(x); }), world2screen(x.worldpos()), 1, Color.white); @@ -788,7 +788,7 @@ editor.inputs['C-s'] = function() { return; }; - if (editor.selectlist.length !== 1 || !editor.selectlist[0].dirty) return; + if (editor.selectlist.length !== 1 || !editor.selectlist[0]._ed.dirty) return; Object.merge(editor.selectlist[0].ur, editor.selectlist[0].level_obj()); var path = editor.selectlist[0].ur.toString(); path = path.replaceAll('.','/'); @@ -818,7 +818,7 @@ editor.inputs['M-t'] = function() { editor.edit_level.objects.forEach(function(x editor.inputs['M-t'].doc = "Unlock all objects in current level."; editor.inputs['C-n'] = function() { - if (editor.edit_level.dirty) { + if (editor.edit_level._ed.dirty) { Log.info("Level has changed; save before starting a new one."); editor.openpanel(gen_notify("Level is changed. Are you sure you want to close it?", _ => editor.clear_level())); return; @@ -835,14 +835,14 @@ editor.inputs['C-o'].doc = "Open a level."; editor.inputs['C-M-o'] = function() { if (editor.selectlist.length === 1 && editor.selectlist[0].file) { - if (editor.edit_level.dirty) return; + if (editor.edit_level._ed.dirty) return; editor.load(editor.selectlist[0].file); } }; editor.inputs['C-M-o'].doc = "Revert opened level back to its disk form."; editor.inputs['C-S-o'] = function() { - if (!editor.edit_level.dirty) + if (!editor.edit_level._ed.dirty) editor.load_prev(); }; editor.inputs['C-S-o'].doc = "Open previous level."; @@ -1331,13 +1331,19 @@ inputpanel.inputs = {}; inputpanel.inputs.char = function(c) { this.value = this.value.slice(0,this.caret) + c + this.value.slice(this.caret); this.caret++; - Log.warn(this.caret); this.keycb(); } inputpanel.inputs['C-d'] = function() { this.value = this.value.slice(0,this.caret) + this.value.slice(this.caret+1); }; inputpanel.inputs.tab = function() { this.value = tab_complete(this.value, this.assets); } inputpanel.inputs.escape = function() { this.close(); } +inputpanel.inputs['C-b'] = function() { + if (this.caret === 0) return; + this.caret--; +}; +inputpanel.inputs['C-a'] = function() { this.caret = 0; }; +inputpanel.inputs['C-f'] = function() { this.caret = this.value.length; }; inputpanel.inputs.backspace = function() { + if (this.caret === 0) return; this.value = this.value.slice(0,this.caret-1) + this.value.slice(this.caret); this.caret--; this.keycb(); @@ -1413,6 +1419,7 @@ var replpanel = Object.copy(inputpanel, { action() { if (!this.value) return; this.prevthis.unshift(this.value); + this.prevmark = -1; var ecode = ""; var repl_obj = (editor.selectlist.length === 1) ? editor.selectlist[0] : editor.edit_level; ecode += `var $ = repl_obj.objects;`; @@ -1435,6 +1442,7 @@ replpanel.inputs['C-p'] = function() if (this.prevmark >= this.prevthis.length) return; this.prevmark++; this.value = this.prevthis[this.prevmark]; + this.inputs['C-f'].call(this); } replpanel.inputs['C-n'] = function() @@ -1445,6 +1453,8 @@ replpanel.inputs['C-n'] = function() this.value = ""; } else this.value = this.prevthis[this.prevmark]; + + this.inputs['C-f'].call(this); } var objectexplorer = Object.copy(inputpanel, { @@ -1670,10 +1680,7 @@ var quitpanel = Object.copy(inputpanel, { }, guibody () { - Nuke.label("Really quit?"); - Nuke.newline(2); - if (Nuke.button("yes")) - this.submit(); + return Mum.text({str: "Really quit?"}); }, }); @@ -1848,6 +1855,3 @@ Game.stop(); Game.editor_mode(true); load("editorconfig.js"); - -Log.warn(ur.ball); -Log.warn(ur['ball.big']); diff --git a/scripts/entity.js b/scripts/entity.js index 95aef7b..031550e 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -204,6 +204,7 @@ var gameobject = { velocity:[0,0], angularvelocity:0, layer:0, + save:true, selectable:true, ed_locked:false, @@ -290,11 +291,19 @@ var gameobject = { if (ret.empty) return undefined; return ret; }, - + json_obj() { - var ur = gameobject.diff(this,this.__proto__); - - return ur ? ur : {}; + var d = gdiff(this,this.__proto__); + delete d.pos; + delete d.angle; + delete d.velocity; + delete d.angularvelocity; + d.components = []; + this.components.forEach(function(x) { + var c = gdiff(x, x.__proto__); + if (c) d.components.push(c); + }); + return d; }, transform_obj() { @@ -398,17 +407,20 @@ var gameobject = { right() { return [1,0].rotate(Math.deg2rad(this.angle));}, left() { return [-1,0].rotate(Math.deg2rad(this.angle));}, instances: [], + make(level) { level ??= Primum; var obj = Object.create(this); this.instances.push(obj); obj.body = make_gameobject(); + Object.hide(obj, 'body'); obj.components = {}; obj.objects = {}; - Object.complete_assign(obj, gameobject.impl); + Object.mixin(obj, gameobject.impl); Object.hide(obj, 'components'); Object.hide(obj, 'objects'); - obj.toJSON = gameobject.level_json; + obj._ed = {}; + Object.hide(obj, '_ed'); Game.register_obj(obj); @@ -416,6 +428,8 @@ var gameobject = { obj.reparent(level); + Object.hide(obj, 'level') + for (var prop in this) { var p = this[prop]; if (typeof p !== 'object') continue; @@ -423,10 +437,12 @@ var gameobject = { if ('ur' in p) { obj[prop] = obj.spawn(prototypes.get_ur(p.ur)); obj.rename_obj(obj[prop].toString(), prop); + Object.hide(obj, prop); } else if ('comp' in p) { Log.warn(p); obj[prop] = Object.assign(component[p.comp].make(obj), p); obj.components[prop] = obj[prop]; + Object.hide(obj,prop); } }; diff --git a/scripts/gui.js b/scripts/gui.js index 07deb39..eb41536 100644 --- a/scripts/gui.js +++ b/scripts/gui.js @@ -117,8 +117,7 @@ Mum.text = Mum.extend({ this.calc_bb(cursor); var aa = [0,1].sub(this.anchor); var pos = cursor.add(this.wh.scale(aa)).add(this.offset); - if (this.caret > -1) Log.warn(`Drawing box at pos ${this.caret} over letter ${this.str[this.caret-1]}`); - ui_text(this.str, pos, this.font_size, this.color, -1, this.width, this.caret); + ui_text(this.str, pos, this.font_size, this.color, this.width, this.caret); }, update_bb(cursor) { diff --git a/source/engine/ffi.c b/source/engine/ffi.c index 60b4062..02d6611 100644 --- a/source/engine/ffi.c +++ b/source/engine/ffi.c @@ -1622,7 +1622,7 @@ void ffi_load() { DUK_FUNC(register, 3) DUK_FUNC(register_collide, 6) - DUK_FUNC(ui_text, 6) + DUK_FUNC(ui_text, 8) DUK_FUNC(gui_img, 10) DUK_FUNC(inflate_cpv, 3) diff --git a/source/engine/font.c b/source/engine/font.c index d97f1c8..be96485 100644 --- a/source/engine/font.c +++ b/source/engine/font.c @@ -181,6 +181,9 @@ static int curchar = 0; void draw_char_box(struct Character c, HMM_Vec2 cursor, float scale, struct rgba color) { + cursor.Y -= 2; + sdrawCharacter(font->Characters['_'], cursor, scale, color); + return; cpVect wh; wh.x = 8 * scale; @@ -281,37 +284,44 @@ struct boundingbox text_bb(const char *text, float scale, float lw, float tracki return cwh2bb((HMM_Vec2){0,0}, (HMM_Vec2){cursor.X,font->linegap-cursor.Y}); } +void check_caret(int caret, int l, HMM_Vec2 pos, float scale, struct rgba color) +{ + if (caret == l) + draw_char_box(font->Characters[0], pos, scale, color); +} + /* pos given in screen coordinates */ int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, float lw, int caret, float tracking) { int len = strlen(text); HMM_Vec2 cursor = pos; + int l = 0; const unsigned char *line, *wordstart, *drawstart; line = drawstart = (unsigned char *)text; struct rgba usecolor = color; + check_caret(caret, l, cursor, scale, usecolor); - while (*line != '\0') { - if (caret >= 0 && caret == curchar) - draw_char_box(font->Characters[69], cursor, scale, color); - - if (isblank(*line)) { - sdrawCharacter(font->Characters[*line], cursor, scale, usecolor); - cursor.X += font->Characters[*line].Advance * tracking * scale; - line++; - } else if (isspace(*line)) { - sdrawCharacter(font->Characters[*line], cursor, scale, usecolor); + while (line[l] != '\0') { + if (isblank(line[l])) { + sdrawCharacter(font->Characters[line[l]], cursor, scale, usecolor); + cursor.X += font->Characters[line[l]].Advance * tracking * scale; + l++; + check_caret(caret, l, cursor, scale, usecolor); + } else if (isspace(line[l])) { + sdrawCharacter(font->Characters[line[l]], cursor, scale, usecolor); cursor.Y -= scale * font->linegap; cursor.X = pos.X; - line++; + l++; + check_caret(caret, l, cursor, scale, usecolor); } else { - wordstart = line; + wordstart = &line[l]; int wordWidth = 0; - while (!isspace(*line) && *line != '\0') { - wordWidth += font->Characters[*line].Advance * tracking * scale; - line++; + while (!isspace(line[l]) && line[l] != '\0') { + wordWidth += font->Characters[line[l]].Advance * tracking * scale; + l++; } if (lw > 0 && (cursor.X + wordWidth - pos.X) >= lw) { @@ -319,15 +329,14 @@ int renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, f cursor.Y -= scale * font->linegap; } - while (wordstart < line) { + while (wordstart < &line[l]) { sdrawCharacter(font->Characters[*wordstart], cursor, scale, usecolor); cursor.X += font->Characters[*wordstart].Advance * tracking * scale; wordstart++; + check_caret(caret, wordstart-line, cursor, scale, usecolor); } } } -// if (caret > curchar) -// draw_char_box(font->Characters[69], cursor, scale, color); return cursor.Y - pos.Y; }