From cb82b2d8ee1e02d97d3f19d830b6b3df2b64b466 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 10 Aug 2021 14:12:34 -0500 Subject: [PATCH] random various improvements around the core libraries --- core/container/array.onyx | 24 +++++ core/container/bucket_array.onyx | 85 +++++++++++++++++ core/container/iter.onyx | 2 +- core/conv.onyx | 91 ++++++++++++++----- core/std.onyx | 1 + core/string/buffer.onyx | 15 +++ modules/bmfont/position.onyx | 5 + .../immediate_mode/immediate_renderer.onyx | 2 + modules/immediate_mode/transform.onyx | 7 ++ modules/ui/components/button.onyx | 4 + modules/ui/components/scrollable_region.onyx | 18 ++-- modules/ui/components/textbox.onyx | 4 + modules/ui/components/workspace.onyx | 76 +++++++++++----- modules/ui/ui.onyx | 8 +- 14 files changed, 286 insertions(+), 56 deletions(-) create mode 100644 core/container/bucket_array.onyx diff --git a/core/container/array.onyx b/core/container/array.onyx index 30b7aa0a..3893f016 100644 --- a/core/container/array.onyx +++ b/core/container/array.onyx @@ -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 index 00000000..367e3f4f --- /dev/null +++ b/core/container/bucket_array.onyx @@ -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 }; +} diff --git a/core/container/iter.onyx b/core/container/iter.onyx index c9c6c4e9..8f4c8e69 100644 --- a/core/container/iter.onyx +++ b/core/container/iter.onyx @@ -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) { diff --git a/core/conv.onyx b/core/conv.onyx index c4f68ade..52f81f5b 100644 --- a/core/conv.onyx +++ b/core/conv.onyx @@ -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 { diff --git a/core/std.onyx b/core/std.onyx index 139e13c6..ba6f91a0 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -9,6 +9,7 @@ package core #load "./container/list" #load "./container/iter" #load "./container/set" +#load "./container/bucket_array" #load "./conv" #load "./math" diff --git a/core/string/buffer.onyx b/core/string/buffer.onyx index 9e274512..382d9d37 100644 --- a/core/string/buffer.onyx +++ b/core/string/buffer.onyx @@ -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 diff --git a/modules/bmfont/position.onyx b/modules/bmfont/position.onyx index 75e22996..85795384 100644 --- a/modules/bmfont/position.onyx +++ b/modules/bmfont/position.onyx @@ -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 { diff --git a/modules/immediate_mode/immediate_renderer.onyx b/modules/immediate_mode/immediate_renderer.onyx index 4cb38fde..238b718d 100644 --- a/modules/immediate_mode/immediate_renderer.onyx +++ b/modules/immediate_mode/immediate_renderer.onyx @@ -5,6 +5,8 @@ use package core.intrinsics.onyx { __initialize } Vector2 :: struct { x, y: f32; + + zero :: Vector2.{ 0, 0 }; } Color4 :: struct { diff --git a/modules/immediate_mode/transform.onyx b/modules/immediate_mode/transform.onyx index 0489747f..a6cff8f3 100644 --- a/modules/immediate_mode/transform.onyx +++ b/modules/immediate_mode/transform.onyx @@ -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 diff --git a/modules/ui/components/button.onyx b/modules/ui/components/button.onyx index 5158f990..170c7643 100644 --- a/modules/ui/components/button.onyx +++ b/modules/ui/components/button.onyx @@ -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); } diff --git a/modules/ui/components/scrollable_region.onyx b/modules/ui/components/scrollable_region.onyx index edd9082b..1f30bab3 100644 --- a/modules/ui/components/scrollable_region.onyx +++ b/modules/ui/components/scrollable_region.onyx @@ -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(); diff --git a/modules/ui/components/textbox.onyx b/modules/ui/components/textbox.onyx index 660d6015..fc5c8c0f 100644 --- a/modules/ui/components/textbox.onyx +++ b/modules/ui/components/textbox.onyx @@ -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 { diff --git a/modules/ui/components/workspace.onyx b/modules/ui/components/workspace.onyx index 900952d8..84b2a6b5 100644 --- a/modules/ui/components/workspace.onyx +++ b/modules/ui/components/workspace.onyx @@ -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 diff --git a/modules/ui/ui.onyx b/modules/ui/ui.onyx index 3a6c4084..972b3a14 100644 --- a/modules/ui/ui.onyx +++ b/modules/ui/ui.onyx @@ -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) { -- 2.25.1