flush :: (use ir: ^Immediate_Renderer) {
if vertex_count == 0 do return;
- if world_transform_dirty || true {
+ if world_transform_dirty {
world_transform_dirty = false;
world_transform := array.get_ptr(^world_transform_stack, -1);
world_matrix := transform_to_matrix(world_transform);
gl.useProgram(active_shader.program);
}
- @Cleanup // right now, these are raw screen coordinates, with x and y being the bottom left of the screen.
- // This is backwards from what use_ortho_projections does, so it's not great to work with.
push_scissor :: (use ir: ^Immediate_Renderer, x: f32, y: f32, w: f32, h: f32, intersect_previous := true) {
if vertex_count > 0 do ir->flush();
}
pop_scissor :: (use ir: ^Immediate_Renderer) {
+ if vertex_count > 0 do ir->flush();
+
array.pop(^scissor_stack);
if scissor_stack.count > 0 {
world_transform_dirty = true;
array.push(^world_transform_stack, world_transform_stack[world_transform_stack.count - 1]);
- transform_identity(array.get_ptr(^world_transform_stack, -1));
+ *array.get_ptr(^world_transform_stack, -1) = *array.get_ptr(^world_transform_stack, -2);
+
+ // transform_identity(array.get_ptr(^world_transform_stack, -1));
}
pop_matrix :: (use ir: ^Immediate_Renderer) {
return array.get_ptr(^world_transform_stack, -1);
}
+ apply_transform :: (use ir: ^Immediate_Renderer, transform: Transform) {
+ transform_apply(array.get_ptr(^world_transform_stack, -1), transform);
+ }
+
to_screen_coordinates :: (use ir: ^Immediate_Renderer, use v: Vector2) -> Vector2 {
transform := ir->get_transform();
push_matrix :: () do global_renderer->push_matrix();
pop_matrix :: () do global_renderer->pop_matrix();
identity :: () do global_renderer->identity();
+apply_transform :: (transform: Transform) do global_renderer->apply_transform(transform);
0, 0, 1, 0,
tx, ty, 0, 1
];
+}
+
+transform_translate :: (use t: ^Transform, v: Vector2) {
+ translation.x += v.x;
+ translation.y += v.y;
+}
+
+transform_scale :: (use t: ^Transform, v: Vector2) {
+ scale.x *= v.x;
+ scale.y *= v.y;
+}
+
+transform_apply :: (use t: ^Transform, other: Transform) {
+ translation.x += other.translation.x * scale.x;
+ translation.y += other.translation.y * scale.y;
+ scale.x *= other.scale.x;
+ scale.y *= other.scale.y;
}
\ No newline at end of file
hash := get_site_hash(site, increment);
animation_state := map.get(^animation_states, hash);
+ mx, my := get_mouse_position();
if is_active_item(hash) {
if mouse_state.left_button_just_up {
- if is_hot_item(hash) && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if is_hot_item(hash) && Rectangle.contains(r, mx, my) {
result = true;
animation_state.click_time = 1.0f;
}
}
}
- if Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if Rectangle.contains(r, mx, my) {
set_hot_item(hash);
}
hash := get_site_hash(site, increment);
animation_state := map.get(^animation_states, hash);
+ mx, my := get_mouse_position();
if is_active_item(hash) {
if mouse_state.left_button_just_up {
- if is_hot_item(hash) && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if is_hot_item(hash) && Rectangle.contains(r, mx, my) {
result = true;
*value = !*value;
animation_state.click_time = 1.0f;
}
}
- if Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if Rectangle.contains(r, mx, my) {
set_hot_item(hash);
}
package ui
+
use package core
-scrollable_area_start :: (use r: Rectangle) {
+#private_file
+SA_State :: struct {
+ transform: gfx.Transform = .{
+ translation = .{ 0, 0 },
+ scale = .{ 1, 1 },
+ };
+
+ dragging := false;
+}
+
+#private
+scrollable_area_states : map.Map(UI_Id, SA_State);
+
+scrollable_area_start :: (use r: Rectangle, site := #callsite) {
+ hash := get_site_hash(site, 0);
+ x, y := Rectangle.top_left(r);
+ width, height := Rectangle.dimensions(r);
+
+ state := map.get(^scrollable_area_states, hash);
+
+ mx, my := get_mouse_position();
+ if Rectangle.contains(r, mx, my) {
+ if hot_item == 0 do set_hot_item(hash);
+ }
+
+ if is_hot_item(hash) {
+ speed :: 30.0f; @ThemeConfiguration
+ scale_speed :: 0.02f; @ThemeConfiguration
+
+ if is_key_down(38) do state.transform.translation.y += speed;
+ if is_key_down(40) do state.transform.translation.y -= speed;
+ if is_key_down(39) do state.transform.translation.x -= speed;
+ if is_key_down(37) do state.transform.translation.x += speed;
+
+ if is_key_down(187) do zoom(^state, r, 1.02);
+ if is_key_down(189) do zoom(^state, r, 0.98);
+
+ if mouse_state.left_button_just_down && !state.dragging {
+ state.dragging = true;
+ }
+
+ if state.dragging {
+ if !mouse_state.left_button_down {
+ state.dragging = false;
+
+ } else {
+ dx, dy := get_mouse_delta();
+ state.transform.translation.x -= dx;
+ state.transform.translation.y -= dy;
+ }
+ }
+
+ if mouse_state.dwheel > 0 do zoom(^state, r, 1.04);
+ if mouse_state.dwheel < 0 do zoom(^state, r, 0.96);
+
+ } else {
+ state.dragging = false;
+ }
+
+ map.put(^scrollable_area_states, hash, state);
+
+ gfx.push_scissor(x, y, width, height);
+ gfx.push_matrix();
+ gfx.apply_transform(state.transform);
+
+ zoom :: (state: ^SA_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;
+ }
}
scrollable_area_end :: () {
-
+ gfx.pop_scissor();
+ gfx.pop_matrix();
}
\ No newline at end of file
hash := get_site_hash(site, increment);
animation_state := map.get(^animation_states, hash);
+ mx, my := get_mouse_position();
border_width := theme.border_width;
width, height := Rectangle.dimensions(r);
text_y := y0 + ~~ font.common.baseline * theme.font_size + (height - text_height) / 2;
if is_hot_item(hash) && !is_active_item(hash) {
- if mouse_state.left_button_down && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if mouse_state.left_button_down && Rectangle.contains(r, mx, my) {
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 mouse_state.left_button_just_down && !Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if mouse_state.left_button_just_down && !Rectangle.contains(r, mx, my) {
set_active_item(0);
textbox_editing_state.hash = 0;
textbox_editing_state.cursor_position = 0;
}
}
- if Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if Rectangle.contains(r, mx, my) {
set_hot_item(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 mouse_state.left_button_down && Rectangle.contains(r, mouse_state.x, mouse_state.y) {
+ if mouse_state.left_button_down && Rectangle.contains(r, mx, my) {
textbox_editing_state.cursor_animation = 1.0f;
- textbox_editing_state.cursor_position = get_cursor_position(text_buffer, text_x, text_y, theme.font_size, mouse_state.x, mouse_state.y);
+ textbox_editing_state.cursor_position = get_cursor_position(text_buffer, text_x, text_y, theme.font_size, mx, my);
}
if keyboard_state.keys_down_this_frame > 0 {
}
gfx.set_texture();
+ gfx.push_scissor(x0, y0, width, height);
gfx.rect(.{ x0, y0 }, .{ width, height }, theme.border_color);
surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
color=cursor_color);
}
+ gfx.pop_scissor();
+
move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
if animation_state.click_time > 0 || animation_state.hover_time > 0 {
--- /dev/null
+package ui
+
+
+Mouse_State :: struct {
+ left_button_down := false;
+ left_button_just_down := false;
+ left_button_just_up := false;
+
+ right_button_down := false;
+ right_button_just_down := false;
+ right_button_just_up := false;
+
+ dwheel : f32 = 0;
+
+ x_ : f32 = 0;
+ y_ : f32 = 0;
+ dx_ : f32 = 0;
+ dy_ : f32 = 0;
+}
+
+mouse_state := Mouse_State.{};
+
+Keyboard_State :: struct {
+ Max_Keys_Per_Frame :: 4;
+
+ Key_Event :: struct {
+ code: u32 = 0;
+ modifiers: Modifiers = ~~0;
+
+ Modifiers :: enum #flags {
+ CTRL; ALT; SHIFT; META;
+ }
+ }
+
+ keycodes_down_this_frame : [Max_Keys_Per_Frame] Key_Event;
+ keys_down_this_frame : u32;
+
+ keycodes_up_this_frame : [Max_Keys_Per_Frame] Key_Event;
+ keys_up_this_frame : u32;
+
+ Key_Count :: 256;
+
+ Key_State :: enum (u8) {
+ Up;
+ Down;
+ Just_Up;
+ Just_Down;
+ }
+
+ state : [Key_Count] Key_State;
+}
+
+@Note // This assumes that this gets zero intialized.
+keyboard_state: Keyboard_State;
+
+
+
+
+//
+// Telling the UI system about hardware updates
+//
+
+update_mouse_position :: (new_x: f32, new_y: f32) {
+ mouse_state.dx_ += mouse_state.x_ - new_x;
+ mouse_state.dy_ += mouse_state.y_ - new_y;
+ mouse_state.x_ = new_x;
+ mouse_state.y_ = new_y;
+}
+
+#private_file Mouse_Button_Kind :: enum { Left; Right; Middle; WheelUp; WheelDown; }
+
+button_pressed :: (kind: Mouse_Button_Kind) {
+ switch kind {
+ case .Left {
+ mouse_state.left_button_down = true;
+ mouse_state.left_button_just_down = true;
+ }
+
+ case .Right {
+ mouse_state.right_button_down = true;
+ mouse_state.right_button_just_down = true;
+ }
+
+ case .WheelUp do mouse_state.dwheel = 1.0;
+ case .WheelDown do mouse_state.dwheel = -1.0;
+ }
+}
+
+button_released :: (kind: Mouse_Button_Kind) {
+ switch kind {
+ case .Left {
+ mouse_state.left_button_down = false;
+ mouse_state.left_button_just_up = true;
+ }
+
+ case .Right {
+ mouse_state.right_button_down = false;
+ mouse_state.right_button_just_up = true;
+ }
+
+ case .WheelUp do mouse_state.dwheel = 0;
+ case .WheelDown do mouse_state.dwheel = 0;
+ }
+}
+
+#private_file
+__key_state_transition_table := Keyboard_State.Key_State.[
+ /* KeyUp */ /* KeyDown */
+ /* Up */ Keyboard_State.Key_State.Up, Keyboard_State.Key_State.Just_Down,
+ /* Down */ Keyboard_State.Key_State.Just_Up, Keyboard_State.Key_State.Down,
+ /* Just_Up */ Keyboard_State.Key_State.Up, Keyboard_State.Key_State.Just_Down,
+ /* Just_Down */ Keyboard_State.Key_State.Just_Up, Keyboard_State.Key_State.Down,
+]
+
+key_down :: (keycode: u32, modifiers: Keyboard_State.Key_Event.Modifiers) {
+ keyboard_state.keycodes_down_this_frame[keyboard_state.keys_down_this_frame] = .{
+ keycode,
+ modifiers
+ };
+
+ keyboard_state.keys_down_this_frame += 1;
+ keyboard_state.state[keycode] = __key_state_transition_table[cast(i32) keyboard_state.state[keycode] * 2 + 1];
+}
+
+key_up :: (keycode: u32, modifiers: Keyboard_State.Key_Event.Modifiers) {
+ keyboard_state.keycodes_up_this_frame[keyboard_state.keys_up_this_frame] = .{
+ keycode,
+ modifiers
+ };
+
+ keyboard_state.keys_up_this_frame += 1;
+ keyboard_state.state[keycode] = __key_state_transition_table[cast(i32) keyboard_state.state[keycode] * 2];
+}
+
+
+
+//
+// Querying the UI system for current input state
+//
+
+// Relative to the current transformations on the immediate renderer
+get_mouse_position :: () -> (x: f32, y: f32) {
+ transform := gfx.global_renderer->get_transform();
+ return (mouse_state.x_ - transform.translation.x) / transform.scale.x,
+ (mouse_state.y_ - transform.translation.y) / transform.scale.y;
+}
+
+get_mouse_delta :: () -> (dx: f32, dy: f32) {
+ transform := gfx.global_renderer->get_transform();
+ return mouse_state.dx_ / transform.scale.x,
+ mouse_state.dy_ / transform.scale.y;
+}
+
+is_key_down :: (keycode: u32) -> bool {
+ s := keyboard_state.state[keycode];
+ return s == .Down || s == .Just_Down;
+}
+
+is_key_up :: (keycode: u32) -> bool {
+ s := keyboard_state.state[keycode];
+ return s == .Up || s == .Just_Up;
+}
\ No newline at end of file
#load "./ui"
#load "./flow"
+#load "./input"
#load "./components/button"
#load "./components/checkbox"
#load "./components/slider"
#load "./components/radio"
#load "./components/textbox"
-
+#load "./components/scrollable_area"
// Package inclusions that are part of all files in the "ui" package.
#private gfx :: package immediate_mode // The immediate_mode module needs to be accessible
#private active_item : UI_Id = 0
#private hot_item_was_set := false
-Mouse_State :: struct {
- left_button_down := false;
- left_button_just_down := false;
- left_button_just_up := false;
-
- right_button_down := false;
- right_button_just_down := false;
- right_button_just_up := false;
-
- x: f32 = 0;
- y: f32 = 0;
-}
-
-mouse_state := Mouse_State.{};
-
-Keyboard_State :: struct {
- Max_Keys_Per_Frame :: 4;
-
- Key_State :: struct {
- code: u32 = 0;
- modifiers: Modifiers = ~~0;
-
- Modifiers :: enum #flags {
- CTRL; ALT; SHIFT; META;
- }
- }
-
- keycodes_down_this_frame : [Max_Keys_Per_Frame] Key_State;
- keys_down_this_frame : u32;
-
- keycodes_up_this_frame : [Max_Keys_Per_Frame] Key_State;
- keys_up_this_frame : u32;
-}
-
-@Note // This assumes that this gets zero intialized.
-keyboard_state: Keyboard_State;
-
init_ui :: () {
init_font();
map.init(^animation_states, default=.{}, hash_count=4);
+ map.init(^scrollable_area_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 :: () {
+end_frame :: () {
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;
+ mouse_state.dwheel = 0;
+ mouse_state.dx_ = 0;
+ mouse_state.dy_ = 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 = .{};
hot_item_was_set = false;
}
-//
-// Telling the UI system about hardware updates
-//
-
-update_mouse_position :: (new_x: f32, new_y: f32) {
- mouse_state.x = new_x;
- mouse_state.y = new_y;
-}
-
-#private_file Mouse_Button_Kind :: enum { Left; Right; Middle; }
-
-button_pressed :: (kind: Mouse_Button_Kind) {
- switch kind {
- case .Left {
- mouse_state.left_button_down = true;
- mouse_state.left_button_just_down = true;
- }
- case .Right {
- mouse_state.right_button_down = true;
- mouse_state.right_button_just_down = true;
- }
- }
-}
-
-button_released :: (kind: Mouse_Button_Kind) {
- switch kind {
- case .Left {
- mouse_state.left_button_down = false;
- mouse_state.left_button_just_up = true;
- }
-
- case .Right {
- mouse_state.right_button_down = false;
- mouse_state.right_button_just_up = true;
- }
- }
-}
-
-key_down :: (keycode: u32, modifiers: Keyboard_State.Key_State.Modifiers) {
- keyboard_state.keycodes_down_this_frame[keyboard_state.keys_down_this_frame] = .{
- keycode,
- modifiers
- };
-
- keyboard_state.keys_down_this_frame += 1;
-}
-
-key_up :: (keycode: u32, modifiers: Keyboard_State.Key_State.Modifiers) {
- keyboard_state.keycodes_up_this_frame[keyboard_state.keys_up_this_frame] = .{
- keycode,
- modifiers
- };
-
- keyboard_state.keys_up_this_frame += 1;
-}
-
set_active_item :: (id: UI_Id) -> bool {
active_item = id;
return true;