#load "./string"
#load "./string/builder"
#load "./string/reader"
+#load "./string/buffer"
#load "./intrinsics/onyx"
#load "./intrinsics/wasm"
--- /dev/null
+// 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
package core.string.reader
-StringReader :: struct {
+String_Reader :: struct {
// Think of this as `use s : str`;
data : ^u8;
count : u32;
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);
return n;
}
-read_u64 :: proc (use reader: ^StringReader) -> u64 {
+read_u64 :: proc (use reader: ^String_Reader) -> u64 {
n: u64 = 0;
skip_whitespace(reader);
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 {
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;
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;
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;
}
// 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 };
return out;
}
-advance_line :: proc (use reader: ^StringReader) {
+advance_line :: proc (use reader: ^String_Reader) {
if count == 0 do return;
adv := 0;
}
-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;
}
}
-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;
-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;
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 };
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.{};
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);
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);
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;
}
}
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 {
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;
}
}
}
#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;
}
#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;
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
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);
keycode,
modifiers
};
-
+
keyboard_state.keys_up_this_frame += 1;
}
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 {
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 {
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);
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);