aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSan Jacobs2023-10-14 13:11:39 +0200
committerSan Jacobs2023-10-14 13:11:39 +0200
commitfd262c11c3c0f627927ecc7fd5115899033018bb (patch)
tree3efec0b37d1069cdedd9b4bc065a701bfed1efc7
parentd6cb7ac37d86d0c4ee03982813b94275fd62016a (diff)
downloadsatscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.tar.gz
satscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.tar.bz2
satscalc-fd262c11c3c0f627927ecc7fd5115899033018bb.zip
New OUI version
-rw-r--r--lib/oui/oui.odin1120
-rw-r--r--lib/oui/rect.odin245
-rw-r--r--lib/rect/rect.odin133
-rw-r--r--src/main.odin53
-rw-r--r--src/ui_implementation.odin59
5 files changed, 680 insertions, 930 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
diff --git a/lib/oui/rect.odin b/lib/oui/rect.odin
deleted file mode 100644
index 23f2e6d..0000000
--- a/lib/oui/rect.odin
+++ /dev/null
@@ -1,245 +0,0 @@
-package oui
-
-import "core:math"
-
-Rect :: struct {
- l, r, t, b: int,
-}
-
-RECT_INF :: Rect {
- max(int),
- -max(int),
- max(int),
- -max(int),
-}
-
-// build a rectangle from multiple
-rect_inf_push :: proc(rect: ^Rect, other: Rect) {
- rect.t = min(rect.t, other.t)
- rect.l = min(rect.l, other.l)
- rect.b = max(rect.b, other.b)
- rect.r = max(rect.r, other.r)
-}
-
-rect_one :: #force_inline proc(a: int) -> Rect {
- return { a, a, a, a }
-}
-
-rect_one_inv :: #force_inline proc(a: int) -> Rect {
- return { a, -a, a, -a }
-}
-
-rect_negate :: #force_inline proc(a: Rect) -> Rect {
- return {
- -a.l,
- -a.r,
- -a.t,
- -a.b,
- }
-}
-
-rect_valid :: #force_inline proc(a: Rect) -> bool {
- return a.r > a.l && a.b > a.t
-}
-
-rect_invalid :: #force_inline proc(rect: Rect) -> bool {
- return !rect_valid(rect)
-}
-
-rect_width_invalid :: #force_inline proc(rect: Rect) -> bool {
- return rect.r < rect.l
-}
-
-rect_height_invalid :: #force_inline proc(rect: Rect) -> bool {
- return rect.b < rect.t
-}
-
-rect_wh :: #force_inline proc(x, y, w, h: int) -> Rect {
- return { x, x + w, y, y + h }
-}
-
-rect_center :: #force_inline proc(a: Rect) -> (x, y: f32) {
- return f32(a.l) + f32(a.r - a.l) / 2, f32(a.t) + f32(a.b - a.t) / 2
-}
-
-// width
-rect_width :: #force_inline proc(a: Rect) -> int {
- return (a.r - a.l)
-}
-rect_widthf :: #force_inline proc(a: Rect) -> f32 {
- return f32(a.r - a.l)
-}
-rect_width_halfed :: #force_inline proc(a: Rect) -> int {
- return (a.r - a.l) / 2
-}
-rect_widthf_halfed :: #force_inline proc(a: Rect) -> f32 {
- return f32(a.r - a.l) / 2
-}
-
-// height
-rect_height :: #force_inline proc(a: Rect) -> int {
- return (a.b - a.t)
-}
-rect_heightf :: #force_inline proc(a: Rect) -> f32 {
- return f32(a.b - a.t)
-}
-rect_height_halfed :: #force_inline proc(a: Rect) -> int {
- return (a.b - a.t) / 2
-}
-rect_heightf_halfed :: #force_inline proc(a: Rect) -> f32 {
- return f32(a.b - a.t) / 2
-}
-
-// width / height by option
-rect_opt_v :: #force_inline proc(a: Rect, vertical: bool) -> int {
- return vertical ? rect_height(a) : rect_width(a)
-}
-rect_opt_h :: #force_inline proc(a: Rect, horizontal: bool) -> int {
- return horizontal ? rect_width(a) : rect_height(a)
-}
-rect_opt_vf :: #force_inline proc(a: Rect, vertical: bool) -> f32 {
- return vertical ? rect_heightf(a) : rect_widthf(a)
-}
-rect_opt_hf :: #force_inline proc(a: Rect, horizontal: bool) -> f32 {
- return horizontal ? rect_widthf(a) : rect_heightf(a)
-}
-
-rect_xxyy :: #force_inline proc(x, y: int) -> Rect {
- return { x, x, y, y }
-}
-
-rect_intersection :: proc(a, b: Rect) -> Rect {
- a := a
- if a.l < b.l do a.l = b.l
- if a.t < b.t do a.t = b.t
- if a.r > b.r do a.r = b.r
- if a.b > b.b do a.b = b.b
- return a
-}
-
-// smallest rectangle
-rect_bounding :: proc(a, b: Rect) -> Rect {
- a := a
- if a.l > b.l do a.l = b.l
- if a.t > b.t do a.t = b.t
- if a.r < b.r do a.r = b.r
- if a.b < b.b do a.b = b.b
- return a
-}
-
-rect_contains :: proc(a: Rect, x, y: int) -> bool {
- return a.l <= x && a.r > x && a.t <= y && a.b > y
-}
-
-rect_offset :: proc(rect: ^Rect, x, y: int) {
- rect.l += x
- rect.r += x
- rect.t += y
- rect.b += y
-}
-
-rect_sized :: #force_inline proc(rect: ^Rect, pos: [2]int, size: [2]int) {
- rect.l = pos.x
- rect.r = pos.x + size.x
- rect.t = pos.y
- rect.b = pos.y + size.y
-}
-
-// rect cutting with HARD CUT, will result in invalid rectangles when out of size
-
-rect_cut_left :: proc(rect: ^Rect, a: int) -> (res: Rect) {
- res = rect^
- res.r = rect.l + a
- rect.l = res.r
- return
-}
-
-rect_cut_right :: proc(rect: ^Rect, a: int) -> (res: Rect) {
- res = rect^
- res.l = rect.r - a
- rect.r = res.l
- return
-}
-
-rect_cut_top :: proc(rect: ^Rect, a: int) -> (res: Rect) {
- res = rect^
- res.b = rect.t + a
- rect.t = res.b
- return
-}
-
-rect_cut_bottom :: proc(rect: ^Rect, a: int) -> (res: Rect) {
- res = rect^
- res.t = rect.b - a
- rect.b = res.t
- return
-}
-
-// add another rect as padding
-rect_padding :: proc(a, b: Rect) -> Rect {
- a := a
- a.l += b.l
- a.t += b.t
- a.r -= b.r
- a.b -= b.b
- return a
-}
-
-// add another rect as padding
-rect_margin :: proc(a: Rect, value: int) -> Rect {
- a := a
- a.l += value
- a.t += value
- a.r -= value
- a.b -= value
- return a
-}
-
-rect_add :: proc(a, b: Rect) -> Rect {
- a := a
- a.l += b.l
- a.t += b.t
- a.r += b.r
- a.b += b.b
- return a
-}
-
-rect_translate :: proc(a, b: Rect) -> Rect {
- a := a
- a.l += b.l
- a.t += b.t
- a.r += b.l
- a.b += b.t
- return a
-}
-
-rect_overlap :: proc(a, b: Rect) -> bool {
- return b.r >= a.l && b.l <= a.r && b.b >= a.t && b.t <= a.b
-}
-
-rect_inside :: proc(a, b: Rect) -> bool {
- return b.r >= a.l && b.l <= a.r && b.t >= a.t && b.b <= a.b
-}
-
-// cuts out rect b from a and returns the left regions
-rect_cut_out_rect :: proc(a, b: Rect) -> (res: [4]Rect) {
- // top
- res[0] = a
- res[0].b = b.t
-
- // bottom
- res[1] = a
- res[1].t = b.b
-
- // middle
- last := rect_intersection(res[0], res[1])
-
- // left
- res[2] = last
- res[2].r = b.l
-
- // right
- res[3] = last
- res[3].l = b.r
- return
-}
diff --git a/lib/rect/rect.odin b/lib/rect/rect.odin
new file mode 100644
index 0000000..197a966
--- /dev/null
+++ b/lib/rect/rect.odin
@@ -0,0 +1,133 @@
+package rect
+
+Rect :: struct($T: typeid) {
+ l, r, t, b: T,
+}
+
+RECTF_INF :: RectF { max(f32), -max(f32), max(f32), -max(f32) }
+
+RectF :: Rect(f32)
+RectI :: Rect(int)
+
+i2f :: proc(rect: RectI) -> RectF {
+ return { f32(rect.l), f32(rect.r), f32(rect.t), f32(rect.b) }
+}
+
+wh :: proc(x, y, w, h: $T) -> (res: Rect(T)) {
+ res.l = x
+ res.r = x + w
+ res.t = y
+ res.b = y + h
+ return
+}
+
+sized :: proc(rect: ^Rect($T), pos: [2]T, size: [2]T) {
+ rect.l = pos.x
+ rect.r = pos.x + size.x
+ rect.t = pos.y
+ rect.b = pos.y + size.y
+}
+
+overlap :: proc(a, b: Rect($T)) -> bool {
+ return b.r >= a.l && b.l <= a.r && b.b >= a.t && b.t <= a.b
+}
+
+inf_push :: proc(rect: ^Rect($T), pos: [2]T) {
+ rect.l = min(rect.l, pos.x)
+ rect.r = max(rect.r, pos.x)
+ rect.t = min(rect.t, pos.y)
+ rect.b = max(rect.b, pos.y)
+}
+
+// center :: proc(rect: Rect($T)) -> [2]T {
+// return { f32(rect.l) + f32(rect.r - rect.l) / 2, f32(rect.t) + f32(rect.b - rect.t) / 2 }
+// }
+
+valid :: proc(rect: Rect($T)) -> bool {
+ return (rect.r - rect.l) > 0 && (rect.b - rect.t) > 0
+}
+
+invalid :: #force_inline proc(rect: Rect($T)) -> bool { return !valid(rect) }
+
+intersection :: proc(a, b: Rect($T)) -> (res: Rect(T)) {
+ res = a
+ if a.l < b.l do res.l = b.l
+ if a.t < b.t do res.t = b.t
+ if a.r > b.r do res.r = b.r
+ if a.b > b.b do res.b = b.b
+ return
+}
+
+margin :: proc(a: Rect($T), value: T) -> Rect(T) {
+ a := a
+ a.l += value
+ a.t += value
+ a.r -= value
+ a.b -= value
+ return a
+}
+
+offset :: proc(rect: Rect($T), x, y: T) -> (res: Rect(T)) {
+ res = rect
+ res.l += x
+ res.r += x
+ res.t += y
+ res.b += y
+ return
+}
+
+contains :: proc(a: Rect($T), x, y: T) -> bool {
+ return a.l <= x && a.r > x && a.t <= y && a.b > y
+}
+
+widthf :: proc(a: RectI) -> f32 {
+ return f32(a.r - a.l)
+}
+
+heightf :: proc(a: RectI) -> f32 {
+ return f32(a.b - a.t)
+}
+
+widthi :: proc(a: RectI) -> int {
+ return int(a.r - a.l)
+}
+
+heighti :: proc(a: RectI) -> int {
+ return int(a.b - a.t)
+}
+
+splitv :: proc(rect: RectF) -> (left, right: RectF) {
+ left = rect
+ right = rect
+ left.r = rect.l + (rect.r - rect.l) / 2
+ right.l = left.r
+ return
+}
+
+cut_left :: proc(rect: ^Rect($T), a: T) -> (res: Rect(T)) {
+ res = rect^
+ res.r = rect.l + a
+ rect.l = res.r
+ return
+}
+
+cut_right :: proc(rect: ^Rect($T), a: T) -> (res: Rect(T)) {
+ res = rect^
+ res.l = rect.r - a
+ rect.r = res.l
+ return
+}
+
+cut_top :: proc(rect: ^Rect($T), a: T) -> (res: Rect(T)) {
+ res = rect^
+ res.b = rect.t + a
+ rect.t = res.b
+ return
+}
+
+cut_bottom :: proc(rect: ^Rect($T), a: T) -> (res: Rect(T)) {
+ res = rect^
+ res.t = rect.b - a
+ rect.b = res.t
+ return
+}
diff --git a/src/main.odin b/src/main.odin
index ac3f693..79b1a1e 100644
--- a/src/main.odin
+++ b/src/main.odin
@@ -13,6 +13,8 @@ font : rl.Font
big_font : rl.Font
small_font : rl.Font
+c0 : ^oui.Context
+
main :: proc() {
// TODO: Replace the dynamic array of Workday-pointers with
@@ -87,9 +89,10 @@ main :: proc() {
// oui stuff
- c0 := oui.context_create(1028, 1028 * 8)
+ c0 = new(oui.Context)
+ defer free(c0)
+ oui.context_init(c0, 1028, 1028 * 8)
defer oui.context_destroy(c0)
- oui.context_make_current(c0)
// Setting up the timelines
@@ -130,8 +133,8 @@ main :: proc() {
// bottom left of the screen.
mousePosition: = rl.GetMousePosition()
- oui.set_cursor(int(mousePosition.x), int(mousePosition.y))
- oui.set_button(.Left, rl.IsMouseButtonPressed(rl.MouseButton(0)))
+ oui.set_cursor(c0, int(mousePosition.x), int(mousePosition.y))
+ oui.set_button(c0, .Left, rl.IsMouseButtonPressed(rl.MouseButton(0)))
// DRAW
// ------------------------------------------
@@ -141,26 +144,42 @@ main :: proc() {
when true {
// hotloop
- oui.begin_layout()
+ oui.begin_layout(c0)
master_container := panel()
- oui.set_layout(master_container, .Absolute)
- oui.set_size(master_container, int(GetScreenWidth()), int(GetScreenHeight()))
+ master_container.id = oui.push_id(c0, "big_mr_boss_man")
+ master_container.layout = .Absolute
+ master_container.layout_size = {int(GetScreenWidth()), int(GetScreenHeight())}
- top_bar := panel(BGCOLOR)
- oui.set_cut(master_container, .Top)
- oui.set_height(top_bar, 30)
- oui.set_margin(top_bar, 6)
- oui.item_insert(master_container, top_bar)
+ {
+ top_bar := panel_line(master_container, BGCOLOR, 30)
+ top_bar.id = oui.push_id(c0, "small_boie")
+ defer oui.pop_id(c0)
+ top_bar.layout_margin = 6
date_label := label("Date", font, .Left)
oui.item_insert(top_bar, date_label)
+ }
+ {
bottom_bar := panel(PBGCOLOR)
- oui.set_cut(master_container, .Bottom)
- oui.set_height(bottom_bar, 50)
+ bottom_bar.id = oui.push_id(c0, "not_small_boie")
+ defer oui.pop_id(c0)
+ bottom_bar.layout_cut_children = .Left
+ master_container.layout_cut_children = .Bottom
+ bottom_bar.layout_size.y = 50
oui.item_insert(master_container, bottom_bar)
+ }
+
+ {
+ middle_section := panel(WBGCOLOR)
+ middle_section.id = oui.push_id(c0, "middle_section")
+ defer oui.pop_id(c0)
+ master_container.layout_cut_children = .Fill
+ oui.item_insert(master_container, middle_section)
+ }
+
@@ -174,11 +193,11 @@ when true {
oui.item_insert(a_panel, a_button)*/
- oui.end_layout()
+ oui.end_layout(c0)
- ui_draw(0)
+ ui_draw(master_container)
// DRAW HERE OR BELOW
- oui.process()
+ oui.process(c0)
} else {
DrawTextEx(font, "Date", {20, 8}, font_size, 0, RAYWHITE);
diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin
index 27bcc65..cf38cc3 100644
--- a/src/ui_implementation.odin
+++ b/src/ui_implementation.odin
@@ -8,6 +8,7 @@ import rl "vendor:raylib"
BGCOLOR : rl.Color : {30, 30, 30, 255}
+WBGCOLOR : rl.Color : {20, 25, 25, 255}
PBGCOLOR : rl.Color : {40, 40, 40, 255}
DAY_HEIGHT :: 35
@@ -51,8 +52,8 @@ Data_Label :: struct {
alignment: Text_Alignment,
}
-button_callback :: proc(item: Item, event: Call) -> int {
- data := cast(^Data_Button) oui.get_handle(item)
+button_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int {
+ data := cast(^Data_Button) item.handle
#partial switch event {
case .Cursor_Handle:
@@ -62,22 +63,39 @@ button_callback :: proc(item: Item, event: Call) -> int {
return -1
}
-panel :: proc(color : rl.Color = rl.RED) -> Item {
- item := oui.item_make()
+panel :: proc(color : rl.Color = rl.RED) -> ^Item {
+ item := oui.item_make(c0)
- data := oui.alloc_typed(item, Data_Panel)
+ data := oui.alloc_typed(c0, item, Data_Panel)
data.subtype = .Panel
data.color = color
- return item
+ return item
+}
+
+panel_line :: proc(parent: ^Item, color : rl.Color, height: int = 40) -> (item: ^Item) {
+ item = oui.item_make(c0)
+ item.layout_cut_children = .Left
+ item.layout_size.y = height
+
+ old := parent.layout_cut_children
+ parent.layout_cut_children = .Top
+ oui.item_insert(parent, item)
+ parent.layout_cut_children = old
+
+ data := oui.alloc_typed(c0, item, Data_Panel)
+ data.subtype = .Panel
+ data.color = color
+
+ return
}
-button :: proc(text: string, width: int, selected := false) -> Item {
- item := oui.item_make()
- oui.set_size(item, width, 35)
- oui.set_callback(item, button_callback)
+button :: proc(text: string, width: int, selected := false) -> ^Item {
+ item := oui.item_make(c0)
+ item.layout_size = {width, 35}
+ item.callback = button_callback
- data := oui.alloc_typed(item, Data_Button)
+ data := oui.alloc_typed(c0, item, Data_Button)
data.subtype = .Button
data.text = text
data.selected = selected
@@ -91,10 +109,10 @@ Text_Alignment :: enum int {
Right,
Center,
}
-label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> Item {
- item := oui.item_make()
+label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> ^Item {
+ item := oui.item_make(c0)
- data := oui.alloc_typed(item, Data_Label)
+ data := oui.alloc_typed(c0, item, Data_Label)
data.subtype = .Label
data.text = strings.unsafe_string_to_cstring(text)
data.font = font
@@ -104,16 +122,17 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) ->
}
// recursive loop
-ui_draw_children :: proc(item: oui.Item) {
- list := oui.children_sorted(item)
+ui_draw_children :: proc(item: ^oui.Item) {
+ list := oui.children_list(c0, item)
+ if len(list)>0 do fmt.println(list[len(list)-1])
for kid in list {
ui_draw(kid)
}
}
-ui_draw :: proc(item: oui.Item) {
- head := cast(^Data_Head) oui.get_handle(item)
- rect := oui.get_rect(item)
+ui_draw :: proc(item: ^oui.Item) {
+ head := cast(^Data_Head) item.handle
+ rect := item.bounds
//fmt.println(rect, head, item)
@@ -132,7 +151,7 @@ ui_draw :: proc(item: oui.Item) {
rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), data.color)
ui_draw_children(item)
case .Label:
- data := cast(^Data_Label) oui.get_handle(item)
+ data := cast(^Data_Label) item.handle
horizontal_position : f32