diff options
author | San Jacobs | 2023-10-14 13:11:39 +0200 |
---|---|---|
committer | San Jacobs | 2023-10-14 13:11:39 +0200 |
commit | fd262c11c3c0f627927ecc7fd5115899033018bb (patch) | |
tree | 3efec0b37d1069cdedd9b4bc065a701bfed1efc7 /lib/oui/oui.odin | |
parent | d6cb7ac37d86d0c4ee03982813b94275fd62016a (diff) | |
download | satscalc-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.odin | 1120 |
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 |