From 28f38bd537e54728c445c8d0472eec03a625ebdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ma=C5=A1karinec?= Date: Tue, 30 Jan 2024 22:27:22 +0100 Subject: [PATCH] ui: Add proper unicode handling in TextBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marek Maškarinec --- .editorconfig | 2 +- src/staembed.c | 71 ++++++++++++++++++++++++++++++++++---------------- tests/uit.um | 4 ++- umka/ui.um | 49 ++++++++++++++++++++-------------- 4 files changed, 81 insertions(+), 45 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3e066bb2..efbc7675 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,6 @@ root = true charset = utf-8 end_of_line = lf indent_style = tab -indent_size = 8 +indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true diff --git a/src/staembed.c b/src/staembed.c index f7c928a5..c8547ead 100644 --- a/src/staembed.c +++ b/src/staembed.c @@ -2941,6 +2941,7 @@ const char *th_em_modulesrc[] = { "\t\"placeholders.um\"\n" "\t\"rect.um\"\n" "\t\"th.um\"\n" +"\t\"utf8.um\"\n" ")\n" "\n" "//~~struct BoxStyle\n" @@ -3572,21 +3573,36 @@ const char *th_em_modulesrc[] = { "\n" "//~~struct TextBox\n" "type TextBox* = struct {\n" -"\t// the content of the textbox\n" -"\tbuffer: str\n" "\t// index of the cursor\n" "\tcursor: int\n" -"}\n" +"\t// contains other unexported rules...\n" "//~~\n" +"\tbuffer: []utf8.Rune\n" +"}\n" "\n" "//~~fn TextBox.clear\n" "// Clears the textbox\n" "fn (this: ^TextBox) clear*() {\n" "//~~\n" -"\tthis.buffer = \"\"\n" +"\tthis.buffer = {}\n" "\tthis.cursor = 0\n" "}\n" "\n" +"//~~fn TextBox.getBuf()\n" +"// Get the content of the textbox.\n" +"fn (this: ^TextBox) getBuf*(): str {\n" +"//~~\n" +"\treturn utf8.encode(this.buffer)\n" +"}\n" +"\n" +"//~~fn TextBox.setBuf()\n" +"// Get the content of the textbox.\n" +"fn (this: ^TextBox) setBuf*(s: str) {\n" +"//~~\n" +"\tthis.buffer = utf8.decode(s)\n" +"\tthis.cursor = len(this.buffer)\n" +"}\n" +"\n" "//~~fn Gui.textBox\n" "// Adds a single line textbox to the gui.\n" "// TODO:\n" @@ -3635,8 +3651,7 @@ const char *th_em_modulesrc[] = { "\t\t\tinput.isPressedRepeat(input.key_backspace) {\n" "\n" "\t\t\tif tb.cursor > 0 {\n" -"\t\t\t\ttb.buffer = slice(tb.buffer, 0, tb.cursor - 1) +\n" -"\t\t\t\t\tslice(tb.buffer, tb.cursor)\n" +"\t\t\t\ttb.buffer = append(slice(tb.buffer, 0, tb.cursor - 1), slice(tb.buffer, tb.cursor))\n" "\t\t\t\ttb.cursor--\n" "\t\t\t}\n" "\t\t}\n" @@ -3648,34 +3663,28 @@ const char *th_em_modulesrc[] = { "\t\t\tv = false\n" "\t\t}\n" "\n" -"\t\ts := input.getStr()\n" -"\t\tfor i in s {\n" -"\t\t\tif int(s[i]) < int(\' \') || int(s[i]) == int(\'z\') + 1 {\n" -"\t\t\t\tv = false\n" -"\t\t\t}\n" -"\t\t}\n" -"\t\tif len(s) > 0 && v {\n" -"\t\t\ttb.buffer = slice(tb.buffer, 0, tb.cursor) + s +\n" -"\t\t\t\tslice(tb.buffer, tb.cursor)\n" -"\t\t\ttb.cursor += len(s)\n" +"\t\trunes := utf8.decode(input.getStr())\n" +"\t\tif len(runes) > 0 && v {\n" +"\t\t\ttb.buffer = append(slice(tb.buffer, 0, tb.cursor), append(slice(tb.buffer, tb.cursor), runes))\n" +"\t\t\ttb.cursor += len(runes)\n" "\t\t}\n" "\n" "\t\treturn\n" "\t}\n" "\n" -"\n" "\tstyle := gui.getStyle()\n" "\n" "\tstyle.negBox.draw(r)\n" "\tcanvas.beginScissorRect(r)\n" "\n" -"\tdm := style.ft.measure(tb.buffer).mulf(style.ftScale)\n" +"\tbuf := utf8.encode(tb.buffer)\n" +"\tdm := style.ft.measure(buf).mulf(style.ftScale)\n" "\n" "\tp := th.Vf2{}\n" "\tp.y = r.y + r.h/2 - dm.y/2\n" "\tc := th.Vf2{r.x, p.y}\n" "\n" -"\tcdmx := style.ft.measure(slice(tb.buffer, 0, tb.cursor)).x * style.ftScale\n" +"\tcdmx := style.ft.measure(utf8.encode(slice(tb.buffer, 0, tb.cursor))).x * style.ftScale\n" "\tif cdmx < r.w - 2 {\n" "\t\tp.x = r.x + 1\n" "\t\tc.x = p.x + cdmx\n" @@ -3686,7 +3695,7 @@ const char *th_em_modulesrc[] = { "\n" "\taW := style.ft.measure(\"A\").x * style.ftScale\n" "\n" -"\tstyle.ft.draw(tb.buffer, p, style.ftColor, style.ftScale)\n" +"\tstyle.ft.draw(buf, p, style.ftColor, style.ftScale)\n" "\tif gui.selection == gui.idx {\n" "\t\tcanvas.drawRect(style.ftColor, rect.mk(c.x, c.y, aW / 4, dm.y))\n" "\t}\n" @@ -6792,11 +6801,9 @@ const char *th_em_moduledocs[] = { "\n" "```\n" "type TextBox* = struct {\n" -"\t// the content of the textbox\n" -"\tbuffer: str\n" "\t// index of the cursor\n" "\tcursor: int\n" -"}\n" +"\t// contains other unexported rules...\n" "```\n" "\n" "\n" @@ -6810,6 +6817,24 @@ const char *th_em_moduledocs[] = { "Clears the textbox\n" "\n" "\n" +"## fn TextBox.getBuf()\n" +"\n" +"```\n" +"fn (this: ^TextBox) getBuf*(): str {\n" +"```\n" +"\n" +"Get the content of the textbox.\n" +"\n" +"\n" +"## fn TextBox.setBuf()\n" +"\n" +"```\n" +"fn (this: ^TextBox) setBuf*(s: str) {\n" +"```\n" +"\n" +"Get the content of the textbox.\n" +"\n" +"\n" "## fn Gui.textBox\n" "\n" "```\n" diff --git a/tests/uit.um b/tests/uit.um index c34e2b06..463cd4a6 100644 --- a/tests/uit.um +++ b/tests/uit.um @@ -15,9 +15,11 @@ fn init*() { window.setup("gui test", 600, 600) window.setViewport(th.Vf2{ 200, 200 }) - ft := font.load("etc/roboto.ttf", 64) + ft := font.load("etc/roboto.ttf", 96, font.filterLinear) gui = ui.mk(rect.mk(0, 0, 200, 200), ui.getDefaultStyle()) + gui.getStyle().ft = ft + gui.getStyle().ftScale = 0.1 win = ui.mk(rect.mk(100, 100, 80, 50), ui.getDefaultStyle()) win.getStyle().containerBox.color = 0x88aa88ff diff --git a/umka/ui.um b/umka/ui.um index 9f881be1..5ecabd90 100644 --- a/umka/ui.um +++ b/umka/ui.um @@ -12,6 +12,7 @@ import ( "placeholders.um" "rect.um" "th.um" + "utf8.um" ) //~~struct BoxStyle @@ -643,21 +644,36 @@ type TextBoxConfig* = struct { //~~struct TextBox type TextBox* = struct { - // the content of the textbox - buffer: str // index of the cursor cursor: int -} + // contains other unexported rules... //~~ + buffer: []utf8.Rune +} //~~fn TextBox.clear // Clears the textbox fn (this: ^TextBox) clear*() { //~~ - this.buffer = "" + this.buffer = {} this.cursor = 0 } +//~~fn TextBox.getBuf() +// Get the content of the textbox. +fn (this: ^TextBox) getBuf*(): str { +//~~ + return utf8.encode(this.buffer) +} + +//~~fn TextBox.setBuf() +// Get the content of the textbox. +fn (this: ^TextBox) setBuf*(s: str) { +//~~ + this.buffer = utf8.decode(s) + this.cursor = len(this.buffer) +} + //~~fn Gui.textBox // Adds a single line textbox to the gui. // TODO: @@ -706,8 +722,7 @@ fn (gui: ^Gui) textBox*(tb: ^TextBox, cfg: TextBoxConfig = {}) { input.isPressedRepeat(input.key_backspace) { if tb.cursor > 0 { - tb.buffer = slice(tb.buffer, 0, tb.cursor - 1) + - slice(tb.buffer, tb.cursor) + tb.buffer = append(slice(tb.buffer, 0, tb.cursor - 1), slice(tb.buffer, tb.cursor)) tb.cursor-- } } @@ -719,34 +734,28 @@ fn (gui: ^Gui) textBox*(tb: ^TextBox, cfg: TextBoxConfig = {}) { v = false } - s := input.getStr() - for i in s { - if int(s[i]) < int(' ') || int(s[i]) == int('z') + 1 { - v = false - } - } - if len(s) > 0 && v { - tb.buffer = slice(tb.buffer, 0, tb.cursor) + s + - slice(tb.buffer, tb.cursor) - tb.cursor += len(s) + runes := utf8.decode(input.getStr()) + if len(runes) > 0 && v { + tb.buffer = append(slice(tb.buffer, 0, tb.cursor), append(slice(tb.buffer, tb.cursor), runes)) + tb.cursor += len(runes) } return } - style := gui.getStyle() style.negBox.draw(r) canvas.beginScissorRect(r) - dm := style.ft.measure(tb.buffer).mulf(style.ftScale) + buf := utf8.encode(tb.buffer) + dm := style.ft.measure(buf).mulf(style.ftScale) p := th.Vf2{} p.y = r.y + r.h/2 - dm.y/2 c := th.Vf2{r.x, p.y} - cdmx := style.ft.measure(slice(tb.buffer, 0, tb.cursor)).x * style.ftScale + cdmx := style.ft.measure(utf8.encode(slice(tb.buffer, 0, tb.cursor))).x * style.ftScale if cdmx < r.w - 2 { p.x = r.x + 1 c.x = p.x + cdmx @@ -757,7 +766,7 @@ fn (gui: ^Gui) textBox*(tb: ^TextBox, cfg: TextBoxConfig = {}) { aW := style.ft.measure("A").x * style.ftScale - style.ft.draw(tb.buffer, p, style.ftColor, style.ftScale) + style.ft.draw(buf, p, style.ftColor, style.ftScale) if gui.selection == gui.idx { canvas.drawRect(style.ftColor, rect.mk(c.x, c.y, aW / 4, dm.y)) }