aboutsummaryrefslogtreecommitdiff
path: root/lib/oui/oui.odin
diff options
context:
space:
mode:
authorSan Jacobs2023-10-14 13:11:39 +0200
committerSan Jacobs2023-10-14 13:11:39 +0200
commitfd262c11c3c0f627927ecc7fd5115899033018bb (patch)
tree3efec0b37d1069cdedd9b4bc065a701bfed1efc7 /lib/oui/oui.odin
parentd6cb7ac37d86d0c4ee03982813b94275fd62016a (diff)
downloadsatscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.tar.gz
satscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.tar.bz2
satscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.zip
New OUI version
Diffstat (limited to 'lib/oui/oui.odin')
-rw-r--r--lib/oui/oui.odin1120
1 files changed, 472 insertions, 648 deletions
diff --git a/lib/oui/oui.odin b/lib/oui/oui.odin
index d14f369..2933b8c 100644
--- a/lib/oui/oui.odin
+++ b/lib/oui/oui.odin
@@ -3,16 +3,15 @@ package oui
import "core:mem"
import "core:fmt"
import "core:time"
+import "core:hash"
import "core:slice"
+import "../rect"
// LAYOUTING IS CUSTOMIZED
// 1. RectCut Layouting (directionality + cut distance)
// 2. Absolute Layouting (set the rect yourself)
// 3. Relative Layouting (set the rect relative to a parent)
-// 4. Custom Layouting (callback - roll your own thing)
-
-// TODO
-// Animation
+// 4. Custom Layouting (callback - roll your own thing) // TODO
MAX_DATASIZE :: 4096
MAX_DEPTH :: 64
@@ -20,10 +19,12 @@ MAX_INPUT_EVENTS :: 64
CLICK_THRESHOLD :: time.Millisecond * 250
MAX_CONSUME :: 256
+RectI :: rect.RectI
+I2 :: [2]int
+
Input_Event :: struct {
- key: int,
+ key: string,
char: rune,
- repeat: bool,
call: Call,
}
@@ -35,10 +36,16 @@ Mouse_Button :: enum {
Mouse_Buttons :: bit_set[Mouse_Button]
Context :: struct {
+ // userdata
+ user_ptr: rawptr,
+
+ // mouse state
buttons: Mouse_Buttons,
last_buttons: Mouse_Buttons,
button_capture: Mouse_Button,
+ button_ignore: Maybe(Mouse_Button),
+ // cursor
cursor_start: I2,
cursor_last_frame: I2,
cursor_delta_frame: I2,
@@ -47,20 +54,22 @@ Context :: struct {
cursor_handle_callback: proc(new: int), // callback on handle change
scroll: I2,
- active_item: Item,
- focus_item: Item,
- focus_redirect_item: Item, // redirect messages if no item is focused to this item
- last_hot_item: Item,
- last_click_item: Item,
- hot_item: Item,
- clicked_item: Item,
+ // item ids
+ active_item: u32,
+ focus_item: u32,
+ focus_redirect_item: u32, // redirect messages if no item is focused to this item
+ last_hot_item: u32,
+ last_click_item: u32,
+ hot_item: u32,
+ clicked_item: u32,
+ // stages
state: State,
stage: Stage,
- active_key: int,
- active_key_repeat: bool,
+
+ // key info
+ active_key: string,
active_char: rune,
- mods: u32, // keyboard mods
// consume building
consume: [MAX_CONSUME]u8,
@@ -68,29 +77,33 @@ Context :: struct {
consume_index: int,
// defaults
- escape_key: int,
+ // escape_key: int,
// click counting
clicks: int,
click_time: time.Time,
- count: int,
- last_count: int,
- event_count: int,
- data_size: int,
-
// items data
- items: []Item_Raw,
- last_items: []Item_Raw,
- item_map: []Item, // last -> current matches
- item_sort: []Item,
+ items: []Item,
+ items_index: int,
+ items_last_index: int,
+ last_items: []Item,
+ item_map: map[u32]Animation,
+ item_sort: []^Item, // temp array for storing sorted results before clone
// buffer data
// TODO could be an arena
buffer: []byte,
+ buffer_index: int,
// input event buffer
events: [MAX_INPUT_EVENTS]Input_Event,
+ event_count: int,
+
+ // id stack
+ ids: [32]u32,
+ ids_index: int,
+ last_id: u32,
}
State :: enum {
@@ -169,21 +182,19 @@ Call :: enum {
Find_Ignore,
// Key / Char
- Key_Down,
- Key_Up,
+ Key,
Char,
}
-I2 :: [2]int
-Item :: int
-
-Callback :: proc(Item, Call) -> int
+Callback :: proc(^Context, ^Item, Call) -> int
-Item_Raw :: struct {
+Item :: struct {
handle: rawptr, // item handle
-
- // callback class will always be called / set usually - user can override class calls
- callback: Callback,
+ callback: Callback, // callback class that can be used to do things based on events
+
+ // unique id
+ id: u32,
+ sort_children: bool,
state: Item_State,
ignore: bool,
@@ -194,12 +205,12 @@ Item_Raw :: struct {
z_index: int,
// tree info
- parent: Item,
- first_kid: Item,
- next_item: Item,
+ parent: ^Item,
+ first_item: ^Item,
+ next_item: ^Item,
// layout final
- rect: Rect,
+ bounds: RectI,
// layout info
layout_offset: [2]int,
@@ -213,68 +224,49 @@ Item_Raw :: struct {
layout_cut_gap: int, // how much to insert a gap after a cut
// persistent data per item - queryable after end_layout
+ anim: Animation,
+}
+
+Animation :: struct {
hot: f32,
active: f32,
+ trigger: f32,
}
////////////////////////////////////////////////////////////////////////////////
// CONTEXT MANAGEMENT
////////////////////////////////////////////////////////////////////////////////
-ui: ^Context
-
@private
-context_clear_data :: proc() #no_bounds_check {
- ui.last_count = ui.count
- ui.count = 0
- ui.data_size = 0
- ui.hot_item = -1
+context_clear_data :: proc(ctx: ^Context) #no_bounds_check {
+ ctx.items_last_index = ctx.items_index
+ ctx.items_index = 0
+ ctx.buffer_index = 0
+ ctx.hot_item = 0
+ ctx.ids_index = 0
// swap buffers
- ui.items, ui.last_items = ui.last_items, ui.items
-
- // set map
- for i in 0..<ui.last_count {
- ui.item_map[i] = -1
- }
+ ctx.items, ctx.last_items = ctx.last_items, ctx.items
}
-context_create :: proc(item_capacity, buffer_capacity: int) -> ^Context {
- ctx := new(Context)
+context_init :: proc(ctx: ^Context, item_capacity, buffer_capacity: int) {
ctx.buffer = make([]byte, buffer_capacity)
- ctx.items = make([]Item_Raw, item_capacity)
- ctx.last_items = make([]Item_Raw, item_capacity)
- ctx.item_map = make([]Item, item_capacity)
- ctx.item_sort = make([]Item, item_capacity)
+ ctx.items = make([]Item, item_capacity)
+ ctx.last_items = make([]Item, item_capacity)
+ ctx.item_sort = make([]^Item, item_capacity)
+ ctx.item_map = make(map[u32]Animation, item_capacity)
ctx.stage = .Process
- old_ctx := ui
- context_make_current(ctx)
- context_clear_data()
- context_clear_state()
- context_make_current(old_ctx)
- return ctx
-}
-
-context_make_current :: #force_inline proc(ctx: ^Context) {
- ui = ctx
+ context_clear_data(ctx)
+ context_clear_state(ctx)
}
context_destroy :: proc(ctx: ^Context) {
- if ui == ctx {
- context_make_current(nil)
- }
-
delete(ctx.buffer)
delete(ctx.items)
delete(ctx.last_items)
delete(ctx.item_map)
- free(ctx)
-}
-
-context_get :: #force_inline proc() -> ^Context {
- return ui
}
////////////////////////////////////////////////////////////////////////////////
@@ -282,436 +274,424 @@ context_get :: #force_inline proc() -> ^Context {
////////////////////////////////////////////////////////////////////////////////
@private
-add_input_event :: proc(event: Input_Event) #no_bounds_check {
- if ui.event_count == MAX_INPUT_EVENTS {
+add_input_event :: proc(ctx: ^Context, event: Input_Event) #no_bounds_check {
+ if ctx.event_count == MAX_INPUT_EVENTS {
return
}
- ui.events[ui.event_count] = event
- ui.event_count += 1
+ ctx.events[ctx.event_count] = event
+ ctx.event_count += 1
}
@private
-clear_input_events :: proc() {
- ui.event_count = 0
- ui.scroll = {}
+clear_input_events :: proc(ctx: ^Context) {
+ ctx.event_count = 0
+ ctx.scroll = {}
}
-set_cursor :: #force_inline proc(x, y: int) {
- ui.cursor = { x, y }
+set_cursor :: #force_inline proc(ctx: ^Context, x, y: int) {
+ ctx.cursor = { x, y }
}
-set_cursor_handle_callback :: proc(callback: proc(new: int)) {
- ui.cursor_handle_callback = callback
+set_cursor_handle_callback :: proc(ctx: ^Context, callback: proc(new: int)) {
+ ctx.cursor_handle_callback = callback
}
-get_cursor :: #force_inline proc() -> I2 {
- return ui.cursor
+get_cursor :: #force_inline proc(ctx: ^Context) -> I2 {
+ return ctx.cursor
}
-get_cursor_start :: #force_inline proc() -> I2 {
- return ui.cursor_start
+get_cursor_start :: #force_inline proc(ctx: ^Context) -> I2 {
+ return ctx.cursor_start
}
-get_cursor_delta :: #force_inline proc() -> I2 {
- return ui.cursor - ui.cursor_start
+get_cursor_delta :: #force_inline proc(ctx: ^Context) -> I2 {
+ return ctx.cursor - ctx.cursor_start
}
-get_cursor_delta_frame :: #force_inline proc() -> I2 {
- return ui.cursor_delta_frame
+get_cursor_delta_frame :: #force_inline proc(ctx: ^Context) -> I2 {
+ return ctx.cursor_delta_frame
}
-set_button :: proc(button: Mouse_Button, enabled: bool) {
+set_button :: proc(ctx: ^Context, button: Mouse_Button, enabled: bool) {
if enabled {
- incl(&ui.buttons, button)
+ incl(&ctx.buttons, button)
} else {
- excl(&ui.buttons, button)
- }
-}
+ if ctx.button_ignore != nil && (button in ctx.buttons) {
+ ctx.button_ignore = nil
+ }
-set_mods :: proc(mods: u32, enabled: bool) {
- if enabled {
- ui.mods |= mods
- } else {
- ui.mods = mods
+ excl(&ctx.buttons, button)
}
}
-get_last_button :: proc(button: Mouse_Button) -> bool {
- return button in ui.last_buttons
-}
-
-get_button :: proc(button: Mouse_Button) -> bool {
- return button in ui.buttons
-}
-
-button_pressed :: proc(button: Mouse_Button) -> bool {
- return !get_last_button(button) && get_button(button)
+button_pressed :: proc(ctx: ^Context, button: Mouse_Button) -> bool {
+ return button not_in ctx.last_buttons && button in ctx.buttons
}
-button_released :: proc(button: Mouse_Button) -> bool {
- return get_last_button(button) && !get_button(button)
+button_released :: proc(ctx: ^Context, button: Mouse_Button) -> bool {
+ return button in ctx.last_buttons && button not_in ctx.buttons
}
-get_clicks :: #force_inline proc() -> int {
- return ui.clicks
+get_clicks :: #force_inline proc(ctx: ^Context) -> int {
+ return ctx.clicks
}
-set_key :: proc(key: int, down: bool, repeat: bool) {
- add_input_event({ key, 0, repeat, down ? .Key_Down : .Key_Up })
+set_key :: proc(ctx: ^Context, key: string) {
+ add_input_event(ctx, { key, 0, .Key })
}
-set_char :: proc(value: rune) {
- add_input_event({ 0, value, false, .Char })
-}
-
-set_scroll :: proc(x, y: int) {
- ui.scroll += { x, y }
-}
-
-get_scroll :: #force_inline proc() -> I2 {
- return ui.scroll
+set_char :: proc(ctx: ^Context, value: rune) {
+ add_input_event(ctx, { "", value, .Char })
}
////////////////////////////////////////////////////////////////////////////////
// STAGES
////////////////////////////////////////////////////////////////////////////////
-begin_layout :: proc() {
- assert(ui.stage == .Process)
- context_clear_data()
- ui.stage = .Layout
+begin_layout :: proc(ctx: ^Context) {
+ assert(ctx.stage == .Process)
+ context_clear_data(ctx)
+ ctx.stage = .Layout
}
-end_layout :: proc() #no_bounds_check {
- assert(ui.stage == .Layout)
+end_layout :: proc(ctx: ^Context) #no_bounds_check {
+ assert(ctx.stage == .Layout)
+
+ if ctx.items_index > 0 {
+ root := item_root(ctx)
+ compute_size(root)
+ arrange(root, nil, 0)
- if ui.count > 0 {
- compute_size(0)
- arrange(0, nil, 0)
+ if ctx.items_last_index > 0 {
+ // map old item content
+ clear(&ctx.item_map)
+ for i in 0..<ctx.items_last_index {
+ item := ctx.last_items[i]
- if ui.last_count > 0 {
- map_items(0, 0)
+ if item.id != 0 {
+ ctx.item_map[item.id] = item.anim
+ }
+ }
- // remap hot/activeness of matched items
- for i in 0..<ui.last_count {
- old := ui.item_map[i]
+ // map new content
+ for i in 0..<ctx.items_index {
+ item := &ctx.items[i]
- if old != -1 {
- pold := &ui.last_items[old]
- pcurr := &ui.items[i]
- pcurr.hot = pold.hot
- pcurr.active = pold.active
+ if item.id != 0 {
+ if old, ok := ctx.item_map[item.id]; ok {
+ item.anim = old
+ }
}
}
}
}
- validate_state_items()
- if ui.count > 0 {
- update_hot_item()
+ // validate_state_items(ctx)
+ if ctx.items_index > 0 {
+ update_hot_item(ctx)
}
- ui.stage = .Post_Layout
+ ctx.stage = .Post_Layout
// update hot/active for animation
speed := f32(0.05)
- for i in 0..<ui.count {
- p := &ui.items[i]
- p.hot = clamp(p.hot + (i == ui.hot_item ? speed : -speed), 0, 1)
- p.active = clamp(p.active + (i == ui.active_item ? speed : -speed), 0, 1)
+ for i in 0..<ctx.items_index {
+ p := &ctx.items[i]
+ p.anim.hot = clamp(p.anim.hot + (p.id == ctx.hot_item ? speed : -speed), 0, 1)
+ p.anim.active = clamp(p.anim.active + (p.id == ctx.active_item ? speed : -speed), 0, 1)
+ p.anim.trigger = max(p.anim.trigger - speed, 0)
}
}
-update_hot_item :: proc() {
- if ui.count == 0 {
+update_hot_item :: proc(ctx: ^Context) {
+ if ctx.items_index == 0 {
return
}
- ui.hot_item = find_item(0, &ui.items[0], ui.cursor.x, ui.cursor.y)
+ item := find_item(ctx, item_root(ctx), ctx.cursor.x, ctx.cursor.y)
+ ctx.hot_item = item.id
+}
+
+// TODO this is shit
+temp_find :: proc(ctx: ^Context, id: u32) -> (res: ^Item) {
+ for i in 0..<ctx.items_index {
+ item := &ctx.items[i]
+ if item.id == id {
+ res = item
+ break
+ }
+ }
+
+ return
}
process_button :: proc(
+ ctx: ^Context,
button: Mouse_Button,
- hot_item: ^int,
- active_item: ^int,
- focus_item: ^int,
+ hot_item: ^u32,
+ active_item: ^u32,
+ focus_item: ^u32,
) -> bool {
- if get_button(button) {
- hot_item^ = -1
- active_item^ = ui.hot_item
+ if button in ctx.buttons {
+ hot_item^ = 0
+ active_item^ = ctx.hot_item
if active_item^ != focus_item^ {
- focus_item^ = -1
- ui.focus_item = -1
+ focus_item^ = 0
+ ctx.focus_item = 0
}
- if active_item^ >= 0 {
- diff := time.since(ui.click_time)
+ capture := -1
+ if active_item^ != 0 {
+ diff := time.since(ctx.click_time)
if diff > CLICK_THRESHOLD {
- ui.clicks = 0
+ ctx.clicks = 0
}
- ui.clicks += 1
- ui.last_click_item = active_item^
- ui.click_time = time.now()
+ ctx.clicks += 1
+ ctx.last_click_item = active_item^
+ ctx.click_time = time.now()
+ active := temp_find(ctx, active_item^)
switch button {
- case .Left: item_callback(active_item^, .Left_Down)
- case .Middle: item_callback(active_item^, .Middle_Down)
- case .Right: item_callback(active_item^, .Right_Down)
+ case .Left: capture = item_callback(ctx, active, .Left_Down)
+ case .Middle: capture = item_callback(ctx, active, .Middle_Down)
+ case .Right: capture = item_callback(ctx, active, .Right_Down)
}
}
// only capture if wanted
- ui.button_capture = button
- ui.state = .Capture
- return true
+ if capture != -1 || ctx.button_ignore != nil {
+ ctx.button_ignore = button
+ active_item^ = 0
+ focus_item^ = 0
+ return false
+ } else {
+ ctx.button_capture = button
+ ctx.state = .Capture
+ return true
+ }
}
return false
}
-process :: proc() {
- assert(ui.stage != .Layout)
- if ui.stage == .Process {
- update_hot_item()
+process :: proc(ctx: ^Context) {
+ assert(ctx.stage != .Layout)
+ if ctx.stage == .Process {
+ update_hot_item(ctx)
}
- ui.stage = .Process
+ ctx.stage = .Process
- if ui.count == 0 {
- clear_input_events()
+ if ctx.items_index == 0 {
+ clear_input_events(ctx)
return
}
- hot_item := ui.last_hot_item
- active_item := ui.active_item
- focus_item := ui.focus_item
- cursor_handle := ui.cursor_handle
+ hot_item := ctx.last_hot_item
+ active_item := ctx.active_item
+ focus_item := ctx.focus_item
+ cursor_handle := ctx.cursor_handle
// send all keyboard events
- if focus_item >= 0 {
- for i in 0..<ui.event_count {
- event := ui.events[i]
- ui.active_key = event.key
- ui.active_key_repeat = event.repeat
- ui.active_char = event.char
+ if focus_item != 0 {
+ for i in 0..<ctx.event_count {
+ event := ctx.events[i]
+ ctx.active_key = event.key
+ ctx.active_char = event.char
// consume char calls when active
- if event.call == .Char && ui.consume_focused {
- if ui.consume_index < MAX_CONSUME {
+ if event.call == .Char && ctx.consume_focused {
+ if ctx.consume_index < MAX_CONSUME {
// TODO proper utf8 insertion
- ui.consume[ui.consume_index] = u8(event.char)
- ui.consume_index += 1
+ ctx.consume[ctx.consume_index] = u8(event.char)
+ ctx.consume_index += 1
}
} else {
- item_callback(focus_item, event.call)
+ focus := temp_find(ctx, focus_item)
+ item_callback(ctx, focus, event.call)
}
- // check for escape
- if event.key == ui.escape_key && ui.mods == 0 {
- ui.focus_item = -1
- }
+ // TODO this
+ // // check for escape
+ // if event.key == ctx.escape_key {
+ // ctx.focus_item = nil
+ // }
}
} else {
- ui.focus_item = -1
+ ctx.focus_item = 0
}
// use redirect instead
- if focus_item == -1 {
- item := ui.focus_redirect_item
+ if focus_item == 0 {
+ item := temp_find(ctx, ctx.focus_redirect_item)
- for i in 0..<ui.event_count {
- event := ui.events[i]
- ui.active_key = event.key
- ui.active_key_repeat = event.repeat
- ui.active_char = event.char
- item_callback(item, event.call)
+ for i in 0..<ctx.event_count {
+ event := ctx.events[i]
+ ctx.active_key = event.key
+ ctx.active_char = event.char
+ item_callback(ctx, item, event.call)
}
}
// apply scroll callback
- if ui.scroll != {} {
- item_callback(hot_item, .Scroll)
+ if ctx.scroll != {} {
+ item := temp_find(ctx, ctx.hot_item)
+ item_callback(ctx, item, .Scroll)
}
- clear_input_events()
+ clear_input_events(ctx)
- hot := ui.hot_item
- ui.clicked_item = -1
+ hot := ctx.hot_item
+ ctx.clicked_item = 0
- switch ui.state {
+ switch ctx.state {
case .Idle:
- ui.cursor_start = ui.cursor
+ ctx.cursor_start = ctx.cursor
- left := process_button(.Left, &hot_item, &active_item, &focus_item)
- middle := process_button(.Middle, &hot_item, &active_item, &focus_item)
- right := process_button(.Right, &hot_item, &active_item, &focus_item)
+ left := process_button(ctx, .Left, &hot_item, &active_item, &focus_item)
+ middle := process_button(ctx, .Middle, &hot_item, &active_item, &focus_item)
+ right := process_button(ctx, .Right, &hot_item, &active_item, &focus_item)
if !left && !right && !middle {
hot_item = hot
}
case .Capture:
- if !get_button(ui.button_capture) {
- if active_item >= 0 {
- switch ui.button_capture {
- case .Left: item_callback(active_item, .Left_Up)
- case .Middle: item_callback(active_item, .Middle_Up)
- case .Right: item_callback(active_item, .Right_Up)
+ if ctx.button_capture not_in ctx.buttons {
+ if active_item != 0 {
+ active := temp_find(ctx, active_item)
+ switch ctx.button_capture {
+ case .Left: item_callback(ctx, active, .Left_Up)
+ case .Middle: item_callback(ctx, active, .Middle_Up)
+ case .Right: item_callback(ctx, active, .Right_Up)
}
if active_item == hot {
- switch ui.button_capture {
- case .Left: item_callback(active_item, .Left_Hot_Up)
- case .Middle: item_callback(active_item, .Middle_Hot_Up)
- case .Right: item_callback(active_item, .Right_Hot_Up)
+ switch ctx.button_capture {
+ case .Left: item_callback(ctx, active, .Left_Hot_Up)
+ case .Middle: item_callback(ctx, active, .Middle_Hot_Up)
+ case .Right: item_callback(ctx, active, .Right_Hot_Up)
}
- ui.clicked_item = active_item
+ ctx.clicked_item = active_item
}
}
- active_item = -1
- ui.state = .Idle
+ active_item = 0
+ ctx.state = .Idle
} else {
- if active_item >= 0 {
- switch ui.button_capture {
- case .Left: item_callback(active_item, .Left_Capture)
- case .Middle: item_callback(active_item, .Middle_Capture)
- case .Right: item_callback(active_item, .Right_Capture)
+ active := temp_find(ctx, active_item)
+ if active_item != 0 {
+ switch ctx.button_capture {
+ case .Left: item_callback(ctx, active, .Left_Capture)
+ case .Middle: item_callback(ctx, active, .Middle_Capture)
+ case .Right: item_callback(ctx, active, .Right_Capture)
}
}
- hot_item = hot == active_item ? hot : -1
+ hot_item = hot == active_item ? hot : 0
}
}
// look for possible cursor handle
- if hot_item != -1 {
- wanted_handle := item_callback(hot_item, .Cursor_Handle)
+ if hot_item != 0 {
+ hot := temp_find(ctx, hot_item)
+ wanted_handle := item_callback(ctx, hot, .Cursor_Handle)
if wanted_handle != -1 {
- ui.cursor_handle = wanted_handle
+ ctx.cursor_handle = wanted_handle
} else {
// change back to zero - being the default arrow type
- if ui.cursor_handle != 0 {
- ui.cursor_handle = 0
+ if ctx.cursor_handle != 0 {
+ ctx.cursor_handle = 0
}
}
}
// change of cursor handle
- if cursor_handle != ui.cursor_handle {
- if ui.cursor_handle_callback != nil {
- ui.cursor_handle_callback(ui.cursor_handle)
+ if cursor_handle != ctx.cursor_handle {
+ if ctx.cursor_handle_callback != nil {
+ ctx.cursor_handle_callback(ctx.cursor_handle)
}
}
- ui.cursor_delta_frame = ui.cursor_last_frame - ui.cursor
- ui.cursor_last_frame = ui.cursor
- ui.last_hot_item = hot_item
- ui.active_item = active_item
- ui.last_buttons = ui.buttons
+ ctx.cursor_delta_frame = ctx.cursor_last_frame - ctx.cursor
+ ctx.cursor_last_frame = ctx.cursor
+ ctx.last_hot_item = hot_item
+ ctx.active_item = active_item
+ ctx.last_buttons = ctx.buttons
}
-context_clear_state :: proc() {
- ui.last_hot_item = -1
- ui.active_item = -1
- ui.focus_item = -1
- ui.last_click_item = -1
+context_clear_state :: proc(ctx: ^Context) {
+ ctx.last_hot_item = 0
+ ctx.active_item = 0
+ ctx.focus_item = 0
+ ctx.last_click_item = 0
}
////////////////////////////////////////////////////////////////////////////////
// UI DECLARATION
////////////////////////////////////////////////////////////////////////////////
-item_make :: proc() -> Item {
- assert(ui.stage == .Layout)
- assert(ui.count < len(ui.items))
-
- idx := ui.count
- ui.count += 1
+item_make :: proc(ctx: ^Context) -> ^Item {
+ assert(ctx.stage == .Layout)
+ assert(ctx.items_index < len(ctx.items))
- item := &ui.items[idx]
+ item := &ctx.items[ctx.items_index]
+ ctx.items_index += 1
mem.zero_item(item)
- item.first_kid = -1
- item.next_item = -1
+ item.first_item = nil
+ item.next_item = nil
- return idx
+ return item
}
-item_callback :: proc(item: Item, call: Call) -> int #no_bounds_check {
- pitem := &ui.items[item]
-
- if pitem.callback == nil {
+item_callback :: proc(ctx: ^Context, item: ^Item, call: Call) -> int #no_bounds_check {
+ if item == nil || item.callback == nil {
return -1
}
- return pitem.callback(item, call)
-}
-
-// call the callback for the parent of the item
-item_callback_parent :: proc(item: Item, call: Call) -> int #no_bounds_check {
- pitem := &ui.items[item]
- return item_callback(pitem.parent, call)
+ return item.callback(ctx, item, call)
}
-set_frozen :: proc(item: Item, enable: bool) {
- pitem := &ui.items[item]
+set_frozen :: proc(item: ^Item, enable: bool) {
if enable {
- pitem.state = .Frozen
+ item.state = .Frozen
} else {
- pitem.state = .Cold
+ item.state = .Cold
}
}
-set_handle :: proc(item: Item, handle: rawptr) {
- pitem := &ui.items[item]
- pitem.handle = handle
-}
-
-alloc_handle :: proc(item: Item, size: int, loc := #caller_location) -> rawptr {
- pitem := &ui.items[item]
- assert(pitem.handle == nil)
- assert(ui.data_size + size <= len(ui.buffer))
- pitem.handle = &ui.buffer[ui.data_size]
- ui.data_size += size
- return pitem.handle
-}
-
-alloc_typed :: proc(item: Item, $T: typeid) -> ^T {
- return cast(^T) alloc_handle(item, size_of(T))
+alloc_handle :: proc(ctx: ^Context, item: ^Item, size: int, loc := #caller_location) -> rawptr {
+ assert(item.handle == nil)
+ assert(ctx.buffer_index + size <= len(ctx.buffer))
+ item.handle = &ctx.buffer[ctx.buffer_index]
+ ctx.buffer_index += size
+ return item.handle
}
-set_callback :: #force_inline proc(item: Item, callback: Callback) {
- pitem := &ui.items[item]
- pitem.callback = callback
+alloc_typed :: proc(ctx: ^Context, item: ^Item, $T: typeid) -> ^T {
+ return cast(^T) alloc_handle(ctx, item, size_of(T))
}
-item_append :: proc(item: Item, sibling: Item) -> Item {
- assert(sibling > 0)
- pitem := &ui.items[item]
- psibling := &ui.items[sibling]
- psibling.parent = pitem.parent
+item_append :: proc(item, sibling: ^Item) -> ^Item {
+ sibling.parent = item.parent
// TODO cut append
- psibling.next_item = pitem.next_item
- pitem.next_item = sibling
+ sibling.next_item = item.next_item
+ item.next_item = sibling
return sibling
}
-item_insert :: proc(item: Item, child: Item) -> Item {
- assert(child > 0)
- pparent := &ui.items[item]
- pchild := &ui.items[child]
-
+item_insert :: proc(parent, child: ^Item) -> ^Item {
// set cut direction
- pchild.parent = item
- pchild.layout_cut_self = pparent.layout_cut_children
+ child.parent = parent
+ child.layout_cut_self = parent.layout_cut_children
- if pparent.first_kid < 0 {
- pparent.first_kid = child
+ if parent.first_item == nil {
+ parent.first_item = child
} else {
- item_append(last_child(item), child)
+ item_append(last_child(parent), child)
}
return child
@@ -719,157 +699,93 @@ item_insert :: proc(item: Item, child: Item) -> Item {
insert_front :: item_insert
-item_insert_back :: proc(item: Item, child: Item) -> Item {
- assert(child > 0)
- pparent := &ui.items[item]
- pchild := &ui.items[child]
- pchild.parent = item
- pchild.layout_cut_self = pparent.layout_cut_children
+item_insert_back :: proc(parent, child: ^Item) -> ^Item {
+ child.parent = parent
+ child.layout_cut_self = parent.layout_cut_children
- pchild.next_item = pparent.first_kid
- pparent.first_kid = child
+ child.next_item = parent.first_item
+ parent.first_item = child
return child
}
-set_size :: proc(item: Item, w, h: int) {
- pitem := &ui.items[item]
- pitem.layout_size = { w, h }
-}
-
-set_height :: proc(item: Item, h: int) {
- pitem := &ui.items[item]
- pitem.layout_size.y = h
-}
-
-set_width :: proc(item: Item, w: int) {
- pitem := &ui.items[item]
- pitem.layout_size.x = w
-}
-
-set_ratio :: proc(item: Item, width, height: f32) {
- pitem := &ui.items[item]
+set_ratio :: proc(item: ^Item, width, height: f32) {
w := clamp(width, 0, 1)
h := clamp(height, 0, 1)
- pitem.layout_ratio = { w, h }
+ item.layout_ratio = { w, h }
if w != 0 && h != 0 {
- pitem.ratio = .XY
+ item.ratio = .XY
} else {
if w != 0 {
- pitem.ratio = .X
+ item.ratio = .X
} else if h != 0 {
- pitem.ratio = .Y
+ item.ratio = .Y
}
}
}
-set_gap :: proc(item: Item, gap: int) {
- pitem := &ui.items[item]
- pitem.layout_cut_gap = gap
-}
-
-set_margin :: proc(item: Item, margin: int) {
- pitem := &ui.items[item]
- pitem.layout_margin = margin
-}
-
-set_offset :: proc(item: Item, x, y: int) {
- pitem := &ui.items[item]
- pitem.layout_offset = { x, y }
+focus :: proc(ctx: ^Context, item: ^Item, consume := false) {
+ assert(item != nil && uintptr(item) < uintptr(&ctx.items[ctx.items_index]))
+ assert(ctx.stage != .Layout)
+ ctx.focus_item = item.id
+ ctx.consume_focused = consume
+ ctx.consume_index = 0
}
-// set a custom layouting method - default is by cut
-set_layout :: proc(item: Item, layout: Layout) {
- pitem := &ui.items[item]
- pitem.layout = layout
+consume_result :: proc(ctx: ^Context) -> string {
+ return transmute(string) mem.Raw_String { &ctx.consume[0], ctx.consume_index }
}
-// set the cut direction on the item - only applies to children
-set_cut :: proc(item: Item, cut: Cut) {
- pitem := &ui.items[item]
- pitem.layout_cut_children = cut
-}
-
-set_z :: proc(item: Item, z_index: int) {
- pitem := &ui.items[item]
- pitem.z_index = z_index
-}
-
-set_ignore :: proc(item: Item) {
- pitem := &ui.items[item]
- pitem.ignore = true
-}
-
-focus :: proc(item: Item, consume := false) {
- assert(item >= -1 && item < ui.count)
- assert(ui.stage != .Layout)
- ui.focus_item = item
- ui.consume_focused = consume
- ui.consume_index = 0
-}
-
-consume_result :: proc() -> string {
- return transmute(string) mem.Raw_String { &ui.consume[0], ui.consume_index }
+consume_decrease :: proc(ctx: ^Context) {
+ if ctx.consume_index > 0 {
+ ctx.consume_index -= 1
+ }
}
-focus_redirect :: proc(item: Item) {
- ui.focus_redirect_item = item
+focus_redirect :: proc(ctx: ^Context, item: ^Item) {
+ ctx.focus_redirect_item = item.id
}
////////////////////////////////////////////////////////////////////////////////
// ITERATION
////////////////////////////////////////////////////////////////////////////////
-last_child :: proc(item: Item) -> Item {
- item := first_child(item)
-
- if item < 0 {
- return -1
- }
+last_child :: proc(item: ^Item) -> ^Item {
+ item := item.first_item
- for {
- next_item := next_sibling(item)
- if next_item < 0 {
+ for item != nil {
+ next_item := item.next_item
+ if next_item == nil {
return item
}
item = next_item
}
- return -1
-}
-
-first_child :: proc(item: Item) -> Item {
- return ui.items[item].first_kid
-}
-
-next_sibling :: proc(item: Item) -> Item {
- return ui.items[item].next_item
-}
-
-get_parent :: proc(item: Item) -> Item {
- return ui.items[item].parent
+ return nil
}
// NOTE: uses temp allocator, since item_sort gets reused and the output needs to be stable!
// return z sorted list of children
-children_sorted :: proc(item: Item) -> []Item {
+children_list :: proc(ctx: ^Context, item: ^Item) -> []^Item {
count: int
// loop through children and push items
- kid := first_child(item)
- for kid > 0 {
- ui.item_sort[count] = kid
- kid = next_sibling(kid)
+ kid := item.first_item
+ for kid != nil {
+ ctx.item_sort[count] = kid
+ kid = kid.next_item
count += 1
}
- // SHITTY since we need to refetch the items, could maybe perform sorting better
- list := ui.item_sort[:count]
- slice.sort_by(list, proc(a, b: Item) -> bool {
- aa := ui.items[a]
- bb := ui.items[b]
- return aa.z_index < bb.z_index
- })
+ list := ctx.item_sort[:count]
+
+ // optionally sort the children by z_index
+ if item.sort_children {
+ // sort and return a cloned list
+ slice.sort_by(list, proc(a, b: ^Item) -> bool {
+ return a.z_index < b.z_index
+ })
+ }
return slice.clone(list, context.temp_allocator)
}
@@ -878,167 +794,43 @@ children_sorted :: proc(item: Item) -> []Item {
// QUERYING
////////////////////////////////////////////////////////////////////////////////
-get_item_count :: #force_inline proc() -> int {
- return ui.count
-}
-
-get_alloc_size :: #force_inline proc() -> int {
- return ui.data_size
-}
-
-get_handle :: #force_inline proc(item: Item) -> rawptr {
- pitem := ui.items[item]
- return pitem.handle
-}
-
-get_hot_item :: #force_inline proc() -> Item {
- return ui.hot_item
-}
-
-get_focused_item :: #force_inline proc() -> Item {
- return ui.focus_item
-}
-
find_item :: proc(
- item: Item,
- pitem: ^Item_Raw,
+ ctx: ^Context,
+ item: ^Item,
x, y: int,
loc := #caller_location,
-) -> Item #no_bounds_check {
+) -> ^Item #no_bounds_check {
// TODO frozen
// if pitem.state == .Frozen {
// return -1
- // }
-
- kid := pitem.first_kid
- for kid >= 0 {
- pkid := &ui.items[kid]
+ // }
+ list := children_list(ctx, item)
+ for kid in list {
// fetch ignore status
- ignore := pkid.ignore
- if item_callback(kid, .Find_Ignore) >= 0 {
+ ignore := kid.ignore
+ if item_callback(ctx, kid, .Find_Ignore) >= 0 {
ignore = true
}
- if !ignore && rect_contains(pkid.rect, x, y) {
- return find_item(kid, pkid, x, y)
- }
-
- kid = pkid.next_item
+ if !ignore && rect.contains(kid.bounds, x, y) {
+ return find_item(ctx, kid, x, y)
+ }
}
return item
}
-get_key :: proc() -> int {
- return ui.active_key
+get_key :: proc(ctx: ^Context) -> string {
+ return ctx.active_key
}
-get_key_repeat :: proc() -> bool {
- return ui.active_key_repeat
+get_char :: proc(ctx: ^Context) -> rune {
+ return ctx.active_char
}
-get_char :: proc() -> rune {
- return ui.active_char
-}
-
-get_mods :: proc() -> u32 {
- return ui.mods
-}
-
-get_rect :: proc(item: Item) -> Rect #no_bounds_check {
- return ui.items[item].rect
-}
-
-contains :: proc(item: Item, x, y: int) -> bool #no_bounds_check {
- return rect_contains(ui.items[item].rect, x, y)
-}
-
-get_width :: #force_inline proc(item: Item) -> int #no_bounds_check {
- return ui.items[item].layout_size.x
-}
-
-get_height :: #force_inline proc(item: Item) -> int #no_bounds_check {
- return ui.items[item].layout_size.y
-}
-
-recover_item :: proc(old_item: Item) -> Item #no_bounds_check {
- assert(old_item >= -1 && old_item < ui.last_count)
- if old_item == -1 {
- return -1
- }
- return ui.item_map[old_item]
-}
-
-remap_item :: proc(old_item, new_item: Item) #no_bounds_check {
- assert(old_item >= 0 && old_item < ui.last_count)
- assert(new_item >= -1 && new_item < ui.count)
- ui.item_map[old_item] = new_item
-}
-
-get_last_item_count :: proc() -> int {
- return ui.last_count
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// PRIVATE
-////////////////////////////////////////////////////////////////////////////////
-
-@private
-compare_items :: proc(p1, p2: ^Item_Raw) -> bool {
- // return (p1.flags & MASK_COMPARE) == (p2.flags & MASK_COMPARE)
- return p1.layout == p2.layout && p1.ratio == p2.ratio
-}
-
-@private
-map_items :: proc(i1, i2: Item) -> bool #no_bounds_check {
- p1 := &ui.last_items[i1]
- if i2 == -1 {
- return false
- }
-
- p2 := &ui.items[i2]
-
- if !compare_items(p1, p2) {
- return false
- }
-
- count := 0
- failed := 0
- kid1 := p1.first_kid
- kid2 := p2.first_kid
-
- for kid1 != -1 {
- pkid1 := &ui.last_items[kid1]
- count += 1
-
- if !map_items(kid1, kid2) {
- failed = count
- break
- }
-
- kid1 = pkid1.next_item
-
- if kid2 != -1 {
- kid2 = ui.items[kid2].next_item
- }
- }
-
- if count > 0 && failed == 1 {
- return false
- }
-
- // same item
- ui.item_map[i1] = i2
- return true
-}
-
-@private
-validate_state_items :: proc() {
- ui.last_hot_item = recover_item(ui.last_hot_item)
- ui.active_item = recover_item(ui.active_item)
- ui.focus_item = recover_item(ui.focus_item)
- ui.last_click_item = recover_item(ui.last_click_item)
+contains :: proc(item: ^Item, x, y: int) -> bool #no_bounds_check {
+ return rect.contains(item.bounds, x, y)
}
////////////////////////////////////////////////////////////////////////////////
@@ -1046,113 +838,84 @@ validate_state_items :: proc() {
////////////////////////////////////////////////////////////////////////////////
// true if the item match the active
-is_active :: #force_inline proc(item: Item) -> bool {
- return ui.active_item == item
+is_active :: #force_inline proc(ctx: ^Context, item: ^Item) -> bool {
+ return ctx.active_item == item.id
}
// true if the item match the hot
-is_hot :: #force_inline proc(item: Item) -> bool {
- return ui.last_hot_item == item
+is_hot :: #force_inline proc(ctx: ^Context, item: ^Item) -> bool {
+ return ctx.last_hot_item == item.id
}
// true if the item match the focused
-is_focused :: #force_inline proc(item: Item) -> bool {
- return ui.focus_item == item
+is_focused :: #force_inline proc(ctx: ^Context, item: ^Item) -> bool {
+ return ctx.focus_item == item.id
}
// true if the item match the clicked (HOT_UP)
-is_clicked :: #force_inline proc(item: Item) -> bool {
- return ui.clicked_item == item
-}
-
-// get the latest appended item
-get_latest :: #force_inline proc() -> int {
- return ui.count - 1
+is_clicked :: #force_inline proc(ctx: ^Context, item: ^Item) -> bool {
+ return ctx.clicked_item == item.id
}
// shorthand for is_clicked(get_latest())
-latest_clicked :: #force_inline proc() -> bool {
- return ui.clicked_item == ui.count - 1
+latest_clicked :: #force_inline proc(ctx: ^Context) -> bool {
+ item := &ctx.items[ctx.items_index - 1]
+ return ctx.clicked_item == item.id
}
// float activeness of an item 0 | 0.5 | 1
-activeness :: proc(item: Item) -> f32 {
- return ui.active_item == item ? 1 : (ui.last_hot_item == item ? 0.5 : 0)
-}
-
-// get hot animation value
-get_hot :: proc(item: Item) -> f32 #no_bounds_check {
- pitem := &ui.items[item]
- return pitem.hot;
-}
-
-// get active animation value
-get_active :: proc(item: Item) -> f32 #no_bounds_check {
- pitem := &ui.items[item]
- return pitem.active;
+activeness :: proc(ctx: ^Context, item: ^Item) -> f32 {
+ return ctx.active_item == item.id ? 1 : (ctx.last_hot_item == item.id ? 0.5 : 0)
}
// hot + active activeness
-get_activeness :: proc(item: Item) -> f32 #no_bounds_check {
- pitem := &ui.items[item]
- return max(pitem.hot * 0.5, pitem.active)
+activeness2 :: proc(item: ^Item) -> f32 #no_bounds_check {
+ return max(item.anim.hot * 0.5, item.anim.active)
}
// compute the size of an item
// optional HSIZED / VSIZED for custom sizes
-compute_size :: proc(item: Item) #no_bounds_check {
- pitem := &ui.items[item]
-
- // if flags_check(pitem.flags, ITEM_HSIZED) {
- // pitem.layout_size.x = item_callback(item, ITEM_HSIZED)
- // }
-
- // if flags_check(pitem.flags, ITEM_VSIZED) {
- // pitem.layout_size.y = item_callback(item, ITEM_VSIZED)
- // }
-
- pitem.rect.r = pitem.layout_size.x
- pitem.rect.b = pitem.layout_size.y
+compute_size :: proc(item: ^Item) #no_bounds_check {
+ item.bounds.r = item.layout_size.x
+ item.bounds.b = item.layout_size.y
// iterate children
- kid := first_child(item)
- for kid > 0 {
+ kid := item.first_item
+ for kid != nil {
compute_size(kid)
- kid = next_sibling(kid)
+ kid = kid.next_item
}
}
// layouts items based on rect-cut by default or custom ones
-arrange :: proc(item: Item, layout: ^Rect, gap: int) #no_bounds_check {
- pitem := &ui.items[item]
-
+arrange :: proc(item: ^Item, layout: ^RectI, gap: int) #no_bounds_check {
// check for wanted ratios -> size conversion which depend on parent rect
- switch pitem.ratio {
+ switch item.ratio {
case .None:
- case .X: pitem.layout_size.x = int(rect_widthf(layout^) * pitem.layout_ratio.x)
- case .Y: pitem.layout_size.y = int(rect_heightf(layout^) * pitem.layout_ratio.y)
+ case .X: item.layout_size.x = int(rect.widthf(layout^) * item.layout_ratio.x)
+ case .Y: item.layout_size.y = int(rect.heightf(layout^) * item.layout_ratio.y)
case .XY:
- pitem.layout_size.x = int(rect_widthf(layout^) * pitem.layout_ratio.x)
- pitem.layout_size.y = int(rect_heightf(layout^) * pitem.layout_ratio.y)
+ item.layout_size.x = int(rect.widthf(layout^) * item.layout_ratio.x)
+ item.layout_size.y = int(rect.heightf(layout^) * item.layout_ratio.y)
}
- switch pitem.layout {
+ switch item.layout {
// DEFAULT
case .Cut:
// directionality
- switch pitem.layout_cut_self {
- case .Left: pitem.rect = rect_cut_left(layout, pitem.layout_size.x)
- case .Right: pitem.rect = rect_cut_right(layout, pitem.layout_size.x)
- case .Top: pitem.rect = rect_cut_top(layout, pitem.layout_size.y)
- case .Bottom: pitem.rect = rect_cut_bottom(layout, pitem.layout_size.y)
+ switch item.layout_cut_self {
+ case .Left: item.bounds = rect.cut_left(layout, item.layout_size.x)
+ case .Right: item.bounds = rect.cut_right(layout, item.layout_size.x)
+ case .Top: item.bounds = rect.cut_top(layout, item.layout_size.y)
+ case .Bottom: item.bounds = rect.cut_bottom(layout, item.layout_size.y)
case .Fill:
- pitem.rect = layout^
+ item.bounds = layout^
layout^ = {}
}
// apply gapping
if gap > 0 {
- switch pitem.layout_cut_self {
+ switch item.layout_cut_self {
case .Left: layout.l += gap
case .Right: layout.r -= gap
case .Top: layout.t += gap
@@ -1165,22 +928,83 @@ arrange :: proc(item: Item, layout: ^Rect, gap: int) #no_bounds_check {
// item_callback(item, LAYOUT_CUSTOM)
case .Absolute:
- rect_sized(&pitem.rect, pitem.layout_offset, pitem.layout_size)
+ rect.sized(&item.bounds, item.layout_offset, item.layout_size)
case .Relative:
- rect_sized(&pitem.rect, { layout.l, layout.t } + pitem.layout_offset, pitem.layout_size)
+ rect.sized(&item.bounds, [2]int { layout.l, layout.t } + item.layout_offset, item.layout_size)
}
// layout children with this resultant rect for LAYOUT_CUT
- layout_with := pitem.rect
- kid := first_child(item)
+ layout_with := item.bounds
+ kid := item.first_item
+
+ if item.layout_margin > 0 {
+ layout_with = rect.margin(layout_with, item.layout_margin)
+ }
- if pitem.layout_margin > 0 {
- layout_with = rect_margin(layout_with, pitem.layout_margin)
+ for kid != nil {
+ arrange(kid, &layout_with, item.layout_cut_gap)
+ kid = kid.next_item
}
+}
+
+item_root :: proc(ctx: ^Context) -> ^Item {
+ assert(ctx.items_index > 0)
+ return &ctx.items[0]
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ID gen - same as microui
+////////////////////////////////////////////////////////////////////////////////
+
+gen_id_bytes :: proc(ctx: ^Context, input: []byte) -> (res: u32) {
+ seed := ctx.ids_index > 0 ? ctx.ids[ctx.ids_index - 1] : 2166136261
+ res = hash.fnv32a(input, seed)
+ ctx.last_id = res
+ return
+}
+gen_id_string :: proc(ctx: ^Context, input: string) -> u32 {
+ return gen_id_bytes(ctx, transmute([]byte) input)
+}
+gen_id :: proc { gen_id_bytes, gen_id_string }
+
+gen_idf :: proc(ctx: ^Context, format: string, args: ..any) -> u32 {
+ return gen_id_bytes(ctx, transmute([]byte) fmt.tprintf(format, ..args))
+}
+
+push_id_bytes :: proc(ctx: ^Context, input: []byte) -> (res: u32) {
+ res = gen_id_bytes(ctx, input)
+ ctx.ids[ctx.ids_index] = res
+ ctx.ids_index += 1
+ return
+}
+push_id_string :: proc(ctx: ^Context, input: string) -> (res: u32) {
+ res = gen_id_string(ctx, input)
+ ctx.ids[ctx.ids_index] = res
+ ctx.ids_index += 1
+ return
+}
+push_id :: proc { push_id_bytes, push_id_string }
+
+// push_id_latest :: proc(ctx: ^Context) {
+// ctx.ids[ctx.ids_index] = ctx.last_id
+// ctx.ids_index += 1
+// }
+
+pop_id :: proc(ctx: ^Context) {
+ if ctx.ids_index > 0 {
+ ctx.ids_index -= 1
+ }
+}
+
+print_ids :: proc(ctx: ^Context) {
+ fmt.eprintln("~~~~~~~~~~")
+ for i in 0..<ctx.ids_index {
+ for j in 0..<i {
+ fmt.eprint('\t')
+ }
- for kid > 0 {
- arrange(kid, &layout_with, pitem.layout_cut_gap)
- kid = next_sibling(kid)
+ id := ctx.ids[i]
+ fmt.eprintf("ID %d\n", id)
}
} \ No newline at end of file