editor_update :: (dt: f32) {
move_towards(^editor_openness, editor_target_openness, dt * 6);
+
+ if is_key_just_up(GLFW_KEY_1) do clicked_tab = .Create;
+ if is_key_just_up(GLFW_KEY_2) do clicked_tab = .Edit;
handle_clicking_tab(dt);
switch active_tab {
// Don't draw the "None" item;
if it.value == 0 do continue;
- contains_mouse := Rect.contains(.{x, y, w, h}, mouse_get_position_vector());
- if contains_mouse && is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
- clicked_tab = ~~ it.value;
+ theme := Button_Theme.{};
+ theme.active = active_tab == ~~it.value;
+ if draw_button(.{x, y, w, h}, it.name, ^theme, increment=~~it.value) {
+ clicked_tab = ~~it.value;
}
- if ~~it.value == active_tab {
- immediate_set_color(.{0.4, 0.4, 0.5, 1});
- } else {
- // This would be nice to have some kind of lerping, but that would require
- // a lot more structure to this, which I don't think will be worth it.
- if contains_mouse {
- immediate_set_color(.{0.3, 0.3, 0.3, 1});
- } else {
- immediate_set_color(.{0.2, 0.2, 0.2, 1});
- }
- }
- immediate_rectangle(x, y, w, h);
-
- text_width := font_get_width(editor_big_font, it.name);
- font_draw(editor_big_font, x + (w - text_width) / 2, y + h / 2 + 24 / 4, it.name);
x += w + 2;
}
}
type := it.key;
info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(type);
- if active_index == i {
- immediate_set_color(.{.5,.5,.3});
- } elseif Rect.contains(.{x, y, w, 36.0f}, mouse_get_position_vector()) {
- immediate_set_color(.{.4,.4,.4});
-
- if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) {
- active_index = i;
- }
- } else {
- immediate_set_color(.{.3,.3,.3});
+ y += 40.0f;
+ theme := Button_Theme.{};
+ theme.active = active_index == i;
+ if draw_button(.{x + 4, y - 18, w, 36.0f}, info.name, ^theme, increment=i) {
+ active_index = i;
}
+ }
- immediate_rectangle(x, y, w, 36.0f);
-
- y += 40.0f;
- font_draw_centered(editor_font, x + 4, y - 18.0f, w, info.name);
+ #persist test : [..] u8;
+ if test.data == null {
+ test << #char "t";
+ test << #char "e";
+ test << #char "s";
+ test << #char "t";
}
+
+ theme := Textbox_Theme.{};
+ draw_textbox(.{x, y + 72.0f, w, 36.0f}, ^test, theme=^theme);
}
#local render_entity_fields :: (entity: ^Entity, x, y, w, h: f32) {
--- /dev/null
+//
+// Very simple immediate mode UI
+//
+
+use package core
+use package opengles
+use package glfw3
+
+UI_Id :: u32
+
+ui_end_frame :: () {
+ hot_item_depth_needed = hot_item_depth;
+ if !hot_item_was_set do set_hot_item(0);
+ hot_item_depth = 0;
+ hot_item_was_set = false;
+
+ for^ animation_states.entries {
+ if !it.value.accessed_this_frame || (it.value.click_time == 0 && it.value.hover_time == 0) {
+ map.delete(^animation_states, it.key);
+ }
+ }
+
+ for^ animation_states.entries {
+ it.value.accessed_this_frame = false;
+ }
+}
+
+//
+// Buttons
+//
+Button_Theme :: struct {
+ use text_theme := Text_Theme.{};
+ use animation_theme := Animation_Theme.{};
+
+ background_color := Color.{ 0.1, 0.1, 0.1 };
+ hover_color := Color.{ 0.3, 0.3, 0.3 };
+ click_color := Color.{ 0.5, 0.5, 0.7 };
+
+ border_color := Color.{0.2, 0.2, 0.2};
+ border_width := 2.0f;
+
+ active := false;
+}
+
+@CompilerBug // This should work...
+// default_button_theme := Button_Theme.{};
+
+draw_button :: (use r: Rect, text: str, theme: ^Button_Theme, site := #callsite, increment := 0) -> bool {
+ // HMMM.... this should be gotten rid of as quick as possible.
+ __ASDF := Button_Theme.{};
+ if theme == null {
+ theme = ^__ASDF;
+ }
+ result := false;
+
+ hash := get_site_hash(site, increment);
+ animation_state := get_animation(hash);
+ mx, my := mouse_get_position();
+
+ contains := Rect.contains(r, .{~~mx, ~~my});
+
+ if is_active_item(hash) {
+ if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
+ if is_hot_item(hash) && contains {
+ result = true;
+ animation_state.click_time = 1.0f;
+ }
+
+ set_active_item(0);
+ }
+ } elseif is_hot_item(hash) {
+ if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
+ set_active_item(hash);
+ }
+ }
+
+ if contains {
+ set_hot_item(hash);
+ }
+
+ if is_hot_item(hash) || theme.active {
+ move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+ } else {
+ move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+ }
+
+ border_width := theme.border_width;
+
+ immediate_set_color(theme.border_color);
+ immediate_rectangle(x, y, w, h);
+
+ surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
+ surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+ immediate_set_color(surface_color);
+ immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
+
+ font : Font;
+ if theme.font == null do font = font_lookup(.{"./assets/fonts/calibri.ttf", 16});
+ else do font = *theme.font;
+ font_height := font_get_height(font, text);
+ font_set_color(theme.text_color);
+ font_draw_centered(font, x, y + (h - font_height) / 2 + font.em - 2, w, text);
+
+ move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+ return result;
+}
+
+
+//
+// Textbox
+//
+Textbox_Theme :: struct {
+ use text_theme := Text_Theme.{
+ text_color = .{ 0, 0, 0 }
+ };
+
+ use animation_theme := Animation_Theme.{};
+
+ background_color := Color.{ 0.8, 0.8, 0.8 };
+ hover_color := Color.{ 1.0, 1.0, 1.0 };
+ click_color := Color.{ 0.5, 0.5, 0.7 };
+
+ border_color := Color.{ 0.2, 0.2, 0.2 };
+ border_width := 6.0f; @InPixels
+
+ cursor_color := Color.{ 0.5, 0.5, 0.5 };
+ cursor_width := 4.0f; @InPixels
+ cursor_blink_speed := 0.04f; // Bigger is faster
+
+ placeholder_text_color := Color.{ 0.5, 0.5, 0.5 };
+}
+
+@CompilerBug
+// default_textbox_theme := Textbox_Theme.{};
+
+#local {
+ Textbox_Editing_State :: struct {
+ hash: UI_Id = 0;
+
+ cursor_position: i32 = 0;
+ cursor_animation := 0.0f;
+ cursor_animation_speed := 0.02f;
+ }
+
+ textbox_editing_state := Textbox_Editing_State.{};
+}
+
+draw_textbox :: (use r: Rect, text_buffer: ^[..] u8, placeholder := null_str, theme: ^Textbox_Theme = null, site := #callsite, increment := 0) -> bool {
+ result := false;
+
+ hash := get_site_hash(site, increment);
+ animation_state := get_animation(hash);
+ mx, my := mouse_get_position();
+
+ border_width := theme.border_width;
+ text_color := theme.text_color;
+ text := str.{text_buffer.data, text_buffer.count};
+ if text.count == 0 && placeholder.count >0 {
+ text = placeholder;
+ text_color = theme.placeholder_text_color;
+ }
+
+ font : Font;
+ if theme.font == null do font = font_lookup(.{"./assets/fonts/calibri.ttf", 16});
+ else do font = *theme.font;
+ text_width := font_get_width(font, text);
+ text_height := font_get_height(font, text);
+
+ text_x := x + border_width;
+ text_y := y + font.em + (h - text_height) / 2;
+
+ contains := Rect.contains(r, .{~~mx, ~~my});
+
+ if is_hot_item(hash) && !is_active_item(hash) {
+ if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
+ set_active_item(hash);
+ textbox_editing_state.hash = hash;
+ textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed;
+ }
+ }
+
+ if is_active_item(hash) {
+ if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains {
+ set_active_item(0);
+ textbox_editing_state.hash = 0;
+ textbox_editing_state.cursor_position = 0;
+ }
+ }
+
+ if contains {
+ set_hot_item(hash);
+ }
+
+ if textbox_editing_state.hash == hash {
+ move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed);
+ if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f;
+
+ if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
+ textbox_editing_state.cursor_animation = 1.0f;
+ // textbox_editing_state.cursor_position = get_cursor_position(text_buffer, text_x, text_y, theme.font_size, ~~mx, ~~my);
+ }
+
+ keys := input_get_keys_this_frame();
+ if keys.count > 0 {
+ for key: keys {
+ switch key {
+ case GLFW_KEY_LEFT do textbox_editing_state.cursor_position -= 1;
+ case GLFW_KEY_RIGHT do textbox_editing_state.cursor_position += 1;
+ case GLFW_KEY_END do textbox_editing_state.cursor_position = text_buffer.count;
+ case GLFW_KEY_HOME do textbox_editing_state.cursor_position = 0;
+
+ case GLFW_KEY_BACKSPACE {
+ array.pop(text_buffer);
+ textbox_editing_state.cursor_position = math.max(~~0, textbox_editing_state.cursor_position - 1);
+ }
+
+ case GLFW_KEY_DELETE {
+ array.delete(text_buffer, 0);
+ }
+
+ case #default {
+ if key >= #char " " && key <= 128 {
+ array.push(text_buffer, ~~key);
+ textbox_editing_state.cursor_position += 1;
+ }
+ }
+ }
+ }
+
+ textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count);
+ textbox_editing_state.cursor_animation = 1.0f;
+ }
+ }
+
+ if is_hot_item(hash) {
+ move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+ } else {
+ move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+ }
+
+ // immediate_push_scissor(x, y, w, h);
+ immediate_set_color(theme.border_color);
+ immediate_rectangle(x, y, w, h);
+
+ surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
+ surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+ immediate_set_color(surface_color);
+ immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
+
+ font_set_color(theme.text_color);
+ font_draw(font, text_x, text_y, text); // This is technically a frame late for updating the text?
+
+ move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+ // immediate_pop_scissor();
+ return result;
+}
+
+
+
+
+
+#local {
+ hot_item : UI_Id = 0
+ active_item : UI_Id = 0
+ hot_item_was_set := false
+
+ hot_item_depth := 0;
+ hot_item_depth_needed := 0;
+
+ set_active_item :: (id: UI_Id) -> bool {
+ active_item = id;
+ return true;
+ }
+
+ set_hot_item :: (id: UI_Id, force := false) -> bool {
+ if active_item != 0 do return false;
+
+ if force {
+ hot_item_was_set = true;
+ hot_item = id;
+ return true;
+ }
+
+ hot_item_depth += 1;
+ if hot_item_depth >= hot_item_depth_needed {
+ hot_item_was_set = true;
+ hot_item = id;
+ return true;
+ }
+
+ return false;
+ }
+
+ is_active_item :: (id: UI_Id) -> bool {
+ return active_item == id;
+ }
+
+ is_hot_item :: (id: UI_Id) -> bool {
+ return hot_item == id;
+ }
+
+ Text_Theme :: struct {
+ text_color := Color.{1, 1, 1};
+ font : ^Font = null;
+ }
+
+ Animation_Theme :: struct {
+ hover_speed := 0.1f;
+ click_decay_speed := 0.08f;
+ }
+
+ Animation_State :: struct {
+ hover_time := 0.0f;
+ click_time := 0.0f;
+
+ accessed_this_frame := false;
+ }
+
+ animation_states : Map(UI_Id, Animation_State);
+
+ get_animation :: (id: UI_Id) -> ^Animation_State {
+ retval := map.get_ptr(^animation_states, id);
+ if retval == null {
+ animation_states[id] = .{};
+ retval = ^animation_states[id];
+ }
+
+ retval.accessed_this_frame = true;
+ return retval;
+ }
+
+ has_active_animation :: () -> bool {
+ for^ animation_states.entries {
+ if it.value.hover_time != 0.0f || it.value.hover_time != 0.0f do return true;
+ if it.value.click_time != 0.0f || it.value.click_time != 0.0f do return true;
+ }
+
+ return false;
+ }
+
+ get_site_hash :: macro (site: CallSite, increment := 0) -> UI_Id {
+ hash :: package core.hash
+ file_hash := hash.to_u32(site.file);
+ line_hash := hash.to_u32(site.line);
+ column_hash := hash.to_u32(site.column);
+
+ return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment);
+ }
+
+ color_lerp :: macro (t: f32, c1, c2: Color) => Color.{
+ r = c1.r * (1 - t) + c2.r * t,
+ g = c1.g * (1 - t) + c2.g * t,
+ b = c1.b * (1 - t) + c2.b * t,
+ a = c1.a * (1 - t) + c2.a * t,
+ };
+}
\ No newline at end of file