font caret underlining; repl line editing

This commit is contained in:
John Alanbrook 2023-10-05 13:02:12 +00:00
parent 5578b0f7e4
commit 8c69dfd71f
8 changed files with 128 additions and 65 deletions

View file

@ -17,7 +17,7 @@ Reflect = {};
Symbol = {}; Symbol = {};
URIError = {}; URIError = {};
Object.complete_assign = function(target, source) Object.mixin = function(target, source)
{ {
if (typeof source === 'undefined') return target; if (typeof source === 'undefined') return target;
@ -150,7 +150,7 @@ Object.copy = function(proto, ...objs)
{ {
var c = Object.create(proto); var c = Object.create(proto);
for (var obj of objs) for (var obj of objs)
Object.complete_assign(c, obj); Object.mixin(c, obj);
return c; return c;
} }
@ -160,15 +160,17 @@ Object.defHidden = function(obj, prop)
Object.defineProperty(obj, prop, {enumerable:false, writable:true}); Object.defineProperty(obj, prop, {enumerable:false, writable:true});
} }
Object.hide = function(obj,prop) Object.hide = function(obj,...props)
{ {
var p = Object.getOwnPropertyDescriptor(obj,prop); for (var prop of props) {
if (!p) { var p = Object.getOwnPropertyDescriptor(obj,prop);
Log.warn(`No property of name ${prop}.`); if (!p) {
return; 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', { Object.defineProperty(Object.prototype, 'obscure', {

View file

@ -30,17 +30,17 @@ component.sprite = Object.copy(component, {
layer:0, layer:0,
enabled:true, enabled:true,
path: "", path: "",
rect: {s0:0, s1: 1, t0: 0, t1: 1},
toString() { return "sprite"; }, toString() { return "sprite"; },
make(go) { make(go) {
var nsprite = Object.create(this); var nsprite = Object.create(this);
nsprite.gameobject = go; nsprite.gameobject = go;
Object.assign(nsprite, make_sprite(go.body)); 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) for (var p in component.sprite.impl)
if (typeof this[p] !== 'undefined') { if (typeof this[p] !== 'undefined')
Log.warn(`setting ${p}`);
nsprite[p] = this[p]; nsprite[p] = this[p];
}
return nsprite; return nsprite;
}, },
}); });
@ -55,7 +55,6 @@ component.sprite.impl = {
hide() { this.enabled = false; }, hide() { this.enabled = false; },
show() { this.enabled = true; }, show() { this.enabled = true; },
asset(str) { this.path = str; }, asset(str) { this.path = str; },
rect: {s0:0, s1: 1, t0: 0, t1: 1},
get enabled() { return cmd(114,this.id); }, get enabled() { return cmd(114,this.id); },
set enabled(x) { cmd(20,this.id,x); }, set enabled(x) { cmd(20,this.id,x); },
set color(x) { cmd(96,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); var circle = Object.create(this);
circle.gameobject = go; circle.gameobject = go;
Object.assign(circle, make_circle2d(go.body)); 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; return circle;
}, },
}); });

View file

@ -88,6 +88,45 @@ function positive_diff(from, to)
var diff = {}; 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) { function diff(from, to) {
var obj = {}; var obj = {};
@ -102,10 +141,5 @@ function diff(from, to) {
} }
} }
for (var e in from) {
if (!to.hasOwnProperty(e))
obj[e] = "DELETE";
}
return obj; return obj;
}; };

View file

@ -402,7 +402,7 @@ var editor = {
lvlchain.forEach(function(x,i) { lvlchain.forEach(function(x,i) {
lvlcolor = colormap.sample(lvlcolorsample); lvlcolor = colormap.sample(lvlcolorsample);
var lvlstr = x.toString(); var lvlstr = x.toString();
if (x.dirty) if (x._ed.dirty)
lvlstr += "*"; lvlstr += "*";
if (i === lvlchain.length-1) lvlstr += "[this]"; if (i === lvlchain.length-1) lvlstr += "[this]";
GUI.text(lvlstr, [0, ypos], 1, lvlcolor); GUI.text(lvlstr, [0, ypos], 1, lvlcolor);
@ -421,12 +421,12 @@ var editor = {
this.selectlist.forEach(function(x) { this.selectlist.forEach(function(x) {
var sname = x.__proto__.toString(); var sname = x.__proto__.toString();
if (!x.level_obj().empty) if (!x.json_obj().empty)
x.dirty = true; x._ed.dirty = true;
else 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(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); 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; 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()); Object.merge(editor.selectlist[0].ur, editor.selectlist[0].level_obj());
var path = editor.selectlist[0].ur.toString(); var path = editor.selectlist[0].ur.toString();
path = path.replaceAll('.','/'); 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['M-t'].doc = "Unlock all objects in current level.";
editor.inputs['C-n'] = function() { 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."); 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())); editor.openpanel(gen_notify("Level is changed. Are you sure you want to close it?", _ => editor.clear_level()));
return; return;
@ -835,14 +835,14 @@ editor.inputs['C-o'].doc = "Open a level.";
editor.inputs['C-M-o'] = function() { editor.inputs['C-M-o'] = function() {
if (editor.selectlist.length === 1 && editor.selectlist[0].file) { 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.load(editor.selectlist[0].file);
} }
}; };
editor.inputs['C-M-o'].doc = "Revert opened level back to its disk form."; editor.inputs['C-M-o'].doc = "Revert opened level back to its disk form.";
editor.inputs['C-S-o'] = function() { editor.inputs['C-S-o'] = function() {
if (!editor.edit_level.dirty) if (!editor.edit_level._ed.dirty)
editor.load_prev(); editor.load_prev();
}; };
editor.inputs['C-S-o'].doc = "Open previous level."; editor.inputs['C-S-o'].doc = "Open previous level.";
@ -1331,13 +1331,19 @@ inputpanel.inputs = {};
inputpanel.inputs.char = function(c) { inputpanel.inputs.char = function(c) {
this.value = this.value.slice(0,this.caret) + c + this.value.slice(this.caret); this.value = this.value.slice(0,this.caret) + c + this.value.slice(this.caret);
this.caret++; this.caret++;
Log.warn(this.caret);
this.keycb(); this.keycb();
} }
inputpanel.inputs['C-d'] = function() { this.value = this.value.slice(0,this.caret) + this.value.slice(this.caret+1); }; 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.tab = function() { this.value = tab_complete(this.value, this.assets); }
inputpanel.inputs.escape = function() { this.close(); } 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() { inputpanel.inputs.backspace = function() {
if (this.caret === 0) return;
this.value = this.value.slice(0,this.caret-1) + this.value.slice(this.caret); this.value = this.value.slice(0,this.caret-1) + this.value.slice(this.caret);
this.caret--; this.caret--;
this.keycb(); this.keycb();
@ -1413,6 +1419,7 @@ var replpanel = Object.copy(inputpanel, {
action() { action() {
if (!this.value) return; if (!this.value) return;
this.prevthis.unshift(this.value); this.prevthis.unshift(this.value);
this.prevmark = -1;
var ecode = ""; var ecode = "";
var repl_obj = (editor.selectlist.length === 1) ? editor.selectlist[0] : editor.edit_level; var repl_obj = (editor.selectlist.length === 1) ? editor.selectlist[0] : editor.edit_level;
ecode += `var $ = repl_obj.objects;`; ecode += `var $ = repl_obj.objects;`;
@ -1435,6 +1442,7 @@ replpanel.inputs['C-p'] = function()
if (this.prevmark >= this.prevthis.length) return; if (this.prevmark >= this.prevthis.length) return;
this.prevmark++; this.prevmark++;
this.value = this.prevthis[this.prevmark]; this.value = this.prevthis[this.prevmark];
this.inputs['C-f'].call(this);
} }
replpanel.inputs['C-n'] = function() replpanel.inputs['C-n'] = function()
@ -1445,6 +1453,8 @@ replpanel.inputs['C-n'] = function()
this.value = ""; this.value = "";
} else } else
this.value = this.prevthis[this.prevmark]; this.value = this.prevthis[this.prevmark];
this.inputs['C-f'].call(this);
} }
var objectexplorer = Object.copy(inputpanel, { var objectexplorer = Object.copy(inputpanel, {
@ -1670,10 +1680,7 @@ var quitpanel = Object.copy(inputpanel, {
}, },
guibody () { guibody () {
Nuke.label("Really quit?"); return Mum.text({str: "Really quit?"});
Nuke.newline(2);
if (Nuke.button("yes"))
this.submit();
}, },
}); });
@ -1848,6 +1855,3 @@ Game.stop();
Game.editor_mode(true); Game.editor_mode(true);
load("editorconfig.js"); load("editorconfig.js");
Log.warn(ur.ball);
Log.warn(ur['ball.big']);

View file

@ -204,6 +204,7 @@ var gameobject = {
velocity:[0,0], velocity:[0,0],
angularvelocity:0, angularvelocity:0,
layer:0, layer:0,
save:true, save:true,
selectable:true, selectable:true,
ed_locked:false, ed_locked:false,
@ -290,11 +291,19 @@ var gameobject = {
if (ret.empty) return undefined; if (ret.empty) return undefined;
return ret; return ret;
}, },
json_obj() { json_obj() {
var ur = gameobject.diff(this,this.__proto__); var d = gdiff(this,this.__proto__);
delete d.pos;
return ur ? ur : {}; 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() { transform_obj() {
@ -398,17 +407,20 @@ var gameobject = {
right() { return [1,0].rotate(Math.deg2rad(this.angle));}, right() { return [1,0].rotate(Math.deg2rad(this.angle));},
left() { return [-1,0].rotate(Math.deg2rad(this.angle));}, left() { return [-1,0].rotate(Math.deg2rad(this.angle));},
instances: [], instances: [],
make(level) { make(level) {
level ??= Primum; level ??= Primum;
var obj = Object.create(this); var obj = Object.create(this);
this.instances.push(obj); this.instances.push(obj);
obj.body = make_gameobject(); obj.body = make_gameobject();
Object.hide(obj, 'body');
obj.components = {}; obj.components = {};
obj.objects = {}; obj.objects = {};
Object.complete_assign(obj, gameobject.impl); Object.mixin(obj, gameobject.impl);
Object.hide(obj, 'components'); Object.hide(obj, 'components');
Object.hide(obj, 'objects'); Object.hide(obj, 'objects');
obj.toJSON = gameobject.level_json; obj._ed = {};
Object.hide(obj, '_ed');
Game.register_obj(obj); Game.register_obj(obj);
@ -416,6 +428,8 @@ var gameobject = {
obj.reparent(level); obj.reparent(level);
Object.hide(obj, 'level')
for (var prop in this) { for (var prop in this) {
var p = this[prop]; var p = this[prop];
if (typeof p !== 'object') continue; if (typeof p !== 'object') continue;
@ -423,10 +437,12 @@ var gameobject = {
if ('ur' in p) { if ('ur' in p) {
obj[prop] = obj.spawn(prototypes.get_ur(p.ur)); obj[prop] = obj.spawn(prototypes.get_ur(p.ur));
obj.rename_obj(obj[prop].toString(), prop); obj.rename_obj(obj[prop].toString(), prop);
Object.hide(obj, prop);
} else if ('comp' in p) { } else if ('comp' in p) {
Log.warn(p); Log.warn(p);
obj[prop] = Object.assign(component[p.comp].make(obj), p); obj[prop] = Object.assign(component[p.comp].make(obj), p);
obj.components[prop] = obj[prop]; obj.components[prop] = obj[prop];
Object.hide(obj,prop);
} }
}; };

View file

@ -117,8 +117,7 @@ Mum.text = Mum.extend({
this.calc_bb(cursor); this.calc_bb(cursor);
var aa = [0,1].sub(this.anchor); var aa = [0,1].sub(this.anchor);
var pos = cursor.add(this.wh.scale(aa)).add(this.offset); 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, this.width, this.caret);
ui_text(this.str, pos, this.font_size, this.color, -1, this.width, this.caret);
}, },
update_bb(cursor) { update_bb(cursor) {

View file

@ -1622,7 +1622,7 @@ void ffi_load() {
DUK_FUNC(register, 3) DUK_FUNC(register, 3)
DUK_FUNC(register_collide, 6) DUK_FUNC(register_collide, 6)
DUK_FUNC(ui_text, 6) DUK_FUNC(ui_text, 8)
DUK_FUNC(gui_img, 10) DUK_FUNC(gui_img, 10)
DUK_FUNC(inflate_cpv, 3) DUK_FUNC(inflate_cpv, 3)

View file

@ -181,6 +181,9 @@ static int curchar = 0;
void draw_char_box(struct Character c, HMM_Vec2 cursor, float scale, struct rgba color) 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; cpVect wh;
wh.x = 8 * scale; 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}); 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 */ /* 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 renderText(const char *text, HMM_Vec2 pos, float scale, struct rgba color, float lw, int caret, float tracking) {
int len = strlen(text); int len = strlen(text);
HMM_Vec2 cursor = pos; HMM_Vec2 cursor = pos;
int l = 0;
const unsigned char *line, *wordstart, *drawstart; const unsigned char *line, *wordstart, *drawstart;
line = drawstart = (unsigned char *)text; line = drawstart = (unsigned char *)text;
struct rgba usecolor = color; struct rgba usecolor = color;
check_caret(caret, l, cursor, scale, usecolor);
while (*line != '\0') { while (line[l] != '\0') {
if (caret >= 0 && caret == curchar) if (isblank(line[l])) {
draw_char_box(font->Characters[69], cursor, scale, color); sdrawCharacter(font->Characters[line[l]], cursor, scale, usecolor);
cursor.X += font->Characters[line[l]].Advance * tracking * scale;
if (isblank(*line)) { l++;
sdrawCharacter(font->Characters[*line], cursor, scale, usecolor); check_caret(caret, l, cursor, scale, usecolor);
cursor.X += font->Characters[*line].Advance * tracking * scale; } else if (isspace(line[l])) {
line++; sdrawCharacter(font->Characters[line[l]], cursor, scale, usecolor);
} else if (isspace(*line)) {
sdrawCharacter(font->Characters[*line], cursor, scale, usecolor);
cursor.Y -= scale * font->linegap; cursor.Y -= scale * font->linegap;
cursor.X = pos.X; cursor.X = pos.X;
line++; l++;
check_caret(caret, l, cursor, scale, usecolor);
} else { } else {
wordstart = line; wordstart = &line[l];
int wordWidth = 0; int wordWidth = 0;
while (!isspace(*line) && *line != '\0') { while (!isspace(line[l]) && line[l] != '\0') {
wordWidth += font->Characters[*line].Advance * tracking * scale; wordWidth += font->Characters[line[l]].Advance * tracking * scale;
line++; l++;
} }
if (lw > 0 && (cursor.X + wordWidth - pos.X) >= lw) { 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; cursor.Y -= scale * font->linegap;
} }
while (wordstart < line) { while (wordstart < &line[l]) {
sdrawCharacter(font->Characters[*wordstart], cursor, scale, usecolor); sdrawCharacter(font->Characters[*wordstart], cursor, scale, usecolor);
cursor.X += font->Characters[*wordstart].Advance * tracking * scale; cursor.X += font->Characters[*wordstart].Advance * tracking * scale;
wordstart++; 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; return cursor.Y - pos.Y;
} }