added String_Buffer; refactored some textbox code
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 13 Jun 2021 21:34:51 +0000 (16:34 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 13 Jun 2021 21:34:51 +0000 (16:34 -0500)
bin/onyx
core/std.onyx
core/string/buffer.onyx [new file with mode: 0644]
core/string/reader.onyx
modules/ui/components/textbox.onyx
modules/ui/ui.onyx
tests/aoc-2020/day16.onyx
tests/aoc-2020/day18.onyx

index e2a16a3c5f2ae7b95c1315812291b60944cca81d..4ca266c4f8696b50f8fea097d1a67da146048885 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index 776d715d7ff0c3ce1f363795553b344db2726b65..3c0b55277fda38f6e39eb2d1acefe741eeb651e8 100644 (file)
@@ -18,6 +18,7 @@ package core
 #load "./string"
 #load "./string/builder"
 #load "./string/reader"
+#load "./string/buffer"
 
 #load "./intrinsics/onyx"
 #load "./intrinsics/wasm"
diff --git a/core/string/buffer.onyx b/core/string/buffer.onyx
new file mode 100644 (file)
index 0000000..9e27451
--- /dev/null
@@ -0,0 +1,66 @@
+// String_Buffer was originally made to support the UI library's textbox buffer,
+// where single character editing was the only operation that was needed. Therefore,
+// this is very barebones and does not contain a lot of functionality at the moment.
+// It basically just represents a string stored in a non-resizable buffer, but the
+// string can be resized within the buffer.
+
+package core.string
+
+#private_file math :: package core.math
+
+String_Buffer :: struct {
+    data     : ^u8;
+    count    : u32;
+    capacity : u32;
+}
+
+buffer_make :: (buffer_memory: [] u8, initial_str := null_str) -> String_Buffer {
+    buffer : String_Buffer;
+    buffer.data = buffer_memory.data;
+    buffer.capacity = buffer_memory.count;
+    buffer.count = 0;
+
+    if initial_str.count > 0 {
+        initial_length := math.min(initial_str.count, buffer.capacity);
+        buffer.count += initial_length;
+
+        for i: initial_length { 
+            buffer.data[i] = initial_str.data[i];
+        }
+    }
+
+    return buffer;
+}
+
+buffer_insert :: (use buffer: ^String_Buffer, position: i32, ch: u8) -> bool {
+    if position >= capacity do return false;
+    if count >= capacity do return false;
+
+    while i := cast(i32) count; i > position {
+        defer i -= 1;
+
+        data[i] = data[i - 1];
+    }        
+
+    data[position] = ch;
+    count += 1;
+    return true;
+}
+
+buffer_delete :: (use buffer: ^String_Buffer, position: i32) -> bool {
+    if position > capacity do return false;
+    if position > count do return false;
+    if count == 0 do return false;
+    if position == 0 do return false;
+
+    while i := position - 1; i < cast(i32) count - 1 {
+        defer i += 1;
+
+        data[i] = data[i + 1];
+    }
+
+    count -= 1;
+    return true;
+}
+
+buffer_to_str :: (use buffer: ^String_Buffer) -> str do return .{data, count};
\ No newline at end of file
index 4c323792d9547d1b928463fa52bc9ea1902d0425..ed690798dfa6a5f4ec3ba29e5be0f3946b2698f6 100644 (file)
@@ -1,6 +1,6 @@
 package core.string.reader
 
-StringReader :: struct {
+String_Reader :: struct {
     // Think of this as `use s : str`;
     data : ^u8;
     count : u32;
@@ -9,26 +9,26 @@ StringReader :: struct {
     original_str : str;
 }
 
-make :: proc (s: str) -> StringReader {
-    reader : StringReader;
+make :: proc (s: str) -> String_Reader {
+    reader : String_Reader;
     init(^reader, s);
     return reader;
 }
 
-init :: proc (use reader: ^StringReader, orig_str: str) {
+init :: proc (use reader: ^String_Reader, orig_str: str) {
     original_str = orig_str;
     data  = original_str.data;
     count = original_str.count;
 }
 
-reset :: proc (use reader: ^StringReader) {
+reset :: proc (use reader: ^String_Reader) {
     data  = original_str.data;
     count = original_str.count;
 }
 
-empty :: proc (use reader: ^StringReader) -> bool do return count == 0;
+empty :: proc (use reader: ^String_Reader) -> bool do return count == 0;
 
-read_u32 :: proc (use reader: ^StringReader) -> u32 {
+read_u32 :: proc (use reader: ^String_Reader) -> u32 {
     n: u32 = 0;
 
     skip_whitespace(reader);
@@ -44,7 +44,7 @@ read_u32 :: proc (use reader: ^StringReader) -> u32 {
     return n;
 }
 
-read_u64 :: proc (use reader: ^StringReader) -> u64 {
+read_u64 :: proc (use reader: ^String_Reader) -> u64 {
     n: u64 = 0;
 
     skip_whitespace(reader);
@@ -60,7 +60,7 @@ read_u64 :: proc (use reader: ^StringReader) -> u64 {
     return n;
 }
 
-read_byte :: proc (use reader: ^StringReader) -> u8 {
+read_byte :: proc (use reader: ^String_Reader) -> u8 {
     if count == 0 do return #char "\0";
 
     defer {
@@ -71,7 +71,7 @@ read_byte :: proc (use reader: ^StringReader) -> u8 {
     return data[0];
 }
 
-read_bytes :: proc (use reader: ^StringReader, byte_count := 1) -> str {
+read_bytes :: proc (use reader: ^String_Reader, byte_count := 1) -> str {
     bc := byte_count;
     if count < bc do bc = count;
 
@@ -83,7 +83,7 @@ read_bytes :: proc (use reader: ^StringReader, byte_count := 1) -> str {
     return str.{ data, bc };
 }
 
-read_line :: proc (use reader: ^StringReader) -> str {
+read_line :: proc (use reader: ^String_Reader) -> str {
     out : str;
     out.data = data;
     out.count = 0;
@@ -104,7 +104,7 @@ read_line :: proc (use reader: ^StringReader) -> str {
     return out;
 }
 
-read_until :: proc (use reader: ^StringReader, skip: u32, uptos: ..u8) -> str {
+read_until :: proc (use reader: ^String_Reader, skip: u32, uptos: ..u8) -> str {
     if count == 0 do return str.{ null, 0 };
 
     out : str;
@@ -131,7 +131,7 @@ read_until :: proc (use reader: ^StringReader, skip: u32, uptos: ..u8) -> str {
 }
 
 // Reads a continuous string of alphabetic chars along with underscores '_'
-read_word :: proc (use reader: ^StringReader) -> str {
+read_word :: proc (use reader: ^String_Reader) -> str {
     if count == 0 do return str.{ null, 0 };
 
     out := str.{ data, 0 };
@@ -150,7 +150,7 @@ read_word :: proc (use reader: ^StringReader) -> str {
     return out;
 }
 
-advance_line :: proc (use reader: ^StringReader) {
+advance_line :: proc (use reader: ^String_Reader) {
     if count == 0 do return;
 
     adv := 0;
@@ -161,7 +161,7 @@ advance_line :: proc (use reader: ^StringReader) {
 }
 
 
-skip_whitespace :: proc (use reader: ^StringReader) {
+skip_whitespace :: proc (use reader: ^String_Reader) {
     while count > 0 do switch data[0] {
         case #char " ", #char "\t", #char "\n", #char "\r" {
             data += 1;
@@ -172,7 +172,7 @@ skip_whitespace :: proc (use reader: ^StringReader) {
     }
 }
 
-skip_bytes :: proc (use reader: ^StringReader, byte_count := 1) {
+skip_bytes :: proc (use reader: ^String_Reader, byte_count := 1) {
     bc := byte_count;
     if count < bc do bc = count;
 
@@ -182,7 +182,7 @@ skip_bytes :: proc (use reader: ^StringReader, byte_count := 1) {
 
 
 
-starts_with :: proc (use reader: ^StringReader, s: str) -> bool {
+starts_with :: proc (use reader: ^String_Reader, s: str) -> bool {
     if count < s.count do return false;
     while i := 0; i < s.count {
         if data[i] != s[i] do return false;
index d6e4d75ccc508b718e02cadf1462fa224ca3c357..c4a60f23badd1615b5c8c2e913a18ccd91ce2982 100644 (file)
@@ -4,8 +4,13 @@ use package core
 Textbox_Theme :: struct {
     use text_theme := Text_Theme.{
         text_color = .{ 0, 0, 0 }
+        // text_color = .{1, 1, 1}
     };
 
+    // background_color := gfx.Color4.{ 0.1, 0.1, 0.1 };
+    // hover_color      := gfx.Color4.{ 0.3, 0.3, 0.3 };
+    // click_color      := gfx.Color4.{ 0.5, 0.5, 0.7 };
+
     background_color := gfx.Color4.{ 0.8, 0.8, 0.8 };
     hover_color      := gfx.Color4.{ 1.0, 1.0, 1.0 };
     click_color      := gfx.Color4.{ 0.5, 0.5, 0.7 };
@@ -13,7 +18,7 @@ Textbox_Theme :: struct {
     border_color := gfx.Color4.{ 0.2, 0.2, 0.2 };
     border_width := 6.0f;   @InPixels
 
-    cursor_color     := gfx.Color4.{ 0, 0, 0 };
+    cursor_color     := gfx.Color4.{ 0.5, 0.5, 0.5 };
 }
 
 default_textbox_theme := Textbox_Theme.{};
@@ -28,9 +33,10 @@ Textbox_Editing_State :: struct {
     cursor_animation_speed := 0.02f;
 }
 
+#private
 textbox_editing_state := Textbox_Editing_State.{};
 
-textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool {
+textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool {
     result := false;
 
     hash := get_site_hash(site, increment);
@@ -39,7 +45,7 @@ textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_tex
     border_width  := theme.border_width;
     width, height := Rectangle.dimensions(r);
 
-    text := text_buffer.buffer;
+    text := string.buffer_to_str(text_buffer);
     text_width  := bmfont.get_width(^font, text, theme.font_size);
     text_height := bmfont.get_height(^font, text, theme.font_size);
 
@@ -50,6 +56,8 @@ textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_tex
         if mouse_state.left_button_down && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
             set_active_item(hash);
             textbox_editing_state.hash = hash;
+
+            // animation_state.click_time = 1.0f;
         }
     }
 
@@ -82,19 +90,19 @@ textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_tex
                 switch key.code {
                     case 0x25 do textbox_editing_state.cursor_position -= 1;
                     case 0x27 do textbox_editing_state.cursor_position += 1;
-                    case 0x23 do textbox_editing_state.cursor_position = text_buffer.buffer.count;
+                    case 0x23 do textbox_editing_state.cursor_position = text_buffer.count;
                     case 0x24 do textbox_editing_state.cursor_position = 0;
 
                     case 0x08 {
                         // Backspace
-                        if text_buffer->delete(textbox_editing_state.cursor_position) {
+                        if string.buffer_delete(text_buffer, textbox_editing_state.cursor_position) {
                             textbox_editing_state.cursor_position -= 1;
                         }
                     }
 
                     case 0x2E {
                         // Delete
-                        text_buffer->delete(textbox_editing_state.cursor_position + 1);
+                        string.buffer_delete(text_buffer, textbox_editing_state.cursor_position + 1);
                     }
 
                     case #default {
@@ -104,14 +112,14 @@ textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_tex
 
                         char := key_map[index];
                         if char != #char "\0" {
-                            if text_buffer->insert(textbox_editing_state.cursor_position, char) {
+                            if string.buffer_insert(text_buffer, textbox_editing_state.cursor_position, char) {
                                 textbox_editing_state.cursor_position += 1;
                             }
                         }
                     }
                 }
 
-                textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.buffer.count);
+                textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count);
                 textbox_editing_state.cursor_animation = 1.0f;
             }
         }
@@ -157,12 +165,14 @@ textbox :: (use r: Rectangle, text_buffer: ^String_Buffer, theme := ^default_tex
 }
 
 #private_file
-get_cursor_location :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, text_size: f32, cursor_position: i32) -> f32 {
+get_cursor_location :: (text_buffer: ^string.String_Buffer, text_x: f32, text_y: f32, text_size: f32, cursor_position: i32) -> f32 {
     countdown := cursor_position + 1;
     last_x : f32 = text_x;
     last_w : f32;
 
-    for glyph: bmfont.get_character_positions(^font, text_size, text_buffer.buffer, text_x, text_y) {
+    text := string.buffer_to_str(text_buffer);
+
+    for glyph: bmfont.get_character_positions(^font, text_size, text, text_x, text_y) {
         if countdown == 0 do return last_x;
 
         last_x = glyph.pos_x;
@@ -177,12 +187,13 @@ get_cursor_location :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, t
 }
 
 #private_file
-get_cursor_position :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, text_size: f32, mouse_x: f32, mouse_y: f32) -> i32 {
+get_cursor_position :: (text_buffer: ^string.String_Buffer, text_x: f32, text_y: f32, text_size: f32, mouse_x: f32, mouse_y: f32) -> i32 {
     cursor_position := 0;
 
     last_x: f32 = text_x;
+    text := string.buffer_to_str(text_buffer);
 
-    for glyph: bmfont.get_character_positions(^font, text_size, text_buffer.buffer, text_x, text_y) {
+    for glyph: bmfont.get_character_positions(^font, text_size, text, text_x, text_y) {
         cursor_position += 1;
         if cursor_position == 1 do continue;
 
@@ -194,47 +205,9 @@ get_cursor_position :: (text_buffer: ^String_Buffer, text_x: f32, text_y: f32, t
         last_x = glyph.pos_x + glyph.pos_w / 2;
     }
 
-    return text_buffer.buffer.count;
+    return text_buffer.count;
 }
 
-@Relocate // I think this should be part of the standard library? Or something similar to it?
-String_Buffer :: struct {
-    @Todo // make 'use buffer : [] u8' work here
-    buffer   : [] u8;
-    capacity : u32;
-
-    insert :: (use sb: ^String_Buffer, position: i32, ch: u8) -> bool {
-        if position >= capacity do return false;
-        if buffer.count >= capacity do return false;
-
-        while i := cast(i32) buffer.count; i > position {
-            defer i -= 1;
-
-            buffer.data[i] = buffer.data[i - 1];
-        }        
-
-        buffer.data[position] = ch;
-        buffer.count += 1;
-        return true;
-    }
-
-    delete :: (use sb: ^String_Buffer, position: i32) -> bool {
-        if position > capacity do return false;
-        if buffer.count == 0 do return false;
-        if position == 0 do return false;
-
-        while i := position - 1; i < cast(i32) buffer.count - 1{
-            defer i += 1;
-
-            buffer.data[i] = buffer.data[i + 1];
-        }
-
-        buffer.count -= 1;
-        return true;
-    }
-}
-
-
 @Relocate @Cleanup
 // This keymap is very wrong in a lot of ways. It works for my standard US keyboard, but will break horribly
 // for any other keyboard layout. I would like to use something else from the browser, but unsurprisingly the
index ae02792063cfadc4e0c8739ac3ee4b18b863d10d..526c2ca135dc31cfd2388726cbd4bf81c442bb4d 100644 (file)
@@ -61,16 +61,20 @@ init_ui :: () {
     map.init(^animation_states, default=.{}, hash_count=4);
 }
 
+// This function should be called at the end of drawing a frame, after all of the UI elements
+// had a chance to interact with the hardware events.
 clear_buttons :: () {
     mouse_state.left_button_just_up    = false;
     mouse_state.left_button_just_down  = false;
     mouse_state.right_button_just_up   = false;
     mouse_state.right_button_just_down = false;
 
-    for ^key: keyboard_state.keycodes_down_this_frame do *key = .{};
-    keyboard_state.keys_down_this_frame = 0;
+    // I don't think these need to be cleared every frame, so long as you don't try
+    // to use them without checking keys_down_this_frame first.
+    // for ^key: keyboard_state.keycodes_down_this_frame do *key = .{};
+    // for ^key: keyboard_state.keycodes_up_this_frame do *key = .{};
 
-    for ^key: keyboard_state.keycodes_up_this_frame do *key = .{};
+    keyboard_state.keys_down_this_frame = 0;
     keyboard_state.keys_up_this_frame = 0;
 
     if !hot_item_was_set do set_hot_item(0);
@@ -129,7 +133,7 @@ key_up :: (keycode: u32, modifiers: Keyboard_State.Key_State.Modifiers) {
         keycode,
         modifiers
     };
-    
+
     keyboard_state.keys_up_this_frame += 1;
 }
 
index f15c9db178f837e83f4560db3547a011cae57069..5228289d7aebc2b41c32012607c71355b9f4bb1f 100644 (file)
@@ -20,7 +20,7 @@ field_valid :: (use field: ^Field, n: u32) -> bool {
 
 total_scanning_error := 0;
 
-read_ticket_and_validate :: (file: ^reader.StringReader, fields: [..] Field, ticket_store: ^u32) -> bool {
+read_ticket_and_validate :: (file: ^reader.String_Reader, fields: [..] Field, ticket_store: ^u32) -> bool {
        retval := true;
 
        for i: 0 .. fields.count {
index 294fe09aea0077ce6f6ec6974ae4a9ee31bc5186..645121861c6fb0dd1ea134661f85148839cb6803 100644 (file)
@@ -3,7 +3,7 @@
 use package core
 reader :: package core.string.reader
 
-parse_factor :: (file: ^reader.StringReader) -> u64 {
+parse_factor :: (file: ^reader.String_Reader) -> u64 {
        reader.skip_whitespace(file);
 
        switch *file.data {
@@ -27,7 +27,7 @@ parse_factor :: (file: ^reader.StringReader) -> u64 {
        return 0;
 }
 
-parse_expression_add :: (file: ^reader.StringReader) -> u64 {
+parse_expression_add :: (file: ^reader.String_Reader) -> u64 {
        reader.skip_whitespace(file);
 
        left := parse_factor(file);
@@ -45,7 +45,7 @@ parse_expression_add :: (file: ^reader.StringReader) -> u64 {
        return left;
 }
 
-parse_expression_mul :: (file: ^reader.StringReader) -> u64 {
+parse_expression_mul :: (file: ^reader.String_Reader) -> u64 {
        reader.skip_whitespace(file);
 
        left := parse_expression_add(file);