From 8b45221d075a31b27bdb74454f24899782dde723 Mon Sep 17 00:00:00 2001 From: Patrick Cleavelin Date: Sun, 2 Mar 2025 20:17:47 -0600 Subject: [PATCH] new ui lib --- src/core/core.odin | 1 + src/main.odin | 35 +++++- src/ui/ui.odin | 306 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 3 deletions(-) diff --git a/src/core/core.odin b/src/core/core.odin index 9bbd282..1d7d0fa 100644 --- a/src/core/core.odin +++ b/src/core/core.odin @@ -62,6 +62,7 @@ State :: struct { L: ^lua.State, sdl_renderer: ^sdl2.Renderer, font_atlas: FontAtlas, + ui: rawptr, mode: Mode, should_close: bool, diff --git a/src/main.odin b/src/main.odin index c31a951..e042402 100644 --- a/src/main.odin +++ b/src/main.odin @@ -324,13 +324,37 @@ draw :: proc(state_with_ui: ^StateWithUi) { sdl2.SetRenderDrawColor(state_with_ui.state.sdl_renderer, render_color.r, render_color.g, render_color.b, render_color.a); sdl2.RenderClear(state_with_ui.state.sdl_renderer); - // if state_with_ui.state.window != nil && state_with_ui.state.window.draw != nil { - // state_with_ui.state.window.draw(state_with_ui.state.plugin_vtable, state_with_ui.state.window.user_data); - // } + new_ui := transmute(^ui.State)state.ui + + ui.open_element(new_ui, nil, { + kind = {ui.Fit{}, ui.Exact(400)}, + }) + { + ui.open_element(new_ui, "Hello, I am a text thingy", {}) + ui.close_element(new_ui) + + ui.open_element(new_ui, "Number 2", {}) + ui.close_element(new_ui) + + ui.open_element(new_ui, "I am on the right hopefully", { + kind = {ui.Exact(state.screen_width-128), ui.Grow{}} + }) + ui.close_element(new_ui) + + ui.open_element(new_ui, "Number 4", { + kind = {ui.Exact(state.screen_width-128), ui.Grow{}} + }) + ui.close_element(new_ui) + } + ui.close_element(new_ui) + + ui.compute_layout_2(new_ui) ui.compute_layout(state_with_ui.ui_context, { state_with_ui.state.screen_width, state_with_ui.state.screen_height }, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); ui.draw(state_with_ui.ui_context, state_with_ui.state, state_with_ui.state.source_font_width, state_with_ui.state.source_font_height, state_with_ui.ui_context.root); + ui.new_draw(new_ui, &state) + if state_with_ui.state.mode != .Insert && state_with_ui.state.current_input_map != &state_with_ui.state.input_map.mode[state_with_ui.state.mode] { longest_description := 0; for key, action in state_with_ui.state.current_input_map.key_actions { @@ -1068,6 +1092,11 @@ main :: proc() { context.logger = log.create_console_logger(); state.ctx = context; + state.ui = &ui.State { + curr_elements = make([]ui.UI_Element, 8192), + prev_elements = make([]ui.UI_Element, 8192), + } + // TODO: don't use this mem.scratch_allocator_init(&scratch, 1024*1024); scratch_alloc = mem.scratch_allocator(&scratch); diff --git a/src/ui/ui.odin b/src/ui/ui.odin index d9f9ecb..a762f86 100644 --- a/src/ui/ui.odin +++ b/src/ui/ui.odin @@ -1,6 +1,312 @@ package ui import "core:math" +import "core:mem" +import "core:log" import "../core" import "../theme" + +State :: struct { + current_open_element: Maybe(int), + num_curr: int, + num_prev: int, + curr_elements: []UI_Element, + prev_elements: []UI_Element, +} + +UI_Element :: struct { + first: Maybe(int), + last: Maybe(int), + next: Maybe(int), + prev: Maybe(int), + parent: Maybe(int), + + kind: UI_Element_Kind, + layout: UI_Layout, +} + +UI_Element_Kind :: union { + UI_Element_Kind_Text, + UI_Element_Kind_Image, +} + +UI_Element_Kind_Text :: distinct string +UI_Element_Kind_Image :: distinct u64 + +UI_Layout :: struct { + dir: UI_Direction, + + kind: [2]UI_Size_Kind, + size: [2]int, + pos: [2]int, +} + +UI_Size_Kind :: union { + Exact, + Fit, + Grow, +} + +Exact :: distinct i32 +Grow :: struct {} +Fit :: struct {} + +UI_Direction :: enum { + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop, +} + +open_element :: proc(state: ^State, kind: UI_Element_Kind, layout: UI_Layout) { + e := UI_Element { + kind = kind, + layout = layout, + } + + if parent, ok := state.current_open_element.?; ok { + e.parent = parent + + if last, ok := state.curr_elements[parent].last.?; ok { + e.prev = last + + state.curr_elements[e.prev.?].next = state.num_curr + } + + state.curr_elements[parent].last = state.num_curr + + if state.curr_elements[parent].first == nil { + state.curr_elements[parent].first = state.num_curr + } + } + + state.curr_elements[state.num_curr] = e + state.current_open_element = state.num_curr + state.num_curr += 1 +} + +close_element :: proc(state: ^State, loc := #caller_location) { + if curr, ok := state.current_open_element.?; ok { + e := &state.curr_elements[curr] + + e.layout.size = {0,0} + + switch v in e.layout.kind[0] { + case nil: { + switch v in e.kind { + case UI_Element_Kind_Text: { + // FIXME: properly use font size + e.layout.size[0] = len(v) * 9 + } + case UI_Element_Kind_Image: { + // TODO + } + } + } + + case Exact: { e.layout.size[0] = int(v) } + case Fit: { + child_index := e.first + for child_index != nil { + child := &state.curr_elements[child_index.?] + + switch e.layout.dir { + case .RightToLeft: fallthrough + case .LeftToRight: { + e.layout.size[0] += child.layout.size[0] + } + + case .BottomToTop: fallthrough + case .TopToBottom: { + e.layout.size[0] = math.max(e.layout.size[0], child.layout.size[0]) + } + } + + child_index = child.next + } + } + case Grow: { /* Done in the Grow pass */ } + } + + switch v in e.layout.kind[1] { + case nil: { + switch v in e.kind { + case UI_Element_Kind_Text: { + // TODO: wrap text + // FIXME: properly use font size + e.layout.size[1] = 16 + } + case UI_Element_Kind_Image: { + // TODO + } + } + } + + case Exact: { e.layout.size[1] = int(v) } + case Fit: { + child_index := e.first + for child_index != nil { + child := &state.curr_elements[child_index.?] + + switch e.layout.dir { + case .RightToLeft: fallthrough + case .LeftToRight: { + e.layout.size[1] = math.max(e.layout.size[1], child.layout.size[1]) + } + + case .BottomToTop: fallthrough + case .TopToBottom: { + e.layout.size[1] += child.layout.size[1] + } + } + + child_index = child.next + } + } + case Grow: { /* Done in the Grow pass */ } + } + + grow_children(state, curr) + + state.current_open_element = e.parent + } else { + log.error("'close_element' has unmatched 'open_element' at", loc) + } +} + +@(private) +grow_children :: proc(state: ^State, index: int) { + e := &state.curr_elements[index] + + children_size: [2]int + num_growing: [2]int + + child_index := e.first + for child_index != nil { + child := &state.curr_elements[child_index.?] + + if _, ok := child.layout.kind.x.(Grow); ok { + num_growing.x += 1 + } + if _, ok := child.layout.kind.y.(Grow); ok { + num_growing.y += 1 + } + + switch e.layout.dir { + case .RightToLeft: fallthrough + case .LeftToRight: { + children_size.x += child.layout.size.x + } + + case .BottomToTop: fallthrough + case .TopToBottom: { + children_size.y += child.layout.size.y + } + } + + child_index = child.next + } + + if num_growing.x > 0 || num_growing.y > 0 { + remaining_size := e.layout.size - children_size + to_grow: [2]int + to_grow.x = 0 if num_growing.x < 1 else remaining_size.x/num_growing.x + to_grow.y = 0 if num_growing.y < 1 else remaining_size.y/num_growing.y + + child_index := e.first + for child_index != nil { + child := &state.curr_elements[child_index.?] + + switch e.layout.dir { + case .RightToLeft: fallthrough + case .LeftToRight: { + if _, ok := child.layout.kind.x.(Grow); ok { + child.layout.size.x = to_grow.x + } + if _, ok := child.layout.kind.y.(Grow); ok { + child.layout.size.y = remaining_size.y + } + } + case .BottomToTop: fallthrough + case .TopToBottom: { + if _, ok := child.layout.kind.x.(Grow); ok { + child.layout.size.x = remaining_size.x + } + if _, ok := child.layout.kind.y.(Grow); ok { + child.layout.size.y = to_grow.y + } + } + } + + child_index = child.next + } + } +} + +compute_layout_2 :: proc(state: ^State) { + for i in 0..