random various improvements around the core libraries
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 10 Aug 2021 19:12:34 +0000 (14:12 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 10 Aug 2021 19:12:34 +0000 (14:12 -0500)
14 files changed:
core/container/array.onyx
core/container/bucket_array.onyx [new file with mode: 0644]
core/container/iter.onyx
core/conv.onyx
core/std.onyx
core/string/buffer.onyx
modules/bmfont/position.onyx
modules/immediate_mode/immediate_renderer.onyx
modules/immediate_mode/transform.onyx
modules/ui/components/button.onyx
modules/ui/components/scrollable_region.onyx
modules/ui/components/textbox.onyx
modules/ui/components/workspace.onyx
modules/ui/ui.onyx

index 30b7aa0a418b6ebc18a14bb8730bf306668cd3f5..3893f016a725d191dfa842a3dbe032bb421d94f8 100644 (file)
@@ -136,6 +136,30 @@ pop :: (arr: ^[..] $T) -> T {
     return arr.data[arr.count];
 }
 
+transplant :: (arr: ^[..] $T, old_index: i32, new_index: i32) -> bool {
+    if old_index < 0 || old_index >= arr.count do return false;
+    if new_index < 0 || new_index >= arr.count do return false;
+    if old_index == new_index do return true;
+
+    value := arr.data[old_index];
+
+    if old_index < new_index { // Moving forward
+        while i := old_index; i < new_index {
+            defer i += 1;
+            arr.data[i] = arr.data[i + 1];
+        }
+
+    } else { // Moving backward
+        while i := old_index; i > new_index {
+            defer i -= 1;
+            arr.data[i] = arr.data[i - 1];
+        }
+    }
+
+    arr.data[new_index] = value;
+    return true;
+}
+
 get :: (arr: ^[..] $T, idx: i32) -> T {
     if arr.count == 0 do return __zero_value(T);
 
diff --git a/core/container/bucket_array.onyx b/core/container/bucket_array.onyx
new file mode 100644 (file)
index 0000000..367e3f4
--- /dev/null
@@ -0,0 +1,85 @@
+@Incomplete // This implementation is not functional at all but is something I want to get around to adding.
+
+package core.bucket_array
+
+#private_file array :: package core.array
+
+Bucket_Array :: struct (T: type_expr) {
+    allocator : Allocator;
+    elements_per_bucket : i32;
+    buckets : [..] Bucket(T);
+
+    Bucket :: struct (T: type_expr) {
+        count : i32;
+        data : ^T; // Actually an array of elements_per_bucket things, but putting that
+                   // that into the type system makes these cumbersome to work with.
+    }
+}
+
+make :: ($T: type_expr, elements_per_bucket: i32,
+         array_allocator := context.allocator, bucket_allocator := context.allocator) -> Bucket_Array(T) {
+
+    buckets : Bucket_Array(T);
+    init(^buckets);
+    return buckets;
+}
+
+init :: (use b: ^Bucket_Array($T), elements_per_bucket: i32,
+         array_allocator := context.allocator, bucket_allocator := context.allocator) {
+    
+    allocator = bucket_allocator;
+    buckets   = array.make(#type Bucket(T), allocator=array_allocator);
+
+    initial_bucket := alloc_bucket(b);
+    array.push(^buckets, initial_bucket);
+}
+
+// Frees all the buckets
+clear :: (use b: ^Bucket_Array($T)) {
+    for ^bucket: ^buckets {
+        raw_free(bucket_allocator, bucket.data);
+        bucket.count = 0;
+    }
+
+    array.clear(^buckets);
+}
+
+get :: (use b: ^Bucket_Array($T), idx: i32) -> T {
+    bucket_index := idx / elements_per_bucket;
+    elem_index   := idx % elements_per_bucket;
+    return buckets[bucket_index].data[elem_index];
+}
+
+get_ptr :: (use b: ^Bucket_Array($T), idx: i32) -> ^T {
+    bucket_index := idx / elements_per_bucket;
+    elem_index   := idx % elements_per_bucket;
+    return ^buckets[bucket_index].data[elem_index];
+}
+
+push :: (use b: ^Bucket_Array($T), elem: T) -> bool {
+    last_bucket := ^buckets[buckets.count - 1];
+    if last_bucket.count < elements_per_bucket {
+        last_bucket.data[last_bucket.count] = elem;
+        last_bucket.count += 1;
+
+    } else {
+        new_bucket := alloc_bucket(b);
+        array.push(^buckets, new_bucket);
+
+        last_bucket = ^buckets[buckets.count - 1];
+        last_bucket.data[last_bucket.count] = elem;
+        last_bucket.count += 1;
+    }
+}
+
+pop :: (use b: ^Bucket_Array($T)) {
+    last_bucket := ^buckets[buckets.count - 1];
+    last_bucket.count -= 1;
+}
+
+
+#private
+alloc_bucket :: (use b: ^Bucket_Array($T)) -> Bucket(T) {
+    data := raw_alloc(allocator, sizeof T * elements_per_bucket);
+    return .{ 0, data };
+}
index c9c6c4e9ceadb88300fe7e55e8973258322c2388..8f4c8e699738fded0afe5d65bd43833e67a87034 100644 (file)
@@ -250,7 +250,7 @@ enumerate :: (it: Iterator($T), start_index: i32 = 0) -> Iterator(Enumeration_Va
 
 from_array :: #match {
     (arr: [..] $T) -> Iterator(^T) {
-        return from_slice((#type [] T).{ arr.data, arr.count });
+        return from_array((#type [] T).{ arr.data, arr.count });
     },
 
     (arr: [] $T) -> Iterator(^T) {
index c4f68ade33154ebf855565d95eba51b4433aba33..52f81f5b6945a146c737a1ed8d13237cfac71ab4 100644 (file)
@@ -94,7 +94,7 @@ str_to_f64 :: (s_: str) -> f64 {
     }
 }
 
-i64_to_str :: (n: i64, base: u64, buf: [] u8, min_length := 0) -> str {
+i64_to_str :: (n: i64, base: u64, buf: [] u8, min_length := 0, prefix := false) -> str {
     is_neg := false;
     if n < 0 && base == 10 {
         is_neg = true;
@@ -130,22 +130,24 @@ i64_to_str :: (n: i64, base: u64, buf: [] u8, min_length := 0) -> str {
         }
     }
 
-    if base == 16 {
-        *c = #char "x";
-        len += 1;
-        c -= 1;
-        *c = #char "0";
-        len += 1;
-        c -= 1;
-    }
+    if prefix {
+        if base == 16 {
+            *c = #char "x";
+            len += 1;
+            c -= 1;
+            *c = #char "0";
+            len += 1;
+            c -= 1;
+        }
 
-    if base == 2 {
-        *c = #char "b";
-        len += 1;
-        c -= 1;
-        *c = #char "0";
-        len += 1;
-        c -= 1;
+        if base == 2 {
+            *c = #char "b";
+            len += 1;
+            c -= 1;
+            *c = #char "0";
+            len += 1;
+            c -= 1;
+        }
     }
 
     if is_neg {
@@ -253,10 +255,12 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
 
     Format :: struct {
         pretty_printing      := false;
+        quote_strings        := false;
         digits_after_decimal := cast(u32) 4;
 
-        indentation := cast(u32) 0;
-        base        := cast(u64) 10;
+        indentation   := cast(u32) 0;
+        base          := cast(u64) 10;
+        minimum_width := cast(u32) 0;
     }
 
     vararg_index := 0;
@@ -303,6 +307,32 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
                         formatting.base = 16;
                     }
 
+                    case #char "b" {
+                        i += 1;
+
+                        digits := 0;
+                        while format[i] >= #char "0" && format[i] <= #char "9" {
+                            digits *= 10;
+                            digits += ~~(format[i] - #char "0");
+                            i += 1;
+                        }
+
+                        formatting.base = ~~digits;
+                    }
+
+                    case #char "w" {
+                        i += 1;
+
+                        digits := 0;
+                        while format[i] >= #char "0" && format[i] <= #char "9" {
+                            digits *= 10;
+                            digits += ~~(format[i] - #char "0");
+                            i += 1;
+                        }
+
+                        formatting.minimum_width = ~~digits;
+                    }
+
                     case #default do break break;
                 }
             }
@@ -346,7 +376,7 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
 
                 } else {
                     ibuf : [128] u8;
-                    istr := i64_to_str(~~value, 16, ~~ibuf);
+                    istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true);
                     output->write(istr);
                 }
             }
@@ -355,7 +385,7 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
                 value := *(cast(^i32) v.data);
 
                 ibuf : [128] u8;
-                istr := i64_to_str(~~value, formatting.base, ~~ibuf);
+                istr := i64_to_str(~~value, formatting.base, ~~ibuf, min_length=formatting.minimum_width);
                 output->write(istr);
             }
 
@@ -383,14 +413,23 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
                 output->write(fstr);
             }
 
-            case str do output->write(*(cast(^str) v.data));
+            case str {
+                if formatting.quote_strings do output->write("\"");
+                @Todo // escape '"' when quote_strings is enabled.
+                output->write(*(cast(^str) v.data));
+                if formatting.quote_strings do output->write("\"");
+            }
 
             case rawptr {
                 value := *(cast(^rawptr) v.data);
 
-                ibuf : [128] u8;
-                istr := i64_to_str(~~value, 16, ~~ibuf);
-                output->write(istr);
+                if value == null {
+                    output->write("(null)");
+                } else {
+                    ibuf : [128] u8;
+                    istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true);
+                    output->write(istr);
+                }
             }
 
             case type_expr {
@@ -421,6 +460,7 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
 
                     {
                         format := formatting;
+                        format.quote_strings = true;
                         if format.pretty_printing {
                             format.indentation += 4;
                         }
@@ -466,7 +506,7 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
                     value := *(cast(^rawptr) v.data);
 
                     ibuf : [128] u8;
-                    istr := i64_to_str(~~value, 16, ~~ibuf);
+                    istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true);
                     output->write(istr);
                 }
 
@@ -480,6 +520,7 @@ str_format_va :: (format: str, buffer: [] u8, va: [] any) -> str {
                     count := arr.count;
 
                     format := formatting;
+                    format.quote_strings = true;
                     if format.pretty_printing do format.indentation += 2;
 
                     for i: count {
index 139e13c6f675b9237d3dc40949b63a7407f365b7..ba6f91a0db989df723bbc2256164705cdb169745 100644 (file)
@@ -9,6 +9,7 @@ package core
 #load "./container/list"
 #load "./container/iter"
 #load "./container/set"
+#load "./container/bucket_array"
 
 #load "./conv"
 #load "./math"
index 9e2745124e854bc5afe2f64b5ea033a85e40f8d4..382d9d37f6bec85f87ac38029b40f27b4726c946 100644 (file)
@@ -47,6 +47,17 @@ buffer_insert :: (use buffer: ^String_Buffer, position: i32, ch: u8) -> bool {
     return true;
 }
 
+buffer_append :: (use buffer: ^String_Buffer, end: str) -> bool {
+    if count + end.count == capacity do return false;
+
+    for i: end.count {
+        data[i + count] = end[i];
+    }
+
+    count += end.count;
+    return true;
+}
+
 buffer_delete :: (use buffer: ^String_Buffer, position: i32) -> bool {
     if position > capacity do return false;
     if position > count do return false;
@@ -63,4 +74,8 @@ buffer_delete :: (use buffer: ^String_Buffer, position: i32) -> bool {
     return true;
 }
 
+buffer_clear :: (use buffer: ^String_Buffer) {
+    count = 0;
+}
+
 buffer_to_str :: (use buffer: ^String_Buffer) -> str do return .{data, count};
\ No newline at end of file
index 75e229963a84b24fad82294c10991254e5d44fdf..857953849f7cad08d09129658f46024475f56030 100644 (file)
@@ -61,6 +61,11 @@ get_character_positions :: (font: ^BMFont, size: f32, text: str, x: f32, y: f32)
                 continue;
             }
 
+            if char == #char "\t" {
+                rc.x += ~~(rc.font->get_glyph(#char " ")).xadvance * 4.0f;
+                continue;
+            }
+
             glyph := rc.font->get_glyph(char);
 
             if glyph == null {
index 4cb38fde9dd28b5c6c1171f5d5c86cd0f1c79369..238b718d049575919758e74c1fa4669b1bc3ca31 100644 (file)
@@ -5,6 +5,8 @@ use package core.intrinsics.onyx { __initialize }
 
 Vector2 :: struct {
     x, y: f32;
+
+    zero :: Vector2.{ 0, 0 };
 }
 
 Color4 :: struct {
index 0489747fe9cf2e803d6a3946ec6f71e57fb0b4b7..a6cff8f31903d99432e3cc7efb28d60e31994b4e 100644 (file)
@@ -51,4 +51,11 @@ transform_apply :: (use t: ^Transform, other: Transform) {
     translation.y += other.translation.y * scale.y;
     scale.x *= other.scale.x;
     scale.y *= other.scale.y;
+}
+
+transform_point :: (use t: ^Transform, v: Vector2) -> Vector2 {
+    return .{
+        x = (v.x - translation.x) / scale.x,
+        y = (v.y - translation.y) / scale.y,
+    };
 }
\ No newline at end of file
index 5158f990eac386e96a8b2e213ff9ed004a0098e5..170c764383f68f4d3655db67e20e58e796aa92fe 100644 (file)
@@ -44,6 +44,10 @@ button :: (use r: Rectangle, text: str, theme := ^default_button_theme, site :=
 
     if is_hot_item(hash) {
         move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+
+        #if #defined(set_cursor) {
+            set_cursor(Cursors.Pointer);
+        }
     } else {
         move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
     }
index edd9082b57b064e10453a21b88038501e22da9c9..1f30bab38f7a3ce373cd01780d266015bedadd92 100644 (file)
@@ -2,7 +2,6 @@ package ui
 
 use package core
 
-#private_file
 Scrollable_Region_State :: struct {
     transform: gfx.Transform = .{
         translation = .{ 0, 0 },
@@ -13,12 +12,19 @@ Scrollable_Region_State :: struct {
 #private
 scrollable_region_states : map.Map(UI_Id, Scrollable_Region_State);
 
-scrollable_region_start :: (use r: Rectangle, x_scroll: ^f32, y_scroll: ^f32, site := #callsite) {
+scrollable_region_start :: (use r: Rectangle, minimum_y := 0.0f, maximum_y := 10000.0f, site := #callsite, state: ^Scrollable_Region_State = null) {
     hash := get_site_hash(site, 0);
-    x, y := Rectangle.top_left(r);    
+    x, y := Rectangle.top_left(r);
     width, height := Rectangle.dimensions(r);
 
-    state := map.get(^scrollable_region_states, hash);
+    if state == null {
+        state = map.get_ptr(^scrollable_region_states, hash);
+
+        if state == null {
+            map.put(^scrollable_region_states, hash, .{});
+            state = map.get_ptr(^scrollable_region_states, hash);
+        }
+    }
 
     mx, my := get_mouse_position();
     if Rectangle.contains(r, mx, my) {
@@ -33,9 +39,9 @@ scrollable_region_start :: (use r: Rectangle, x_scroll: ^f32, y_scroll: ^f32, si
 
         if mouse_state.dwheel > 0 do state.transform.translation.y += speed;
         if mouse_state.dwheel < 0 do state.transform.translation.y -= speed;
-    }
 
-    map.put(^scrollable_region_states, hash, state);
+        state.transform.translation.y = math.clamp(state.transform.translation.y, -maximum_y, minimum_y);
+    }
 
     gfx.push_scissor(x, y, width, height);
     gfx.push_matrix();
index 660d60158db7c2c2e4ce099b50fe21af8c7fc887..fc5c8c0f1ec2297703a68563df89206cb0503320 100644 (file)
@@ -86,6 +86,10 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, placeholder :=
 
     if Rectangle.contains(r, mx, my) {
         set_hot_item(hash);
+
+        #if #defined(set_cursor) {
+            if is_hot_item(hash) do set_cursor(Cursors.Text);
+        }
     }
 
     if textbox_editing_state.hash == hash {
index 900952d89d9f75c8602f7891605f7993779a8c67..84b2a6b546d9d740fdfb9315f95bb3b9d0fc4760 100644 (file)
@@ -1,26 +1,57 @@
 package ui
 
 use package core
+use package core.intrinsics.onyx { __zero_value, __initialize }
 
-#private_file
 Workspace_State :: struct {
     transform: gfx.Transform = .{
         translation = .{ 0, 0 },
         scale       = .{ 1, 1 },
     };
 
+    @Transient
+    target_transform: gfx.Transform = .{
+        translation = .{ 0, 0 },
+        scale       =. { 1, 1 },
+    };
+
+    transform_transition := 0.0f;
+
     dragging := false;
 }
 
 #private
 workspace_states : map.Map(UI_Id, Workspace_State);
 
-workspace_start :: (use r: Rectangle, site := #callsite) {
+workspace_start :: (use r: Rectangle, site := #callsite, state: ^Workspace_State = null) {
     hash := get_site_hash(site, 0);
-    x, y := Rectangle.top_left(r);    
+    x, y := Rectangle.top_left(r);
     width, height := Rectangle.dimensions(r);
 
-    state := map.get(^workspace_states, hash);
+    @Hack // This whole situtation is a hack of trying to a pointer to a valid state.
+    if state == null {
+        state = map.get_ptr(^workspace_states, hash);
+
+        if state == null {
+            map.put(^workspace_states, hash, .{});
+            state = map.get_ptr(^workspace_states, hash);
+        }
+    }
+
+    if state.transform_transition > 0.0f {
+        move_towards(^state.transform_transition, 0, 0.08f);
+
+        use state;
+
+        if transform_transition == 0.0f {
+            transform.translation.x = target_transform.translation.x;
+            transform.translation.y = target_transform.translation.y;
+
+        } else {
+            transform.translation.x = (transform.translation.x + target_transform.translation.x) / 2;
+            transform.translation.y = (transform.translation.y + target_transform.translation.y) / 2;
+        }
+    }
 
     mx, my := get_mouse_position();
     if Rectangle.contains(r, mx, my) {
@@ -37,8 +68,8 @@ workspace_start :: (use r: Rectangle, site := #callsite) {
         if is_key_down(37) do state.transform.translation.x += speed;
 
         // These keys are weird because keycode is not standard between all browsers... ugh
-        if is_key_down(187) || is_key_down(61)  do zoom(^state, r, 1.02);
-        if is_key_down(189) || is_key_down(173) do zoom(^state, r, 0.98);
+        if is_key_down(187) || is_key_down(61)  do workspace_zoom(state, r, 1.02);
+        if is_key_down(189) || is_key_down(173) do workspace_zoom(state, r, 0.98);
 
         if mouse_state.left_button_just_down && !state.dragging {
             state.dragging = true;
@@ -55,35 +86,34 @@ workspace_start :: (use r: Rectangle, site := #callsite) {
             }
         }
 
-        if mouse_state.dwheel > 0 do zoom(^state, r, 1.04);
-        if mouse_state.dwheel < 0 do zoom(^state, r, 0.96);
+        if mouse_state.dwheel > 0 do workspace_zoom(state, r, 1.04);
+        if mouse_state.dwheel < 0 do workspace_zoom(state, r, 0.96);
 
     } else {
         state.dragging = false;
     }
 
-    map.put(^workspace_states, hash, state);
-
     gfx.push_scissor(x, y, width, height);
     gfx.push_matrix();
     gfx.apply_transform(state.transform);
 
-    zoom :: (state: ^Workspace_State, r: Rectangle, scale := 1.0f) {
-        x, y := Rectangle.top_left(r);    
-        width, height := Rectangle.dimensions(r);
-
-        bx: f32 = (state.transform.translation.x - (width / 2) - x) / state.transform.scale.x;
-        by: f32 = (state.transform.translation.y - (height / 2) - y) / state.transform.scale.y;
-
-        state.transform.scale.x *= scale;
-        state.transform.scale.y *= scale;
-
-        state.transform.translation.x = bx * state.transform.scale.x + (width / 2) + x;
-        state.transform.translation.y = by * state.transform.scale.y + (height / 2) + y;
-    }
 }
 
 workspace_end :: () {
     gfx.pop_scissor();
     gfx.pop_matrix();
+}
+
+workspace_zoom :: (state: ^Workspace_State, r: Rectangle, scale := 1.0f) {
+    x, y := Rectangle.top_left(r);    
+    width, height := Rectangle.dimensions(r);
+
+    bx: f32 = (state.transform.translation.x - (width / 2) - x) / state.transform.scale.x;
+    by: f32 = (state.transform.translation.y - (height / 2) - y) / state.transform.scale.y;
+
+    state.transform.scale.x *= scale;
+    state.transform.scale.y *= scale;
+
+    state.transform.translation.x = bx * state.transform.scale.x + (width / 2) + x;
+    state.transform.translation.y = by * state.transform.scale.y + (height / 2) + y;
 }
\ No newline at end of file
index 3a6c4084ba1090b0a02667dfbbb6697c3c1b0fbd..972b3a14349bbff7fcf327e963fa1d8bf3ecf97f 100644 (file)
@@ -148,6 +148,10 @@ Rectangle :: struct {
         return math.min(x0, x1) <= x && x <= math.max(x0, x1) &&
                math.min(y0, y1) <= y && y <= math.max(y0, y1);
     }
+
+    intersects :: (use r: Rectangle, o: Rectangle) -> bool {
+        return x1 >= o.x0 && x0 <= o.x1 && y1 >= o.y0 && y0 <= o.y1;
+    }
 }
 
 
@@ -206,7 +210,9 @@ get_text_width :: (text: str, size := DEFAULT_TEXT_SIZE) -> f32 {
     return current_font->get_width(text, size);
 }
 
-
+get_text_height  :: (text: str, size := DEFAULT_TEXT_SIZE) -> f32 {
+    return current_font->get_height(text, size);
+}
 
 @Relocate
 move_towards :: (value: ^$T, target: T, step: T) {