From: Brendan Hansen Date: Thu, 8 Jul 2021 15:43:46 +0000 (-0500) Subject: scrolling_area; better mouse support X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=2c0b14d1cdd2efb88b7a81e78321cd4f62372292;p=onyx.git scrolling_area; better mouse support --- diff --git a/modules/immediate_mode/immediate_renderer.onyx b/modules/immediate_mode/immediate_renderer.onyx index a8455f69..517f23c0 100644 --- a/modules/immediate_mode/immediate_renderer.onyx +++ b/modules/immediate_mode/immediate_renderer.onyx @@ -144,7 +144,7 @@ Immediate_Renderer :: struct { 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); @@ -295,8 +295,6 @@ Immediate_Renderer :: struct { 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(); @@ -319,6 +317,8 @@ Immediate_Renderer :: struct { } pop_scissor :: (use ir: ^Immediate_Renderer) { + if vertex_count > 0 do ir->flush(); + array.pop(^scissor_stack); if scissor_stack.count > 0 { @@ -382,7 +382,9 @@ Immediate_Renderer :: struct { 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) { @@ -400,6 +402,10 @@ Immediate_Renderer :: struct { 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(); @@ -503,3 +509,4 @@ set_window_size :: (width: i32, height: i32) { 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); diff --git a/modules/immediate_mode/transform.onyx b/modules/immediate_mode/transform.onyx index 56ed1a71..0489747f 100644 --- a/modules/immediate_mode/transform.onyx +++ b/modules/immediate_mode/transform.onyx @@ -34,4 +34,21 @@ transform_to_matrix :: (use t: ^Transform) -> [16] f32 { 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 diff --git a/modules/ui/components/button.onyx b/modules/ui/components/button.onyx index 0ba70b53..7e27da18 100644 --- a/modules/ui/components/button.onyx +++ b/modules/ui/components/button.onyx @@ -20,10 +20,11 @@ button :: (use r: Rectangle, text: str, theme := ^default_button_theme, site := 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; } @@ -37,7 +38,7 @@ button :: (use r: Rectangle, text: str, theme := ^default_button_theme, site := } } - if Rectangle.contains(r, mouse_state.x, mouse_state.y) { + if Rectangle.contains(r, mx, my) { set_hot_item(hash); } diff --git a/modules/ui/components/checkbox.onyx b/modules/ui/components/checkbox.onyx index d844a507..1d6066a2 100644 --- a/modules/ui/components/checkbox.onyx +++ b/modules/ui/components/checkbox.onyx @@ -24,10 +24,11 @@ checkbox :: (use r: Rectangle, value: ^bool, text: str, theme := ^default_checkb 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; @@ -42,7 +43,7 @@ checkbox :: (use r: Rectangle, value: ^bool, text: str, theme := ^default_checkb } } - if Rectangle.contains(r, mouse_state.x, mouse_state.y) { + if Rectangle.contains(r, mx, my) { set_hot_item(hash); } diff --git a/modules/ui/components/scrollable_area.onyx b/modules/ui/components/scrollable_area.onyx index 5cab4ab9..b13fef58 100644 --- a/modules/ui/components/scrollable_area.onyx +++ b/modules/ui/components/scrollable_area.onyx @@ -1,10 +1,88 @@ 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 diff --git a/modules/ui/components/textbox.onyx b/modules/ui/components/textbox.onyx index 52d26a7e..ed5d979a 100644 --- a/modules/ui/components/textbox.onyx +++ b/modules/ui/components/textbox.onyx @@ -46,6 +46,7 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa 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); @@ -58,7 +59,7 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa 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; @@ -68,14 +69,14 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa } 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); } @@ -84,9 +85,9 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa 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 { @@ -139,6 +140,7 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa } 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); @@ -161,6 +163,8 @@ textbox :: (use r: Rectangle, text_buffer: ^string.String_Buffer, theme := ^defa 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 { diff --git a/modules/ui/input.onyx b/modules/ui/input.onyx new file mode 100644 index 00000000..5406e4c1 --- /dev/null +++ b/modules/ui/input.onyx @@ -0,0 +1,162 @@ +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 diff --git a/modules/ui/module.onyx b/modules/ui/module.onyx index 2174ce95..341a5dc0 100644 --- a/modules/ui/module.onyx +++ b/modules/ui/module.onyx @@ -41,13 +41,14 @@ package ui #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 diff --git a/modules/ui/ui.onyx b/modules/ui/ui.onyx index dcb5027a..6310c56b 100644 --- a/modules/ui/ui.onyx +++ b/modules/ui/ui.onyx @@ -17,58 +17,26 @@ UI_Id :: #type u32 #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 = .{}; @@ -81,62 +49,6 @@ clear_buttons :: () { 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;