| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707 |
- /*
- ** Original work: Copyright (c) 2020 rxi
- ** Modified work: Copyright (c) 2020 oskarnp
- ** Modified work: Copyright (c) 2021 gingerBill
- **
- ** Permission is hereby granted, free of charge, to any person obtaining a copy
- ** of this software and associated documentation files (the "Software"), to
- ** deal in the Software without restriction, including without limitation the
- ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- ** sell copies of the Software, and to permit persons to whom the Software is
- ** furnished to do so, subject to the following conditions:
- **
- ** The above copyright notice and this permission notice shall be included in
- ** all copies or substantial portions of the Software.
- **
- ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- ** IN THE SOFTWARE.
- */
- package microui
- import "core:fmt"
- import "core:math"
- import "core:sort"
- import "core:strconv"
- import "core:strings"
- import textedit "core:text/edit"
- COMMAND_LIST_SIZE :: #config(MICROUI_COMMAND_LIST_SIZE, 256 * 1024)
- ROOT_LIST_SIZE :: #config(MICROUI_ROOT_LIST_SIZE, 32)
- CONTAINER_STACK_SIZE :: #config(MICROUI_CONTAINER_STACK_SIZE, 32)
- CLIP_STACK_SIZE :: #config(MICROUI_CLIP_STACK_SIZE, 32)
- ID_STACK_SIZE :: #config(MICROUI_ID_STACK_SIZE, 32)
- LAYOUT_STACK_SIZE :: #config(MICROUI_LAYOUT_STACK_SIZE, 16)
- CONTAINER_POOL_SIZE :: #config(MICROUI_CONTAINER_POOL_SIZE, 48)
- TREENODE_POOL_SIZE :: #config(MICROUI_TREENODE_POOL_SIZE, 48)
- MAX_WIDTHS :: #config(MICROUI_MAX_WIDTHS, 16)
- SLIDER_FMT :: #config(MICROUI_SLIDER_FMT, "%.2f")
- MAX_FMT :: #config(MICROUI_MAX_FMT, 127)
- MAX_TEXT_STORE :: #config(MICROUI_MAX_TEXT_STORE, 1024)
- Clip :: enum u32 {
- NONE,
- PART,
- ALL,
- }
- Color_Type :: enum u32 {
- TEXT,
- SELECTION_BG,
- BORDER,
- WINDOW_BG,
- TITLE_BG,
- TITLE_TEXT,
- PANEL_BG,
- BUTTON,
- BUTTON_HOVER = BUTTON + 1,
- BUTTON_FOCUS = BUTTON + 2,
- BASE,
- BASE_HOVER = BASE + 1,
- BASE_FOCUS = BASE + 2,
- SCROLL_BASE,
- SCROLL_THUMB,
- }
- Icon :: enum u32 {
- NONE,
- CLOSE,
- CHECK,
- COLLAPSED,
- EXPANDED,
- RESIZE,
- }
- Result :: enum u32 {
- ACTIVE,
- SUBMIT,
- CHANGE,
- }
- Result_Set :: bit_set[Result;u32]
- Opt :: enum u32 {
- ALIGN_CENTER,
- ALIGN_RIGHT,
- NO_INTERACT,
- NO_FRAME,
- NO_RESIZE,
- NO_SCROLL,
- NO_CLOSE,
- NO_TITLE,
- HOLD_FOCUS,
- AUTO_SIZE,
- POPUP,
- CLOSED,
- EXPANDED,
- }
- Options :: distinct bit_set[Opt;u32]
- Mouse :: enum u32 {
- LEFT,
- RIGHT,
- MIDDLE,
- }
- Mouse_Set :: distinct bit_set[Mouse;u32]
- Key :: enum u32 {
- SHIFT,
- CTRL,
- ALT,
- BACKSPACE,
- DELETE,
- RETURN,
- LEFT,
- RIGHT,
- HOME,
- END,
- A,
- X,
- C,
- V,
- }
- Key_Set :: distinct bit_set[Key;u32]
- Id :: distinct u32
- Real :: f32
- Font :: distinct rawptr
- Vec2 :: distinct [2]i32
- Rect :: struct {
- x, y, w, h: i32,
- }
- Color :: struct {
- r, g, b, a: u8,
- }
- Frame_Index :: distinct i32
- Pool_Item :: struct {
- id: Id,
- last_update: Frame_Index,
- }
- Command_Variant :: union {
- ^Command_Jump,
- ^Command_Clip,
- ^Command_Rect,
- ^Command_Text,
- ^Command_Icon,
- }
- Command :: struct {
- variant: Command_Variant,
- size: i32,
- }
- Command_Jump :: struct {
- using command: Command,
- dst: rawptr,
- }
- Command_Clip :: struct {
- using command: Command,
- rect: Rect,
- }
- Command_Rect :: struct {
- using command: Command,
- rect: Rect,
- color: Color,
- }
- Command_Text :: struct {
- using command: Command,
- font: Font,
- pos: Vec2,
- color: Color,
- str: string, /* + string data (VLA) */
- }
- Command_Icon :: struct {
- using command: Command,
- rect: Rect,
- id: Icon,
- color: Color,
- }
- Layout_Type :: enum {
- NONE = 0,
- RELATIVE = 1,
- ABSOLUTE = 2,
- }
- Layout :: struct {
- body, next: Rect,
- position, size, max: Vec2,
- widths: [MAX_WIDTHS]i32,
- items, item_index, next_row: i32,
- next_type: Layout_Type,
- indent: i32,
- }
- Container :: struct {
- head, tail: ^Command,
- rect, body: Rect,
- content_size: Vec2,
- scroll: Vec2,
- zindex: i32,
- open: b32,
- }
- Style :: struct {
- font: Font,
- size: Vec2,
- padding: i32,
- spacing: i32,
- indent: i32,
- title_height: i32,
- footer_height: i32,
- scrollbar_size: i32,
- thumb_size: i32,
- colors: [Color_Type]Color,
- }
- Context :: struct {
- /* callbacks */
- text_width: proc(font: Font, str: string) -> i32,
- text_height: proc(font: Font) -> i32,
- draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type),
- /* core state */
- _style: Style,
- style: ^Style,
- hover_id, focus_id, last_id: Id,
- last_rect: Rect,
- last_zindex: i32,
- updated_focus: b32,
- frame: Frame_Index,
- hover_root, next_hover_root: ^Container,
- scroll_target: ^Container,
- number_edit_buf: [MAX_FMT]u8,
- number_edit_len: int,
- number_edit_id: Id,
- /* stacks */
- command_list: Stack(u8, COMMAND_LIST_SIZE),
- root_list: Stack(^Container, ROOT_LIST_SIZE),
- container_stack: Stack(^Container, CONTAINER_STACK_SIZE),
- clip_stack: Stack(Rect, CLIP_STACK_SIZE),
- id_stack: Stack(Id, ID_STACK_SIZE),
- layout_stack: Stack(Layout, LAYOUT_STACK_SIZE),
- /* retained state pools */
- container_pool: [CONTAINER_POOL_SIZE]Pool_Item,
- containers: [CONTAINER_POOL_SIZE]Container,
- treenode_pool: [TREENODE_POOL_SIZE]Pool_Item,
- /* input state */
- mouse_pos, last_mouse_pos: Vec2,
- mouse_delta, scroll_delta: Vec2,
- mouse_down_bits: Mouse_Set,
- mouse_pressed_bits: Mouse_Set,
- mouse_released_bits: Mouse_Set,
- key_down_bits, key_pressed_bits: Key_Set,
- _text_store: [MAX_TEXT_STORE]u8,
- text_input: strings.Builder, // uses `_text_store` as backing store with nil_allocator.
- textbox_state: textedit.State,
- textbox_offset: i32,
- }
- Stack :: struct($T: typeid, $N: int) {
- idx: i32,
- items: [N]T,
- }
- push :: #force_inline proc(stk: ^$T/Stack($V, $N), val: V) {
- assert(stk.idx < len(stk.items))
- stk.items[stk.idx] = val
- stk.idx += 1
- }
- pop :: #force_inline proc(stk: ^$T/Stack($V, $N)) {
- assert(stk.idx > 0)
- stk.idx -= 1
- }
- unclipped_rect := Rect{0, 0, 0x1000000, 0x1000000}
- default_style := Style {
- font = nil,
- size = {68, 10},
- padding = 5,
- spacing = 4,
- indent = 24,
- title_height = 24,
- footer_height = 20,
- scrollbar_size = 12,
- thumb_size = 8,
- colors = {
- .TEXT = {230, 230, 230, 255},
- .SELECTION_BG = {90, 90, 90, 255},
- .BORDER = {25, 25, 25, 255},
- .WINDOW_BG = {50, 50, 50, 255},
- .TITLE_BG = {25, 25, 25, 255},
- .TITLE_TEXT = {240, 240, 240, 255},
- .PANEL_BG = {0, 0, 0, 0},
- .BUTTON = {75, 75, 75, 255},
- .BUTTON_HOVER = {95, 95, 95, 255},
- .BUTTON_FOCUS = {115, 115, 115, 255},
- .BASE = {30, 30, 30, 255},
- .BASE_HOVER = {35, 35, 35, 255},
- .BASE_FOCUS = {40, 40, 40, 255},
- .SCROLL_BASE = {43, 43, 43, 255},
- .SCROLL_THUMB = {30, 30, 30, 255},
- },
- }
- expand_rect :: proc(rect: Rect, n: i32) -> Rect {
- return Rect{rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2}
- }
- intersect_rects :: proc(r1, r2: Rect) -> Rect {
- x1 := max(r1.x, r2.x)
- y1 := max(r1.y, r2.y)
- x2 := min(r1.x + r1.w, r2.x + r2.w)
- y2 := min(r1.y + r1.h, r2.y + r2.h)
- if x2 < x1 {x2 = x1}
- if y2 < y1 {y2 = y1}
- return Rect{x1, y1, x2 - x1, y2 - y1}
- }
- rect_overlaps_vec2 :: proc(r: Rect, p: Vec2) -> bool {
- return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h
- }
- @(private)
- default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) {
- draw_rect(ctx, rect, ctx.style.colors[colorid])
- if colorid == .SCROLL_BASE || colorid == .SCROLL_THUMB || colorid == .TITLE_BG {
- return
- }
- if ctx.style.colors[.BORDER].a != 0 { /* draw border */
- draw_box(ctx, expand_rect(rect, 1), ctx.style.colors[.BORDER])
- }
- }
- init :: proc(
- ctx: ^Context,
- set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool) = nil,
- get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool) = nil,
- clipboard_user_data: rawptr = nil,
- ) {
- ctx^ = {} // zero memory
- ctx.draw_frame = default_draw_frame
- ctx._style = default_style
- ctx.style = &ctx._style
- ctx.text_input = strings.builder_from_bytes(ctx._text_store[:])
- ctx.textbox_state.set_clipboard = set_clipboard
- ctx.textbox_state.get_clipboard = get_clipboard
- ctx.textbox_state.clipboard_user_data = clipboard_user_data
- }
- begin :: proc(ctx: ^Context) {
- assert(ctx.text_width != nil, "ctx.text_width is not set")
- assert(ctx.text_height != nil, "ctx.text_height is not set")
- ctx.command_list.idx = 0
- ctx.root_list.idx = 0
- ctx.scroll_target = nil
- ctx.hover_root = ctx.next_hover_root
- ctx.next_hover_root = nil
- ctx.mouse_delta.x = ctx.mouse_pos.x - ctx.last_mouse_pos.x
- ctx.mouse_delta.y = ctx.mouse_pos.y - ctx.last_mouse_pos.y
- ctx.frame += 1
- }
- end :: proc(ctx: ^Context) {
- /* check stacks */
- assert(ctx.container_stack.idx == 0)
- assert(ctx.clip_stack.idx == 0)
- assert(ctx.id_stack.idx == 0)
- assert(ctx.layout_stack.idx == 0)
- /* handle scroll input */
- if ctx.scroll_target != nil {
- ctx.scroll_target.scroll.x += ctx.scroll_delta.x
- ctx.scroll_target.scroll.y += ctx.scroll_delta.y
- }
- /* unset focus if focus id was not touched this frame */
- if !ctx.updated_focus {
- ctx.focus_id = 0
- }
- ctx.updated_focus = false
- /* bring hover root to front if mouse was pressed */
- if mouse_pressed(ctx) &&
- ctx.next_hover_root != nil &&
- ctx.next_hover_root.zindex < ctx.last_zindex &&
- ctx.next_hover_root.zindex >= 0 {
- bring_to_front(ctx, ctx.next_hover_root)
- }
- /* reset input state */
- ctx.key_pressed_bits = {} // clear
- strings.builder_reset(&ctx.text_input)
- ctx.mouse_pressed_bits = {} // clear
- ctx.mouse_released_bits = {} // clear
- ctx.scroll_delta = Vec2{0, 0}
- ctx.last_mouse_pos = ctx.mouse_pos
- /* sort root containers by zindex */
- n := ctx.root_list.idx
- sort.quick_sort_proc(ctx.root_list.items[:n], proc(a, b: ^Container) -> int {
- return int(a.zindex) - int(b.zindex)
- })
- /* set root container jump commands */
- for i: i32 = 0; i < n; i += 1 {
- cnt := ctx.root_list.items[i]
- /* if this is the first container then make the first command jump to it.
- ** otherwise set the previous container's tail to jump to this one */
- if i == 0 {
- cmd := (^Command_Jump)(&ctx.command_list.items[0])
- cmd.dst = rawptr(uintptr(cnt.head) + size_of(Command_Jump))
- } else {
- prev := ctx.root_list.items[i - 1]
- prev.tail.variant.(^Command_Jump).dst = rawptr(
- uintptr(cnt.head) + size_of(Command_Jump),
- )
- }
- /* make the last container's tail jump to the end of command list */
- if i == n - 1 {
- cnt.tail.variant.(^Command_Jump).dst = rawptr(
- &ctx.command_list.items[ctx.command_list.idx],
- )
- }
- }
- }
- set_focus :: proc(ctx: ^Context, id: Id) {
- ctx.focus_id = id
- ctx.updated_focus = true
- }
- get_id :: proc {
- get_id_string,
- get_id_bytes,
- get_id_rawptr,
- get_id_uintptr,
- }
- get_id_string :: #force_inline proc(ctx: ^Context, str: string) -> Id {return get_id_bytes(
- ctx,
- transmute([]byte)str,
- )}
- get_id_rawptr :: #force_inline proc(
- ctx: ^Context,
- data: rawptr,
- size: int,
- ) -> Id {return get_id_bytes(ctx, ([^]u8)(data)[:size])}
- get_id_uintptr :: #force_inline proc(ctx: ^Context, ptr: uintptr) -> Id {
- ptr := ptr
- return get_id_bytes(ctx, ([^]u8)(&ptr)[:size_of(ptr)])
- }
- get_id_bytes :: proc(ctx: ^Context, bytes: []byte) -> Id {
- /* 32bit fnv-1a hash */
- HASH_INITIAL :: 2166136261
- hash :: proc(hash: ^Id, data: []byte) {
- size := len(data)
- cptr := ([^]u8)(raw_data(data))
- for ; size > 0; size -= 1 {
- hash^ = Id(u32(hash^) ~ u32(cptr[0])) * 16777619
- cptr = cptr[1:]
- }
- }
- idx := ctx.id_stack.idx
- res := ctx.id_stack.items[idx - 1] if idx > 0 else HASH_INITIAL
- hash(&res, bytes)
- ctx.last_id = res
- return res
- }
- push_id :: proc {
- push_id_string,
- push_id_bytes,
- push_id_rawptr,
- push_id_uintptr,
- }
- push_id_string :: #force_inline proc(ctx: ^Context, str: string) {push(
- &ctx.id_stack,
- get_id(ctx, str),
- )}
- push_id_rawptr :: #force_inline proc(ctx: ^Context, data: rawptr, size: int) {push(
- &ctx.id_stack,
- get_id(ctx, data, size),
- )}
- push_id_uintptr :: #force_inline proc(ctx: ^Context, ptr: uintptr) {push(
- &ctx.id_stack,
- get_id(ctx, ptr),
- )}
- push_id_bytes :: #force_inline proc(ctx: ^Context, bytes: []byte) {push(
- &ctx.id_stack,
- get_id(ctx, bytes),
- )}
- pop_id :: proc(ctx: ^Context) {
- pop(&ctx.id_stack)
- }
- push_clip_rect :: proc(ctx: ^Context, rect: Rect) {
- last := get_clip_rect(ctx)
- push(&ctx.clip_stack, intersect_rects(rect, last))
- }
- pop_clip_rect :: proc(ctx: ^Context) {
- pop(&ctx.clip_stack)
- }
- get_clip_rect :: proc(ctx: ^Context) -> Rect {
- assert(ctx.clip_stack.idx > 0)
- return ctx.clip_stack.items[ctx.clip_stack.idx - 1]
- }
- check_clip :: proc(ctx: ^Context, r: Rect) -> Clip {
- cr := get_clip_rect(ctx)
- if r.x > cr.x + cr.w || r.x + r.w < cr.x || r.y > cr.y + cr.h || r.y + r.h < cr.y {
- return .ALL
- }
- if r.x >= cr.x && r.x + r.w <= cr.x + cr.w && r.y >= cr.y && r.y + r.h <= cr.y + cr.h {
- return .NONE
- }
- return .PART
- }
- get_layout :: proc(ctx: ^Context) -> ^Layout {
- return &ctx.layout_stack.items[ctx.layout_stack.idx - 1]
- }
- @(private)
- push_layout :: proc(ctx: ^Context, body: Rect, scroll: Vec2) {
- layout: Layout
- layout.body = Rect{body.x - scroll.x, body.y - scroll.y, body.w, body.h}
- layout.max = Vec2{-0x1000000, -0x1000000}
- push(&ctx.layout_stack, layout)
- layout_row(ctx, {0})
- }
- @(private)
- pop_container :: proc(ctx: ^Context) {
- cnt := get_current_container(ctx)
- layout := get_layout(ctx)
- cnt.content_size.x = layout.max.x - layout.body.x
- cnt.content_size.y = layout.max.y - layout.body.y
- /* pop container, layout and id */
- pop(&ctx.container_stack)
- pop(&ctx.layout_stack)
- pop_id(ctx)
- }
- get_current_container :: proc(ctx: ^Context) -> ^Container {
- assert(ctx.container_stack.idx > 0)
- return ctx.container_stack.items[ctx.container_stack.idx - 1]
- }
- @(private)
- internal_get_container :: proc(ctx: ^Context, id: Id, opt: Options) -> ^Container {
- /* try to get existing container from pool */
- idx, ok := pool_get(ctx, ctx.container_pool[:], id)
- if ok {
- if ctx.containers[idx].open || .CLOSED not_in opt {
- pool_update(ctx, &ctx.container_pool[idx])
- }
- return &ctx.containers[idx]
- }
- if .CLOSED in opt {return nil}
- /* container not found in pool: init new container */
- idx = pool_init(ctx, ctx.container_pool[:], id)
- cnt := &ctx.containers[idx]
- cnt^ = {} // clear memory
- cnt.open = true
- bring_to_front(ctx, cnt)
- return cnt
- }
- get_container :: proc(ctx: ^Context, name: string, opt := Options{}) -> ^Container {
- id := get_id(ctx, name)
- return internal_get_container(ctx, id, opt)
- }
- bring_to_front :: proc(ctx: ^Context, cnt: ^Container) {
- ctx.last_zindex += 1
- cnt.zindex = ctx.last_zindex
- }
- /*============================================================================
- ** pool
- **============================================================================*/
- pool_init :: proc(ctx: ^Context, items: []Pool_Item, id: Id) -> int {
- f := ctx.frame
- n := -1
- for _, i in items {
- if items[i].last_update < f {
- f = items[i].last_update
- n = i
- }
- }
- assert(n > -1)
- items[n].id = id
- pool_update(ctx, &items[n])
- return n
- }
- pool_get :: proc(ctx: ^Context, items: []Pool_Item, id: Id) -> (int, bool) {
- for _, i in items {
- if items[i].id == id {
- return i, true
- }
- }
- return -1, false
- }
- pool_update :: proc(ctx: ^Context, item: ^Pool_Item) {
- item.last_update = ctx.frame
- }
- /*============================================================================
- ** input handlers
- **============================================================================*/
- input_mouse_move :: proc(ctx: ^Context, x, y: i32) {
- ctx.mouse_pos = Vec2{x, y}
- }
- input_mouse_down :: proc(ctx: ^Context, x, y: i32, btn: Mouse) {
- input_mouse_move(ctx, x, y)
- ctx.mouse_down_bits += {btn}
- ctx.mouse_pressed_bits += {btn}
- }
- input_mouse_up :: proc(ctx: ^Context, x, y: i32, btn: Mouse) {
- input_mouse_move(ctx, x, y)
- ctx.mouse_down_bits -= {btn}
- ctx.mouse_released_bits += {btn}
- }
- input_scroll :: proc(ctx: ^Context, x, y: i32) {
- ctx.scroll_delta.x += x
- ctx.scroll_delta.y += y
- }
- input_key_down :: proc(ctx: ^Context, key: Key) {
- ctx.key_pressed_bits += {key}
- ctx.key_down_bits += {key}
- }
- input_key_up :: proc(ctx: ^Context, key: Key) {
- ctx.key_down_bits -= {key}
- }
- input_text :: proc(ctx: ^Context, text: string) {
- strings.write_string(&ctx.text_input, text)
- }
- /*============================================================================
- ** commandlist
- **============================================================================*/
- push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type {
- size := i32(size_of(Type) + extra_size)
- cmd := transmute(^Type)&ctx.command_list.items[ctx.command_list.idx]
- assert(ctx.command_list.idx + size < COMMAND_LIST_SIZE)
- ctx.command_list.idx += size
- cmd.variant = cmd
- cmd.size = size
- return cmd
- }
- next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool {
- cmd := pcmd^
- defer pcmd^ = cmd
- if cmd != nil {
- cmd = (^Command)(uintptr(cmd) + uintptr(cmd.size))
- } else {
- cmd = (^Command)(&ctx.command_list.items[0])
- }
- invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command {
- return (^Command)(&ctx.command_list.items[ctx.command_list.idx])
- }
- for cmd != invalid_command(ctx) {
- if jmp, ok := cmd.variant.(^Command_Jump); ok {
- cmd = (^Command)(jmp.dst)
- continue
- }
- return true
- }
- return false
- }
- next_command_iterator :: proc "contextless" (
- ctx: ^Context,
- pcm: ^^Command,
- ) -> (
- Command_Variant,
- bool,
- ) {
- if next_command(ctx, pcm) {
- return pcm^.variant, true
- }
- return nil, false
- }
- @(private)
- push_jump :: proc(ctx: ^Context, dst: ^Command) -> ^Command {
- cmd := push_command(ctx, Command_Jump)
- cmd.dst = dst
- return cmd
- }
- set_clip :: proc(ctx: ^Context, rect: Rect) {
- cmd := push_command(ctx, Command_Clip)
- cmd.rect = rect
- }
- draw_rect :: proc(ctx: ^Context, rect: Rect, color: Color) {
- rect := rect
- rect = intersect_rects(rect, get_clip_rect(ctx))
- if rect.w > 0 && rect.h > 0 {
- cmd := push_command(ctx, Command_Rect)
- cmd.rect = rect
- cmd.color = color
- }
- }
- draw_box :: proc(ctx: ^Context, rect: Rect, color: Color) {
- draw_rect(ctx, Rect{rect.x + 1, rect.y, rect.w - 2, 1}, color)
- draw_rect(ctx, Rect{rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1}, color)
- draw_rect(ctx, Rect{rect.x, rect.y, 1, rect.h}, color)
- draw_rect(ctx, Rect{rect.x + rect.w - 1, rect.y, 1, rect.h}, color)
- }
- draw_text :: proc(ctx: ^Context, font: Font, str: string, pos: Vec2, color: Color) {
- rect := Rect{pos.x, pos.y, ctx.text_width(font, str), ctx.text_height(font)}
- clipped := check_clip(ctx, rect)
- switch clipped {
- case .NONE: // okay
- case .ALL:
- return
- case .PART:
- set_clip(ctx, get_clip_rect(ctx))
- }
- /* add command */
- text_cmd := push_command(ctx, Command_Text, len(str))
- text_cmd.pos = pos
- text_cmd.color = color
- text_cmd.font = font
- /* copy string */
- dst_str := ([^]byte)(text_cmd)[size_of(Command_Text):][:len(str)]
- copy(dst_str, str)
- text_cmd.str = string(dst_str)
- /* reset clipping if it was set */
- if clipped != .NONE {
- set_clip(ctx, unclipped_rect)
- }
- }
- draw_icon :: proc(ctx: ^Context, id: Icon, rect: Rect, color: Color) {
- /* do clip command if the rect isn't fully contained within the cliprect */
- clipped := check_clip(ctx, rect)
- switch clipped {
- case .NONE: // okay
- case .ALL:
- return
- case .PART:
- set_clip(ctx, get_clip_rect(ctx))
- }
- /* do icon command */
- cmd := push_command(ctx, Command_Icon)
- cmd.id = id
- cmd.rect = rect
- cmd.color = color
- /* reset clipping if it was set */
- if clipped != .NONE {
- set_clip(ctx, unclipped_rect)
- }
- }
- /*============================================================================
- ** layout
- **============================================================================*/
- layout_begin_column :: proc(ctx: ^Context) {
- push_layout(ctx, layout_next(ctx), Vec2{0, 0})
- }
- layout_end_column :: proc(ctx: ^Context) {
- b := get_layout(ctx)
- pop(&ctx.layout_stack)
- /* inherit position/next_row/max from child layout if they are greater */
- a := get_layout(ctx)
- a.position.x = max(a.position.x, b.position.x + b.body.x - a.body.x)
- a.next_row = max(a.next_row, b.next_row + b.body.y - a.body.y)
- a.max.x = max(a.max.x, b.max.x)
- a.max.y = max(a.max.y, b.max.y)
- }
- @(deferred_in = layout_end_column)
- layout_column :: proc(ctx: ^Context) -> bool {
- layout_begin_column(ctx)
- return true
- }
- layout_row :: proc(ctx: ^Context, widths: []i32, height: i32 = 0) {
- layout := get_layout(ctx)
- items := len(widths)
- if len(widths) > 0 {
- items = copy(layout.widths[:], widths[:])
- }
- layout.items = i32(items)
- layout.position = Vec2{layout.indent, layout.next_row}
- layout.size.y = height
- layout.item_index = 0
- }
- layout_row_items :: proc(ctx: ^Context, items: i32, height: i32 = 0) {
- layout := get_layout(ctx)
- layout.items = items
- layout.position = Vec2{layout.indent, layout.next_row}
- layout.size.y = height
- layout.item_index = 0
- }
- layout_width :: proc(ctx: ^Context, width: i32) {
- get_layout(ctx).size.x = width
- }
- layout_height :: proc(ctx: ^Context, height: i32) {
- get_layout(ctx).size.y = height
- }
- layout_set_next :: proc(ctx: ^Context, r: Rect, relative: bool) {
- layout := get_layout(ctx)
- layout.next = r
- layout.next_type = .RELATIVE if relative else .ABSOLUTE
- }
- layout_next :: proc(ctx: ^Context) -> (res: Rect) {
- layout := get_layout(ctx)
- style := ctx.style
- defer ctx.last_rect = res
- if layout.next_type != .NONE {
- /* handle rect set by `layout_set_next` */
- type := layout.next_type
- layout.next_type = .NONE
- res = layout.next
- if type == .ABSOLUTE {
- return
- }
- } else {
- /* handle next row */
- if layout.item_index == layout.items {
- layout_row_items(ctx, layout.items, layout.size.y)
- }
- /* position */
- res.x = layout.position.x
- res.y = layout.position.y
- /* size */
- res.w = layout.items > 0 ? layout.widths[layout.item_index] : layout.size.x
- res.h = layout.size.y
- if res.w == 0 {res.w = style.size.x + style.padding * 2}
- if res.h == 0 {res.h = style.size.y + style.padding * 2}
- if res.w < 0 {res.w += layout.body.w - res.x + 1}
- if res.h < 0 {res.h += layout.body.h - res.y + 1}
- layout.item_index += 1
- }
- /* update position */
- layout.position.x += res.w + style.spacing
- layout.next_row = max(layout.next_row, res.y + res.h + style.spacing)
- /* apply body offset */
- res.x += layout.body.x
- res.y += layout.body.y
- /* update max position */
- layout.max.x = max(layout.max.x, res.x + res.w)
- layout.max.y = max(layout.max.y, res.y + res.h)
- return
- }
- /*============================================================================
- ** controls
- **============================================================================*/
- @(private)
- in_hover_root :: proc(ctx: ^Context) -> bool {
- for i := ctx.container_stack.idx - 1; i >= 0; i -= 1 {
- if ctx.container_stack.items[i] == ctx.hover_root {
- return true
- }
- /* only root containers have their `head` field set; stop searching if we've
- ** reached the current root container */
- if ctx.container_stack.items[i].head != nil {
- break
- }
- }
- return false
- }
- draw_control_frame :: proc(
- ctx: ^Context,
- id: Id,
- rect: Rect,
- colorid: Color_Type,
- opt := Options{},
- ) {
- if .NO_FRAME in opt {
- return
- }
- assert(colorid == .BUTTON || colorid == .BASE)
- colorid := colorid
- colorid = Color_Type(
- int(colorid) + int((ctx.focus_id == id) ? 2 : (ctx.hover_id == id) ? 1 : 0),
- )
- ctx.draw_frame(ctx, rect, colorid)
- }
- draw_control_text :: proc(
- ctx: ^Context,
- str: string,
- rect: Rect,
- colorid: Color_Type,
- opt := Options{},
- ) {
- pos: Vec2
- font := ctx.style.font
- tw := ctx.text_width(font, str)
- push_clip_rect(ctx, rect)
- pos.y = rect.y + (rect.h - ctx.text_height(font)) / 2
- if .ALIGN_CENTER in opt {
- pos.x = rect.x + (rect.w - tw) / 2
- } else if .ALIGN_RIGHT in opt {
- pos.x = rect.x + rect.w - tw - ctx.style.padding
- } else {
- pos.x = rect.x + ctx.style.padding
- }
- draw_text(ctx, font, str, pos, ctx.style.colors[colorid])
- pop_clip_rect(ctx)
- }
- mouse_over :: proc(ctx: ^Context, rect: Rect) -> bool {
- return(
- rect_overlaps_vec2(rect, ctx.mouse_pos) &&
- rect_overlaps_vec2(get_clip_rect(ctx), ctx.mouse_pos) &&
- in_hover_root(ctx) \
- )
- }
- update_control :: proc(ctx: ^Context, id: Id, rect: Rect, opt := Options{}) {
- mouseover := mouse_over(ctx, rect)
- if ctx.focus_id == id {
- ctx.updated_focus = true
- }
- if .NO_INTERACT in opt {
- return
- }
- if mouseover && !mouse_down(ctx) {
- ctx.hover_id = id
- }
- if ctx.focus_id == id {
- if mouse_pressed(ctx) && !mouseover {
- set_focus(ctx, 0)
- }
- if !mouse_down(ctx) && .HOLD_FOCUS not_in opt {
- set_focus(ctx, 0)
- }
- }
- if ctx.hover_id == id {
- if mouse_pressed(ctx) {
- set_focus(ctx, id)
- } else if !mouseover {
- ctx.hover_id = 0
- }
- }
- }
- text :: proc(ctx: ^Context, text: string) {
- text := text
- font := ctx.style.font
- color := ctx.style.colors[.TEXT]
- layout_begin_column(ctx)
- layout_row(ctx, {-1}, ctx.text_height(font))
- for len(text) > 0 {
- w: i32
- start: int
- end: int = len(text)
- r := layout_next(ctx)
- for ch, i in text {
- if ch == ' ' || ch == '\n' {
- word := text[start:i]
- w += ctx.text_width(font, word)
- if w > r.w && start != 0 {
- end = start
- break
- }
- w += ctx.text_width(font, text[i:i + 1])
- if ch == '\n' {
- end = i + 1
- break
- }
- start = i + 1
- }
- }
- draw_text(ctx, font, text[:end], Vec2{r.x, r.y}, color)
- text = text[end:]
- }
- layout_end_column(ctx)
- }
- label :: proc(ctx: ^Context, text: string) {
- draw_control_text(ctx, text, layout_next(ctx), .TEXT)
- }
- button :: proc(
- ctx: ^Context,
- label: string,
- icon: Icon = .NONE,
- opt: Options = {.ALIGN_CENTER},
- ) -> (
- res: Result_Set,
- ) {
- id := len(label) > 0 ? get_id(ctx, label) : get_id(ctx, uintptr(icon))
- r := layout_next(ctx)
- update_control(ctx, id, r, opt)
- /* handle click */
- if ctx.mouse_pressed_bits == {.LEFT} && ctx.focus_id == id {
- res += {.SUBMIT}
- }
- /* draw */
- draw_control_frame(ctx, id, r, .BUTTON, opt)
- if len(label) > 0 {
- draw_control_text(ctx, label, r, .TEXT, opt)
- }
- if icon != .NONE {
- draw_icon(ctx, icon, r, ctx.style.colors[.TEXT])
- }
- return
- }
- checkbox :: proc(ctx: ^Context, label: string, state: ^bool) -> (res: Result_Set) {
- id := get_id(ctx, uintptr(state))
- r := layout_next(ctx)
- box := Rect{r.x, r.y, r.h, r.h}
- update_control(ctx, id, r, {})
- /* handle click */
- if .LEFT in ctx.mouse_released_bits && ctx.hover_id == id {
- res += {.CHANGE}
- state^ = !state^
- }
- /* draw */
- draw_control_frame(ctx, id, box, .BASE, {})
- if state^ {
- draw_icon(ctx, .CHECK, box, ctx.style.colors[.TEXT])
- }
- r = Rect{r.x + box.w, r.y, r.w - box.w, r.h}
- draw_control_text(ctx, label, r, .TEXT)
- return
- }
- textbox_raw :: proc(
- ctx: ^Context,
- textbuf: []u8,
- textlen: ^int,
- id: Id,
- r: Rect,
- opt := Options{},
- ) -> (
- res: Result_Set,
- ) {
- update_control(ctx, id, r, opt | {.HOLD_FOCUS})
- font := ctx.style.font
- if ctx.focus_id == id {
- /* create a builder backed by the user's buffer */
- builder := strings.builder_from_bytes(textbuf)
- non_zero_resize(&builder.buf, textlen^)
- ctx.textbox_state.builder = &builder
- if ctx.textbox_state.id != u64(id) {
- ctx.textbox_state.id = u64(id)
- ctx.textbox_state.selection = {}
- }
- /* check selection bounds */
- if ctx.textbox_state.selection[0] > textlen^ || ctx.textbox_state.selection[1] > textlen^ {
- ctx.textbox_state.selection = {}
- }
- /* handle text input */
- // if strings.builder_len(ctx.text_input) > 0 {
- // if textedit.input_text(&ctx.textbox_state, strings.to_string(ctx.text_input)) > 0 {
- // textlen^ = strings.builder_len(builder)
- // res += {.CHANGE}
- // }
- // }
- /* handle ctrl+a */
- if .A in ctx.key_pressed_bits &&
- .CTRL in ctx.key_down_bits &&
- .ALT not_in ctx.key_down_bits {
- ctx.textbox_state.selection = {textlen^, 0}
- }
- /* handle ctrl+x */
- if .X in ctx.key_pressed_bits &&
- .CTRL in ctx.key_down_bits &&
- .ALT not_in ctx.key_down_bits {
- if textedit.cut(&ctx.textbox_state) {
- textlen^ = strings.builder_len(builder)
- res += {.CHANGE}
- }
- }
- /* handle ctrl+c */
- if .C in ctx.key_pressed_bits &&
- .CTRL in ctx.key_down_bits &&
- .ALT not_in ctx.key_down_bits {
- textedit.copy(&ctx.textbox_state)
- }
- /* handle ctrl+v */
- if .V in ctx.key_pressed_bits &&
- .CTRL in ctx.key_down_bits &&
- .ALT not_in ctx.key_down_bits {
- if textedit.paste(&ctx.textbox_state) {
- textlen^ = strings.builder_len(builder)
- res += {.CHANGE}
- }
- }
- /* handle left/right */
- if .LEFT in ctx.key_pressed_bits {
- move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
- if .SHIFT in ctx.key_down_bits {
- textedit.select_to(&ctx.textbox_state, move)
- } else {
- textedit.move_to(&ctx.textbox_state, move)
- }
- }
- if .RIGHT in ctx.key_pressed_bits {
- move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
- if .SHIFT in ctx.key_down_bits {
- textedit.select_to(&ctx.textbox_state, move)
- } else {
- textedit.move_to(&ctx.textbox_state, move)
- }
- }
- /* handle home/end */
- if .HOME in ctx.key_pressed_bits {
- if .SHIFT in ctx.key_down_bits {
- textedit.select_to(&ctx.textbox_state, .Start)
- } else {
- textedit.move_to(&ctx.textbox_state, .Start)
- }
- }
- if .END in ctx.key_pressed_bits {
- if .SHIFT in ctx.key_down_bits {
- textedit.select_to(&ctx.textbox_state, .End)
- } else {
- textedit.move_to(&ctx.textbox_state, .End)
- }
- }
- /* handle backspace/delete */
- if .BACKSPACE in ctx.key_pressed_bits && textlen^ > 0 {
- move: textedit.Translation = .Word_Left if .CTRL in ctx.key_down_bits else .Left
- textedit.delete_to(&ctx.textbox_state, move)
- textlen^ = strings.builder_len(builder)
- res += {.CHANGE}
- }
- if .DELETE in ctx.key_pressed_bits && textlen^ > 0 {
- move: textedit.Translation = .Word_Right if .CTRL in ctx.key_down_bits else .Right
- textedit.delete_to(&ctx.textbox_state, move)
- textlen^ = strings.builder_len(builder)
- res += {.CHANGE}
- }
- /* handle return */
- if .RETURN in ctx.key_pressed_bits {
- set_focus(ctx, 0)
- res += {.SUBMIT}
- }
- /* handle click/drag */
- if .LEFT in ctx.mouse_down_bits {
- idx := textlen^
- for i in 0 ..< textlen^ {
- /* skip continuation bytes */
- if textbuf[i] >= 0x80 && textbuf[i] < 0xc0 {
- continue
- }
- if ctx.mouse_pos.x <
- r.x + ctx.textbox_offset + ctx.text_width(font, string(textbuf[:i])) {
- idx = i
- break
- }
- }
- ctx.textbox_state.selection[0] = idx
- if .LEFT in ctx.mouse_pressed_bits && .SHIFT not_in ctx.key_down_bits {
- ctx.textbox_state.selection[1] = idx
- }
- }
- }
- textstr := string(textbuf[:textlen^])
- /* draw */
- draw_control_frame(ctx, id, r, .BASE, opt)
- if ctx.focus_id == id {
- text_color := ctx.style.colors[.TEXT]
- sel_color := ctx.style.colors[.SELECTION_BG]
- textw := ctx.text_width(font, textstr)
- texth := ctx.text_height(font)
- headx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[0]])
- tailx := ctx.text_width(font, textstr[:ctx.textbox_state.selection[1]])
- ofmin := max(ctx.style.padding - headx, r.w - textw - ctx.style.padding)
- ofmax := min(r.w - headx - ctx.style.padding, ctx.style.padding)
- ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)
- textx := r.x + ctx.textbox_offset
- texty := r.y + (r.h - texth) / 2
- push_clip_rect(ctx, r)
- draw_rect(
- ctx,
- Rect{textx + min(headx, tailx), texty, abs(headx - tailx), texth},
- sel_color,
- )
- draw_text(ctx, font, textstr, Vec2{textx, texty}, text_color)
- draw_rect(ctx, Rect{textx + headx, texty, 1, texth}, text_color)
- pop_clip_rect(ctx)
- } else {
- draw_control_text(ctx, textstr, r, .TEXT, opt)
- }
- return
- }
- @(private)
- parse_real :: #force_inline proc(s: string) -> (Real, bool) {
- f, ok := strconv.parse_f64(s)
- return Real(f), ok
- }
- number_textbox :: proc(ctx: ^Context, value: ^Real, r: Rect, id: Id, fmt_string: string) -> bool {
- if ctx.mouse_pressed_bits == {.LEFT} && .SHIFT in ctx.key_down_bits && ctx.hover_id == id {
- ctx.number_edit_id = id
- nstr := fmt.bprintf(ctx.number_edit_buf[:], fmt_string, value^)
- ctx.number_edit_len = len(nstr)
- }
- if ctx.number_edit_id == id {
- res := textbox_raw(ctx, ctx.number_edit_buf[:], &ctx.number_edit_len, id, r, {})
- if .SUBMIT in res || ctx.focus_id != id {
- value^, _ = parse_real(string(ctx.number_edit_buf[:ctx.number_edit_len]))
- ctx.number_edit_id = 0
- } else {
- return true
- }
- }
- return false
- }
- textbox :: proc(ctx: ^Context, buf: []u8, textlen: ^int, opt := Options{}) -> Result_Set {
- id := get_id(ctx, uintptr(&buf[0]))
- r := layout_next(ctx)
- return textbox_raw(ctx, buf, textlen, id, r, opt)
- }
- slider :: proc(
- ctx: ^Context,
- value: ^Real,
- low, high: Real,
- step: Real = 0.0,
- fmt_string: string = SLIDER_FMT,
- opt: Options = {.ALIGN_CENTER},
- ) -> (
- res: Result_Set,
- ) {
- last := value^
- v := last
- id := get_id(ctx, uintptr(value))
- base := layout_next(ctx)
- /* handle text input mode */
- if number_textbox(ctx, &v, base, id, fmt_string) {
- return
- }
- /* handle normal mode */
- update_control(ctx, id, base, opt)
- /* handle input */
- if ctx.focus_id == id && ctx.mouse_down_bits == {.LEFT} {
- v = low + Real(ctx.mouse_pos.x - base.x) * (high - low) / Real(base.w)
- if step != 0.0 {
- v = math.floor((v + step / 2) / step) * step
- }
- }
- /* clamp and store value, update res */
- v = clamp(v, low, high);value^ = v
- if last != v {
- res += {.CHANGE}
- }
- /* draw base */
- draw_control_frame(ctx, id, base, .BASE, opt)
- /* draw thumb */
- w := ctx.style.thumb_size
- x := i32((v - low) * Real(base.w - w) / (high - low))
- thumb := Rect{base.x + x, base.y, w, base.h}
- draw_control_frame(ctx, id, thumb, .BUTTON, opt)
- /* draw text */
- text_buf: [4096]byte
- draw_control_text(ctx, fmt.bprintf(text_buf[:], fmt_string, v), base, .TEXT, opt)
- return
- }
- number :: proc(
- ctx: ^Context,
- value: ^Real,
- step: Real,
- fmt_string: string = SLIDER_FMT,
- opt: Options = {.ALIGN_CENTER},
- ) -> (
- res: Result_Set,
- ) {
- id := get_id(ctx, uintptr(value))
- base := layout_next(ctx)
- last := value^
- /* handle text input mode */
- if number_textbox(ctx, value, base, id, fmt_string) {
- return
- }
- /* handle normal mode */
- update_control(ctx, id, base, opt)
- /* handle input */
- if ctx.focus_id == id && ctx.mouse_down_bits == {.LEFT} {
- value^ += Real(ctx.mouse_delta.x) * step
- }
- /* set flag if value changed */
- if value^ != last {
- res += {.CHANGE}
- }
- /* draw base */
- draw_control_frame(ctx, id, base, .BASE, opt)
- /* draw text */
- text_buf: [4096]byte
- draw_control_text(ctx, fmt.bprintf(text_buf[:], fmt_string, value^), base, .TEXT, opt)
- return
- }
- @(private)
- _header :: proc(ctx: ^Context, label: string, is_treenode: bool, opt := Options{}) -> Result_Set {
- id := get_id(ctx, label)
- idx, active := pool_get(ctx, ctx.treenode_pool[:], id)
- expanded := .EXPANDED in opt ? !active : active
- layout_row(ctx, {-1})
- r := layout_next(ctx)
- update_control(ctx, id, r, {})
- /* handle click */
- if ctx.mouse_pressed_bits == {.LEFT} && ctx.focus_id == id {
- active = !active
- }
- /* update pool ref */
- if idx >= 0 {
- if active {
- pool_update(ctx, &ctx.treenode_pool[idx])
- } else {
- ctx.treenode_pool[idx] = {}
- }
- } else if active {
- pool_init(ctx, ctx.treenode_pool[:], id)
- }
- /* draw */
- if is_treenode {
- if ctx.hover_id == id {
- ctx.draw_frame(ctx, r, .BUTTON_HOVER)
- }
- } else {
- draw_control_frame(ctx, id, r, .BUTTON)
- }
- draw_icon(
- ctx,
- expanded ? .EXPANDED : .COLLAPSED,
- Rect{r.x, r.y, r.h, r.h},
- ctx.style.colors[.TEXT],
- )
- r.x += r.h - ctx.style.padding
- r.w -= r.h - ctx.style.padding
- draw_control_text(ctx, label, r, .TEXT)
- return expanded ? {.ACTIVE} : {}
- }
- header :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
- return _header(ctx, label, false, opt)
- }
- begin_treenode :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
- res := _header(ctx, label, true, opt)
- if .ACTIVE in res {
- get_layout(ctx).indent += ctx.style.indent
- push(&ctx.id_stack, ctx.last_id)
- }
- return res
- }
- end_treenode :: proc(ctx: ^Context) {
- get_layout(ctx).indent -= ctx.style.indent
- pop_id(ctx)
- }
- scoped_end_treenode :: proc(ctx: ^Context, _: string, _: Options, result_set: Result_Set) {
- if result_set != nil {
- end_treenode(ctx)
- }
- }
- /* This is scoped and is intended to be use in the condition of a if-statement */
- @(deferred_in_out = scoped_end_treenode)
- treenode :: proc(ctx: ^Context, label: string, opt := Options{}) -> Result_Set {
- return begin_treenode(ctx, label, opt)
- }
- @(private)
- scrollbar :: proc(ctx: ^Context, cnt: ^Container, _b: ^Rect, cs: Vec2, id_string: string, i: int) {
- b := (^struct {
- pos, size: [2]i32,
- })(_b)
- #assert(size_of(b^) == size_of(_b^))
- /* only add scrollbar if content size is larger than body */
- maxscroll := cs[i] - b.size[i]
- contentsize := b.size[i]
- if maxscroll > 0 && contentsize > 0 {
- id := get_id(ctx, id_string)
- /* get sizing / positioning */
- base := b^
- base.pos[1 - i] = b.pos[1 - i] + b.size[1 - i]
- base.size[1 - i] = ctx.style.scrollbar_size
- /* handle input */
- update_control(ctx, id, transmute(Rect)base)
- if ctx.focus_id == id && .LEFT in ctx.mouse_down_bits {
- cnt.scroll[i] += ctx.mouse_delta[i] * cs[i] / base.size[i]
- }
- /* clamp scroll to limits */
- cnt.scroll[i] = clamp(cnt.scroll[i], 0, maxscroll)
- /* draw base and thumb */
- ctx.draw_frame(ctx, transmute(Rect)base, .SCROLL_BASE)
- thumb := base
- thumb.size[i] = max(ctx.style.thumb_size, base.size[i] * b.size[i] / cs[i])
- thumb.pos[i] += cnt.scroll[i] * (base.size[i] - thumb.size[i]) / maxscroll
- ctx.draw_frame(ctx, transmute(Rect)thumb, .SCROLL_THUMB)
- /* set this as the scroll_target (will get scrolled on mousewheel) */
- /* if the mouse is over it */
- if mouse_over(ctx, transmute(Rect)b^) {
- ctx.scroll_target = cnt
- }
- } else {
- cnt.scroll[i] = 0
- }
- }
- @(private)
- scrollbars :: proc(ctx: ^Context, cnt: ^Container, body: ^Rect) {
- sz := ctx.style.scrollbar_size
- cs := cnt.content_size
- cs.x += ctx.style.padding * 2
- cs.y += ctx.style.padding * 2
- push_clip_rect(ctx, body^)
- /* resize body to make room for scrollbars */
- if cs.y > cnt.body.h {body.w -= sz}
- if cs.x > cnt.body.w {body.h -= sz}
- /* to create a horizontal or vertical scrollbar almost-identical code is
- ** used; only the references to `x|y` `w|h` need to be switched */
- scrollbar(ctx, cnt, body, cs, "!scrollbarv", 1) // 1 = y,h
- scrollbar(ctx, cnt, body, cs, "!scrollbarh", 0) // 0 = x,w
- pop_clip_rect(ctx)
- }
- @(private)
- push_container_body :: proc(ctx: ^Context, cnt: ^Container, body: Rect, opt := Options{}) {
- body := body
- if .NO_SCROLL not_in opt {
- scrollbars(ctx, cnt, &body)
- }
- push_layout(ctx, expand_rect(body, -ctx.style.padding), cnt.scroll)
- cnt.body = body
- }
- @(private)
- begin_root_container :: proc(ctx: ^Context, cnt: ^Container) {
- push(&ctx.container_stack, cnt)
- /* push container to roots list and push head command */
- push(&ctx.root_list, cnt)
- cnt.head = push_jump(ctx, nil)
- /* set as hover root if the mouse is overlapping this container and it has a
- ** higher zindex than the current hover root */
- if rect_overlaps_vec2(cnt.rect, ctx.mouse_pos) &&
- (ctx.next_hover_root == nil || cnt.zindex > ctx.next_hover_root.zindex) {
- ctx.next_hover_root = cnt
- }
- /* clipping is reset here in case a root-container is made within
- ** another root-containers's begin/end block; this prevents the inner
- ** root-container being clipped to the outer */
- push(&ctx.clip_stack, unclipped_rect)
- }
- @(private)
- end_root_container :: proc(ctx: ^Context) {
- /* push tail 'goto' jump command and set head 'skip' command. the final steps
- ** on initing these are done in end() */
- cnt := get_current_container(ctx)
- cnt.tail = push_jump(ctx, nil)
- cnt.head.variant.(^Command_Jump).dst = &ctx.command_list.items[ctx.command_list.idx]
- /* pop base clip rect and container */
- pop_clip_rect(ctx)
- pop_container(ctx)
- }
- begin_window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{}) -> bool {
- assert(title != "", "missing window title")
- id := get_id(ctx, title)
- cnt := internal_get_container(ctx, id, opt)
- if cnt == nil || !cnt.open {
- return false
- }
- push(&ctx.id_stack, id)
- rect := rect
- if cnt.rect.w == 0 {
- cnt.rect = rect
- }
- begin_root_container(ctx, cnt)
- rect = cnt.rect
- body := cnt.rect
- /* draw frame */
- if .NO_FRAME not_in opt {
- ctx.draw_frame(ctx, rect, .WINDOW_BG)
- }
- /* do title bar */
- if .NO_TITLE not_in opt {
- tr := rect
- tr.h = ctx.style.title_height
- ctx.draw_frame(ctx, tr, .TITLE_BG)
- /* do title text */
- if .NO_TITLE not_in opt {
- tid := get_id(ctx, "!title")
- update_control(ctx, tid, tr, opt)
- draw_control_text(ctx, title, tr, .TITLE_TEXT, opt)
- if tid == ctx.focus_id && ctx.mouse_down_bits == {.LEFT} {
- cnt.rect.x += ctx.mouse_delta.x
- cnt.rect.y += ctx.mouse_delta.y
- }
- body.y += tr.h
- body.h -= tr.h
- }
- /* do `close` button */
- if .NO_CLOSE not_in opt {
- cid := get_id(ctx, "!close")
- r := Rect{tr.x + tr.w - tr.h, tr.y, tr.h, tr.h}
- tr.w -= r.w
- draw_icon(ctx, .CLOSE, r, ctx.style.colors[.TITLE_TEXT])
- update_control(ctx, cid, r, opt)
- if .LEFT in ctx.mouse_released_bits && cid == ctx.hover_id {
- cnt.open = false
- }
- }
- }
- /* do `resize` handle */
- if .NO_RESIZE not_in opt {
- @(static)
- tmp_start: Vec2
- @(static)
- tmp_size: Vec2
- sz := ctx.style.footer_height
- rid := get_id(ctx, "!resize")
- r := Rect{rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz}
- draw_icon(ctx, .RESIZE, r, ctx.style.colors[.TEXT])
- update_control(ctx, rid, r, opt)
- if rid == ctx.focus_id && .LEFT in ctx.mouse_pressed_bits {
- tmp_start = ctx.mouse_pos
- tmp_size = {cnt.rect.w, cnt.rect.h}
- }
- if rid == ctx.focus_id && .LEFT in ctx.mouse_down_bits {
- cnt.rect.w = max(96, tmp_size.x + ctx.mouse_pos.x - tmp_start.x)
- cnt.rect.h = max(64, tmp_size.y + ctx.mouse_pos.y - tmp_start.y)
- }
- body.h -= sz
- }
- push_container_body(ctx, cnt, body, opt)
- /* resize to content size */
- if .AUTO_SIZE in opt {
- r := get_layout(ctx).body
- cnt.rect.w = cnt.content_size.x + (cnt.rect.w - r.w)
- cnt.rect.h = cnt.content_size.y + (cnt.rect.h - r.h)
- }
- /* close if this is a popup window and elsewhere was clicked */
- if .POPUP in opt && mouse_pressed(ctx) && ctx.hover_root != cnt {
- cnt.open = false
- }
- push_clip_rect(ctx, cnt.body)
- return true
- }
- end_window :: proc(ctx: ^Context) {
- pop_clip_rect(ctx)
- end_root_container(ctx)
- }
- /* This is scoped and is intended to be use in the condition of a if-statement */
- @(deferred_in_out = scoped_end_window)
- window :: proc(ctx: ^Context, title: string, rect: Rect, opt := Options{}) -> bool {
- return begin_window(ctx, title, rect, opt)
- }
- scoped_end_window :: proc(ctx: ^Context, _: string, _: Rect, _: Options, ok: bool) {
- if ok {
- end_window(ctx)
- }
- }
- open_popup :: proc(ctx: ^Context, name: string) {
- cnt := get_container(ctx, name)
- /* set as hover root so popup isn't closed in begin_window() */
- ctx.hover_root = cnt
- ctx.next_hover_root = cnt
- /* position at mouse cursor, open and bring-to-front */
- cnt.rect = Rect{ctx.mouse_pos.x, ctx.mouse_pos.y, 1, 1}
- cnt.open = true
- bring_to_front(ctx, cnt)
- }
- begin_popup :: proc(ctx: ^Context, name: string) -> bool {
- opt := Options{.POPUP, .AUTO_SIZE, .NO_RESIZE, .NO_SCROLL, .NO_TITLE, .CLOSED}
- return begin_window(ctx, name, Rect{}, opt)
- }
- end_popup :: proc(ctx: ^Context) {
- end_window(ctx)
- }
- /* This is scoped and is intended to be use in the condition of a if-statement */
- @(deferred_in_out = scoped_end_popup)
- popup :: proc(ctx: ^Context, name: string) -> bool {
- return begin_popup(ctx, name)
- }
- scoped_end_popup :: proc(ctx: ^Context, _: string, ok: bool) {
- if ok {
- end_popup(ctx)
- }
- }
- begin_panel :: proc(ctx: ^Context, name: string, opt := Options{}) {
- assert(name != "", "missing panel name")
- push_id(ctx, name)
- cnt := internal_get_container(ctx, ctx.last_id, opt)
- cnt.rect = layout_next(ctx)
- if .NO_FRAME not_in opt {
- ctx.draw_frame(ctx, cnt.rect, .PANEL_BG)
- }
- push(&ctx.container_stack, cnt)
- push_container_body(ctx, cnt, cnt.rect, opt)
- push_clip_rect(ctx, cnt.body)
- }
- end_panel :: proc(ctx: ^Context) {
- pop_clip_rect(ctx)
- pop_container(ctx)
- }
- @(private)
- mouse_released :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_released_bits != nil}
- @(private)
- mouse_pressed :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_pressed_bits != nil}
- @(private)
- mouse_down :: #force_inline proc(ctx: ^Context) -> bool {return ctx.mouse_down_bits != nil}
|