re-implemented text box
also includes - small layout fix for grow elements - ElemEvents now includes has_focus flag
This commit is contained in:
parent
81cc3dae65
commit
34b92c93b4
@ -38,13 +38,14 @@ bitstruct ElemFlags : uint {
|
||||
bitstruct ElemEvents : uint {
|
||||
bool key_press : 0;
|
||||
bool key_release : 1;
|
||||
bool key_hold : 2;
|
||||
bool key_repeat : 2;
|
||||
bool mouse_hover : 3;
|
||||
bool mouse_press : 4;
|
||||
bool mouse_release : 5;
|
||||
bool mouse_hold : 6;
|
||||
bool update : 7;
|
||||
bool text_input : 8;
|
||||
bool has_focus : 9;
|
||||
}
|
||||
|
||||
// element structure
|
||||
@ -334,7 +335,7 @@ macro bool Ctx.elem_focus(&ctx, Elem *elem)
|
||||
fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
|
||||
{
|
||||
bool hover = ctx.is_hovered(elem);
|
||||
bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT));
|
||||
bool focus = ctx.elem_focus(elem) || (hover && ctx.is_mouse_pressed(BTN_LEFT));
|
||||
|
||||
if (ctx.is_mouse_pressed(BTN_ANY) && !hover){
|
||||
focus = false;
|
||||
@ -345,10 +346,14 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
|
||||
if (focus) { ctx.focus_id = elem.id; }
|
||||
|
||||
ElemEvents ev = {
|
||||
.has_focus = focus,
|
||||
.mouse_hover = hover,
|
||||
.mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY),
|
||||
.mouse_release = hover && focus && ctx.is_mouse_released(BTN_ANY),
|
||||
.mouse_hold = hover && focus && ctx.is_mouse_down(BTN_ANY),
|
||||
.key_press = focus && ctx.input.events.key_press,
|
||||
.key_release = focus && ctx.input.events.key_release,
|
||||
.key_repeat = focus && ctx.input.events.key_repeat,
|
||||
.text_input = focus && (ctx.input.keyboard.text_len || ctx.input.keyboard.modkeys & KMOD_TXT),
|
||||
};
|
||||
return ev;
|
||||
|
@ -300,15 +300,20 @@ fn TextSize? Ctx.measure_string(&ctx, String text)
|
||||
return ts;
|
||||
}
|
||||
|
||||
fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue)
|
||||
// layout a string inside a bounding box, following the given alignment (anchor).
|
||||
// returns the position of the cursor, the returned height is the line height and the width is the last
|
||||
// character's advance value
|
||||
fn Rect? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_index, Color hue, isz cursor = -1)
|
||||
{
|
||||
if (text.len == 0 || bounds.w <= 0 || bounds.h <= 0) return;
|
||||
Font* font = &ctx.font;
|
||||
short line_height = (short)font.line_height();
|
||||
Rect cursor_rect = {.h = line_height};
|
||||
|
||||
if (text == "" || bounds.w <= 0 || bounds.h <= 0) return cursor_rect;
|
||||
ctx.push_scissor(bounds, z_index)!;
|
||||
|
||||
Font* font = &ctx.font;
|
||||
Id texture_id = font.id;
|
||||
short baseline = (short)font.ascender;
|
||||
short line_height = (short)font.line_height();
|
||||
short line_gap = (short)font.linegap;
|
||||
short space_width = font.get_glyph(' ').adv!;
|
||||
short tab_width = space_width * TAB_SIZE;
|
||||
@ -375,6 +380,7 @@ fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_
|
||||
if (line_end == line_start) break;
|
||||
|
||||
// with the line width calculate the right origin and layout the line
|
||||
origin.x = bounds.x;
|
||||
switch (anchor) {
|
||||
case TOP_LEFT: nextcase;
|
||||
case LEFT: nextcase;
|
||||
@ -391,6 +397,9 @@ fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_
|
||||
origin.x += (short)(bounds.w - line_width);
|
||||
}
|
||||
|
||||
cursor_rect.x = origin.x;
|
||||
cursor_rect.y = origin.y;
|
||||
|
||||
// see the fixme when measuring the height
|
||||
//ctx.push_rect({.x = origin.x,.y=origin.y,.w=(short)line_width,.h=(short)string_height}, z_index, &&(Style){.bg=0xff000042u.@to_rgba()})!;
|
||||
String line = text[line_start:line_end-line_start];
|
||||
@ -419,9 +428,15 @@ fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_
|
||||
};
|
||||
ctx.push_sprite(b, uv, texture_id, z_index, hue)!;
|
||||
//ctx.push_rect(b, z_index, &&(Style){.bg=0x0000ff66u.@to_rgba()})!;
|
||||
origin.x += gp.adv;
|
||||
|
||||
if (line_start + off < cursor) {
|
||||
cursor_rect.x = origin.x + gp.adv;
|
||||
cursor_rect.y = origin.y;
|
||||
cursor_rect.w = gp.adv;
|
||||
}
|
||||
|
||||
origin.x += gp.adv;
|
||||
}
|
||||
}
|
||||
// done with the line
|
||||
line_start = line_end;
|
||||
@ -429,4 +444,6 @@ fn void? Ctx.layout_string(&ctx, String text, Rect bounds, Anchor anchor, int z_
|
||||
} while(line_end < text.len);
|
||||
|
||||
ctx.reset_scissor(z_index)!;
|
||||
|
||||
return cursor_rect;
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ bitstruct InputEvents : uint {
|
||||
bool mouse_scroll : 4; // mouse scroll wheel. x or y
|
||||
bool text_input : 5;
|
||||
bool mod_key : 6;
|
||||
bool key_press : 7;
|
||||
bool key_release : 8;
|
||||
bool key_repeat : 9;
|
||||
}
|
||||
|
||||
bitstruct MouseButtons : uint {
|
||||
@ -156,6 +159,21 @@ fn void Ctx.input_mouse_wheel(&ctx, short x, short y, float scale = 1.0)
|
||||
ctx.input.events.mouse_scroll = x !=0 || y != 0;
|
||||
}
|
||||
|
||||
fn void Ctx.input_key_press(&ctx)
|
||||
{
|
||||
ctx.input.events.key_press = true;
|
||||
}
|
||||
|
||||
fn void Ctx.input_key_release(&ctx)
|
||||
{
|
||||
ctx.input.events.key_release = true;
|
||||
}
|
||||
|
||||
fn void Ctx.input_key_repeat(&ctx)
|
||||
{
|
||||
ctx.input.events.key_repeat = true;
|
||||
}
|
||||
|
||||
// append utf-8 encoded text to the context text input
|
||||
fn void Ctx.input_text_utf8(&ctx, char[] text)
|
||||
{
|
||||
@ -196,6 +214,7 @@ fn void Ctx.input_char(&ctx, char c)
|
||||
}
|
||||
|
||||
fn String Ctx.get_keys(&ctx) => (String)ctx.input.keyboard.text[:ctx.input.keyboard.text_len];
|
||||
fn ModKeys Ctx.get_mod(&ctx) => ctx.input.keyboard.modkeys;
|
||||
|
||||
// Modifier keys, like control or backspace
|
||||
// TODO: make this call repetible to input modkeys one by one
|
||||
|
@ -88,6 +88,12 @@ macro Point Layout.get_dimensions(&el)
|
||||
// GROSS HACK FOR EXACT DIMENSIONS
|
||||
if (el.w.@is_exact()) dim.x = el.w.min + el.content_offset.x + el.content_offset.w;
|
||||
if (el.h.@is_exact()) dim.y = el.h.min + el.content_offset.y + el.content_offset.h;
|
||||
|
||||
// GROSS HACK FOR GROW DIMENSIONS
|
||||
// FIXME: does this always work?
|
||||
if (el.w.@is_grow()) dim.x = 0;
|
||||
if (el.h.@is_grow()) dim.y = 0;
|
||||
|
||||
return dim;
|
||||
}
|
||||
|
||||
|
108
lib/ugui.c3l/src/ugui_textedit.c3
Normal file
108
lib/ugui.c3l/src/ugui_textedit.c3
Normal file
@ -0,0 +1,108 @@
|
||||
module ugui;
|
||||
|
||||
import grapheme;
|
||||
import std::ascii;
|
||||
|
||||
struct TextEdit {
|
||||
char[] buffer;
|
||||
usz chars;
|
||||
usz cursor;
|
||||
}
|
||||
|
||||
fn String TextEdit.to_string(&te) => (String)te.buffer[:te.chars];
|
||||
fn String TextEdit.until_cursor(&te) => (String)te.buffer[:te.cursor];
|
||||
fn String TextEdit.from_cursor(&te) => (String)te.buffer[te.cursor..];
|
||||
|
||||
// implement text editing operations on the buffer
|
||||
// returns true if the buffer is full
|
||||
fn bool Ctx.text_edit(&ctx, TextEdit* te)
|
||||
{
|
||||
String in = ctx.get_keys();
|
||||
ModKeys mod = ctx.get_mod();
|
||||
usz free = te.buffer.len - te.chars;
|
||||
usz after = te.chars - te.cursor;
|
||||
|
||||
// append text input to the buffer
|
||||
if (in.len <= free) {
|
||||
// make space
|
||||
te.buffer[te.cursor+in.len : after] = te.buffer[te.cursor : after];
|
||||
// insert characters
|
||||
te.buffer[te.cursor : in.len] = in[..];
|
||||
// increment characters and cursor
|
||||
te.chars += in.len;
|
||||
te.cursor += in.len;
|
||||
free -= in.len;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle modkeys
|
||||
if (te.chars) {
|
||||
// handle backspace and delete
|
||||
if (mod.bkspc) {
|
||||
if (te.cursor > 0) {
|
||||
// TODO: only delete until punctuation
|
||||
usz how_many = mod & KMOD_CTRL ? te.until_cursor().prev_word_off() : te.until_cursor().prev_char_off();
|
||||
te.buffer[te.cursor-how_many : after] = te.buffer[te.cursor : after];
|
||||
te.cursor -= how_many;
|
||||
te.chars -= how_many;
|
||||
free += how_many;
|
||||
}
|
||||
}
|
||||
if (mod.del) {
|
||||
if (after > 0 && te.cursor < te.chars) {
|
||||
usz how_many = mod & KMOD_CTRL ? te.from_cursor().next_word_off() : te.from_cursor().next_char_off();
|
||||
te.buffer[te.cursor : after] = te.buffer[te.cursor+how_many : after];
|
||||
te.chars -= how_many;
|
||||
after -= how_many;
|
||||
free += how_many;
|
||||
}
|
||||
}
|
||||
|
||||
// handle arrow keys
|
||||
if (mod.left) {
|
||||
if (te.cursor > 0) {
|
||||
usz how_many = mod & KMOD_CTRL ? te.until_cursor().prev_word_off() : te.until_cursor().prev_char_off();
|
||||
te.cursor -= how_many;
|
||||
after += how_many;
|
||||
}
|
||||
}
|
||||
if (mod.right) {
|
||||
if (after > 0) {
|
||||
usz how_many = mod & KMOD_CTRL ? te.from_cursor().next_word_off() : te.from_cursor().next_char_off();
|
||||
te.cursor += how_many;
|
||||
after -= how_many;
|
||||
}
|
||||
}
|
||||
if (mod.up) {
|
||||
// TODO
|
||||
}
|
||||
if (mod.down) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
return free == 0;
|
||||
}
|
||||
|
||||
macro isz char[].next_char_off(b) => grapheme::next_character_break_utf8(b.ptr, b.len);
|
||||
macro isz char[].next_word_off(b) => grapheme::next_word_break_utf8(b.ptr, b.len);
|
||||
|
||||
fn isz char[].prev_char_off(b)
|
||||
{
|
||||
foreach_r (off, c: b) {
|
||||
if (c & 0xC0 == 0x80) continue;
|
||||
return b.len - off;
|
||||
}
|
||||
return b.len;
|
||||
}
|
||||
|
||||
fn isz char[].prev_word_off(b)
|
||||
{
|
||||
for (isz off = b.len-1; off > 0;) {
|
||||
isz c_off = b[..off].prev_char_off();
|
||||
off -= c_off;
|
||||
if (ascii::is_punct(b[off]) || ascii::is_space(b[off])) return b.len - off - 1;
|
||||
}
|
||||
return b.len;
|
||||
}
|
@ -3,9 +3,9 @@ module ugui;
|
||||
import std::io;
|
||||
|
||||
struct ElemText {
|
||||
usz cursor; // cursor offset
|
||||
Id hash;
|
||||
TextSize size;
|
||||
TextEdit* te;
|
||||
}
|
||||
|
||||
macro Ctx.text(&ctx, String text, ...)
|
||||
@ -35,10 +35,10 @@ fn void? Ctx.text_id(&ctx, Id id, String text)
|
||||
ctx.layout_string(text, elem.bounds.pad(elem.layout.content_offset), TOP_LEFT, parent.div.z_index, style.fg)!;
|
||||
}
|
||||
|
||||
/*
|
||||
macro Ctx.text_box(&ctx, Rect size, char[] text, usz* text_len, ...)
|
||||
=> ctx.text_box_id(@compute_id($vasplat), size, text, text_len);
|
||||
fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_len)
|
||||
|
||||
macro Ctx.text_box(&ctx, Size w, Size h, TextEdit* te, ...)
|
||||
=> ctx.text_box_id(@compute_id($vasplat), w, h, te);
|
||||
fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Size w, Size h, TextEdit* te)
|
||||
{
|
||||
id = ctx.gen_id(id)!;
|
||||
|
||||
@ -46,38 +46,45 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le
|
||||
Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!;
|
||||
Style* style = ctx.styles.get_style(@str_hash("text-box"));
|
||||
|
||||
elem.text.str = (String)text;
|
||||
elem.text.te = te;
|
||||
|
||||
// layout the text box
|
||||
elem.bounds = ctx.layout_element(parent, size, style);
|
||||
Id text_hash = te.to_string().hash();
|
||||
if (elem.flags.is_new || elem.text.hash != text_hash) {
|
||||
elem.text.size = ctx.measure_string(te.to_string())!;
|
||||
}
|
||||
elem.text.hash = text_hash;
|
||||
|
||||
elem.layout.w = w;
|
||||
elem.layout.h = h;
|
||||
elem.layout.text = elem.text.size;
|
||||
elem.layout.content_offset = style.margin + style.border + style.padding;
|
||||
|
||||
update_parent_grow(elem, parent);
|
||||
update_parent_size(elem, parent);
|
||||
|
||||
// check input and update the text
|
||||
elem.events = ctx.get_elem_events(elem);
|
||||
|
||||
if (elem.events.text_input) {
|
||||
usz l = ctx.input.keyboard.text_len;
|
||||
char[] t = ctx.input.keyboard.text[..l];
|
||||
|
||||
if (l != 0 && l < text.len - *text_len) {
|
||||
text[*text_len..*text_len+l] = t[..];
|
||||
*text_len += l;
|
||||
if (elem.events.text_input || elem.events.key_press) {
|
||||
ctx.text_edit(elem.text.te);
|
||||
}
|
||||
|
||||
if (ctx.input.keyboard.modkeys.bkspc) {
|
||||
*text_len = *text_len > 0 ? *text_len-1 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
elem.text.cursor = *text_len;
|
||||
|
||||
// draw the box
|
||||
short line_height = (short)ctx.font.line_height();
|
||||
Rect text_box = elem.bounds;
|
||||
ctx.push_rect(text_box, parent.div.z_index, style)!;
|
||||
ctx.push_string(text_box, text[:*text_len], parent.div.z_index, style.fg, true)!;
|
||||
|
||||
// TODO: draw cursor
|
||||
Rect bg_bounds = elem.bounds.pad(style.margin);
|
||||
Rect text_bounds = elem.bounds.pad(elem.layout.content_offset);
|
||||
ctx.push_rect(bg_bounds, parent.div.z_index, style)!;
|
||||
Rect cur;
|
||||
cur = ctx.layout_string(elem.text.te.to_string(), text_bounds, TOP_LEFT, parent.div.z_index, style.fg, elem.text.te.cursor)!;
|
||||
|
||||
// draw the cursor if the element has focus
|
||||
if (elem.events.has_focus) {
|
||||
cur.w = 2;
|
||||
// FIXME: gross hack for when text is empty
|
||||
if (cur.x == 0 && cur.y == 0) {
|
||||
cur.x = text_bounds.x;
|
||||
cur.y = text_bounds.y;
|
||||
}
|
||||
ctx.push_rect(cur, parent.div.z_index, &&(Style){.bg = style.fg})!;
|
||||
}
|
||||
return elem.events;
|
||||
}
|
||||
*/
|
@ -61,3 +61,14 @@ slider {
|
||||
secondary: #458588ff;
|
||||
accent: #fabd2fff;
|
||||
}
|
||||
|
||||
text-box {
|
||||
bg: #4a4543ff;
|
||||
fg: #fbf1c7ff;
|
||||
primary: #cc241dff;
|
||||
secondary: #458588ff;
|
||||
accent: #fabd2fff;
|
||||
border: 1;
|
||||
padding: 4;
|
||||
margin: 2;
|
||||
}
|
||||
|
27
src/main.c3
27
src/main.c3
@ -120,6 +120,13 @@ fn int main(String[] args)
|
||||
// ========================================================================================== //
|
||||
io::printfn("imported %d styles", ui.import_style_from_file(STYLESHEET_PATH));
|
||||
|
||||
// ========================================================================================== //
|
||||
// OTHER VARIABLES //
|
||||
// ========================================================================================== //
|
||||
TextEdit te;
|
||||
te.buffer = mem::new_array(char, 256);
|
||||
defer mem::free(te.buffer);
|
||||
|
||||
isz frame;
|
||||
double fps;
|
||||
time::Clock clock;
|
||||
@ -128,7 +135,6 @@ fn int main(String[] args)
|
||||
Times ui_times;
|
||||
Times draw_times;
|
||||
|
||||
|
||||
// ========================================================================================== //
|
||||
// MAIN LOOP //
|
||||
// ========================================================================================== //
|
||||
@ -147,11 +153,21 @@ fn int main(String[] args)
|
||||
switch (e.type) {
|
||||
case EVENT_QUIT:
|
||||
quit = true;
|
||||
case EVENT_KEY_UP: nextcase;
|
||||
case EVENT_KEY_UP:
|
||||
ui.input_key_release();
|
||||
nextcase;
|
||||
case EVENT_KEY_DOWN:
|
||||
ui.input_key_press();
|
||||
if (e.key.repeat) ui.input_key_repeat();
|
||||
|
||||
mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl;
|
||||
mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl;
|
||||
mod.bkspc = e.key.key == K_BACKSPACE ? !!(e.type == EVENT_KEY_DOWN) : mod.bkspc;
|
||||
mod.del = e.key.key == K_DELETE ? !!(e.type == EVENT_KEY_DOWN) : mod.del;
|
||||
mod.up = e.key.key == K_UP ? !!(e.type == EVENT_KEY_DOWN) : mod.up;
|
||||
mod.down = e.key.key == K_DOWN ? !!(e.type == EVENT_KEY_DOWN) : mod.down;
|
||||
mod.left = e.key.key == K_LEFT ? !!(e.type == EVENT_KEY_DOWN) : mod.left;
|
||||
mod.right = e.key.key == K_RIGHT ? !!(e.type == EVENT_KEY_DOWN) : mod.right;
|
||||
|
||||
// pressing ctrl+key or alt+key does not generate a character as such no
|
||||
// TEXT_INPUT event is generated. When those keys are pressed we have to
|
||||
@ -206,7 +222,7 @@ $switch APPLICATION:
|
||||
$case "debug":
|
||||
debug_app(&ui);
|
||||
$case "calculator":
|
||||
calculator(&ui);
|
||||
calculator(&ui, &te);
|
||||
$endswitch
|
||||
|
||||
// Timings counter
|
||||
@ -326,7 +342,7 @@ fn void debug_app(ugui::Ctx* ui)
|
||||
|
||||
import std::os::process;
|
||||
|
||||
fn void calculator(ugui::Ctx* ui)
|
||||
fn void calculator(ugui::Ctx* ui, TextEdit* te)
|
||||
{
|
||||
static char[128] buffer;
|
||||
static usz len;
|
||||
@ -421,6 +437,9 @@ fn void calculator(ugui::Ctx* ui)
|
||||
ui.slider_hor(ugui::@exact(100), ugui::@exact(20), &f)!!;
|
||||
ui.slider_ver(ugui::@exact(20), ugui::@exact(100), &f)!!;
|
||||
}!!;
|
||||
ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER, scroll_y: true) {
|
||||
ui.text_box(ugui::@grow(), ugui::@exact(100), te)!!;
|
||||
}!!;
|
||||
|
||||
}!!; }!!;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user