From a7c2d13bcca784350d851b665b8a19697e113b34 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Thu, 17 Aug 2023 01:48:00 +0200 Subject: Screw everything, we do oui now --- lib/oui/oui.odin | 1186 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/oui/rect.odin | 245 +++++++++++ src/main.odin | 127 +++++- 3 files changed, 1556 insertions(+), 2 deletions(-) create mode 100644 lib/oui/oui.odin create mode 100644 lib/oui/rect.odin diff --git a/lib/oui/oui.odin b/lib/oui/oui.odin new file mode 100644 index 0000000..d14f369 --- /dev/null +++ b/lib/oui/oui.odin @@ -0,0 +1,1186 @@ +package oui + +import "core:mem" +import "core:fmt" +import "core:time" +import "core:slice" + +// 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 + +MAX_DATASIZE :: 4096 +MAX_DEPTH :: 64 +MAX_INPUT_EVENTS :: 64 +CLICK_THRESHOLD :: time.Millisecond * 250 +MAX_CONSUME :: 256 + +Input_Event :: struct { + key: int, + char: rune, + repeat: bool, + call: Call, +} + +Mouse_Button :: enum { + Left, + Middle, + Right, +} +Mouse_Buttons :: bit_set[Mouse_Button] + +Context :: struct { + buttons: Mouse_Buttons, + last_buttons: Mouse_Buttons, + button_capture: Mouse_Button, + + cursor_start: I2, + cursor_last_frame: I2, + cursor_delta_frame: I2, + cursor: I2, + cursor_handle: int, // handle <-> looks + 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, + + state: State, + stage: Stage, + active_key: int, + active_key_repeat: bool, + active_char: rune, + mods: u32, // keyboard mods + + // consume building + consume: [MAX_CONSUME]u8, + consume_focused: bool, // wether consuming is active + consume_index: int, + + // defaults + 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, + + // buffer data + // TODO could be an arena + buffer: []byte, + + // input event buffer + events: [MAX_INPUT_EVENTS]Input_Event, +} + +State :: enum { + Idle, + Capture, +} + +Stage :: enum { + Layout, + Post_Layout, + Process, +} + +Item_State :: enum { + Cold, + Hot, + Active, + Frozen, +} + +// Cut modes +Cut :: enum { + Left, + Right, + Top, + Bottom, + Fill, +} + +// ratio of parent size +Ratio :: enum { + None, + X, + Y, + XY, +} + +// layout modes | Default is CUT +Layout :: enum { + Cut, + Absolute, + Relative, + // Custom, +} + +// MOUSE calls: +// Down / Up called 1 frame +// Hot_Up called when active was over hot +// Capture called while holding active down +Call :: enum { + // left mouse button + Left_Down, + Left_Up, + Left_Hot_Up, + Left_Capture, + + // right mouse button + Right_Down, + Right_Up, + Right_Hot_Up, + Right_Capture, + + // middle mouse button + Middle_Down, + Middle_Up, + Middle_Hot_Up, + Middle_Capture, + + // Scroll info + Scroll, + + // Cursor Info + Cursor_Handle, + + // FIND + Find_Ignore, + + // Key / Char + Key_Down, + Key_Up, + Char, +} + +I2 :: [2]int +Item :: int + +Callback :: proc(Item, Call) -> int + +Item_Raw :: struct { + handle: rawptr, // item handle + + // callback class will always be called / set usually - user can override class calls + callback: Callback, + + state: Item_State, + ignore: bool, + + // item state | makes this item UNIQUE + layout: Layout, + ratio: Ratio, + z_index: int, + + // tree info + parent: Item, + first_kid: Item, + next_item: Item, + + // layout final + rect: Rect, + + // layout info + layout_offset: [2]int, + layout_margin: int, + layout_size: [2]int, // width/height + layout_ratio: [2]f32, // TODO could be merged to somewhere else + + // cut info + layout_cut_self: Cut, // how the item will cut from the rect + layout_cut_children: Cut, // state that the children will inherit + layout_cut_gap: int, // how much to insert a gap after a cut + + // persistent data per item - queryable after end_layout + hot: f32, + active: 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 + + // swap buffers + ui.items, ui.last_items = ui.last_items, ui.items + + // set map + for i in 0.. ^Context { + ctx := new(Context) + 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.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_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 +} + +//////////////////////////////////////////////////////////////////////////////// +// INPUT CONTROL +//////////////////////////////////////////////////////////////////////////////// + +@private +add_input_event :: proc(event: Input_Event) #no_bounds_check { + if ui.event_count == MAX_INPUT_EVENTS { + return + } + + ui.events[ui.event_count] = event + ui.event_count += 1 +} + +@private +clear_input_events :: proc() { + ui.event_count = 0 + ui.scroll = {} +} + +set_cursor :: #force_inline proc(x, y: int) { + ui.cursor = { x, y } +} + +set_cursor_handle_callback :: proc(callback: proc(new: int)) { + ui.cursor_handle_callback = callback +} + +get_cursor :: #force_inline proc() -> I2 { + return ui.cursor +} + +get_cursor_start :: #force_inline proc() -> I2 { + return ui.cursor_start +} + +get_cursor_delta :: #force_inline proc() -> I2 { + return ui.cursor - ui.cursor_start +} + +get_cursor_delta_frame :: #force_inline proc() -> I2 { + return ui.cursor_delta_frame +} + +set_button :: proc(button: Mouse_Button, enabled: bool) { + if enabled { + incl(&ui.buttons, button) + } else { + excl(&ui.buttons, button) + } +} + +set_mods :: proc(mods: u32, enabled: bool) { + if enabled { + ui.mods |= mods + } else { + ui.mods = mods + } +} + +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_released :: proc(button: Mouse_Button) -> bool { + return get_last_button(button) && !get_button(button) +} + +get_clicks :: #force_inline proc() -> int { + return ui.clicks +} + +set_key :: proc(key: int, down: bool, repeat: bool) { + add_input_event({ key, 0, repeat, down ? .Key_Down : .Key_Up }) +} + +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 +} + +//////////////////////////////////////////////////////////////////////////////// +// STAGES +//////////////////////////////////////////////////////////////////////////////// + +begin_layout :: proc() { + assert(ui.stage == .Process) + context_clear_data() + ui.stage = .Layout +} + +end_layout :: proc() #no_bounds_check { + assert(ui.stage == .Layout) + + if ui.count > 0 { + compute_size(0) + arrange(0, nil, 0) + + if ui.last_count > 0 { + map_items(0, 0) + + // remap hot/activeness of matched items + for i in 0.. 0 { + update_hot_item() + } + + ui.stage = .Post_Layout + + // update hot/active for animation + speed := f32(0.05) + for i in 0.. bool { + if get_button(button) { + hot_item^ = -1 + active_item^ = ui.hot_item + + if active_item^ != focus_item^ { + focus_item^ = -1 + ui.focus_item = -1 + } + + if active_item^ >= 0 { + diff := time.since(ui.click_time) + if diff > CLICK_THRESHOLD { + ui.clicks = 0 + } + + ui.clicks += 1 + ui.last_click_item = active_item^ + ui.click_time = time.now() + + 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) + } + } + + // only capture if wanted + ui.button_capture = button + ui.state = .Capture + return true + } + + return false +} + +process :: proc() { + assert(ui.stage != .Layout) + if ui.stage == .Process { + update_hot_item() + } + ui.stage = .Process + + if ui.count == 0 { + clear_input_events() + return + } + + hot_item := ui.last_hot_item + active_item := ui.active_item + focus_item := ui.focus_item + cursor_handle := ui.cursor_handle + + // send all keyboard events + if focus_item >= 0 { + for i in 0..= 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 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) + } + ui.clicked_item = active_item + } + } + + active_item = -1 + ui.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) + } + } + + hot_item = hot == active_item ? hot : -1 + } + } + + // look for possible cursor handle + if hot_item != -1 { + wanted_handle := item_callback(hot_item, .Cursor_Handle) + if wanted_handle != -1 { + ui.cursor_handle = wanted_handle + } else { + // change back to zero - being the default arrow type + if ui.cursor_handle != 0 { + ui.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) + } + } + + 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 +} + +context_clear_state :: proc() { + ui.last_hot_item = -1 + ui.active_item = -1 + ui.focus_item = -1 + ui.last_click_item = -1 +} + +//////////////////////////////////////////////////////////////////////////////// +// UI DECLARATION +//////////////////////////////////////////////////////////////////////////////// + +item_make :: proc() -> Item { + assert(ui.stage == .Layout) + assert(ui.count < len(ui.items)) + + idx := ui.count + ui.count += 1 + + item := &ui.items[idx] + mem.zero_item(item) + + item.first_kid = -1 + item.next_item = -1 + + return idx +} + +item_callback :: proc(item: Item, call: Call) -> int #no_bounds_check { + pitem := &ui.items[item] + + if pitem.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) +} + +set_frozen :: proc(item: Item, enable: bool) { + pitem := &ui.items[item] + if enable { + pitem.state = .Frozen + } else { + pitem.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)) +} + +set_callback :: #force_inline proc(item: Item, callback: Callback) { + pitem := &ui.items[item] + pitem.callback = callback +} + +item_append :: proc(item: Item, sibling: Item) -> Item { + assert(sibling > 0) + pitem := &ui.items[item] + psibling := &ui.items[sibling] + psibling.parent = pitem.parent + // TODO cut append + psibling.next_item = pitem.next_item + pitem.next_item = sibling + return sibling +} + +item_insert :: proc(item: Item, child: Item) -> Item { + assert(child > 0) + pparent := &ui.items[item] + pchild := &ui.items[child] + + // set cut direction + pchild.parent = item + pchild.layout_cut_self = pparent.layout_cut_children + + if pparent.first_kid < 0 { + pparent.first_kid = child + } else { + item_append(last_child(item), child) + } + + return child +} + +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 + + pchild.next_item = pparent.first_kid + pparent.first_kid = 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] + w := clamp(width, 0, 1) + h := clamp(height, 0, 1) + pitem.layout_ratio = { w, h } + + if w != 0 && h != 0 { + pitem.ratio = .XY + } else { + if w != 0 { + pitem.ratio = .X + } else if h != 0 { + pitem.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 } +} + +// set a custom layouting method - default is by cut +set_layout :: proc(item: Item, layout: Layout) { + pitem := &ui.items[item] + pitem.layout = layout +} + +// 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 } +} + +focus_redirect :: proc(item: Item) { + ui.focus_redirect_item = item +} + +//////////////////////////////////////////////////////////////////////////////// +// ITERATION +//////////////////////////////////////////////////////////////////////////////// + +last_child :: proc(item: Item) -> Item { + item := first_child(item) + + if item < 0 { + return -1 + } + + for { + next_item := next_sibling(item) + if next_item < 0 { + 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 +} + +// 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 { + count: int + + // loop through children and push items + kid := first_child(item) + for kid > 0 { + ui.item_sort[count] = kid + kid = next_sibling(kid) + 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 + }) + + return slice.clone(list, context.temp_allocator) +} + +//////////////////////////////////////////////////////////////////////////////// +// 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, + x, y: int, + loc := #caller_location, +) -> Item #no_bounds_check { + // TODO frozen + // if pitem.state == .Frozen { + // return -1 + // } + + kid := pitem.first_kid + for kid >= 0 { + pkid := &ui.items[kid] + + // fetch ignore status + ignore := pkid.ignore + if item_callback(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 + } + + return item +} + +get_key :: proc() -> int { + return ui.active_key +} + +get_key_repeat :: proc() -> bool { + return ui.active_key_repeat +} + +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) +} + +//////////////////////////////////////////////////////////////////////////////// +// OTHER +//////////////////////////////////////////////////////////////////////////////// + +// true if the item match the active +is_active :: #force_inline proc(item: Item) -> bool { + return ui.active_item == item +} + +// true if the item match the hot +is_hot :: #force_inline proc(item: Item) -> bool { + return ui.last_hot_item == item +} + +// true if the item match the focused +is_focused :: #force_inline proc(item: Item) -> bool { + return ui.focus_item == item +} + +// 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 +} + +// shorthand for is_clicked(get_latest()) +latest_clicked :: #force_inline proc() -> bool { + return ui.clicked_item == ui.count - 1 +} + +// 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; +} + +// hot + active activeness +get_activeness :: proc(item: Item) -> f32 #no_bounds_check { + pitem := &ui.items[item] + return max(pitem.hot * 0.5, pitem.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 + + // iterate children + kid := first_child(item) + for kid > 0 { + compute_size(kid) + kid = next_sibling(kid) + } +} + +// 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] + + // check for wanted ratios -> size conversion which depend on parent rect + switch pitem.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 .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) + } + + switch pitem.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) + case .Fill: + pitem.rect = layout^ + layout^ = {} + } + + // apply gapping + if gap > 0 { + switch pitem.layout_cut_self { + case .Left: layout.l += gap + case .Right: layout.r -= gap + case .Top: layout.t += gap + case .Bottom: layout.b -= gap + case .Fill: + } + } + + // case .Custom: + // item_callback(item, LAYOUT_CUSTOM) + + case .Absolute: + rect_sized(&pitem.rect, pitem.layout_offset, pitem.layout_size) + + case .Relative: + rect_sized(&pitem.rect, { layout.l, layout.t } + pitem.layout_offset, pitem.layout_size) + } + + // layout children with this resultant rect for LAYOUT_CUT + layout_with := pitem.rect + kid := first_child(item) + + if pitem.layout_margin > 0 { + layout_with = rect_margin(layout_with, pitem.layout_margin) + } + + for kid > 0 { + arrange(kid, &layout_with, pitem.layout_cut_gap) + kid = next_sibling(kid) + } +} \ No newline at end of file diff --git a/lib/oui/rect.odin b/lib/oui/rect.odin new file mode 100644 index 0000000..23f2e6d --- /dev/null +++ b/lib/oui/rect.odin @@ -0,0 +1,245 @@ +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/src/main.odin b/src/main.odin index bcbaeec..17517c7 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,4 +1,5 @@ package main +import "../lib/oui" import "core:fmt" import "core:math" @@ -78,6 +79,11 @@ main :: proc() { big_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", big_font_size, nil, 0) defer UnloadFont(big_font) + // oui stuff + + c0 := oui.context_create(1028, 1028 * 8) + defer oui.context_destroy(c0) + oui.context_make_current(c0) // Setting up the timelines @@ -116,13 +122,42 @@ main :: proc() { // clicking will put a white border around the timeblock, // and display information about the block in the // 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))) // DRAW // ------------------------------------------ BeginDrawing() ClearBackground(BGCOLOR) + + // hotloop + oui.begin_layout() + + a_panel := panel() + oui.set_layout(a_panel, .Absolute) + oui.set_size(a_panel, 200, 200) + oui.set_offset(a_panel, 20, 20) + + a_button := button("Testerino", 50) + if oui.latest_clicked() { + fmt.println("CLICKO BOIO") + } + oui.set_cut(a_panel, .Left) + oui.set_height(a_button, 50) + oui.set_offset(a_button, 20, 20) + + oui.item_insert(a_panel, a_button) + + oui.end_layout() + + ui_draw(0) + // DRAW HERE OR BELOW + oui.process() + + /* DrawTextEx(font, "Date", {20, 8}, font_size, 0, RAYWHITE); DrawTextEx(font, "Calltime", {105, 8}, font_size, 0, RAYWHITE); DrawTextEx(font, "Wraptime", {f32(width)-83, 8}, font_size, 0, RAYWHITE); @@ -176,7 +211,7 @@ main :: proc() { DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, small_font_size, 0, RAYWHITE); DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, big_font_size, 0, RAYWHITE); - + */ EndDrawing() } } @@ -187,3 +222,91 @@ PBGCOLOR : rl.Color : {40, 40, 40, 255} DAY_HEIGHT :: 35 TIMELINE_START :: 175 TIMELINE_END :: -85 + +Item :: oui.Item +Call :: oui.Call + +Data_Element :: enum int { + Panel, + Button, + Text_Input, + Timeblock, +// ... +} + +Data_Head :: struct { + subtype: Data_Element, +} + +Data_Panel :: struct { + using _: Data_Head, +} + +Data_Button :: struct { + using _: Data_Head, + text: string, + selected: bool, +} + +button_callback :: proc(item: Item, event: Call) -> int { + data := cast(^Data_Button) oui.get_handle(item) + + #partial switch event { + case .Cursor_Handle: + //return int(Cursor_Type.Hand) + } + + return -1 +} + +panel :: proc() -> Item { + item := oui.item_make() + + data := oui.alloc_typed(item, Data_Panel) + data.subtype = .Panel + + return item +} + +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) + + data := oui.alloc_typed(item, Data_Button) + data.subtype = .Button + data.text = text + data.selected = selected + + return item +} + +// recursive loop +ui_draw_children :: proc(item: oui.Item) { + list := oui.children_sorted(item) + 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) + + //fmt.println(rect, head, item) + + if head == nil { + ui_draw_children(item) + return + } + + #partial switch head.subtype { + //case .Panel_Root: + // ... render any type of item + case .Button: + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), PBGCOLOR) + case .Panel: + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), rl.RED) + ui_draw_children(item) + } +} \ No newline at end of file -- cgit v1.2.1 From 408c473839fcc8bdb0f7ac843031507ae99e33f0 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Thu, 17 Aug 2023 02:35:08 +0200 Subject: Made it easier to switch back to old unfinished UI for reference --- src/main.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.odin b/src/main.odin index 17517c7..1657c0f 100644 --- a/src/main.odin +++ b/src/main.odin @@ -133,6 +133,7 @@ main :: proc() { ClearBackground(BGCOLOR) +when true { // hotloop oui.begin_layout() @@ -157,7 +158,7 @@ main :: proc() { // DRAW HERE OR BELOW oui.process() - /* +} else { DrawTextEx(font, "Date", {20, 8}, font_size, 0, RAYWHITE); DrawTextEx(font, "Calltime", {105, 8}, font_size, 0, RAYWHITE); DrawTextEx(font, "Wraptime", {f32(width)-83, 8}, font_size, 0, RAYWHITE); @@ -211,7 +212,7 @@ main :: proc() { DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, small_font_size, 0, RAYWHITE); DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, big_font_size, 0, RAYWHITE); - */ +} EndDrawing() } } -- cgit v1.2.1 From 40687c8c5624dda5bd6b9c6b0e281fb079755040 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 26 Aug 2023 12:57:44 +0200 Subject: WIP OUI stuff --- src/main.odin | 120 +++++++---------------------------------- src/ui_implementation.odin | 130 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 100 deletions(-) create mode 100644 src/ui_implementation.odin diff --git a/src/main.odin b/src/main.odin index 1657c0f..25cdf22 100644 --- a/src/main.odin +++ b/src/main.odin @@ -137,12 +137,26 @@ when true { // hotloop oui.begin_layout() - a_panel := panel() - oui.set_layout(a_panel, .Absolute) - oui.set_size(a_panel, 200, 200) - oui.set_offset(a_panel, 20, 20) - a_button := button("Testerino", 50) + master_container := panel() + oui.set_layout(master_container, .Absolute) + oui.set_size(master_container, int(GetScreenWidth()), int(GetScreenHeight())) + + top_bar := panel(BGCOLOR) + oui.set_cut(master_container, .Top) + oui.set_height(top_bar, 30) + oui.item_insert(master_container, top_bar) + + date_label := label("Date", font) + + bottom_bar := panel(PBGCOLOR) + oui.set_cut(master_container, .Bottom) + oui.set_height(bottom_bar, 50) + oui.item_insert(master_container, bottom_bar) + + + +/* a_button := button("Testerino", 50) if oui.latest_clicked() { fmt.println("CLICKO BOIO") } @@ -150,7 +164,7 @@ when true { oui.set_height(a_button, 50) oui.set_offset(a_button, 20, 20) - oui.item_insert(a_panel, a_button) + oui.item_insert(a_panel, a_button)*/ oui.end_layout() @@ -217,97 +231,3 @@ when true { } } -BGCOLOR : rl.Color : {30, 30, 30, 255} -PBGCOLOR : rl.Color : {40, 40, 40, 255} - -DAY_HEIGHT :: 35 -TIMELINE_START :: 175 -TIMELINE_END :: -85 - -Item :: oui.Item -Call :: oui.Call - -Data_Element :: enum int { - Panel, - Button, - Text_Input, - Timeblock, -// ... -} - -Data_Head :: struct { - subtype: Data_Element, -} - -Data_Panel :: struct { - using _: Data_Head, -} - -Data_Button :: struct { - using _: Data_Head, - text: string, - selected: bool, -} - -button_callback :: proc(item: Item, event: Call) -> int { - data := cast(^Data_Button) oui.get_handle(item) - - #partial switch event { - case .Cursor_Handle: - //return int(Cursor_Type.Hand) - } - - return -1 -} - -panel :: proc() -> Item { - item := oui.item_make() - - data := oui.alloc_typed(item, Data_Panel) - data.subtype = .Panel - - return item -} - -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) - - data := oui.alloc_typed(item, Data_Button) - data.subtype = .Button - data.text = text - data.selected = selected - - return item -} - -// recursive loop -ui_draw_children :: proc(item: oui.Item) { - list := oui.children_sorted(item) - 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) - - //fmt.println(rect, head, item) - - if head == nil { - ui_draw_children(item) - return - } - - #partial switch head.subtype { - //case .Panel_Root: - // ... render any type of item - case .Button: - rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), PBGCOLOR) - case .Panel: - rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), rl.RED) - ui_draw_children(item) - } -} \ No newline at end of file diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin new file mode 100644 index 0000000..4842a76 --- /dev/null +++ b/src/ui_implementation.odin @@ -0,0 +1,130 @@ +package main + +import "../lib/oui" +import rl "vendor:raylib" + + + +BGCOLOR : rl.Color : {30, 30, 30, 255} +PBGCOLOR : rl.Color : {40, 40, 40, 255} + +DAY_HEIGHT :: 35 +TIMELINE_START :: 175 +TIMELINE_END :: -85 + + + +Item :: oui.Item +Call :: oui.Call + +Data_Element :: enum int { + Panel, + Button, + Label, + Text_Input, + Timeblock, +// ... +} + +Data_Head :: struct { + subtype: Data_Element, +} + +Data_Panel :: struct { + using _: Data_Head, + color: rl.Color, +} + +Data_Button :: struct { + using _: Data_Head, + text: string, + selected: bool, +} + +Data_Label :: struct { + using _: Data_Head, + text: string, + alignment: Text_Alignment, +} + +button_callback :: proc(item: Item, event: Call) -> int { + data := cast(^Data_Button) oui.get_handle(item) + + #partial switch event { + case .Cursor_Handle: + //return int(Cursor_Type.Hand) + } + + return -1 +} + +panel :: proc(color : rl.Color = rl.RED) -> Item { + item := oui.item_make() + + data := oui.alloc_typed(item, Data_Panel) + data.subtype = .Panel + data.color = color + + return item +} + +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) + + data := oui.alloc_typed(item, Data_Button) + data.subtype = .Button + data.text = text + data.selected = selected + + return item +} + +Text_Alignment :: enum int { + Left, + Right, +} +label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> Item { + item := oui.item_make() + + data := oui.alloc_typed(item, Data_Label) + data.subtype = .Label + data.text = text + data.alignment = alignment + + return item +} + +// recursive loop +ui_draw_children :: proc(item: oui.Item) { + list := oui.children_sorted(item) + 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) + + //fmt.println(rect, head, item) + + if head == nil { + ui_draw_children(item) + return + } + + #partial switch head.subtype { + //case .Panel_Root: + // ... render any type of item + case .Button: + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), PBGCOLOR) + case .Panel: + subtyped := cast(^Data_Panel) head + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), subtyped.color) + ui_draw_children(item) + case .Label: + + } +} \ No newline at end of file -- cgit v1.2.1 From e77dec086f94da480af8c42bec6cac8e873a6931 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 12:00:49 +0200 Subject: Progressed ability to write text using new oui system --- src/main.odin | 16 ++++++++++------ src/ui_implementation.odin | 20 +++++++++++++++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main.odin b/src/main.odin index 25cdf22..96a56d5 100644 --- a/src/main.odin +++ b/src/main.odin @@ -7,6 +7,8 @@ import "core:slice" import "core:strings" import rl "vendor:raylib" +UBUNTU_MONO := #load("../res/UbuntuMono-Regular.ttf") + main :: proc() { // TODO: Replace the dynamic array of Workday-pointers with @@ -68,6 +70,7 @@ main :: proc() { // Loading fonts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - font_size :: 18 + //font: Font = LoadFontFromMemory("ttf", &UBUNTU_MONO, i32(len(UBUNTU_MONO)), font_size, nil, 0) font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", font_size, nil, 0) defer UnloadFont(font) @@ -139,20 +142,21 @@ when true { master_container := panel() - oui.set_layout(master_container, .Absolute) - oui.set_size(master_container, int(GetScreenWidth()), int(GetScreenHeight())) + oui.set_layout(master_container, .Absolute) + oui.set_size(master_container, int(GetScreenWidth()), int(GetScreenHeight())) top_bar := panel(BGCOLOR) oui.set_cut(master_container, .Top) oui.set_height(top_bar, 30) oui.item_insert(master_container, top_bar) - date_label := label("Date", font) + date_label := label("Date abcg", big_font) + oui.item_insert(top_bar, date_label) bottom_bar := panel(PBGCOLOR) - oui.set_cut(master_container, .Bottom) - oui.set_height(bottom_bar, 50) - oui.item_insert(master_container, bottom_bar) + oui.set_cut(master_container, .Bottom) + oui.set_height(bottom_bar, 50) + oui.item_insert(master_container, bottom_bar) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 4842a76..7c5f9e3 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -1,6 +1,8 @@ package main import "../lib/oui" +import "core:strings" +import "core:fmt" import rl "vendor:raylib" @@ -44,6 +46,8 @@ Data_Button :: struct { Data_Label :: struct { using _: Data_Head, text: string, + font: rl.Font, + font_size: i32, alignment: Text_Alignment, } @@ -84,6 +88,7 @@ button :: proc(text: string, width: int, selected := false) -> Item { Text_Alignment :: enum int { Left, Right, + Center } label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> Item { item := oui.item_make() @@ -91,6 +96,7 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> data := oui.alloc_typed(item, Data_Label) data.subtype = .Label data.text = text + data.font_size = font.baseSize // This should not be necesssary data.alignment = alignment return item @@ -121,10 +127,18 @@ ui_draw :: proc(item: oui.Item) { case .Button: rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), PBGCOLOR) case .Panel: - subtyped := cast(^Data_Panel) head - rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), subtyped.color) + data := cast(^Data_Panel) head + 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) + + // For some reason, data.font.baseSize == 0 here. It doesn't outside of this function. Dunno why. + font_height := f32(data.font_size) + + rl.DrawTextEx(data.font, strings.unsafe_string_to_cstring(data.text), { 0, 0 }, font_height, 0.0, rl.RAYWHITE); + //rl.DrawTextEx(rl.GetFontDefault(), strings.unsafe_string_to_cstring(data.text), { 0, 0 }, 40, 0, rl.WHITE); + //rl.DrawFPS(0, 0) + //fmt.println(font_height) } } \ No newline at end of file -- cgit v1.2.1 From da32e6dfa55f6550185e8a9b5e7c7f51f41abac7 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 12:40:59 +0200 Subject: Added test.ics to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 44f6d15..63c70cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ __pycache__ obj/*.o bin/ +res/test.ics a.out a.exe main.exe @@ -13,4 +14,4 @@ satscalc.exe satscalc32.exe notes.txt *.pdb -remedy.rdbg +remedy.rdbg \ No newline at end of file -- cgit v1.2.1 From 0256cea0b389506bc97d1c026d566706e1cc8e76 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 18:29:41 +0200 Subject: Fixed font loading in labels --- src/ui_implementation.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 7c5f9e3..594e5a1 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -88,7 +88,7 @@ button :: proc(text: string, width: int, selected := false) -> Item { Text_Alignment :: enum int { Left, Right, - Center + Center, } label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> Item { item := oui.item_make() @@ -97,6 +97,7 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> data.subtype = .Label data.text = text data.font_size = font.baseSize // This should not be necesssary + data.font = font data.alignment = alignment return item @@ -135,7 +136,7 @@ ui_draw :: proc(item: oui.Item) { // For some reason, data.font.baseSize == 0 here. It doesn't outside of this function. Dunno why. font_height := f32(data.font_size) - + rl.DrawTextEx(data.font, strings.unsafe_string_to_cstring(data.text), { 0, 0 }, font_height, 0.0, rl.RAYWHITE); //rl.DrawTextEx(rl.GetFontDefault(), strings.unsafe_string_to_cstring(data.text), { 0, 0 }, 40, 0, rl.WHITE); //rl.DrawFPS(0, 0) -- cgit v1.2.1 From f9a5f13dcb305ecc868346fa65e9538504896baf Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 18:29:49 +0200 Subject: Added debug flag to makefile --- makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/makefile b/makefile index f9f3042..ec24455 100644 --- a/makefile +++ b/makefile @@ -1,2 +1,2 @@ main: - odin run src/ -out:main.out + odin run src/ -debug -out:main.out -- cgit v1.2.1 From a32fc0f0f6bf3acc18e03fb436564cc135717157 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 18:33:26 +0200 Subject: Leveraged fix to remove redundancies --- src/ui_implementation.odin | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 594e5a1..5aa294c 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -96,7 +96,6 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> data := oui.alloc_typed(item, Data_Label) data.subtype = .Label data.text = text - data.font_size = font.baseSize // This should not be necesssary data.font = font data.alignment = alignment @@ -134,10 +133,7 @@ ui_draw :: proc(item: oui.Item) { case .Label: data := cast(^Data_Label) oui.get_handle(item) - // For some reason, data.font.baseSize == 0 here. It doesn't outside of this function. Dunno why. - font_height := f32(data.font_size) - - rl.DrawTextEx(data.font, strings.unsafe_string_to_cstring(data.text), { 0, 0 }, font_height, 0.0, rl.RAYWHITE); + rl.DrawTextEx(data.font, strings.unsafe_string_to_cstring(data.text), { 0, 0 }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); //rl.DrawTextEx(rl.GetFontDefault(), strings.unsafe_string_to_cstring(data.text), { 0, 0 }, 40, 0, rl.WHITE); //rl.DrawFPS(0, 0) //fmt.println(font_height) -- cgit v1.2.1 From a8ae49ea33b9575597012cb5b3927583184c4c73 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 18:47:06 +0200 Subject: Fonts are now embedded in the program --- src/main.odin | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main.odin b/src/main.odin index 96a56d5..1ae20c6 100644 --- a/src/main.odin +++ b/src/main.odin @@ -70,16 +70,18 @@ main :: proc() { // Loading fonts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - font_size :: 18 - //font: Font = LoadFontFromMemory("ttf", &UBUNTU_MONO, i32(len(UBUNTU_MONO)), font_size, nil, 0) - font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", font_size, nil, 0) + font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), font_size, nil, 0) + //font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", font_size, nil, 0) defer UnloadFont(font) small_font_size :: 14 - small_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", small_font_size, nil, 0) + small_font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), small_font_size, nil, 0) + //small_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", small_font_size, nil, 0) defer UnloadFont(small_font) big_font_size :: 24 - big_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", big_font_size, nil, 0) + big_font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), big_font_size, nil, 0) + //big_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", big_font_size, nil, 0) defer UnloadFont(big_font) // oui stuff @@ -150,7 +152,7 @@ when true { oui.set_height(top_bar, 30) oui.item_insert(master_container, top_bar) - date_label := label("Date abcg", big_font) + date_label := label("Date abcg", font) oui.item_insert(top_bar, date_label) bottom_bar := panel(PBGCOLOR) @@ -234,4 +236,3 @@ when true { EndDrawing() } } - -- cgit v1.2.1 From 1197fa1b73bb60dd7bbc67d046e5f8172c3dcfab Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Fri, 13 Oct 2023 19:11:55 +0200 Subject: Added text justification setting --- src/main.odin | 2 +- src/ui_implementation.odin | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main.odin b/src/main.odin index 1ae20c6..e06639f 100644 --- a/src/main.odin +++ b/src/main.odin @@ -152,7 +152,7 @@ when true { oui.set_height(top_bar, 30) oui.item_insert(master_container, top_bar) - date_label := label("Date abcg", font) + date_label := label("Date", font) oui.item_insert(top_bar, date_label) bottom_bar := panel(PBGCOLOR) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 5aa294c..4706728 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -45,7 +45,7 @@ Data_Button :: struct { Data_Label :: struct { using _: Data_Head, - text: string, + text: cstring, font: rl.Font, font_size: i32, alignment: Text_Alignment, @@ -86,6 +86,7 @@ button :: proc(text: string, width: int, selected := false) -> Item { } Text_Alignment :: enum int { + // Techically called justification, but text_alignment is more self-explanatory. Left, Right, Center, @@ -95,7 +96,7 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> data := oui.alloc_typed(item, Data_Label) data.subtype = .Label - data.text = text + data.text = strings.unsafe_string_to_cstring(text) data.font = font data.alignment = alignment @@ -132,10 +133,18 @@ ui_draw :: proc(item: oui.Item) { ui_draw_children(item) case .Label: data := cast(^Data_Label) oui.get_handle(item) - - rl.DrawTextEx(data.font, strings.unsafe_string_to_cstring(data.text), { 0, 0 }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); - //rl.DrawTextEx(rl.GetFontDefault(), strings.unsafe_string_to_cstring(data.text), { 0, 0 }, 40, 0, rl.WHITE); - //rl.DrawFPS(0, 0) - //fmt.println(font_height) + + horizontal_position : f32 + + switch data.alignment { + case .Left: + horizontal_position = f32(rect.l) + case .Right: + horizontal_position = f32(rect.l) - rl.MeasureTextEx(data.font, data.text, f32(data.font.baseSize), 0.0).x + case .Center: + horizontal_position = f32(rect.l) - f32(int((rl.MeasureTextEx(data.font, data.text, f32(data.font.baseSize), 0.0).x)/2)) + } + + rl.DrawTextEx(data.font, data.text, { horizontal_position, f32(rect.t+50) }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); } } \ No newline at end of file -- cgit v1.2.1 From 56b701ded16bad6f4599be61263c1574d5297288 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 12:14:02 +0200 Subject: Fonts are now in the global scope --- src/main.odin | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main.odin b/src/main.odin index e06639f..9689598 100644 --- a/src/main.odin +++ b/src/main.odin @@ -9,6 +9,10 @@ import rl "vendor:raylib" UBUNTU_MONO := #load("../res/UbuntuMono-Regular.ttf") +font : rl.Font +big_font : rl.Font +small_font : rl.Font + main :: proc() { // TODO: Replace the dynamic array of Workday-pointers with @@ -70,18 +74,15 @@ main :: proc() { // Loading fonts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - font_size :: 18 - font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), font_size, nil, 0) - //font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", font_size, nil, 0) + font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), font_size, nil, 0) defer UnloadFont(font) small_font_size :: 14 - small_font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), small_font_size, nil, 0) - //small_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", small_font_size, nil, 0) + small_font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), small_font_size, nil, 0) defer UnloadFont(small_font) big_font_size :: 24 - big_font: Font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), big_font_size, nil, 0) - //big_font: Font = LoadFontEx("res/UbuntuMono-Regular.ttf", big_font_size, nil, 0) + big_font = LoadFontFromMemory(".ttf", raw_data(UBUNTU_MONO), i32(len(UBUNTU_MONO)), big_font_size, nil, 0) defer UnloadFont(big_font) // oui stuff -- cgit v1.2.1 From d6cb7ac37d86d0c4ee03982813b94275fd62016a Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 12:18:23 +0200 Subject: Added cute padding to text --- src/main.odin | 3 ++- src/ui_implementation.odin | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.odin b/src/main.odin index 9689598..ac3f693 100644 --- a/src/main.odin +++ b/src/main.odin @@ -151,9 +151,10 @@ when true { 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) - date_label := label("Date", font) + date_label := label("Date", font, .Left) oui.item_insert(top_bar, date_label) bottom_bar := panel(PBGCOLOR) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 4706728..27bcc65 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -145,6 +145,6 @@ ui_draw :: proc(item: oui.Item) { horizontal_position = f32(rect.l) - f32(int((rl.MeasureTextEx(data.font, data.text, f32(data.font.baseSize), 0.0).x)/2)) } - rl.DrawTextEx(data.font, data.text, { horizontal_position, f32(rect.t+50) }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); + rl.DrawTextEx(data.font, data.text, { horizontal_position, f32(rect.t) }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); } } \ No newline at end of file -- cgit v1.2.1 From fd262c11c3c0f627927ecc7fd5115899033018bb Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 13:11:39 +0200 Subject: New OUI version --- lib/oui/oui.odin | 1120 +++++++++++++++++++------------------------- lib/oui/rect.odin | 245 ---------- lib/rect/rect.odin | 133 ++++++ src/main.odin | 53 ++- src/ui_implementation.odin | 59 ++- 5 files changed, 680 insertions(+), 930 deletions(-) delete mode 100644 lib/oui/rect.odin create mode 100644 lib/rect/rect.odin 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.. ^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.. 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.. 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.. (res: ^Item) { + for i in 0.. 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..= 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.. 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 -- cgit v1.2.1 From fca6955979f4791acb016ef5da86fed1013b524e Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 14:03:07 +0200 Subject: There are themes! There is button with text! There is increased comfy! --- src/main.odin | 22 ++++++++++---- src/ui_implementation.odin | 71 +++++++++++++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/main.odin b/src/main.odin index 79b1a1e..fcb425d 100644 --- a/src/main.odin +++ b/src/main.odin @@ -111,6 +111,8 @@ main :: proc() { for !WindowShouldClose() { // MAIN LOOP ---- MAIN LOOP ---- MAIN LOOP ---- MAIN LOOP + free_all(context.temp_allocator) + if IsWindowResized() { height = GetScreenHeight() width = GetScreenWidth() @@ -140,7 +142,7 @@ main :: proc() { // ------------------------------------------ BeginDrawing() - ClearBackground(BGCOLOR) + ClearBackground(rl.RED) when true { // hotloop @@ -153,7 +155,7 @@ when true { master_container.layout_size = {int(GetScreenWidth()), int(GetScreenHeight())} { - top_bar := panel_line(master_container, BGCOLOR, 30) + top_bar := panel_line(master_container, theme.background_top, 30) top_bar.id = oui.push_id(c0, "small_boie") defer oui.pop_id(c0) top_bar.layout_margin = 6 @@ -161,9 +163,9 @@ when true { date_label := label("Date", font, .Left) oui.item_insert(top_bar, date_label) } - + { - bottom_bar := panel(PBGCOLOR) + bottom_bar := panel(theme.background_bottom) bottom_bar.id = oui.push_id(c0, "not_small_boie") defer oui.pop_id(c0) bottom_bar.layout_cut_children = .Left @@ -173,11 +175,21 @@ when true { } { - middle_section := panel(WBGCOLOR) + middle_section := panel(theme.background) 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) + + { + line := panel_line(middle_section, theme.background, 40) + line.id = oui.push_id(c0, "a_line") + defer oui.pop_id(c0) + { + a_button := button("Testor", 100) + oui.item_insert(line, a_button) + } + } } diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index cf38cc3..3e9b913 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -6,10 +6,21 @@ import "core:fmt" import rl "vendor:raylib" +Theme :: struct { + background: rl.Color, + background_top: rl.Color, + background_bottom: rl.Color, + button: rl.Color, + text: rl.Color, +} -BGCOLOR : rl.Color : {30, 30, 30, 255} -WBGCOLOR : rl.Color : {20, 25, 25, 255} -PBGCOLOR : rl.Color : {40, 40, 40, 255} +theme : Theme = { + background = {20, 25, 25, 255}, + background_top = {30, 35, 35, 255}, + background_bottom = {40, 40, 40, 255}, + button = {80, 80, 80, 255}, + text = rl.RAYWHITE, +} DAY_HEIGHT :: 35 TIMELINE_START :: 175 @@ -46,7 +57,7 @@ Data_Button :: struct { Data_Label :: struct { using _: Data_Head, - text: cstring, + text: string, font: rl.Font, font_size: i32, alignment: Text_Alignment, @@ -114,17 +125,39 @@ label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> data := oui.alloc_typed(c0, item, Data_Label) data.subtype = .Label - data.text = strings.unsafe_string_to_cstring(text) + data.text = text data.font = font data.alignment = alignment return item } +calculate_text_alignment :: proc(text: cstring, font: rl.Font, alignment: Text_Alignment, rect: oui.RectI) -> (output: [2]int) { + + measurement := rl.MeasureTextEx(font, text, f32(font.baseSize), 0.0) + + switch alignment { + case .Left: + output.x = rect.l + case .Right: + output.x = rect.r - int(measurement.x) + case .Center: + output.x = (rect.l+(rect.r-rect.l)/2) - int(measurement.x/2) + } + + output.y = (rect.t+(rect.b-rect.t)/2) - int(measurement.y/2) + + return +} +i2f :: proc "contextless" (input: [2]int) -> rl.Vector2 { + return { f32(input.x), f32(input.y) } +} +f2i :: proc "contextless" (input: [2]f32) -> [2]int { + return { int(input.x), int(input.y) } +} // recursive loop 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) } @@ -145,25 +178,23 @@ ui_draw :: proc(item: ^oui.Item) { //case .Panel_Root: // ... render any type of item case .Button: - rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), PBGCOLOR) + data := cast(^Data_Button) item.handle + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.button) + + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, font, .Center, rect) + + rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); + case .Panel: data := cast(^Data_Panel) head 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) item.handle - - horizontal_position : f32 - - switch data.alignment { - case .Left: - horizontal_position = f32(rect.l) - case .Right: - horizontal_position = f32(rect.l) - rl.MeasureTextEx(data.font, data.text, f32(data.font.baseSize), 0.0).x - case .Center: - horizontal_position = f32(rect.l) - f32(int((rl.MeasureTextEx(data.font, data.text, f32(data.font.baseSize), 0.0).x)/2)) - } - - rl.DrawTextEx(data.font, data.text, { horizontal_position, f32(rect.t) }, f32(data.font.baseSize), 0.0, rl.RAYWHITE); + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, data.font, data.alignment, rect) + + rl.DrawTextEx(data.font, text, i2f(position), f32(data.font.baseSize), 0.0, theme.text); } } \ No newline at end of file -- cgit v1.2.1 From 44d408583ab8811f2b89d8fcb9e21b98f1409b7d Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 16:19:16 +0200 Subject: There is a theme editor now??? --- src/main.odin | 65 +++++++++++---------- src/time.odin | 14 ++--- src/ui_implementation.odin | 139 +++++++++++++++++++++++++++++++++------------ 3 files changed, 145 insertions(+), 73 deletions(-) diff --git a/src/main.odin b/src/main.odin index fcb425d..bed835a 100644 --- a/src/main.odin +++ b/src/main.odin @@ -4,6 +4,7 @@ import "../lib/oui" import "core:fmt" import "core:math" import "core:slice" +import "core:runtime" import "core:strings" import rl "vendor:raylib" @@ -136,7 +137,8 @@ main :: proc() { mousePosition: = rl.GetMousePosition() oui.set_cursor(c0, int(mousePosition.x), int(mousePosition.y)) - oui.set_button(c0, .Left, rl.IsMouseButtonPressed(rl.MouseButton(0))) + if rl.IsMouseButtonPressed(rl.MouseButton(0)) do oui.set_button(c0, .Left, true) + if rl.IsMouseButtonReleased(rl.MouseButton(0)) do oui.set_button(c0, .Left, false) // DRAW // ------------------------------------------ @@ -150,22 +152,24 @@ when true { master_container := panel() - master_container.id = oui.push_id(c0, "big_mr_boss_man") + master_container.id = oui.push_id(c0, "big_mr_boss_man") // Make ID for master thing just because. + // Does not need to be freed because master. master_container.layout = .Absolute master_container.layout_size = {int(GetScreenWidth()), int(GetScreenHeight())} { - top_bar := panel_line(master_container, theme.background_top, 30) - top_bar.id = oui.push_id(c0, "small_boie") - defer oui.pop_id(c0) + top_bar := panel_line(master_container, theme.background_bar, 30) + top_bar.id = oui.push_id(c0, "small_boie") // Make ID for anything that will have children. + defer oui.pop_id(c0) // These need to be pop'ed before the next item at the same level in the hierachy. top_bar.layout_margin = 6 - date_label := label("Date", font, .Left) - oui.item_insert(top_bar, date_label) + oui.item_insert(top_bar, label("Date", font, .Left)) + oui.item_insert(top_bar, label("Calltime", font, .Left)) + oui.item_insert(top_bar, label("Lunch", font, .Left)) } { - bottom_bar := panel(theme.background_bottom) + bottom_bar := panel(theme.background_bar) bottom_bar.id = oui.push_id(c0, "not_small_boie") defer oui.pop_id(c0) bottom_bar.layout_cut_children = .Left @@ -178,32 +182,35 @@ when true { middle_section := panel(theme.background) middle_section.id = oui.push_id(c0, "middle_section") defer oui.pop_id(c0) + middle_section.layout_margin = 10 // Spacing from edges + middle_section.layout_cut_gap = 5 // Spacing between children master_container.layout_cut_children = .Fill oui.item_insert(master_container, middle_section) - { - line := panel_line(middle_section, theme.background, 40) - line.id = oui.push_id(c0, "a_line") + // To loop over the members of a struct you need to do this goofy shit: + info := runtime.type_info_base(type_info_of(Theme)) + st := info.variant.(runtime.Type_Info_Struct) + root := uintptr(&theme) + for offset, i in st.offsets { + + line := panel_line(middle_section, theme.background, 25) + line.layout_cut_gap = 10 + line.id = oui.push_id(c0, fmt.tprintf("line_%d", i)) defer oui.pop_id(c0) - { - a_button := button("Testor", 100) - oui.item_insert(line, a_button) - } + + oui.item_insert(line, label(st.names[i], font, )) + + // To then access the member of the struct you're looping over + // you need to do this shit: + // v------------------------v + color_sliders(line, cast(^Color) (root+offset)) } - } - - - -/* a_button := button("Testerino", 50) - if oui.latest_clicked() { - fmt.println("CLICKO BOIO") + output_theme_button := button("output theme", 100) + oui.item_insert(middle_section, output_theme_button) + if oui.is_clicked(c0, output_theme_button) do fmt.printf("%#v", theme) } - oui.set_cut(a_panel, .Left) - oui.set_height(a_button, 50) - oui.set_offset(a_button, 20, 20) - - oui.item_insert(a_panel, a_button)*/ + oui.end_layout(c0) @@ -227,7 +234,7 @@ when true { // (At least, given how lunch breaks are currently implemented, // as holes in the workday) - DrawRectangle(10, DAY_HEIGHT*i32(i+1)-4, width-20, DAY_HEIGHT-1, PBGCOLOR) + DrawRectangle(10, DAY_HEIGHT*i32(i+1)-4, width-20, DAY_HEIGHT-1, theme.background_top) for block, j in day.blocks { if j == day.total_timeblocks do break block_color: = GREEN @@ -261,7 +268,7 @@ when true { } } - DrawRectangle(0, height-50, width+10, 60, PBGCOLOR) + DrawRectangle(0, height-50, width+10, 60, theme.background_top) DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, small_font_size, 0, RAYWHITE); DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, big_font_size, 0, RAYWHITE); diff --git a/src/time.odin b/src/time.odin index 44c810a..b607e51 100644 --- a/src/time.odin +++ b/src/time.odin @@ -89,7 +89,7 @@ new_workday :: proc(previous_wrap : Moment, // Paragraph 6.7 says that up to 2 hours of unused warned overtime counts as worktime, // though so that at least one hour of the unused overtime is not counted. // (It's unclear if an 8-hour day that ends 3 hours in counts as having 5 hours of unused overtime) - max(clamp(sub(planned_wrap, {0, 1, 0}), wrap, add(wrap, {0, 2, 0})), + time_max(time_clamp(sub(planned_wrap, {0, 1, 0}), wrap, add(wrap, {0, 2, 0})), add(call, {0, 4, 0})), 1, ""} // ^ Minimum 4 hour day ^ @@ -169,7 +169,7 @@ new_workday :: proc(previous_wrap : Moment, if getweekday(block.start) == .Sunday do upvalue(&block, 2, "Sunday") // Sundays are +100% if !(less(call, Moment{0, 7, call.day, call.month, call.year}) && - less(min(add(call, Delta{0,8,0}), wrap), Moment{0, 17, call.day, call.month, call.year} )) { + less(time_min(add(call, Delta{0,8,0}), wrap), Moment{0, 17, call.day, call.month, call.year} )) { // This was added for rule 6.11c, but in a world without a defined normal workday, // that rule is already covered already by 6.11g, so this is empty. } @@ -389,7 +389,7 @@ maxDelta :: proc(delta_a: Delta, delta_b: Delta) -> Delta { if sortable(delta_a) > sortable(delta_b) do return delta_a return delta_b } -max :: proc{maxDelta, maxMoment} +time_max :: proc{maxDelta, maxMoment} minMoment :: proc(moment_a: Moment, moment_b: Moment) -> Moment { if sortable(moment_a) < sortable(moment_b) do return moment_a @@ -399,15 +399,15 @@ minDelta :: proc(delta_a: Delta, delta_b: Delta) -> Delta { if sortable(delta_a) < sortable(delta_b) do return delta_a return delta_b } -min :: proc{minDelta, minMoment} +time_min :: proc{minDelta, minMoment} clampMoment :: proc(moment: Moment, moment_min: Moment, moment_max: Moment) -> Moment { - return min(max(moment, moment_min), moment_max) + return time_min(time_max(moment, moment_min), moment_max) } clampDelta :: proc(delta: Delta, delta_min: Delta, delta_max: Delta) -> Delta { - return min(max(delta, delta_min), delta_max) + return time_min(time_max(delta, delta_min), delta_max) } -clamp :: proc{clampMoment, clampDelta} +time_clamp :: proc{clampMoment, clampDelta} greatMoment :: proc(moment_a: Moment, moment_b: Moment) -> bool { return bool(sortable(moment_a) > sortable(moment_b)) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 3e9b913..6b0947d 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -8,32 +8,43 @@ import rl "vendor:raylib" Theme :: struct { background: rl.Color, - background_top: rl.Color, - background_bottom: rl.Color, + background_bar: rl.Color, button: rl.Color, + base: rl.Color, + slider_bar: rl.Color, text: rl.Color, } -theme : Theme = { +/*theme : Theme = { background = {20, 25, 25, 255}, background_top = {30, 35, 35, 255}, background_bottom = {40, 40, 40, 255}, button = {80, 80, 80, 255}, + base = {60, 60, 60, 255}, + slider_bar = {170, 170, 170, 255}, text = rl.RAYWHITE, +}*/ + +theme : Theme = { + background = {25 , 27 , 29 , 255,}, + background_bar = {43 , 43 , 48 , 255,}, + button = {91 , 91 , 204, 255,}, + base = {60 , 60 , 60 , 255,}, + slider_bar = {91 , 91 , 204, 255,}, + text = {255, 255, 255, 252,}, } DAY_HEIGHT :: 35 TIMELINE_START :: 175 TIMELINE_END :: -85 - - Item :: oui.Item Call :: oui.Call Data_Element :: enum int { Panel, Button, + Slider, Label, Text_Input, Timeblock, @@ -55,6 +66,12 @@ Data_Button :: struct { selected: bool, } +Data_Slider :: struct { + using _: Data_Head, + text: string, + value: ^u8, +} + Data_Label :: struct { using _: Data_Head, text: string, @@ -63,17 +80,6 @@ Data_Label :: struct { alignment: Text_Alignment, } -button_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { - data := cast(^Data_Button) item.handle - - #partial switch event { - case .Cursor_Handle: - //return int(Cursor_Type.Hand) - } - - return -1 -} - panel :: proc(color : rl.Color = rl.RED) -> ^Item { item := oui.item_make(c0) @@ -105,6 +111,7 @@ button :: proc(text: string, width: int, selected := false) -> ^Item { item := oui.item_make(c0) item.layout_size = {width, 35} item.callback = button_callback + item.id = oui.gen_id(c0, text) data := oui.alloc_typed(c0, item, Data_Button) data.subtype = .Button @@ -113,6 +120,50 @@ button :: proc(text: string, width: int, selected := false) -> ^Item { return item } +button_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { + data := cast(^Data_Button) item.handle + + #partial switch event { + case .Cursor_Handle: + //return int(Cursor_Type.Hand) + } + + return -1 +} + +slider :: proc(id: string, text: string, width: int, value: ^u8) -> ^Item { + item := oui.item_make(c0) + item.layout_size = {width, 25} + item.id = oui.gen_id(c0, id) + item.callback = slider_callback + + data := oui.alloc_typed(c0, item, Data_Slider) + data.subtype = .Slider + data.text = text + data.value = value + + return item +} +slider_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { + data := cast(^Data_Slider) item.handle + rect := item.bounds + + #partial switch event { + case .Left_Capture: + cursor_position := clamp(oui.get_cursor(c0).x, rect.l, rect.r) + + data.value^ = u8(255*(f32(cursor_position - rect.l) / f32(rect.r - rect.l))) + } + + return -1 +} +color_sliders :: proc(parent: ^Item, color: ^rl.Color) { + width :: 167 + oui.item_insert(parent, slider("slider_r", fmt.tprintf("%d", color.r), width, &color.r)) + oui.item_insert(parent, slider("slider_g", fmt.tprintf("%d", color.g), width, &color.g)) + oui.item_insert(parent, slider("slider_b", fmt.tprintf("%d", color.b), width, &color.b)) + oui.item_insert(parent, slider("slider_a", fmt.tprintf("%d", color.a), width, &color.a)) +} Text_Alignment :: enum int { // Techically called justification, but text_alignment is more self-explanatory. @@ -122,6 +173,7 @@ Text_Alignment :: enum int { } label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> ^Item { item := oui.item_make(c0) + item.layout_size = {150, 25} data := oui.alloc_typed(c0, item, Data_Label) data.subtype = .Label @@ -175,26 +227,39 @@ ui_draw :: proc(item: ^oui.Item) { } #partial switch head.subtype { - //case .Panel_Root: - // ... render any type of item - case .Button: - data := cast(^Data_Button) item.handle - rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.button) - - text := strings.clone_to_cstring(data.text, context.temp_allocator) - position := calculate_text_alignment(text, font, .Center, rect) - - rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); - - case .Panel: - data := cast(^Data_Panel) head - 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) item.handle - text := strings.clone_to_cstring(data.text, context.temp_allocator) - position := calculate_text_alignment(text, data.font, data.alignment, rect) - - rl.DrawTextEx(data.font, text, i2f(position), f32(data.font.baseSize), 0.0, theme.text); + //case .Panel_Root: + // ... render any type of item + case .Button: + data := cast(^Data_Button) item.handle + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.button) + + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, font, .Center, rect) + + rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); + + //fmt.println(item.anim) + + case .Slider: + data := cast(^Data_Slider) head + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.base) + rl.DrawRectangle(i32(rect.l+1), i32(rect.t+1), i32(f32(rect.r-rect.l)*(f32(data.value^)/255)-2), i32(rect.b-rect.t-2), theme.slider_bar) + + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, font, .Center, rect) + + rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); + + case .Panel: + data := cast(^Data_Panel) head + 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) item.handle + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, data.font, data.alignment, rect) + + rl.DrawTextEx(data.font, text, i2f(position), f32(data.font.baseSize), 0.0, theme.text); } } \ No newline at end of file -- cgit v1.2.1 From a437a92bb39894beb91d6c4308d04852bc0d5eac Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 17:05:01 +0200 Subject: Advancements on theme and layout --- src/main.odin | 35 ++++++++++++++++++++++++----------- src/ui_implementation.odin | 22 ++++++++++------------ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/main.odin b/src/main.odin index bed835a..e0a4507 100644 --- a/src/main.odin +++ b/src/main.odin @@ -159,23 +159,36 @@ when true { { top_bar := panel_line(master_container, theme.background_bar, 30) - top_bar.id = oui.push_id(c0, "small_boie") // Make ID for anything that will have children. + top_bar.id = oui.push_id(c0, "top_bar") // Make ID for anything that will have children. defer oui.pop_id(c0) // These need to be pop'ed before the next item at the same level in the hierachy. top_bar.layout_margin = 6 - oui.item_insert(top_bar, label("Date", font, .Left)) - oui.item_insert(top_bar, label("Calltime", font, .Left)) - oui.item_insert(top_bar, label("Lunch", font, .Left)) + oui.item_insert(top_bar, label("Date", font, 100, .Center)) + oui.item_insert(top_bar, label("Calltime", font, 100, .Center)) + oui.item_insert(top_bar, label("Lunch", font, 100, .Center)) } { bottom_bar := panel(theme.background_bar) - bottom_bar.id = oui.push_id(c0, "not_small_boie") + bottom_bar.id = oui.push_id(c0, "bottom_bar") defer oui.pop_id(c0) bottom_bar.layout_cut_children = .Left master_container.layout_cut_children = .Bottom bottom_bar.layout_size.y = 50 + bottom_bar.layout_margin = 10 // Spacing from edges oui.item_insert(master_container, bottom_bar) + + pre_sos_price := label("120 000 kr", big_font, 0, .Right) + bottom_bar.layout_cut_children = .Right + oui.item_insert(bottom_bar, pre_sos_price) + + post_sos_price := label("160 000 kr", small_font, 300, .Right) + bottom_bar.layout_cut_children = .Right + oui.item_insert(bottom_bar, post_sos_price) + + price_reason := label("Reason for price of highlighted timeblock", font, 300,) + bottom_bar.layout_cut_children = .Left + oui.item_insert(bottom_bar, price_reason) } { @@ -234,17 +247,17 @@ when true { // (At least, given how lunch breaks are currently implemented, // as holes in the workday) - DrawRectangle(10, DAY_HEIGHT*i32(i+1)-4, width-20, DAY_HEIGHT-1, theme.background_top) + DrawRectangle(10, DAY_HEIGHT*i32(i+1)-4, width-20, DAY_HEIGHT-1, theme.background_bar) for block, j in day.blocks { if j == day.total_timeblocks do break - block_color: = GREEN + block_color: = theme.price_100 switch { case block.value > 2.1: - block_color = PURPLE + block_color = theme.price_300 case block.value > 1.6: - block_color = RED + block_color = theme.price_200 case block.value > 1.1: - block_color = ORANGE + block_color = theme.price_150 } DrawRectangle(TIMELINE_START+i32(math.round(day.fractions[j].start*f32(width+TIMELINE_END-TIMELINE_START))), @@ -268,7 +281,7 @@ when true { } } - DrawRectangle(0, height-50, width+10, 60, theme.background_top) + DrawRectangle(0, height-50, width+10, 60, theme.background_bar) DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, small_font_size, 0, RAYWHITE); DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, big_font_size, 0, RAYWHITE); diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index 6b0947d..e4b4f80 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -13,18 +13,12 @@ Theme :: struct { base: rl.Color, slider_bar: rl.Color, text: rl.Color, + price_100: rl.Color, + price_150: rl.Color, + price_200: rl.Color, + price_300: rl.Color, } -/*theme : Theme = { - background = {20, 25, 25, 255}, - background_top = {30, 35, 35, 255}, - background_bottom = {40, 40, 40, 255}, - button = {80, 80, 80, 255}, - base = {60, 60, 60, 255}, - slider_bar = {170, 170, 170, 255}, - text = rl.RAYWHITE, -}*/ - theme : Theme = { background = {25 , 27 , 29 , 255,}, background_bar = {43 , 43 , 48 , 255,}, @@ -32,6 +26,10 @@ theme : Theme = { base = {60 , 60 , 60 , 255,}, slider_bar = {91 , 91 , 204, 255,}, text = {255, 255, 255, 252,}, + price_100 = {30 , 240, 30 , 255,}, + price_150 = {240, 200, 30 , 255,}, + price_200 = {240, 30 , 30 , 255,}, + price_300 = {240, 30 , 240, 255,}, } DAY_HEIGHT :: 35 @@ -171,9 +169,9 @@ Text_Alignment :: enum int { Right, Center, } -label :: proc(text: string, font: rl.Font, alignment: Text_Alignment = .Left) -> ^Item { +label :: proc(text: string, font: rl.Font, width: int = 150, alignment: Text_Alignment = .Left) -> ^Item { item := oui.item_make(c0) - item.layout_size = {150, 25} + item.layout_size = {width, 25} data := oui.alloc_typed(c0, item, Data_Label) data.subtype = .Label -- cgit v1.2.1 From 68cb0ea6b77d59618b9e92065e3e1b3f8040f588 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Sat, 14 Oct 2023 17:36:08 +0200 Subject: Increased animation speed in oui --- lib/oui/oui.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/oui/oui.odin b/lib/oui/oui.odin index 2933b8c..a1fbbf8 100644 --- a/lib/oui/oui.odin +++ b/lib/oui/oui.odin @@ -395,7 +395,7 @@ end_layout :: proc(ctx: ^Context) #no_bounds_check { ctx.stage = .Post_Layout // update hot/active for animation - speed := f32(0.05) + speed := f32(0.50) for i in 0.. FRACT_MAX do FRACT_MAX = fract.end + if i+1 == day.total_timeblocks do break + } + + line := panel_line(middle_section, theme.background) + line.layout_cut_children = .Left + line.layout_cut_gap = 0 + line.layout_margin = 0 + line.layout_size.y = sizings.timeline + oui.item_insert(line, label(dayprint(day.call), font, sizings.date, .Center)) + oui.item_insert(line, label(clockprint(day.call), font, sizings.call, .Center)) + + line.layout_cut_children = .Right + oui.item_insert(line, label("3500 kr", font, sizings.price, .Center)) + oui.item_insert(line, label(clockprint(day.wrap), font, sizings.wrap, .Center)) + + line.layout_cut_children = .Fill + timeline(line, day) + } + new_workday := button("+", 100) + middle_section.layout_cut_children = .Top + oui.item_insert(middle_section, new_workday) + if oui.is_clicked(c0, new_workday) do fmt.println("NEW WORKDAY!") + + + + // - - - - SIZINGS EDITOR - - - - + { + oui.item_insert(middle_section, label("Sizings Editor", big_font, 100, .Center)) - line := panel_line(middle_section, theme.background, 25) - line.layout_cut_gap = 10 - line.id = oui.push_id(c0, fmt.tprintf("line_%d", i)) - defer oui.pop_id(c0) + // To loop over the members of a struct you need to do this goofy shit: + info := runtime.type_info_base(type_info_of(Sizings)) + st := info.variant.(runtime.Type_Info_Struct) + root := uintptr(&sizings) + for offset, i in st.offsets { - oui.item_insert(line, label(st.names[i], font, )) + line := panel_line(middle_section, theme.background, 25) + line.id = oui.push_id(c0, fmt.tprintf("sizings_line_%d", i)) + defer oui.pop_id(c0) + + oui.item_insert(line, label(st.names[i], font, )) + + // To then access the member of the struct you're looping over + // you need to do this shit: + // v----------------------v + current_value := cast(^int) (root+offset) + + oui.item_insert(line, slider_int(fmt.tprintf("sizings-%d", i), fmt.tprintf("%d", current_value^), 300, current_value, 0, 200)) + } + output_button := button("output sizings", 100) + middle_section.layout_cut_children = .Top + oui.item_insert(middle_section, output_button) + if oui.is_clicked(c0, output_button) do fmt.printf("%#v", sizings) + } + + + // - - - - THEME EDITOR - - - - + { + oui.item_insert(middle_section, label("Theme Editor", big_font, 100, .Center)) + + // To loop over the members of a struct you need to do this goofy shit: + info := runtime.type_info_base(type_info_of(Theme)) + st := info.variant.(runtime.Type_Info_Struct) + root := uintptr(&theme) + for offset, i in st.offsets { - // To then access the member of the struct you're looping over - // you need to do this shit: - // v------------------------v - color_sliders(line, cast(^Color) (root+offset)) + line := panel_line(middle_section, theme.background, 25) + line.layout_cut_gap = 10 + line.id = oui.push_id(c0, fmt.tprintf("line_%d", i)) + defer oui.pop_id(c0) + + oui.item_insert(line, label(st.names[i], font, )) + + // To then access the member of the struct you're looping over + // you need to do this shit: + // v------------------------v + color_sliders(line, cast(^Color) (root+offset)) + } + output_theme_button := button("output theme", 100) + middle_section.layout_cut_children = .Top + oui.item_insert(middle_section, output_theme_button) + if oui.is_clicked(c0, output_theme_button) do fmt.printf("%#v", theme) } - output_theme_button := button("output theme", 100) - oui.item_insert(middle_section, output_theme_button) - if oui.is_clicked(c0, output_theme_button) do fmt.printf("%#v", theme) } - + oui.end_layout(c0) - + ui_draw(master_container) - // DRAW HERE OR BELOW + oui.process(c0) } else { - DrawTextEx(font, "Date", {20, 8}, font_size, 0, RAYWHITE); - DrawTextEx(font, "Calltime", {105, 8}, font_size, 0, RAYWHITE); - DrawTextEx(font, "Wraptime", {f32(width)-83, 8}, font_size, 0, RAYWHITE); + DrawTextEx(font, "Date", {20, 8}, f32(font.baseSize), 0, RAYWHITE); + DrawTextEx(font, "Calltime", {105, 8}, f32(font.baseSize), 0, RAYWHITE); + DrawTextEx(font, "Wraptime", {f32(width)-83, 8}, f32(font.baseSize), 0, RAYWHITE); for day, i in workdays { @@ -271,20 +356,20 @@ when true { copy(wrap_text, clockprint(day.wrap)) copy(date_text, toString(day.call)) - text_height = math.round(f32(i+1)*DAY_HEIGHT+(DAY_HEIGHT-font_size)*0.25) + text_height := math.round(f32(i+1)*DAY_HEIGHT+(DAY_HEIGHT-f32(font.baseSize))*0.25) - DrawTextEx(font, cstring(&date_text[0]), {20, text_height}, font_size, 0, RAYWHITE); - DrawTextEx(font, cstring(&wrap_text[0]), {f32(width)-70, text_height}, font_size, 0, RAYWHITE); + DrawTextEx(font, cstring(&date_text[0]), {20, text_height}, f32(font.baseSize), 0, RAYWHITE); + DrawTextEx(font, cstring(&wrap_text[0]), {f32(width)-70, text_height}, f32(font.baseSize), 0, RAYWHITE); if i == len(workdays)-1 { - DrawTextEx(big_font, "+", {20, DAY_HEIGHT*f32(i+2)}, big_font_size, 0, RAYWHITE) + DrawTextEx(big_font, "+", {20, DAY_HEIGHT*f32(i+2)}, f32(big_font.baseSize), 0, RAYWHITE) } } DrawRectangle(0, height-50, width+10, 60, theme.background_bar) - DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, small_font_size, 0, RAYWHITE); - DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, big_font_size, 0, RAYWHITE); + DrawTextEx(small_font, total_sum, {f32(width)-120, f32(height)-43}, f32(small_font.baseSize), 0, RAYWHITE); + DrawTextEx(big_font, inc_soc, {f32(width)-120, f32(height)-29}, f32(big_font.baseSize), 0, RAYWHITE); } EndDrawing() } diff --git a/src/time.odin b/src/time.odin index b607e51..1f2ffbd 100644 --- a/src/time.odin +++ b/src/time.odin @@ -602,6 +602,16 @@ clockprintTimeblock :: proc(block: Timeblock) -> string { } clockprint :: proc{clockprintTimeblock, clockprintMoment} +dayprintMoment :: proc(moment: Moment) -> string { + using moment + return fmt.tprintf("%4i-%2i-%2i", year, month, day) +} +dayprintTimeblock :: proc(block: Timeblock) -> string { + using block + return fmt.tprintf("%s -> %s", dayprint(start), dayprint(end)) +} +dayprint :: proc{dayprintTimeblock, dayprintMoment} + popBlock :: proc(workday: ^Workday, index: int, count: int = 1) { using workday when ODIN_DEBUG do fmt.printf("popBlock() running to remove %i block(s) from index %i\n", count, index) diff --git a/src/ui_implementation.odin b/src/ui_implementation.odin index e4b4f80..6839b57 100644 --- a/src/ui_implementation.odin +++ b/src/ui_implementation.odin @@ -10,6 +10,8 @@ Theme :: struct { background: rl.Color, background_bar: rl.Color, button: rl.Color, + button_hover: rl.Color, + button_click: rl.Color, base: rl.Color, slider_bar: rl.Color, text: rl.Color, @@ -23,6 +25,8 @@ theme : Theme = { background = {25 , 27 , 29 , 255,}, background_bar = {43 , 43 , 48 , 255,}, button = {91 , 91 , 204, 255,}, + button_hover = {91 , 91 , 204, 255,}, + button_click = {91 , 91 , 204, 255,}, base = {60 , 60 , 60 , 255,}, slider_bar = {91 , 91 , 204, 255,}, text = {255, 255, 255, 252,}, @@ -32,9 +36,28 @@ theme : Theme = { price_300 = {240, 30 , 240, 255,}, } -DAY_HEIGHT :: 35 -TIMELINE_START :: 175 -TIMELINE_END :: -85 +Sizings :: struct { + date: int, + call: int, + wrap: int, + price: int, + lunch: int, + timeline: int, + inter_timeline: int, +} +sizings : Sizings = { + date = 110, + call = 85, + wrap = 90, + price = 100, + lunch = 100, + timeline = 32, + inter_timeline = 5, +} + +DAY_HEIGHT :: 35 // Only here for legacy UI +TIMELINE_START :: 175 // Only here for legacy UI +TIMELINE_END :: -85 // Only here for legacy UI Item :: oui.Item Call :: oui.Call @@ -42,10 +65,12 @@ Call :: oui.Call Data_Element :: enum int { Panel, Button, - Slider, + SliderU8, + SliderInt, Label, Text_Input, Timeblock, + Timeline, // ... } @@ -64,7 +89,15 @@ Data_Button :: struct { selected: bool, } -Data_Slider :: struct { +Data_SliderInt :: struct { + using _: Data_Head, + text: string, + value: ^int, + min: int, + max: int, +} + +Data_SliderU8 :: struct { using _: Data_Head, text: string, value: ^u8, @@ -78,6 +111,11 @@ Data_Label :: struct { alignment: Text_Alignment, } +Data_Timeline :: struct { + using _: Data_Head, + day: ^Workday, +} + panel :: proc(color : rl.Color = rl.RED) -> ^Item { item := oui.item_make(c0) @@ -129,21 +167,50 @@ button_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { return -1 } -slider :: proc(id: string, text: string, width: int, value: ^u8) -> ^Item { +slider_int :: proc(id: string, text: string, width: int, value: ^int, min: int, max: int) -> ^Item { + item := oui.item_make(c0) + item.layout_size = {width, 25} + item.id = oui.gen_id(c0, id) + item.callback = slider_int_callback + + data := oui.alloc_typed(c0, item, Data_SliderInt) + data.subtype = .SliderInt + data.text = text + data.value = value + data.min = min + data.max = max + + return item +} +slider_int_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { + data := cast(^Data_SliderInt) item.handle + rect := item.bounds + + #partial switch event { + case .Left_Capture: + cursor_position := clamp(oui.get_cursor(c0).x, rect.l, rect.r) + + data.value^ = int(f32(data.min) + (f32(data.max - data.min) * (f32(cursor_position - rect.l) / f32(rect.r - rect.l)))) + } + + return -1 +} + +slider_u8 :: proc(id: string, text: string, width: int, value: ^u8) -> ^Item { item := oui.item_make(c0) item.layout_size = {width, 25} item.id = oui.gen_id(c0, id) - item.callback = slider_callback + item.callback = slider_u8_callback - data := oui.alloc_typed(c0, item, Data_Slider) - data.subtype = .Slider + data := oui.alloc_typed(c0, item, Data_SliderU8) + data.subtype = .SliderU8 data.text = text data.value = value return item } -slider_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { - data := cast(^Data_Slider) item.handle +slider_u8_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { + data := cast(^Data_SliderU8) item.handle rect := item.bounds #partial switch event { @@ -157,10 +224,21 @@ slider_callback :: proc(ctxt: ^oui.Context, item: ^Item, event: Call) -> int { } color_sliders :: proc(parent: ^Item, color: ^rl.Color) { width :: 167 - oui.item_insert(parent, slider("slider_r", fmt.tprintf("%d", color.r), width, &color.r)) - oui.item_insert(parent, slider("slider_g", fmt.tprintf("%d", color.g), width, &color.g)) - oui.item_insert(parent, slider("slider_b", fmt.tprintf("%d", color.b), width, &color.b)) - oui.item_insert(parent, slider("slider_a", fmt.tprintf("%d", color.a), width, &color.a)) + oui.item_insert(parent, slider_u8("slider_r", fmt.tprintf("%d", color.r), width, &color.r)) + oui.item_insert(parent, slider_u8("slider_g", fmt.tprintf("%d", color.g), width, &color.g)) + oui.item_insert(parent, slider_u8("slider_b", fmt.tprintf("%d", color.b), width, &color.b)) + oui.item_insert(parent, slider_u8("slider_a", fmt.tprintf("%d", color.a), width, &color.a)) +} + +timeline :: proc(parent: ^Item, day: ^Workday) -> ^Item { + item := oui.item_make(c0) + + data := oui.alloc_typed(c0, item, Data_Timeline) + data.subtype = .Timeline + data.day = day + + oui.item_insert(parent, item) + return item } Text_Alignment :: enum int { @@ -181,9 +259,9 @@ label :: proc(text: string, font: rl.Font, width: int = 150, alignment: Text_Ali return item } -calculate_text_alignment :: proc(text: cstring, font: rl.Font, alignment: Text_Alignment, rect: oui.RectI) -> (output: [2]int) { +calculate_text_alignment :: proc(text: cstring, font: rl.Font, alignment: Text_Alignment, rect: oui.RectI, spacing: f32 = 0) -> (output: [2]int) { - measurement := rl.MeasureTextEx(font, text, f32(font.baseSize), 0.0) + measurement := rl.MeasureTextEx(font, text, f32(font.baseSize), spacing) switch alignment { case .Left: @@ -229,17 +307,28 @@ ui_draw :: proc(item: ^oui.Item) { // ... render any type of item case .Button: data := cast(^Data_Button) item.handle + text_spacing := clamp(item.anim.hot - item.anim.active, 0, item.anim.hot)*2 rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.button) text := strings.clone_to_cstring(data.text, context.temp_allocator) - position := calculate_text_alignment(text, font, .Center, rect) + position := calculate_text_alignment(text, font, .Center, rect, text_spacing) - rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); + rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), text_spacing, theme.text); //fmt.println(item.anim) - case .Slider: - data := cast(^Data_Slider) head + case .SliderInt: + data := cast(^Data_SliderInt) head + rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.base) + rl.DrawRectangle(i32(rect.l+1), i32(rect.t+1), i32(f32(rect.r-rect.l)*(f32(data.value^)/f32(data.max))-2), i32(rect.b-rect.t-2), theme.slider_bar) + + text := strings.clone_to_cstring(data.text, context.temp_allocator) + position := calculate_text_alignment(text, font, .Center, rect) + + rl.DrawTextEx(font, text, i2f(position), f32(font.baseSize), 0.0, theme.text); + + case .SliderU8: + data := cast(^Data_SliderU8) head rl.DrawRectangle(i32(rect.l), i32(rect.t), i32(rect.r-rect.l), i32(rect.b-rect.t), theme.base) rl.DrawRectangle(i32(rect.l+1), i32(rect.t+1), i32(f32(rect.r-rect.l)*(f32(data.value^)/255)-2), i32(rect.b-rect.t-2), theme.slider_bar) @@ -259,5 +348,42 @@ ui_draw :: proc(item: ^oui.Item) { position := calculate_text_alignment(text, data.font, data.alignment, rect) rl.DrawTextEx(data.font, text, i2f(position), f32(data.font.baseSize), 0.0, theme.text); + + case .Timeline: + data := cast(^Data_Timeline) item.handle + + width := int(f32(rect.r - rect.l)/(FRACT_MAX - FRACT_MIN)) + + for fracts, i in data.day.fractions { + + color := theme.price_100 + value := data.day.blocks[i].value + + switch { + case value>2.1: + color = theme.price_300 + case value>1.6: + color = theme.price_200 + case value>1.1: + color = theme.price_150 + } + + + + rl.DrawRectangle(i32(rect.l + int(f32(width)*fracts.start) - int(f32(width)*FRACT_MIN)), + i32(rect.t), + i32(f32(width) * (fracts.end - fracts.start)+0.99), + i32(rect.b - rect.t), + color) + // Dark middle of blocks, glowing edge. Disabled for now. + /*rl.DrawRectangle(i32(rect.l + int(f32(width)*fracts.start) - int(f32(width)*FRACT_MIN) + 1), + i32(rect.t) + 1, + i32(f32(width+1) * (fracts.end - fracts.start)-1.01), + i32(rect.b - rect.t)-2, + {0,0,0,100})*/ + if i+1 == data.day.total_timeblocks { + break + } + } } } \ No newline at end of file -- cgit v1.2.1