commit 4f90f930522d920c913a079b016db8617e44d61e
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Mon, 28 Oct 2024 19:00:42 +0800
initial: odin bindings for libui
Signed-off-by: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Diffstat:
11 files changed, 1614 insertions(+), 0 deletions(-)
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libui"]
+ path = libui
+ url = https://github.com/andlabs/libui.git
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,61 @@
+This project is dual-licensed under the UNLICENSE or
+the MIT license with the SPDX identifier:
+
+SPDX-License-Identifier: Unlicense OR MIT
+
+You may use the project under the terms of either license.
+
+Both licenses are reproduced below.
+
+----
+The MIT License (MIT)
+
+Copyright (c) 2023 Jack Mordaunt
+
+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.
+---
+
+---
+The UNLICENSE
+
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+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 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.
+
+For more information, please refer to <https://unlicense.org/>
+---
diff --git a/README.md b/README.md
@@ -0,0 +1,16 @@
+# odin-libui
+
+This package contains bindings generated for [libui](https://github.com/andlabs/libui).
+
+## Build
+
+libui must be available to the Odin compiler. You need to build and install `libui.a`.
+
+## Generator
+
+Regenerate bindings with `odin run ./generator`.
+
+## Notes
+
+- widgets structures are opaque since their implementation is contained in platform specific code
+- code generator uses ad-hoc C parsing logic that is in no way appropriate for general use as it is tuned to the content of ui.h
diff --git a/examples/drawtext/main.odin b/examples/drawtext/main.odin
@@ -0,0 +1,102 @@
+package main
+
+import "core:c"
+import "core:log"
+
+import libui "../../"
+
+main :: proc() {
+ options: libui.InitOptions
+
+ if err := libui.Init(&options); err != "" {
+ defer libui.FreeInitError(err)
+ log.errorf("init error: %v", err)
+ return
+ }
+
+ window := libui.NewWindow("drawtext", 640, 480, 1)
+ libui.WindowSetMargined(window, 1)
+
+ libui.WindowOnClosing(window, on_closing, nil)
+ libui.OnShouldQuit(should_quit, window)
+
+ box := libui.NewHorizontalBox()
+ libui.BoxSetPadded(box, 1)
+ libui.WindowSetChild(window, auto_cast box)
+
+ control_panel := libui.NewVerticalBox()
+ libui.BoxSetPadded(control_panel, 1)
+ libui.BoxAppend(box, auto_cast control_panel, 1)
+
+ handler := libui.AreaHandler {
+ Draw = handler_draw,
+ MouseEvent = handler_mouse_event,
+ MouseCrossed = handler_mouse_crossed,
+ DragBroken = handler_drag_broken,
+ KeyEvent = handler_key_event,
+ }
+
+ area := libui.NewArea(&handler)
+ libui.BoxAppend(box, auto_cast area, 1)
+
+ libui.BoxAppend(control_panel, auto_cast libui.NewButton("one"), 0)
+ libui.BoxAppend(control_panel, auto_cast libui.NewButton("two"), 0)
+ libui.BoxAppend(control_panel, auto_cast libui.NewButton("three"), 0)
+ libui.BoxAppend(control_panel, auto_cast libui.NewButton("four"), 0)
+ libui.BoxAppend(control_panel, auto_cast libui.NewButton("five"), 0)
+
+ libui.ControlShow(auto_cast window)
+ libui.Main()
+ libui.Uninit()
+}
+
+on_closing :: proc "c" (w: libui.Window, data: rawptr) -> c.int {
+ libui.ControlDestroy(auto_cast w)
+ libui.Quit()
+ return 0
+}
+
+should_quit :: proc "c" (data: rawptr) -> c.int {
+ libui.ControlDestroy(auto_cast data)
+ return 1
+}
+
+handler_draw :: proc(handler: ^libui.AreaHandler, area: libui.Area, p: ^libui.AreaDrawParams) {
+ params: libui.DrawTextLayoutParams
+ default_font: libui.FontDescriptor
+
+ text := libui.NewAttributedString("The quick brown fox jumps over the lazy dog.")
+ defer libui.FreeAttributedString(text)
+
+ params.String = text
+ params.Width = p.AreaWidth
+ params.Align = .DrawTextAlignCenter
+
+ params.DefaultFont = &default_font
+ params.DefaultFont.Family = "Courier New"
+ params.DefaultFont.Size = 18.0
+ params.DefaultFont.Weight = .TextWeightNormal
+ params.DefaultFont.Stretch = .TextStretchNormal
+ params.DefaultFont.Italic = .TextItalicNormal
+
+ text_layout := libui.DrawNewTextLayout(¶ms)
+ defer libui.DrawFreeTextLayout(text_layout)
+
+ libui.DrawText(p.Context, text_layout, 0, 0)
+}
+
+handler_mouse_event :: proc(_: ^libui.AreaHandler, _: libui.Area, _: ^libui.AreaMouseEvent) {
+
+}
+
+handler_mouse_crossed :: proc(_: ^libui.AreaHandler, _: libui.Area, left: c.int) {
+
+}
+handler_drag_broken :: proc(_: ^libui.AreaHandler, _: libui.Area) {
+
+}
+
+handler_key_event :: proc(_: ^libui.AreaHandler, _: libui.Area, _: ^libui.AreaKeyEvent) -> c.int {
+ return 0
+}
+
diff --git a/generator/gen.odin b/generator/gen.odin
@@ -0,0 +1,292 @@
+package main
+
+import "core:bytes"
+import "core:fmt"
+import "core:io"
+import "core:strings"
+
+
+// generate the odin bindings.
+generate :: proc(wr: io.Writer, p: Parser) {
+ fmt.wprintf(wr, "// GENERATED CODE\n")
+ fmt.wprintf(wr, "package libui\n\n")
+
+ fmt.wprintln(wr, `import libc "core:c"`)
+ fmt.wprintln(wr, import_preamble)
+
+ // To handle the opaque (rawptr) types properly we want to avoid declaring
+ // them with a ^, since that would make them a pointer to a rawptr.
+ // However, we can only know if a type should be opaque if we haven't
+ // encountered any definition for it (struct, enum, function). Thus as
+ // quick and dirty hack, this logic will perform a dry run in order to
+ // resolve opaque types. The second and true run then can omit the
+ // superfluous caret appropriately.
+
+ dry_run: bytes.Buffer
+ write_everything(bytes.buffer_to_stream(&dry_run), p)
+
+ fmt.wprintln(wr, "// Unresolved symbols; typically opaque widget pointers")
+
+ outer: for type_name in idents {
+ for s in p.struct_defs {
+ if s.name == type_name {
+ continue outer
+ }
+ }
+ for e in p.enum_defs {
+ if e.name == type_name {
+ continue outer
+ }
+ }
+ for f in p.function_defs {
+ if f.name == type_name {
+ continue outer
+ }
+ }
+ opaque_types[type_name] = {}
+ fmt.wprintf(wr, "%s :: rawptr\n", ident(type_name))
+ }
+
+ fmt.wprintln(wr)
+
+ write_everything(wr, p)
+}
+
+write_everything :: proc(wr: io.Writer, p: Parser) {
+ fmt.println("writing edge-cases")
+
+ fmt.wprintln(wr, "// Hardcoded #defines ")
+ fmt.wprintf(wr, "DrawDefaultMiterLimit :: 10.0\n")
+ fmt.wprintln(wr)
+
+ fmt.wprintln(wr, "// Enum definitions")
+
+ /*
+ Enum definitions.
+ */
+
+ for enum_def in p.enum_defs {
+ fmt.println("writing", enum_def.name)
+ fmt.wprintf(wr, "%s :: enum {{\n", ident(enum_def.name))
+ for variant in enum_def.variants {
+ fmt.wprintf(wr, "\t%s", ident(variant.name))
+ if n, ok := variant.value.?; ok {
+ fmt.wprintf(wr, " = %d", n)
+ }
+ fmt.wprintf(wr, ",\n")
+ }
+ fmt.wprintf(wr, "}\n\n")
+ }
+
+ /*
+ Struct definitions.
+ */
+
+ fmt.wprintln(wr, "// Struct definitions")
+
+ // Explit case for tm (libc time struct)
+ fmt.wprintln(wr, broken_down_time_def)
+
+ for struct_def in p.struct_defs {
+ fmt.println("writing", struct_def.name)
+ fmt.wprintf(wr, "%s :: struct {{\n", ident(struct_def.name))
+ for field in struct_def.fields {
+ fn, fn_ok := field.function.?
+ if fn_ok {
+ fmt.wprintf(wr, "\t%s: %sproc", ident(fn.name), field.is_pointer ? "^" : "")
+ write_procedure_args(wr, fn.args[:])
+ if fn.return_type != "" && !(fn.return_type == "void" && !fn.return_is_pointer) {
+ fmt.wprintf(wr, " -> %s", ident(fn.return_type, fn.return_is_pointer, true))
+ }
+ } else {
+ fmt.wprintf(wr, "\t%s: %s", ident(field.name), ident(field.type, field.is_pointer))
+ }
+ fmt.wprintf(wr, ",\n")
+ }
+ fmt.wprintf(wr, "}\n\n")
+ }
+
+ /*
+ Procedure types are named proc types that are not part of the binding.
+ */
+
+ fmt.wprintln(wr, "// Procedure types")
+
+ for function_def in p.function_defs {
+ if !function_def.is_type do continue
+ fmt.println("writing", function_def.name)
+ fmt.wprintf(
+ wr,
+ "%s :: %s proc",
+ ident(function_def.name),
+ function_def.is_type ? "#type" : "",
+ )
+ write_procedure_args(wr, function_def.args[:])
+ if function_def.return_type != "" &&
+ !(function_def.return_type == "void" && !function_def.return_is_pointer) {
+ fmt.wprintf(
+ wr,
+ " -> %s",
+ ident(function_def.return_type, function_def.return_is_pointer, true),
+ )
+ }
+ if !function_def.is_type {
+ fmt.wprintf(wr, " ---")
+ }
+ fmt.wprintf(wr, "\n")
+ }
+
+ fmt.wprintf(wr, "\n")
+
+ /*
+ Procedure definitions. These procs are implemented by the library.
+ */
+
+ fmt.wprintln(wr, "// Procedure definitions")
+
+ // TODO: maybe check for system installed version of libui.a?
+ // TODO: change to .lib for Windows with "where" clause
+ fmt.wprintln(wr, `@(default_calling_convention = "stdcall", link_prefix = "ui")`)
+ fmt.wprintf(wr, "foreign lib {{\n")
+
+ for function_def in p.function_defs {
+ if function_def.is_type do continue
+ fmt.println("writing", function_def.name)
+ fmt.wprintf(
+ wr,
+ "\t%s :: %s proc",
+ ident(function_def.name),
+ function_def.is_type ? "#type" : "",
+ )
+ write_procedure_args(wr, function_def.args[:])
+ if function_def.return_type != "" &&
+ !(function_def.return_type == "void" && !function_def.return_is_pointer) {
+ fmt.wprintf(
+ wr,
+ " -> %s",
+ ident(function_def.return_type, function_def.return_is_pointer, true),
+ )
+ }
+ if !function_def.is_type {
+ fmt.wprintf(wr, " ---")
+ }
+ fmt.wprintf(wr, "\n")
+ }
+
+ fmt.wprintf(wr, "}\n")
+}
+
+write_procedure_args :: proc(wr: io.Writer, args: []Function_Argument) {
+ fmt.wprintf(wr, "(")
+ for arg, ii in args {
+ fn, fn_ok := arg.function.?
+ if fn_ok {
+ fmt.wprintf(wr, "%s: %sproc", ident(fn.name), arg.is_pointer ? "^" : "")
+ write_procedure_args(wr, fn.args[:])
+ if fn.return_type != "" && !(fn.return_type == "void" && !fn.return_is_pointer) {
+ fmt.wprintf(wr, " -> %s", ident(fn.return_type, fn.return_is_pointer))
+ }
+ } else {
+ if !(arg.type == "void" && !arg.is_pointer) {
+ fmt.wprintf(
+ wr,
+ "%s: %s",
+ arg.name == "" ? "_" : ident(arg.name),
+ ident(arg.type, arg.is_pointer, true),
+ )
+ }
+ }
+ if ii < len(args) - 1 {
+ fmt.wprintf(wr, ", ")
+ }
+ }
+ fmt.wprintf(wr, ")")
+}
+
+// opaque_types is a map of types that have no known definition.
+// Such types will be defined as rawptr.
+opaque_types := map[string]struct {}{}
+
+// idents is a map of encoutered identifiers. Compared against
+// known defintions to figure out which types are opaque.
+idents := map[string]struct {}{}
+
+// ident strips off the "ui" prefix and does name replacements.
+//
+// opaque types (void pointers) must not be prefixed with ^ otherwise
+// they become pointer-to-rawptr which is not correct.
+ident :: proc(text: string, is_pointer := false, is_type := false) -> string {
+ text := text
+
+ b := strings.Builder{}
+ strings.builder_init_none(&b, context.temp_allocator)
+
+ if is_pointer {
+ if _, ok := opaque_types[text]; !ok {
+ strings.write_string(&b, "^")
+ }
+ }
+
+ switch text {
+ case "int":
+ strings.write_string(&b, "libc.int")
+ case "uint32_t":
+ strings.write_string(&b, "libc.uint32_t")
+ case "uint64_t":
+ strings.write_string(&b, "libc.uint64_t")
+ case "uintptr_t":
+ strings.write_string(&b, "libc.uintptr_t")
+ case "size_t":
+ strings.write_string(&b, "libc.size_t")
+ case "void":
+ if is_pointer {
+ strings.builder_reset(&b)
+ strings.write_string(&b, "rawptr")
+ } else {
+ return ""
+ }
+ case "double":
+ strings.write_string(&b, "libc.double")
+ case "char":
+ if is_pointer {
+ strings.builder_reset(&b)
+ strings.write_string(&b, "cstring")
+ } else {
+ strings.write_string(&b, "byte")
+ }
+ case:
+ if is_type && text != "tm" && text != "int" {
+ idents[text] = {}
+ }
+ if strings.has_prefix(text, "ui") {
+ text = text[2:]
+ }
+ strings.write_string(&b, text)
+ }
+
+ return strings.to_string(b)
+}
+
+broken_down_time_def :: `
+// ISO C broken-down time structure.
+tm :: struct {
+ tm_sec: int,
+ tm_min: int,
+ tm_hour: int,
+ tm_mday: int,
+ tm_mon: int,
+ tm_year: int,
+ tm_wday: int,
+ tm_yday: int,
+ tm_isdst: int,
+}
+`
+
+import_preamble :: `
+when ODIN_OS == .Linux {
+ @(extra_linker_flags="-lgtk-3 -lgdk-3 -lz -lharfbuzz -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo -lcairo-gobject -lgdk_pixbuf-2.0 -lgio-2.0 -lglib-2.0 -lgobject-2.0")
+ foreign import lib "system:ui"
+} else {
+ foreign import lib "system:ui"
+}
+`
diff --git a/generator/main.odin b/generator/main.odin
@@ -0,0 +1,63 @@
+// Package generator implements logic for generating bindings for libui.
+//
+// This package has rough parsing logic that is tuned for the C syntax
+// found in ui.h, and does not come even close to being a generally-useful
+// C parser.
+//
+// Specfically we do no type checking and we minimal expression parsing.
+package main
+
+import "core:bufio"
+import "core:fmt"
+import "core:log"
+import "core:mem/virtual"
+import "core:os"
+
+main :: proc() {
+ context.logger = log.create_console_logger()
+
+ dir := os.get_current_directory()
+
+ f, f_err := os.open("libui/ui.h")
+ if f_err != os.ERROR_NONE {
+ log.errorf("failed opening header file: %w", f_err)
+ return
+ }
+
+ defer os.close(f)
+
+ by, by_err := os.read_entire_file_or_err(f)
+ if by_err != os.ERROR_NONE {
+ log.errorf("failed reading header file: %w", by_err)
+ return
+ }
+
+ defer delete(by)
+
+ arena: virtual.Arena
+ if err := virtual.arena_init_growing(&arena); err != nil {
+ panic(fmt.tprintf("creating growing virtual memory arena: %w", err))
+ }
+
+ context.allocator = virtual.arena_allocator(&arena)
+ defer free_all()
+
+ p: Parser
+ parser_parse(&p, by)
+
+ os.remove("libui.odin")
+
+ out_f, out_f_err := os.open("libui.odin", os.O_CREATE | os.O_RDWR, 0o644)
+ if out_f_err != os.ERROR_NONE {
+ log.errorf("failed opening output file: %w", out_f_err)
+ return
+ }
+
+ defer os.close(out_f)
+
+ buf_writer: bufio.Writer
+ bufio.writer_init(&buf_writer, os.stream_from_handle(out_f))
+
+ generate(bufio.writer_to_writer(&buf_writer), p)
+}
+
diff --git a/generator/parse.odin b/generator/parse.odin
@@ -0,0 +1,414 @@
+package main
+
+import "core:bytes"
+import "core:fmt"
+import "core:strconv"
+import "core:strings"
+import "core:unicode"
+
+Struct :: struct {
+ name: string,
+ fields: [dynamic]Struct_Field,
+}
+
+Struct_Field :: struct {
+ type: string,
+ name: string,
+ is_pointer: bool,
+ // Because we don't do generic expression parsing we must explicitly
+ // allow for function pointers.
+ function: Maybe(Function),
+}
+
+Function :: struct {
+ name: string,
+ return_type: string,
+ return_is_pointer: bool,
+ is_type: bool,
+ args: [dynamic]Function_Argument,
+}
+
+Function_Argument :: struct {
+ type: string,
+ name: string,
+ is_pointer: bool,
+ function: Maybe(Function),
+}
+
+Enum :: struct {
+ name: string,
+ type: string,
+ variants: [dynamic]Enum_Variant,
+}
+
+Enum_Variant :: struct {
+ name: string,
+ value: Maybe(uint),
+}
+
+Parser :: struct {
+ data: []byte,
+ offset: int,
+ line: int,
+ column: int,
+ enum_defs: [dynamic]Enum,
+ struct_defs: [dynamic]Struct,
+ function_defs: [dynamic]Function,
+}
+
+Parser_Proc :: #type proc(_: ^Parser)
+
+parser_parse_struct :: proc(p: ^Parser) {
+ struct_def: Struct
+ parser_consume(p, "struct")
+ parser_skip_space(p)
+ struct_def.name = parser_consume_identifier(p)
+ parser_skip_space(p)
+ if !parser_check(p, "{") do return // not all structs have bodies
+ parser_consume(p, "{")
+ for {
+ parser_skip_space(p)
+ if parser_check(p, "};") {
+ break
+ }
+ append(&struct_def.fields, parser_parse_struct_field(p))
+ }
+ parser_consume(p, "};")
+ append(&p.struct_defs, struct_def)
+}
+
+parser_check :: proc(p: ^Parser, text: string) -> bool {
+ return parser_peek(p, len(text)) == text
+}
+
+parser_consume :: proc(p: ^Parser, text: string) {
+ got := p.data[p.offset:p.offset + len(text)]
+ if !bytes.equal(got, transmute([]u8)text) {
+ panic(
+ fmt.tprintf(
+ "expected %q, got %q (offset=%d) (line=%d, col=%d) (context=%q)",
+ text,
+ got,
+ p.offset,
+ p.line + 1,
+ p.column,
+ p.data[p.offset - 10:p.offset + 10],
+ ),
+ )
+ }
+ parser_skip_n(p, len(text))
+}
+
+parser_consume_identifier :: proc(p: ^Parser) -> string {
+ parser_skip_text(p, "const ")
+ parser_skip_text(p, "struct ")
+ start := p.offset
+ for {
+ if !is_ident(p.data[p.offset]) {
+ return cast(string)p.data[start:p.offset]
+ }
+ if !parser_advance(p) {
+ break
+ }
+ }
+ panic("EOF before identifier")
+}
+
+is_ident :: proc(b: byte) -> bool {
+ r := rune(b)
+ return unicode.is_letter(r) || unicode.is_digit(r) || b == '_'
+}
+
+parser_skip_space :: proc(p: ^Parser) -> bool {
+ start := p.offset
+ for {
+ if p.data[p.offset] == '/' {
+ parser_skip_comment(p)
+ }
+ if !unicode.is_space(rune(p.data[p.offset])) {
+ return p.offset != start
+ }
+ if !parser_advance(p) {
+ break
+ }
+ }
+ return false
+}
+
+parser_parse_struct_field :: proc(p: ^Parser) -> (field: Struct_Field) {
+ ident := parser_consume_identifier(p)
+ parser_skip_space(p)
+ if parser_check(p, "*") {
+ parser_consume(p, "*")
+ field.is_pointer = true
+ }
+ parser_skip_space(p)
+ if !parser_check(p, "(") {
+ field.name = parser_consume_identifier(p)
+ } else {
+ field.function = parser_parse_function_pointer(p)
+ }
+ parser_consume(p, ";")
+ // Special case to ensure that the return type is appropriately labeled.
+ if field.function == nil {
+ field.type = ident
+ } else {
+ if f, ok := &field.function.?; ok {
+ f.return_type = ident
+ }
+ }
+ return
+}
+
+parser_parse_function_pointer :: proc(p: ^Parser) -> (f: Function) {
+ f.return_type = parser_consume_identifier(p)
+ parser_skip_space(p)
+ parser_consume(p, "(")
+ parser_skip_space(p)
+ parser_consume(p, "*")
+ parser_skip_space(p)
+ f.name = parser_consume_identifier(p)
+ parser_consume(p, ")")
+ parser_skip_space(p)
+ f.args = parser_parse_function_arguments(p)
+ return
+}
+
+parser_parse_function :: proc(p: ^Parser) {
+ f: Function
+ parser_consume(p, "_UI_EXTERN")
+ parser_skip_space(p)
+ f.return_type = parser_consume_identifier(p)
+ parser_skip_space(p)
+ if parser_check(p, "*") {
+ f.return_is_pointer = true
+ parser_consume(p, "*")
+ }
+ parser_skip_space(p)
+ f.name = parser_consume_identifier(p)
+ f.args = parser_parse_function_arguments(p)
+ parser_skip_space(p)
+ parser_consume(p, ";")
+ append(&p.function_defs, f)
+ return
+}
+
+parser_parse_function_arguments :: proc(p: ^Parser) -> (args: [dynamic]Function_Argument) {
+ parser_consume(p, "(")
+ for {
+ arg: Function_Argument
+ if parser_check(p, ")") {
+ break
+ }
+ parser_skip_space(p)
+ arg.type = parser_consume_identifier(p)
+ parser_skip_space(p)
+ if parser_check(p, "*") {
+ arg.is_pointer = true
+ parser_consume(p, "*")
+ }
+ parser_skip_space(p)
+ if parser_check(p, "(") {
+ fn := parser_parse_function_pointer(p)
+ fn.return_type = arg.type
+ arg.type = ""
+ arg.function = fn
+ } else {
+ arg.name = parser_consume_identifier(p)
+ }
+
+ if parser_check(p, ",") {
+ parser_consume(p, ",")
+ }
+ parser_skip_space(p)
+ append(&args, arg)
+ }
+ parser_consume(p, ")")
+ return
+}
+
+parser_skip_n :: proc(p: ^Parser, n: int = 1) {
+ for ii in 0 ..< n {
+ if !parser_advance(p) do return
+ }
+}
+
+parser_skip_text :: proc(p: ^Parser, text: string) {
+ if parser_check(p, text) {
+ parser_consume(p, text)
+ }
+}
+
+// TODO: might need to handle multiline preprocessor things
+parser_skip_preproccessor_stuff :: proc(p: ^Parser) -> bool {
+ if !parser_check(p, "#") {
+ return false
+ }
+ parser_skip_until(p, "\n")
+ return true
+}
+
+parser_parse_enum :: proc(p: ^Parser) {
+ enum_def: Enum
+ parser_consume(p, "_UI_ENUM")
+ parser_consume(p, "(")
+ enum_def.name = parser_consume_identifier(p)
+ parser_consume(p, ")")
+ parser_skip_space(p)
+ parser_consume(p, "{")
+ for {
+ variant: Enum_Variant
+ parser_skip_space(p)
+ if parser_check(p, "};") {
+ break
+ }
+ append(&enum_def.variants, parser_parse_enum_variant(p))
+ }
+ parser_consume(p, "};")
+ append(&p.enum_defs, enum_def)
+}
+
+parser_skip_comment :: proc(p: ^Parser) -> bool {
+ line_comment := parser_check(p, "//")
+ inline_comment := parser_check(p, "/*")
+
+ if line_comment {
+ parser_skip_until(p, "\n")
+ return true
+ }
+
+ if inline_comment {
+ parser_skip_until(p, "*/")
+ return true
+ }
+
+ return false
+}
+
+parser_skip_until :: proc(p: ^Parser, text: string) {
+ for {
+ if parser_check(p, text) {
+ defer parser_consume(p, text)
+ return
+ }
+ if !parser_advance(p) do return
+ }
+}
+
+parser_advance :: proc(p: ^Parser) -> bool {
+ if p.offset > len(p.data) - 1 {
+ return false
+ }
+ if p.data[p.offset] == '\n' {
+ p.line += 1
+ p.column = 0
+ }
+ p.column += 1
+ p.offset += 1
+ return p.offset < len(p.data)
+}
+
+parser_peek :: proc(p: ^Parser, n: int) -> string {
+ end := p.offset + n
+ if end > len(p.data) - 1 do return ""
+ return cast(string)p.data[p.offset:end]
+}
+
+bitwise_operators :: [?]string{"<<", ">>"}
+
+parser_parse_enum_variant :: proc(p: ^Parser) -> (variant: Enum_Variant) {
+ variant.name = parser_consume_identifier(p)
+ parser_skip_space(p)
+ if parser_check(p, "=") {
+ parser_consume(p, "=")
+ parser_skip_space(p)
+ n_1 := parser_parse_int(p)
+ parser_skip_space(p)
+ // Because some enums use bitshifting we have to account for that,
+ // but because we aren't doing proper expresion parsing it's total
+ // jank.
+ for op in bitwise_operators {
+ if !parser_check(p, op) do continue
+ parser_consume(p, op)
+ parser_skip_space(p)
+ n_2 := parser_parse_int(p)
+ switch op {
+ case "<<":
+ n_1 = n_1 << n_2
+ case ">>":
+ n_1 = n_1 << n_2
+ }
+ }
+ variant.value = n_1
+ }
+ parser_consume(p, ",")
+ return
+}
+
+parser_parse_int :: proc(p: ^Parser) -> uint {
+ start := p.offset
+ index: int
+ for unicode.is_digit(rune(p.data[p.offset + index])) {
+ index += 1
+ }
+ n, n_ok := strconv.parse_uint(cast(string)p.data[p.offset:p.offset + index])
+ if !n_ok {
+ panic("not a number")
+ }
+ parser_skip_n(p, index)
+ return n
+}
+
+// We are only handling typedef'd function pointers.
+parser_parse_typedef :: proc(p: ^Parser) {
+ parser_consume(p, "typedef")
+ parser_skip_space(p)
+ if parser_check(p, "struct") {
+ parser_skip_until(p, ";")
+ return
+ }
+ function := parser_parse_function_pointer(p)
+ function.is_type = true
+ parser_skip_until(p, ";")
+ append(&p.function_defs, function)
+}
+
+keywords :: [?]string{"_UI_ENUM", "_UI_EXTERN", "typedef", "struct"}
+
+parser_parse :: proc(p: ^Parser, by: []byte) {
+ p.data = by
+
+ for p.offset < len(p.data) - 1 {
+ skipped_space := parser_skip_space(p)
+ skipped_comment := parser_skip_comment(p)
+ skipped_preprocessor := parser_skip_preproccessor_stuff(p)
+
+ start := p.offset
+
+
+ defer if !(skipped_space || skipped_comment || skipped_preprocessor) {
+ end := p.offset
+
+ if start == end {
+ parser_skip_n(p, 1)
+ }
+ }
+
+ for keyword in keywords {
+ if !parser_check(p, keyword) {
+ continue
+ }
+ switch keyword {
+ case "struct":
+ parser_parse_struct(p)
+ case "_UI_ENUM":
+ parser_parse_enum(p)
+ case "_UI_EXTERN":
+ parser_parse_function(p)
+ case "typedef":
+ parser_parse_typedef(p)
+ }
+ }
+ }
+}
+
diff --git a/libui b/libui
@@ -0,0 +1 @@
+Subproject commit fea45b2d5b75839be0af9acc842a147c5cba9295
diff --git a/libui.odin b/libui.odin
@@ -0,0 +1,645 @@
+// GENERATED CODE
+package libui
+
+import libc "core:c"
+
+when ODIN_OS == .Linux {
+ @(extra_linker_flags="-lgtk-3 -lgdk-3 -lz -lharfbuzz -lpangocairo-1.0 -lpango-1.0 -latk-1.0 -lcairo -lcairo-gobject -lgdk_pixbuf-2.0 -lgio-2.0 -lglib-2.0 -lgobject-2.0")
+ foreign import lib "system:ui"
+} else {
+ foreign import lib "system:ui"
+}
+
+// Unresolved symbols; typically opaque widget pointers
+OpenTypeFeatures :: rawptr
+Menu :: rawptr
+Separator :: rawptr
+RadioButtons :: rawptr
+AttributedString :: rawptr
+DrawContext :: rawptr
+Grid :: rawptr
+Button :: rawptr
+Window :: rawptr
+TableValue :: rawptr
+ColorButton :: rawptr
+Combobox :: rawptr
+DrawTextLayout :: rawptr
+Area :: rawptr
+FontButton :: rawptr
+DateTimePicker :: rawptr
+Attribute :: rawptr
+DrawPath :: rawptr
+Table :: rawptr
+Slider :: rawptr
+Box :: rawptr
+ProgressBar :: rawptr
+EditableCombobox :: rawptr
+Checkbox :: rawptr
+Entry :: rawptr
+MenuItem :: rawptr
+Spinbox :: rawptr
+Group :: rawptr
+Form :: rawptr
+MultilineEntry :: rawptr
+TableModel :: rawptr
+Image :: rawptr
+Label :: rawptr
+Tab :: rawptr
+
+// Hardcoded #defines
+DrawDefaultMiterLimit :: 10.0
+
+// Enum definitions
+ForEach :: enum {
+ ForEachContinue,
+ ForEachStop,
+}
+
+WindowResizeEdge :: enum {
+ WindowResizeEdgeLeft,
+ WindowResizeEdgeTop,
+ WindowResizeEdgeRight,
+ WindowResizeEdgeBottom,
+ WindowResizeEdgeTopLeft,
+ WindowResizeEdgeTopRight,
+ WindowResizeEdgeBottomLeft,
+ WindowResizeEdgeBottomRight,
+}
+
+DrawBrushType :: enum {
+ DrawBrushTypeSolid,
+ DrawBrushTypeLinearGradient,
+ DrawBrushTypeRadialGradient,
+ DrawBrushTypeImage,
+}
+
+DrawLineCap :: enum {
+ DrawLineCapFlat,
+ DrawLineCapRound,
+ DrawLineCapSquare,
+}
+
+DrawLineJoin :: enum {
+ DrawLineJoinMiter,
+ DrawLineJoinRound,
+ DrawLineJoinBevel,
+}
+
+DrawFillMode :: enum {
+ DrawFillModeWinding,
+ DrawFillModeAlternate,
+}
+
+AttributeType :: enum {
+ AttributeTypeFamily,
+ AttributeTypeSize,
+ AttributeTypeWeight,
+ AttributeTypeItalic,
+ AttributeTypeStretch,
+ AttributeTypeColor,
+ AttributeTypeBackground,
+ AttributeTypeUnderline,
+ AttributeTypeUnderlineColor,
+ AttributeTypeFeatures,
+}
+
+TextWeight :: enum {
+ TextWeightMinimum = 0,
+ TextWeightThin = 100,
+ TextWeightUltraLight = 200,
+ TextWeightLight = 300,
+ TextWeightBook = 350,
+ TextWeightNormal = 400,
+ TextWeightMedium = 500,
+ TextWeightSemiBold = 600,
+ TextWeightBold = 700,
+ TextWeightUltraBold = 800,
+ TextWeightHeavy = 900,
+ TextWeightUltraHeavy = 950,
+ TextWeightMaximum = 1000,
+}
+
+TextItalic :: enum {
+ TextItalicNormal,
+ TextItalicOblique,
+ TextItalicItalic,
+}
+
+TextStretch :: enum {
+ TextStretchUltraCondensed,
+ TextStretchExtraCondensed,
+ TextStretchCondensed,
+ TextStretchSemiCondensed,
+ TextStretchNormal,
+ TextStretchSemiExpanded,
+ TextStretchExpanded,
+ TextStretchExtraExpanded,
+ TextStretchUltraExpanded,
+}
+
+Underline :: enum {
+ UnderlineNone,
+ UnderlineSingle,
+ UnderlineDouble,
+ UnderlineSuggestion,
+}
+
+UnderlineColor :: enum {
+ UnderlineColorCustom,
+ UnderlineColorSpelling,
+ UnderlineColorGrammar,
+ UnderlineColorAuxiliary,
+}
+
+DrawTextAlign :: enum {
+ DrawTextAlignLeft,
+ DrawTextAlignCenter,
+ DrawTextAlignRight,
+}
+
+Modifiers :: enum {
+ ModifierCtrl = 1,
+ ModifierAlt = 2,
+ ModifierShift = 4,
+ ModifierSuper = 8,
+}
+
+ExtKey :: enum {
+ ExtKeyEscape = 1,
+ ExtKeyInsert,
+ ExtKeyDelete,
+ ExtKeyHome,
+ ExtKeyEnd,
+ ExtKeyPageUp,
+ ExtKeyPageDown,
+ ExtKeyUp,
+ ExtKeyDown,
+ ExtKeyLeft,
+ ExtKeyRight,
+ ExtKeyF1,
+ ExtKeyF2,
+ ExtKeyF3,
+ ExtKeyF4,
+ ExtKeyF5,
+ ExtKeyF6,
+ ExtKeyF7,
+ ExtKeyF8,
+ ExtKeyF9,
+ ExtKeyF10,
+ ExtKeyF11,
+ ExtKeyF12,
+ ExtKeyN0,
+ ExtKeyN1,
+ ExtKeyN2,
+ ExtKeyN3,
+ ExtKeyN4,
+ ExtKeyN5,
+ ExtKeyN6,
+ ExtKeyN7,
+ ExtKeyN8,
+ ExtKeyN9,
+ ExtKeyNDot,
+ ExtKeyNEnter,
+ ExtKeyNAdd,
+ ExtKeyNSubtract,
+ ExtKeyNMultiply,
+ ExtKeyNDivide,
+}
+
+Align :: enum {
+ AlignFill,
+ AlignStart,
+ AlignCenter,
+ AlignEnd,
+}
+
+At :: enum {
+ AtLeading,
+ AtTop,
+ AtTrailing,
+ AtBottom,
+}
+
+TableValueType :: enum {
+ TableValueTypeString,
+ TableValueTypeImage,
+ TableValueTypeInt,
+ TableValueTypeColor,
+}
+
+// Struct definitions
+
+// ISO C broken-down time structure.
+tm :: struct {
+ tm_sec: int,
+ tm_min: int,
+ tm_hour: int,
+ tm_mday: int,
+ tm_mon: int,
+ tm_year: int,
+ tm_wday: int,
+ tm_yday: int,
+ tm_isdst: int,
+}
+
+InitOptions :: struct {
+ Size: libc.size_t,
+}
+
+Control :: struct {
+ Signature: libc.uint32_t,
+ OSSignature: libc.uint32_t,
+ TypeSignature: libc.uint32_t,
+ Destroy: proc(_: ^Control),
+ Handle: proc(_: ^Control) -> libc.uintptr_t,
+ Parent: ^proc(_: ^Control) -> Control,
+ SetParent: proc(_: ^Control, _: ^Control),
+ Toplevel: proc(_: ^Control) -> libc.int,
+ Visible: proc(_: ^Control) -> libc.int,
+ Show: proc(_: ^Control),
+ Hide: proc(_: ^Control),
+ Enabled: proc(_: ^Control) -> libc.int,
+ Enable: proc(_: ^Control),
+ Disable: proc(_: ^Control),
+}
+
+AreaHandler :: struct {
+ Draw: proc(_: ^AreaHandler, _: Area, _: ^AreaDrawParams),
+ MouseEvent: proc(_: ^AreaHandler, _: Area, _: ^AreaMouseEvent),
+ MouseCrossed: proc(_: ^AreaHandler, _: Area, left: libc.int),
+ DragBroken: proc(_: ^AreaHandler, _: Area),
+ KeyEvent: proc(_: ^AreaHandler, _: Area, _: ^AreaKeyEvent) -> libc.int,
+}
+
+AreaDrawParams :: struct {
+ Context: DrawContext,
+ AreaWidth: libc.double,
+ AreaHeight: libc.double,
+ ClipX: libc.double,
+ ClipY: libc.double,
+ ClipWidth: libc.double,
+ ClipHeight: libc.double,
+}
+
+DrawMatrix :: struct {
+ M11: libc.double,
+ M12: libc.double,
+ M21: libc.double,
+ M22: libc.double,
+ M31: libc.double,
+ M32: libc.double,
+}
+
+DrawBrush :: struct {
+ Type: DrawBrushType,
+ R: libc.double,
+ G: libc.double,
+ B: libc.double,
+ A: libc.double,
+ X0: libc.double,
+ Y0: libc.double,
+ X1: libc.double,
+ Y1: libc.double,
+ OuterRadius: libc.double,
+ Stops: ^DrawBrushGradientStop,
+ NumStops: libc.size_t,
+}
+
+DrawBrushGradientStop :: struct {
+ Pos: libc.double,
+ R: libc.double,
+ G: libc.double,
+ B: libc.double,
+ A: libc.double,
+}
+
+DrawStrokeParams :: struct {
+ Cap: DrawLineCap,
+ Join: DrawLineJoin,
+ Thickness: libc.double,
+ MiterLimit: libc.double,
+ Dashes: ^libc.double,
+ NumDashes: libc.size_t,
+ DashPhase: libc.double,
+}
+
+FontDescriptor :: struct {
+ Family: cstring,
+ Size: libc.double,
+ Weight: TextWeight,
+ Italic: TextItalic,
+ Stretch: TextStretch,
+}
+
+DrawTextLayoutParams :: struct {
+ String: AttributedString,
+ DefaultFont: ^FontDescriptor,
+ Width: libc.double,
+ Align: DrawTextAlign,
+}
+
+AreaMouseEvent :: struct {
+ X: libc.double,
+ Y: libc.double,
+ AreaWidth: libc.double,
+ AreaHeight: libc.double,
+ Down: libc.int,
+ Up: libc.int,
+ Count: libc.int,
+ Modifiers: Modifiers,
+ Held1To64: libc.uint64_t,
+}
+
+AreaKeyEvent :: struct {
+ Key: byte,
+ ExtKey: ExtKey,
+ Modifier: Modifiers,
+ Modifiers: Modifiers,
+ Up: libc.int,
+}
+
+TableModelHandler :: struct {
+ NumColumns: proc(_: ^TableModelHandler, _: TableModel) -> libc.int,
+ ColumnType: proc(_: ^TableModelHandler, _: TableModel, _: libc.int) -> TableValueType,
+ NumRows: proc(_: ^TableModelHandler, _: TableModel) -> libc.int,
+ CellValue: ^proc(mh: ^TableModelHandler, m: TableModel, row: libc.int, column: libc.int) -> TableValue,
+ SetCellValue: proc(_: ^TableModelHandler, _: TableModel, _: libc.int, _: libc.int, _: TableValue),
+}
+
+TableTextColumnOptionalParams :: struct {
+ ColorModelColumn: libc.int,
+}
+
+TableParams :: struct {
+ Model: TableModel,
+ RowBackgroundColorModelColumn: libc.int,
+}
+
+// Procedure types
+OpenTypeFeaturesForEachFunc :: #type proc(otf: OpenTypeFeatures, a: byte, b: byte, c: byte, d: byte, value: libc.uint32_t, data: rawptr) -> ForEach
+AttributedStringForEachAttributeFunc :: #type proc(s: AttributedString, a: Attribute, start: libc.size_t, end: libc.size_t, data: rawptr) -> ForEach
+
+// Procedure definitions
+@(default_calling_convention = "stdcall", link_prefix = "ui")
+foreign lib {
+ Init :: proc(options: ^InitOptions) -> cstring ---
+ Uninit :: proc() ---
+ FreeInitError :: proc(err: cstring) ---
+ Main :: proc() ---
+ MainSteps :: proc() ---
+ MainStep :: proc(wait: libc.int) -> libc.int ---
+ Quit :: proc() ---
+ QueueMain :: proc(f: proc(data: rawptr), data: rawptr) ---
+ Timer :: proc(milliseconds: libc.int, f: proc(data: rawptr) -> libc.int, data: rawptr) ---
+ OnShouldQuit :: proc(f: proc(data: rawptr) -> libc.int, data: rawptr) ---
+ FreeText :: proc(text: cstring) ---
+ ControlDestroy :: proc(_: ^Control) ---
+ ControlHandle :: proc(_: ^Control) -> libc.uintptr_t ---
+ ControlParent :: proc(_: ^Control) -> ^Control ---
+ ControlSetParent :: proc(_: ^Control, _: ^Control) ---
+ ControlToplevel :: proc(_: ^Control) -> libc.int ---
+ ControlVisible :: proc(_: ^Control) -> libc.int ---
+ ControlShow :: proc(_: ^Control) ---
+ ControlHide :: proc(_: ^Control) ---
+ ControlEnabled :: proc(_: ^Control) -> libc.int ---
+ ControlEnable :: proc(_: ^Control) ---
+ ControlDisable :: proc(_: ^Control) ---
+ AllocControl :: proc(n: libc.size_t, OSsig: libc.uint32_t, typesig: libc.uint32_t, typenamestr: cstring) -> ^Control ---
+ FreeControl :: proc(_: ^Control) ---
+ ControlVerifySetParent :: proc(_: ^Control, _: ^Control) ---
+ ControlEnabledToUser :: proc(_: ^Control) -> libc.int ---
+ UserBugCannotSetParentOnToplevel :: proc(type: cstring) ---
+ WindowTitle :: proc(w: Window) -> cstring ---
+ WindowSetTitle :: proc(w: Window, title: cstring) ---
+ WindowContentSize :: proc(w: Window, width: ^libc.int, height: ^libc.int) ---
+ WindowSetContentSize :: proc(w: Window, width: libc.int, height: libc.int) ---
+ WindowFullscreen :: proc(w: Window) -> libc.int ---
+ WindowSetFullscreen :: proc(w: Window, fullscreen: libc.int) ---
+ WindowOnContentSizeChanged :: proc(w: Window, f: proc(_: Window, _: rawptr), data: rawptr) ---
+ WindowOnClosing :: proc(w: Window, f: proc(w: Window, data: rawptr) -> libc.int, data: rawptr) ---
+ WindowBorderless :: proc(w: Window) -> libc.int ---
+ WindowSetBorderless :: proc(w: Window, borderless: libc.int) ---
+ WindowSetChild :: proc(w: Window, child: ^Control) ---
+ WindowMargined :: proc(w: Window) -> libc.int ---
+ WindowSetMargined :: proc(w: Window, margined: libc.int) ---
+ NewWindow :: proc(title: cstring, width: libc.int, height: libc.int, hasMenubar: libc.int) -> Window ---
+ ButtonText :: proc(b: Button) -> cstring ---
+ ButtonSetText :: proc(b: Button, text: cstring) ---
+ ButtonOnClicked :: proc(b: Button, f: proc(b: Button, data: rawptr), data: rawptr) ---
+ NewButton :: proc(text: cstring) -> Button ---
+ BoxAppend :: proc(b: Box, child: ^Control, stretchy: libc.int) ---
+ BoxDelete :: proc(b: Box, index: libc.int) ---
+ BoxPadded :: proc(b: Box) -> libc.int ---
+ BoxSetPadded :: proc(b: Box, padded: libc.int) ---
+ NewHorizontalBox :: proc() -> Box ---
+ NewVerticalBox :: proc() -> Box ---
+ CheckboxText :: proc(c: Checkbox) -> cstring ---
+ CheckboxSetText :: proc(c: Checkbox, text: cstring) ---
+ CheckboxOnToggled :: proc(c: Checkbox, f: proc(c: Checkbox, data: rawptr), data: rawptr) ---
+ CheckboxChecked :: proc(c: Checkbox) -> libc.int ---
+ CheckboxSetChecked :: proc(c: Checkbox, checked: libc.int) ---
+ NewCheckbox :: proc(text: cstring) -> Checkbox ---
+ EntryText :: proc(e: Entry) -> cstring ---
+ EntrySetText :: proc(e: Entry, text: cstring) ---
+ EntryOnChanged :: proc(e: Entry, f: proc(e: Entry, data: rawptr), data: rawptr) ---
+ EntryReadOnly :: proc(e: Entry) -> libc.int ---
+ EntrySetReadOnly :: proc(e: Entry, readonly: libc.int) ---
+ NewEntry :: proc() -> Entry ---
+ NewPasswordEntry :: proc() -> Entry ---
+ NewSearchEntry :: proc() -> Entry ---
+ LabelText :: proc(l: Label) -> cstring ---
+ LabelSetText :: proc(l: Label, text: cstring) ---
+ NewLabel :: proc(text: cstring) -> Label ---
+ TabAppend :: proc(t: Tab, name: cstring, c: ^Control) ---
+ TabInsertAt :: proc(t: Tab, name: cstring, before: libc.int, c: ^Control) ---
+ TabDelete :: proc(t: Tab, index: libc.int) ---
+ TabNumPages :: proc(t: Tab) -> libc.int ---
+ TabMargined :: proc(t: Tab, page: libc.int) -> libc.int ---
+ TabSetMargined :: proc(t: Tab, page: libc.int, margined: libc.int) ---
+ NewTab :: proc() -> Tab ---
+ GroupTitle :: proc(g: Group) -> cstring ---
+ GroupSetTitle :: proc(g: Group, title: cstring) ---
+ GroupSetChild :: proc(g: Group, c: ^Control) ---
+ GroupMargined :: proc(g: Group) -> libc.int ---
+ GroupSetMargined :: proc(g: Group, margined: libc.int) ---
+ NewGroup :: proc(title: cstring) -> Group ---
+ SpinboxValue :: proc(s: Spinbox) -> libc.int ---
+ SpinboxSetValue :: proc(s: Spinbox, value: libc.int) ---
+ SpinboxOnChanged :: proc(s: Spinbox, f: proc(s: Spinbox, data: rawptr), data: rawptr) ---
+ NewSpinbox :: proc(min: libc.int, max: libc.int) -> Spinbox ---
+ SliderValue :: proc(s: Slider) -> libc.int ---
+ SliderSetValue :: proc(s: Slider, value: libc.int) ---
+ SliderOnChanged :: proc(s: Slider, f: proc(s: Slider, data: rawptr), data: rawptr) ---
+ NewSlider :: proc(min: libc.int, max: libc.int) -> Slider ---
+ ProgressBarValue :: proc(p: ProgressBar) -> libc.int ---
+ ProgressBarSetValue :: proc(p: ProgressBar, n: libc.int) ---
+ NewProgressBar :: proc() -> ProgressBar ---
+ NewHorizontalSeparator :: proc() -> Separator ---
+ NewVerticalSeparator :: proc() -> Separator ---
+ ComboboxAppend :: proc(c: Combobox, text: cstring) ---
+ ComboboxSelected :: proc(c: Combobox) -> libc.int ---
+ ComboboxSetSelected :: proc(c: Combobox, n: libc.int) ---
+ ComboboxOnSelected :: proc(c: Combobox, f: proc(c: Combobox, data: rawptr), data: rawptr) ---
+ NewCombobox :: proc() -> Combobox ---
+ EditableComboboxAppend :: proc(c: EditableCombobox, text: cstring) ---
+ EditableComboboxText :: proc(c: EditableCombobox) -> cstring ---
+ EditableComboboxSetText :: proc(c: EditableCombobox, text: cstring) ---
+ EditableComboboxOnChanged :: proc(c: EditableCombobox, f: proc(c: EditableCombobox, data: rawptr), data: rawptr) ---
+ NewEditableCombobox :: proc() -> EditableCombobox ---
+ RadioButtonsAppend :: proc(r: RadioButtons, text: cstring) ---
+ RadioButtonsSelected :: proc(r: RadioButtons) -> libc.int ---
+ RadioButtonsSetSelected :: proc(r: RadioButtons, n: libc.int) ---
+ RadioButtonsOnSelected :: proc(r: RadioButtons, f: proc(_: RadioButtons, _: rawptr), data: rawptr) ---
+ NewRadioButtons :: proc() -> RadioButtons ---
+ DateTimePickerTime :: proc(d: DateTimePicker, time: ^tm) ---
+ DateTimePickerSetTime :: proc(d: DateTimePicker, time: ^tm) ---
+ DateTimePickerOnChanged :: proc(d: DateTimePicker, f: proc(_: DateTimePicker, _: rawptr), data: rawptr) ---
+ NewDateTimePicker :: proc() -> DateTimePicker ---
+ NewDatePicker :: proc() -> DateTimePicker ---
+ NewTimePicker :: proc() -> DateTimePicker ---
+ MultilineEntryText :: proc(e: MultilineEntry) -> cstring ---
+ MultilineEntrySetText :: proc(e: MultilineEntry, text: cstring) ---
+ MultilineEntryAppend :: proc(e: MultilineEntry, text: cstring) ---
+ MultilineEntryOnChanged :: proc(e: MultilineEntry, f: proc(e: MultilineEntry, data: rawptr), data: rawptr) ---
+ MultilineEntryReadOnly :: proc(e: MultilineEntry) -> libc.int ---
+ MultilineEntrySetReadOnly :: proc(e: MultilineEntry, readonly: libc.int) ---
+ NewMultilineEntry :: proc() -> MultilineEntry ---
+ NewNonWrappingMultilineEntry :: proc() -> MultilineEntry ---
+ MenuItemEnable :: proc(m: MenuItem) ---
+ MenuItemDisable :: proc(m: MenuItem) ---
+ MenuItemOnClicked :: proc(m: MenuItem, f: proc(sender: MenuItem, window: Window, data: rawptr), data: rawptr) ---
+ MenuItemChecked :: proc(m: MenuItem) -> libc.int ---
+ MenuItemSetChecked :: proc(m: MenuItem, checked: libc.int) ---
+ MenuAppendItem :: proc(m: Menu, name: cstring) -> MenuItem ---
+ MenuAppendCheckItem :: proc(m: Menu, name: cstring) -> MenuItem ---
+ MenuAppendQuitItem :: proc(m: Menu) -> MenuItem ---
+ MenuAppendPreferencesItem :: proc(m: Menu) -> MenuItem ---
+ MenuAppendAboutItem :: proc(m: Menu) -> MenuItem ---
+ MenuAppendSeparator :: proc(m: Menu) ---
+ NewMenu :: proc(name: cstring) -> Menu ---
+ OpenFile :: proc(parent: Window) -> cstring ---
+ SaveFile :: proc(parent: Window) -> cstring ---
+ MsgBox :: proc(parent: Window, title: cstring, description: cstring) ---
+ MsgBoxError :: proc(parent: Window, title: cstring, description: cstring) ---
+ AreaSetSize :: proc(a: Area, width: libc.int, height: libc.int) ---
+ AreaQueueRedrawAll :: proc(a: Area) ---
+ AreaScrollTo :: proc(a: Area, x: libc.double, y: libc.double, width: libc.double, height: libc.double) ---
+ AreaBeginUserWindowMove :: proc(a: Area) ---
+ AreaBeginUserWindowResize :: proc(a: Area, edge: WindowResizeEdge) ---
+ NewArea :: proc(ah: ^AreaHandler) -> Area ---
+ NewScrollingArea :: proc(ah: ^AreaHandler, width: libc.int, height: libc.int) -> Area ---
+ DrawNewPath :: proc(fillMode: DrawFillMode) -> DrawPath ---
+ DrawFreePath :: proc(p: DrawPath) ---
+ DrawPathNewFigure :: proc(p: DrawPath, x: libc.double, y: libc.double) ---
+ DrawPathNewFigureWithArc :: proc(p: DrawPath, xCenter: libc.double, yCenter: libc.double, radius: libc.double, startAngle: libc.double, sweep: libc.double, negative: libc.int) ---
+ DrawPathLineTo :: proc(p: DrawPath, x: libc.double, y: libc.double) ---
+ DrawPathArcTo :: proc(p: DrawPath, xCenter: libc.double, yCenter: libc.double, radius: libc.double, startAngle: libc.double, sweep: libc.double, negative: libc.int) ---
+ DrawPathBezierTo :: proc(p: DrawPath, c1x: libc.double, c1y: libc.double, c2x: libc.double, c2y: libc.double, endX: libc.double, endY: libc.double) ---
+ DrawPathCloseFigure :: proc(p: DrawPath) ---
+ DrawPathAddRectangle :: proc(p: DrawPath, x: libc.double, y: libc.double, width: libc.double, height: libc.double) ---
+ DrawPathEnd :: proc(p: DrawPath) ---
+ DrawStroke :: proc(c: DrawContext, path: DrawPath, b: ^DrawBrush, p: ^DrawStrokeParams) ---
+ DrawFill :: proc(c: DrawContext, path: DrawPath, b: ^DrawBrush) ---
+ DrawMatrixSetIdentity :: proc(m: ^DrawMatrix) ---
+ DrawMatrixTranslate :: proc(m: ^DrawMatrix, x: libc.double, y: libc.double) ---
+ DrawMatrixScale :: proc(m: ^DrawMatrix, xCenter: libc.double, yCenter: libc.double, x: libc.double, y: libc.double) ---
+ DrawMatrixRotate :: proc(m: ^DrawMatrix, x: libc.double, y: libc.double, amount: libc.double) ---
+ DrawMatrixSkew :: proc(m: ^DrawMatrix, x: libc.double, y: libc.double, xamount: libc.double, yamount: libc.double) ---
+ DrawMatrixMultiply :: proc(dest: ^DrawMatrix, src: ^DrawMatrix) ---
+ DrawMatrixInvertible :: proc(m: ^DrawMatrix) -> libc.int ---
+ DrawMatrixInvert :: proc(m: ^DrawMatrix) -> libc.int ---
+ DrawMatrixTransformPoint :: proc(m: ^DrawMatrix, x: ^libc.double, y: ^libc.double) ---
+ DrawMatrixTransformSize :: proc(m: ^DrawMatrix, x: ^libc.double, y: ^libc.double) ---
+ DrawTransform :: proc(c: DrawContext, m: ^DrawMatrix) ---
+ DrawClip :: proc(c: DrawContext, path: DrawPath) ---
+ DrawSave :: proc(c: DrawContext) ---
+ DrawRestore :: proc(c: DrawContext) ---
+ FreeAttribute :: proc(a: Attribute) ---
+ AttributeGetType :: proc(a: Attribute) -> AttributeType ---
+ NewFamilyAttribute :: proc(family: cstring) -> Attribute ---
+ AttributeFamily :: proc(a: Attribute) -> cstring ---
+ NewSizeAttribute :: proc(size: libc.double) -> Attribute ---
+ AttributeSize :: proc(a: Attribute) -> libc.double ---
+ NewWeightAttribute :: proc(weight: TextWeight) -> Attribute ---
+ AttributeWeight :: proc(a: Attribute) -> TextWeight ---
+ NewItalicAttribute :: proc(italic: TextItalic) -> Attribute ---
+ AttributeItalic :: proc(a: Attribute) -> TextItalic ---
+ NewStretchAttribute :: proc(stretch: TextStretch) -> Attribute ---
+ AttributeStretch :: proc(a: Attribute) -> TextStretch ---
+ NewColorAttribute :: proc(r: libc.double, g: libc.double, b: libc.double, a: libc.double) -> Attribute ---
+ AttributeColor :: proc(a: Attribute, r: ^libc.double, g: ^libc.double, b: ^libc.double, alpha: ^libc.double) ---
+ NewBackgroundAttribute :: proc(r: libc.double, g: libc.double, b: libc.double, a: libc.double) -> Attribute ---
+ NewUnderlineAttribute :: proc(u: Underline) -> Attribute ---
+ AttributeUnderline :: proc(a: Attribute) -> Underline ---
+ NewUnderlineColorAttribute :: proc(u: UnderlineColor, r: libc.double, g: libc.double, b: libc.double, a: libc.double) -> Attribute ---
+ AttributeUnderlineColor :: proc(a: Attribute, u: ^UnderlineColor, r: ^libc.double, g: ^libc.double, b: ^libc.double, alpha: ^libc.double) ---
+ NewOpenTypeFeatures :: proc() -> OpenTypeFeatures ---
+ FreeOpenTypeFeatures :: proc(otf: OpenTypeFeatures) ---
+ OpenTypeFeaturesClone :: proc(otf: OpenTypeFeatures) -> OpenTypeFeatures ---
+ OpenTypeFeaturesAdd :: proc(otf: OpenTypeFeatures, a: byte, b: byte, c: byte, d: byte, value: libc.uint32_t) ---
+ OpenTypeFeaturesRemove :: proc(otf: OpenTypeFeatures, a: byte, b: byte, c: byte, d: byte) ---
+ OpenTypeFeaturesGet :: proc(otf: OpenTypeFeatures, a: byte, b: byte, c: byte, d: byte, value: ^libc.uint32_t) -> libc.int ---
+ OpenTypeFeaturesForEach :: proc(otf: OpenTypeFeatures, f: OpenTypeFeaturesForEachFunc, data: rawptr) ---
+ NewFeaturesAttribute :: proc(otf: OpenTypeFeatures) -> Attribute ---
+ AttributeFeatures :: proc(a: Attribute) -> OpenTypeFeatures ---
+ NewAttributedString :: proc(initialString: cstring) -> AttributedString ---
+ FreeAttributedString :: proc(s: AttributedString) ---
+ AttributedStringString :: proc(s: AttributedString) -> cstring ---
+ AttributedStringLen :: proc(s: AttributedString) -> libc.size_t ---
+ AttributedStringAppendUnattributed :: proc(s: AttributedString, str: cstring) ---
+ AttributedStringInsertAtUnattributed :: proc(s: AttributedString, str: cstring, at: libc.size_t) ---
+ AttributedStringDelete :: proc(s: AttributedString, start: libc.size_t, end: libc.size_t) ---
+ AttributedStringSetAttribute :: proc(s: AttributedString, a: Attribute, start: libc.size_t, end: libc.size_t) ---
+ AttributedStringForEachAttribute :: proc(s: AttributedString, f: AttributedStringForEachAttributeFunc, data: rawptr) ---
+ AttributedStringNumGraphemes :: proc(s: AttributedString) -> libc.size_t ---
+ AttributedStringByteIndexToGrapheme :: proc(s: AttributedString, pos: libc.size_t) -> libc.size_t ---
+ AttributedStringGraphemeToByteIndex :: proc(s: AttributedString, pos: libc.size_t) -> libc.size_t ---
+ DrawNewTextLayout :: proc(params: ^DrawTextLayoutParams) -> DrawTextLayout ---
+ DrawFreeTextLayout :: proc(tl: DrawTextLayout) ---
+ DrawText :: proc(c: DrawContext, tl: DrawTextLayout, x: libc.double, y: libc.double) ---
+ DrawTextLayoutExtents :: proc(tl: DrawTextLayout, width: ^libc.double, height: ^libc.double) ---
+ FontButtonFont :: proc(b: FontButton, desc: ^FontDescriptor) ---
+ FontButtonOnChanged :: proc(b: FontButton, f: proc(_: FontButton, _: rawptr), data: rawptr) ---
+ NewFontButton :: proc() -> FontButton ---
+ FreeFontButtonFont :: proc(desc: ^FontDescriptor) ---
+ ColorButtonColor :: proc(b: ColorButton, r: ^libc.double, g: ^libc.double, bl: ^libc.double, a: ^libc.double) ---
+ ColorButtonSetColor :: proc(b: ColorButton, r: libc.double, g: libc.double, bl: libc.double, a: libc.double) ---
+ ColorButtonOnChanged :: proc(b: ColorButton, f: proc(_: ColorButton, _: rawptr), data: rawptr) ---
+ NewColorButton :: proc() -> ColorButton ---
+ FormAppend :: proc(f: Form, label: cstring, c: ^Control, stretchy: libc.int) ---
+ FormDelete :: proc(f: Form, index: libc.int) ---
+ FormPadded :: proc(f: Form) -> libc.int ---
+ FormSetPadded :: proc(f: Form, padded: libc.int) ---
+ NewForm :: proc() -> Form ---
+ GridAppend :: proc(g: Grid, c: ^Control, left: libc.int, top: libc.int, xspan: libc.int, yspan: libc.int, hexpand: libc.int, halign: Align, vexpand: libc.int, valign: Align) ---
+ GridInsertAt :: proc(g: Grid, c: ^Control, existing: ^Control, at: At, xspan: libc.int, yspan: libc.int, hexpand: libc.int, halign: Align, vexpand: libc.int, valign: Align) ---
+ GridPadded :: proc(g: Grid) -> libc.int ---
+ GridSetPadded :: proc(g: Grid, padded: libc.int) ---
+ NewGrid :: proc() -> Grid ---
+ NewImage :: proc(width: libc.double, height: libc.double) -> Image ---
+ FreeImage :: proc(i: Image) ---
+ ImageAppend :: proc(i: Image, pixels: rawptr, pixelWidth: libc.int, pixelHeight: libc.int, byteStride: libc.int) ---
+ FreeTableValue :: proc(v: TableValue) ---
+ TableValueGetType :: proc(v: TableValue) -> TableValueType ---
+ NewTableValueString :: proc(str: cstring) -> TableValue ---
+ TableValueString :: proc(v: TableValue) -> cstring ---
+ NewTableValueImage :: proc(img: Image) -> TableValue ---
+ TableValueImage :: proc(v: TableValue) -> Image ---
+ NewTableValueInt :: proc(i: libc.int) -> TableValue ---
+ TableValueInt :: proc(v: TableValue) -> libc.int ---
+ NewTableValueColor :: proc(r: libc.double, g: libc.double, b: libc.double, a: libc.double) -> TableValue ---
+ TableValueColor :: proc(v: TableValue, r: ^libc.double, g: ^libc.double, b: ^libc.double, a: ^libc.double) ---
+ NewTableModel :: proc(mh: ^TableModelHandler) -> TableModel ---
+ FreeTableModel :: proc(m: TableModel) ---
+ TableModelRowInserted :: proc(m: TableModel, newIndex: libc.int) ---
+ TableModelRowChanged :: proc(m: TableModel, index: libc.int) ---
+ TableModelRowDeleted :: proc(m: TableModel, oldIndex: libc.int) ---
+ TableAppendTextColumn :: proc(t: Table, name: cstring, textModelColumn: libc.int, textEditableModelColumn: libc.int, textParams: ^TableTextColumnOptionalParams) ---
+ TableAppendImageColumn :: proc(t: Table, name: cstring, imageModelColumn: libc.int) ---
+ TableAppendImageTextColumn :: proc(t: Table, name: cstring, imageModelColumn: libc.int, textModelColumn: libc.int, textEditableModelColumn: libc.int, textParams: ^TableTextColumnOptionalParams) ---
+ TableAppendCheckboxColumn :: proc(t: Table, name: cstring, checkboxModelColumn: libc.int, checkboxEditableModelColumn: libc.int) ---
+ TableAppendCheckboxTextColumn :: proc(t: Table, name: cstring, checkboxModelColumn: libc.int, checkboxEditableModelColumn: libc.int, textModelColumn: libc.int, textEditableModelColumn: libc.int, textParams: ^TableTextColumnOptionalParams) ---
+ TableAppendProgressBarColumn :: proc(t: Table, name: cstring, progressModelColumn: libc.int) ---
+ TableAppendButtonColumn :: proc(t: Table, name: cstring, buttonModelColumn: libc.int, buttonClickableModelColumn: libc.int) ---
+ NewTable :: proc(params: ^TableParams) -> Table ---
+}
diff --git a/odinfmt.json b/odinfmt.json
@@ -0,0 +1,6 @@
+{
+ "character_width": 80,
+ "tabs": true,
+ "tabs_width": 4,
+ "sort_imports": true
+}
diff --git a/ols.json b/ols.json
@@ -0,0 +1,11 @@
+{
+ "enable_hover": true,
+ "enable_format": true,
+ "enable_rename": true,
+ "enable_snippets": true,
+ "enable_references": true,
+ "enable_fake_methods": true,
+ "enable_semantic_tokens": true,
+ "enable_document_symbols": true,
+ "enable_procedure_snippet": true,
+}