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);
--- /dev/null
+@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 };
+}
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) {
}
}
-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;
}
}
- 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 {
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;
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;
}
}
} else {
ibuf : [128] u8;
- istr := i64_to_str(~~value, 16, ~~ibuf);
+ istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true);
output->write(istr);
}
}
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);
}
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 {
{
format := formatting;
+ format.quote_strings = true;
if format.pretty_printing {
format.indentation += 4;
}
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);
}
count := arr.count;
format := formatting;
+ format.quote_strings = true;
if format.pretty_printing do format.indentation += 2;
for i: count {
#load "./container/list"
#load "./container/iter"
#load "./container/set"
+#load "./container/bucket_array"
#load "./conv"
#load "./math"
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;
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
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 {
Vector2 :: struct {
x, y: f32;
+
+ zero :: Vector2.{ 0, 0 };
}
Color4 :: struct {
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
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);
}
use package core
-#private_file
Scrollable_Region_State :: struct {
transform: gfx.Transform = .{
translation = .{ 0, 0 },
#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) {
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();
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 {
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) {
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;
}
}
- 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
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;
+ }
}
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) {