Ver Fonte

push orca target and old bindings

skytrias há 1 ano atrás
pai
commit
bca4c37f02

+ 1 - 1
base/runtime/heap_allocator_other.odin

@@ -1,4 +1,4 @@
-//+build js, wasi, freestanding, essence
+//+build js, wasi, freestanding, essence, orca
 //+private
 package runtime
 

+ 14 - 0
base/runtime/os_specific_orca.odin

@@ -0,0 +1,14 @@
+//+build orca
+//+private
+package runtime
+
+// TODO
+// foreign import 
+// fd_write :: proc "contextless" ()
+import "core:sys/wasm/wasi"
+
+_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
+	data := (wasi.ciovec_t)(data)
+	n, err := wasi.fd_write(1, {data})
+	return int(n), _OS_Errno(err)
+}

+ 1 - 0
base/runtime/os_specific_wasi.odin

@@ -2,6 +2,7 @@
 //+private
 package runtime
 
+// TODO reimplement fd_write
 import "core:sys/wasm/wasi"
 
 _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {

+ 11 - 0
base/runtime/procs.odin

@@ -25,6 +25,17 @@ when ODIN_NO_CRT && ODIN_OS == .Windows {
 		RtlMoveMemory(dst, src, len)
 		return dst
 	}
+} else when ODIN_OS == .Orca {
+	memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {
+		if ptr != nil && len != 0 {
+			b := byte(val)
+			p := ([^]byte)(ptr)
+			for i := 0; i < len; i += 1 {
+				p[i] = b
+			}
+		}
+		return ptr
+	}
 } else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
 	@(link_name="memset", linkage="strong", require)
 	memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr {

+ 109 - 0
core/os/os_orca.odin

@@ -0,0 +1,109 @@
+package os
+
+import "core:sys/wasm/wasi"
+import "base:runtime"
+
+Handle :: distinct i32
+Errno :: distinct i32
+
+ERROR_NONE :: Errno(wasi.errno_t.SUCCESS)
+
+O_RDONLY   :: 0x00000
+O_WRONLY   :: 0x00001
+O_RDWR     :: 0x00002
+O_CREATE   :: 0x00040
+O_EXCL     :: 0x00080
+O_NOCTTY   :: 0x00100
+O_TRUNC    :: 0x00200
+O_NONBLOCK :: 0x00800
+O_APPEND   :: 0x00400
+O_SYNC     :: 0x01000
+O_ASYNC    :: 0x02000
+O_CLOEXEC  :: 0x80000
+
+stdin:  Handle = 0
+stdout: Handle = 1
+stderr: Handle = 2
+current_dir: Handle = 3
+
+write :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	iovs := wasi.ciovec_t(data)
+	n, err := wasi.fd_write(wasi.fd_t(fd), {iovs})
+	return int(n), Errno(err)
+}
+read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
+	iovs := wasi.iovec_t(data)
+	n, err := wasi.fd_read(wasi.fd_t(fd), {iovs})
+	return int(n), Errno(err)
+}
+write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	iovs := wasi.ciovec_t(data)
+	n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
+	return int(n), Errno(err)
+}
+read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) {
+	iovs := wasi.iovec_t(data)
+	n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset))
+	return int(n), Errno(err)
+}
+open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) {
+	oflags: wasi.oflags_t
+	if mode & O_CREATE == O_CREATE {
+		oflags += {.CREATE}
+	}
+	if mode & O_EXCL == O_EXCL {
+		oflags += {.EXCL}
+	}
+	if mode & O_TRUNC == O_TRUNC {
+		oflags += {.TRUNC}
+	}
+
+	rights: wasi.rights_t = {.FD_SEEK, .FD_FILESTAT_GET}
+	switch mode & (O_RDONLY|O_WRONLY|O_RDWR) {
+	case O_RDONLY: rights += {.FD_READ}
+	case O_WRONLY: rights += {.FD_WRITE}
+	case O_RDWR:   rights += {.FD_READ, .FD_WRITE}
+	}
+
+	fdflags: wasi.fdflags_t
+	if mode & O_APPEND == O_APPEND {
+		fdflags += {.APPEND}
+	}
+	if mode & O_NONBLOCK == O_NONBLOCK {
+		fdflags += {.NONBLOCK}
+	}
+	if mode & O_SYNC == O_SYNC {
+		fdflags += {.SYNC}
+	}
+	fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags)
+	return Handle(fd), Errno(err)
+}
+close :: proc(fd: Handle) -> Errno {
+	err := wasi.fd_close(wasi.fd_t(fd))
+	return Errno(err)
+}
+seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) {
+	n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence))
+	return i64(n), Errno(err)
+}
+current_thread_id :: proc "contextless" () -> int {
+	return 0
+}
+@(private)
+_processor_core_count :: proc() -> int {
+	return 1
+}
+
+file_size :: proc(fd: Handle) -> (i64, Errno) {
+	stat, err := wasi.fd_filestat_get(wasi.fd_t(fd))
+	if err != nil {
+		return 0, Errno(err)
+	}
+	return i64(stat.size), 0
+}
+
+
+exit :: proc "contextless" (code: int) -> ! {
+	runtime._cleanup_runtime_contextless()
+	wasi.proc_exit(wasi.exitcode_t(code))
+}

+ 58 - 0
core/sys/orca/algebra.odin

@@ -0,0 +1,58 @@
+package orca
+
+import "core:math"
+
+// TODO use orcas or native?
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	mat2x3_mul_m :: proc(lhs, rhs: mat2x3) -> mat2x3 ---
+	mat2x3_inv :: proc(x: mat2x3) -> mat2x3 ---
+	mat2x3_mul :: proc(m: mat2x3, p: vec2) -> vec2 ---
+	mat2x3_rotate :: proc(radians: f32) -> mat2x3 ---
+	mat2x3_translate :: proc(x, y: f32) -> mat2x3 ---
+}
+
+// mat2x3_mul_m :: proc "contextless" (lhs, rhs: mat2x3) -> (res: mat2x3) {
+// 	res[0] = lhs[0] * rhs[0] + lhs[1] * rhs[3]
+// 	res[1] = lhs[0] * rhs[1] + lhs[1] * rhs[4]
+// 	res[2] = lhs[0] * rhs[2] + lhs[1] * rhs[5] + lhs[2]
+// 	res[3] = lhs[3] * rhs[0] + lhs[4] * rhs[3]
+// 	res[4] = lhs[3] * rhs[1] + lhs[4] * rhs[4]
+// 	res[5] = lhs[3] * rhs[2] + lhs[4] * rhs[5] + lhs[5]
+// 	return
+// }
+
+// mat2x3_inv :: proc "contextless" (x: mat2x3) -> (res: mat2x3) {
+// 	res[0] = x[4] / (x[0] * x[4] - x[1] * x[3])
+// 	res[1] = x[1] / (x[1] * x[3] - x[0] * x[4])
+// 	res[3] = x[3] / (x[1] * x[3] - x[0] * x[4])
+// 	res[4] = x[0] / (x[0] * x[4] - x[1] * x[3])
+// 	res[2] = -(x[2] * res[0] + x[5] * res[1])
+// 	res[5] = -(x[2] * res[3] + x[5] * res[4])
+// 	return
+// }
+
+// mat2x3_mul :: proc "contextless" (m: mat2x3, p: vec2) -> vec2 {
+// 	return {
+// 		p.x * m[0] + p.y * m[1] + m[2],
+// 		p.x * m[3] + p.y * m[4] + m[5],
+// 	}
+// }
+
+// mat2x3_rotate :: proc "contextless" (radians: f32) -> mat2x3 {
+// 	sinRot := math.sin(radians)
+// 	cosRot := math.cos(radians)
+// 	rot := mat2x3 {
+// 		cosRot, -sinRot, 0,
+// 		sinRot, cosRot, 0,
+// 	}
+// 	return rot
+// }
+
+// mat2x3_translate :: proc "contextless" (x, y: f32) -> mat2x3 {
+// 	return {
+// 		1, 0, x,
+// 		0, 1, y,
+// 	}
+// }

+ 366 - 0
core/sys/orca/app.odin

@@ -0,0 +1,366 @@
+package orca
+
+import "core:c"
+
+window :: u64
+
+mouse_cursor :: enum c.int {
+	ARROW,
+	RESIZE_0,
+	RESIZE_90,
+	RESIZE_45,
+	RESIZE_135,
+	TEXT,
+}
+
+window_style :: enum u32 {
+	NO_TITLE = 0x01 << 0,
+	FIXED_SIZE = 0x01 << 1,
+	NO_CLOSE = 0x01 << 2,
+	NO_MINIFY = 0x01 << 3,
+	NO_FOCUS = 0x01 << 4,
+	FLOAT = 0x01 << 5,
+	POPUPMENU = 0x01 << 6,
+	NO_BUTTONS = 0x01 << 7
+}
+
+event_type :: enum c.int {
+	NONE,
+	KEYBOARD_MODS, //TODO: remove, keep only key?
+	KEYBOARD_KEY,
+	KEYBOARD_CHAR,
+	MOUSE_BUTTON,
+	MOUSE_MOVE,
+	MOUSE_WHEEL,
+	MOUSE_ENTER,
+	MOUSE_LEAVE,
+	CLIPBOARD_PASTE,
+	WINDOW_RESIZE,
+	WINDOW_MOVE,
+	WINDOW_FOCUS,
+	WINDOW_UNFOCUS,
+	WINDOW_HIDE, // rename to minimize?
+	WINDOW_SHOW, // rename to restore?
+	WINDOW_CLOSE,
+	PATHDROP,
+	FRAME,
+	QUIT
+}
+
+key_action :: enum c.int {
+	NO_ACTION,
+	PRESS,
+	RELEASE,
+	REPEAT
+}
+
+key_code :: enum c.int {
+	KEY_UNKNOWN = '\x00',
+	KEY_SPACE = ' ',
+	KEY_APOSTROPHE = '\'',
+	KEY_COMMA = ',',
+	KEY_MINUS = '-',
+	KEY_PERIOD = '.',
+	KEY_SLASH = '/',
+	KEY_0 = '0',
+	KEY_1 = '1',
+	KEY_2 = '2',
+	KEY_3 = '3',
+	KEY_4 = '4',
+	KEY_5 = '5',
+	KEY_6 = '6',
+	KEY_7 = '7',
+	KEY_8 = '8',
+	KEY_9 = '9',
+	KEY_SEMICOLON = ';',
+	KEY_EQUAL = '=',
+	KEY_LEFT_BRACKET = '[',
+	KEY_BACKSLASH = '\\',
+	KEY_RIGHT_BRACKET = ']',
+	KEY_GRAVE_ACCENT = '`',
+	KEY_A = 'a',
+	KEY_B = 'b',
+	KEY_C = 'c',
+	KEY_D = 'd',
+	KEY_E = 'e',
+	KEY_F = 'f',
+	KEY_G = 'g',
+	KEY_H = 'h',
+	KEY_I = 'i',
+	KEY_J = 'j',
+	KEY_K = 'k',
+	KEY_L = 'l',
+	KEY_M = 'm',
+	KEY_N = 'n',
+	KEY_O = 'o',
+	KEY_P = 'p',
+	KEY_Q = 'q',
+	KEY_R = 'r',
+	KEY_S = 's',
+	KEY_T = 't',
+	KEY_U = 'u',
+	KEY_V = 'v',
+	KEY_W = 'w',
+	KEY_X = 'x',
+	KEY_Y = 'y',
+	KEY_Z = 'z',
+	KEY_WORLD_1 = 161,
+	KEY_WORLD_2 = 162,
+	KEY_ESCAPE = 256,
+	KEY_ENTER = 257,
+	KEY_TAB = 258,
+	KEY_BACKSPACE = 259,
+	KEY_INSERT = 260,
+	KEY_DELETE = 261,
+	KEY_RIGHT = 262,
+	KEY_LEFT = 263,
+	KEY_DOWN = 264,
+	KEY_UP = 265,
+	KEY_PAGE_UP = 266,
+	KEY_PAGE_DOWN = 267,
+	KEY_HOME = 268,
+	KEY_END = 269,
+	KEY_CAPS_LOCK = 280,
+	KEY_SCROLL_LOCK = 281,
+	KEY_NUM_LOCK = 282,
+	KEY_PRINT_SCREEN = 283,
+	KEY_PAUSE = 284,
+	KEY_F1 = 290,
+	KEY_F2 = 291,
+	KEY_F3 = 292,
+	KEY_F4 = 293,
+	KEY_F5 = 294,
+	KEY_F6 = 295,
+	KEY_F7 = 296,
+	KEY_F8 = 297,
+	KEY_F9 = 298,
+	KEY_F10 = 299,
+	KEY_F11 = 300,
+	KEY_F12 = 301,
+	KEY_F13 = 302,
+	KEY_F14 = 303,
+	KEY_F15 = 304,
+	KEY_F16 = 305,
+	KEY_F17 = 306,
+	KEY_F18 = 307,
+	KEY_F19 = 308,
+	KEY_F20 = 309,
+	KEY_F21 = 310,
+	KEY_F22 = 311,
+	KEY_F23 = 312,
+	KEY_F24 = 313,
+	KEY_F25 = 314,
+	KEY_KP_0 = 320,
+	KEY_KP_1 = 321,
+	KEY_KP_2 = 322,
+	KEY_KP_3 = 323,
+	KEY_KP_4 = 324,
+	KEY_KP_5 = 325,
+	KEY_KP_6 = 326,
+	KEY_KP_7 = 327,
+	KEY_KP_8 = 328,
+	KEY_KP_9 = 329,
+	KEY_KP_DECIMAL = 330,
+	KEY_KP_DIVIDE = 331,
+	KEY_KP_MULTIPLY = 332,
+	KEY_KP_SUBTRACT = 333,
+	KEY_KP_ADD = 334,
+	KEY_KP_ENTER = 335,
+	KEY_KP_EQUAL = 336,
+	KEY_LEFT_SHIFT = 340,
+	KEY_LEFT_CONTROL = 341,
+	KEY_LEFT_ALT = 342,
+	KEY_LEFT_SUPER = 343,
+	KEY_RIGHT_SHIFT = 344,
+	KEY_RIGHT_CONTROL = 345,
+	KEY_RIGHT_ALT = 346,
+	KEY_RIGHT_SUPER = 347,
+	KEY_MENU = 348,
+}
+
+scan_code :: enum c.int {
+	SCANCODE_UNKNOWN = 0,
+	SCANCODE_SPACE = 32,
+	SCANCODE_APOSTROPHE = 39,
+	SCANCODE_COMMA = 44,
+	SCANCODE_MINUS = 45,
+	SCANCODE_PERIOD = 46,
+	SCANCODE_SLASH = 47,
+	SCANCODE_0 = 48,
+	SCANCODE_1 = 49,
+	SCANCODE_2 = 50,
+	SCANCODE_3 = 51,
+	SCANCODE_4 = 52,
+	SCANCODE_5 = 53,
+	SCANCODE_6 = 54,
+	SCANCODE_7 = 55,
+	SCANCODE_8 = 56,
+	SCANCODE_9 = 57,
+	SCANCODE_SEMICOLON = 59,
+	SCANCODE_EQUAL = 61,
+	SCANCODE_LEFT_BRACKET = 91,
+	SCANCODE_BACKSLASH = 92,
+	SCANCODE_RIGHT_BRACKET = 93,
+	SCANCODE_GRAVE_ACCENT = 96,
+	SCANCODE_A = 97,
+	SCANCODE_B = 98,
+	SCANCODE_C = 99,
+	SCANCODE_D = 100,
+	SCANCODE_E = 101,
+	SCANCODE_F = 102,
+	SCANCODE_G = 103,
+	SCANCODE_H = 104,
+	SCANCODE_I = 105,
+	SCANCODE_J = 106,
+	SCANCODE_K = 107,
+	SCANCODE_L = 108,
+	SCANCODE_M = 109,
+	SCANCODE_N = 110,
+	SCANCODE_O = 111,
+	SCANCODE_P = 112,
+	SCANCODE_Q = 113,
+	SCANCODE_R = 114,
+	SCANCODE_S = 115,
+	SCANCODE_T = 116,
+	SCANCODE_U = 117,
+	SCANCODE_V = 118,
+	SCANCODE_W = 119,
+	SCANCODE_X = 120,
+	SCANCODE_Y = 121,
+	SCANCODE_Z = 122,
+	SCANCODE_WORLD_1 = 161,
+	SCANCODE_WORLD_2 = 162,
+	SCANCODE_ESCAPE = 256,
+	SCANCODE_ENTER = 257,
+	SCANCODE_TAB = 258,
+	SCANCODE_BACKSPACE = 259,
+	SCANCODE_INSERT = 260,
+	SCANCODE_DELETE = 261,
+	SCANCODE_RIGHT = 262,
+	SCANCODE_LEFT = 263,
+	SCANCODE_DOWN = 264,
+	SCANCODE_UP = 265,
+	SCANCODE_PAGE_UP = 266,
+	SCANCODE_PAGE_DOWN = 267,
+	SCANCODE_HOME = 268,
+	SCANCODE_END = 269,
+	SCANCODE_CAPS_LOCK = 280,
+	SCANCODE_SCROLL_LOCK = 281,
+	SCANCODE_NUM_LOCK = 282,
+	SCANCODE_PRINT_SCREEN = 283,
+	SCANCODE_PAUSE = 284,
+	SCANCODE_F1 = 290,
+	SCANCODE_F2 = 291,
+	SCANCODE_F3 = 292,
+	SCANCODE_F4 = 293,
+	SCANCODE_F5 = 294,
+	SCANCODE_F6 = 295,
+	SCANCODE_F7 = 296,
+	SCANCODE_F8 = 297,
+	SCANCODE_F9 = 298,
+	SCANCODE_F10 = 299,
+	SCANCODE_F11 = 300,
+	SCANCODE_F12 = 301,
+	SCANCODE_F13 = 302,
+	SCANCODE_F14 = 303,
+	SCANCODE_F15 = 304,
+	SCANCODE_F16 = 305,
+	SCANCODE_F17 = 306,
+	SCANCODE_F18 = 307,
+	SCANCODE_F19 = 308,
+	SCANCODE_F20 = 309,
+	SCANCODE_F21 = 310,
+	SCANCODE_F22 = 311,
+	SCANCODE_F23 = 312,
+	SCANCODE_F24 = 313,
+	SCANCODE_F25 = 314,
+	SCANCODE_KP_0 = 320,
+	SCANCODE_KP_1 = 321,
+	SCANCODE_KP_2 = 322,
+	SCANCODE_KP_3 = 323,
+	SCANCODE_KP_4 = 324,
+	SCANCODE_KP_5 = 325,
+	SCANCODE_KP_6 = 326,
+	SCANCODE_KP_7 = 327,
+	SCANCODE_KP_8 = 328,
+	SCANCODE_KP_9 = 329,
+	SCANCODE_KP_DECIMAL = 330,
+	SCANCODE_KP_DIVIDE = 331,
+	SCANCODE_KP_MULTIPLY = 332,
+	SCANCODE_KP_SUBTRACT = 333,
+	SCANCODE_KP_ADD = 334,
+	SCANCODE_KP_ENTER = 335,
+	SCANCODE_KP_EQUAL = 336,
+	SCANCODE_LEFT_SHIFT = 340,
+	SCANCODE_LEFT_CONTROL = 341,
+	SCANCODE_LEFT_ALT = 342,
+	SCANCODE_LEFT_SUPER = 343,
+	SCANCODE_RIGHT_SHIFT = 344,
+	SCANCODE_RIGHT_CONTROL = 345,
+	SCANCODE_RIGHT_ALT = 346,
+	SCANCODE_RIGHT_SUPER = 347,
+	SCANCODE_MENU = 348,
+}
+
+keymod_flags :: enum c.int {
+	NONE = 0x00,
+	ALT = 0x01,
+	SHIFT = 0x02,
+	CTRL = 0x04,
+	CMD = 0x08,
+	MAIN_MODIFIER = 0x10 /* CMD on Mac, CTRL on Win32 */
+}
+
+mouse_button :: enum c.int {
+	LEFT = 0x00,
+	RIGHT = 0x01,
+	MIDDLE = 0x02,
+	EXT1 = 0x03,
+	EXT2 = 0x04,
+}
+
+// keyboard and mouse buttons input
+key_event :: struct {
+	action: key_action,
+	scanCode: scan_code,
+	keyCode: key_code,
+	button: mouse_button,
+	mods: keymod_flags,
+	clickCount: u8,
+}
+
+// character input
+char_event :: struct {
+	codepoint: utf32,
+	sequence: [8]c.char,
+	seqLen: u8,
+}
+
+// mouse move/scroll
+mouse_event :: struct {
+	x: f32,
+	y: f32,
+	deltaX: f32,
+	deltaY: f32,
+	mods: keymod_flags,
+}
+
+// window resize / move
+move_event :: struct {
+	frame: rect,
+	content: rect,
+}
+
+event :: struct {
+	//TODO clipboard and path drop
+	window: window,
+	type: event_type,
+
+	_: struct #raw_union {
+		key: key_event,
+		character: char_event,
+		mouse: mouse_event,
+		move: move_event,
+		paths: str8_list,
+	}
+}

+ 246 - 0
core/sys/orca/graphics.odin

@@ -0,0 +1,246 @@
+package orca
+
+import "core:c"
+
+// types
+color :: [4]f32
+utf32 :: u32
+
+// handles
+surface :: u64
+font :: u64
+image :: u64
+canvas :: u64
+
+joint_type :: enum c.int {
+	MITER,
+	BEVEL,
+	NONE,
+}
+
+cap_type :: enum c.int {
+	NONE,
+	SQUARE,
+}
+
+font_metrics :: struct {
+	ascent: f32,    // the extent above the baseline (by convention a positive value extends above the baseline)
+	descent: f32,   // the extent below the baseline (by convention, positive value extends below the baseline)
+	lineGap: f32,   // spacing between one row's descent and the next row's ascent
+	xHeight: f32,   // height of the lower case letter 'x'
+	capHeight: f32, // height of the upper case letter 'M'
+	width: f32,     // maximum width of the font
+}
+
+glyph_metrics :: struct {
+	ink: rect,
+	advance: vec2,
+}
+
+text_metrics :: struct {
+	ink: rect,
+	logical: rect,
+	advance: vec2,
+}
+
+rect_atlas :: struct {
+	arena: ^arena,
+	size: vec2i,
+	pos: vec2i,
+	lineHeight: u32,
+}
+
+image_region :: struct {
+	image: image,
+	rect: rect,
+}
+
+//------------------------------------------------------------------------------------------
+// graphics surface
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	surface_nil :: proc() -> surface ---
+	surface_is_nil :: proc() -> c.bool ---
+	surface_canvas :: proc() -> surface ---
+	surface_gles :: proc() -> surface ---
+	surface_destroy :: proc(surface: surface) ---
+	
+	surface_select :: proc(surface: surface) ---
+	surface_deselect :: proc() ---
+	surface_present :: proc(surface: surface) ---
+	
+	surface_get_size :: proc(surface: surface) -> vec2 ---
+	surface_contents_scaling :: proc(surface: surface) -> vec2 ---
+	surface_bring_to_front :: proc(surface: surface) ---
+	surface_send_to_back :: proc(surface: surface) ---
+}
+
+//------------------------------------------------------------------------------------------
+// 2D canvas command buffer
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	canvas_nil :: proc() -> canvas ---
+	canvas_is_nil :: proc(canvas: canvas) -> c.bool ---
+	canvas_create :: proc() -> canvas ---
+	canvas_destroy :: proc(canvas: canvas) ---
+	canvas_set_current :: proc(_canvas: canvas) -> canvas ---
+	canvas_select :: proc(_canvas: canvas) -> canvas ---
+	render :: proc(canvas: canvas) ---
+}
+
+//------------------------------------------------------------------------------------------
+// transform and clipping
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	matrix_push :: proc(mat: mat2x3) ---
+	matrix_multiply_push :: proc(mat: mat2x3) ---
+	matrix_pop :: proc() ---
+	matrix_top :: proc() -> mat2x3 ---
+
+	clip_push :: proc(x, y, w, h: f32) ---
+	clip_pop :: proc() ---
+	clip_top :: proc() -> rect ---
+}
+
+//------------------------------------------------------------------------------------------
+// graphics attributes setting/getting
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	set_color :: proc(color: color) ---
+	set_color_rgba :: proc(r, g, b, a: f32) ---
+	set_width :: proc(width: f32) ---
+	set_tolerance :: proc(tolerance: f32) ---
+	set_joint :: proc(joint: joint_type) ---
+	set_max_joint_excursion :: proc(maxJointExcursion: f32) ---
+	set_cap :: proc(cap: cap_type) ---
+	set_font :: proc(font: font) ---
+	set_font_size :: proc(size: f32) ---
+	set_text_flip :: proc(flip: c.bool) ---
+	set_image :: proc(image: image) ---
+	set_image_source_region :: proc(region: rect) ---
+
+	get_color :: proc() -> color ---
+	get_width :: proc() -> f32 ---
+	get_tolerance :: proc() -> f32 ---
+	get_joint :: proc() -> joint_type ---
+	get_max_joint_excursion :: proc() -> f32 ---
+	get_cap :: proc() -> cap_type ---
+	get_font :: proc() -> font ---
+	get_font_size :: proc() -> f32 ---
+	get_text_flip :: proc() -> bool ---
+	get_image :: proc() -> image ---
+}
+
+//------------------------------------------------------------------------------------------
+// path construction
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	get_position :: proc() -> vec2 ---
+	move_to :: proc(x, y: f32) ---
+	line_to :: proc(x, y: f32) ---
+	quadratic_to :: proc(x1, y1, x2, y2: f32) ---
+	cubic_to :: proc(x1, y1, x2, y2, x3, y3: f32) ---
+	close_path :: proc() ---
+
+	glyph_outlines :: proc(glyphIndices: str32) -> rect ---
+	codepoints_outlines :: proc(str: str32) ---
+	text_outlines :: proc(str: str8) ---
+}
+
+//------------------------------------------------------------------------------------------
+// clear/fill/stroke
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	clear :: proc() ---
+	fill :: proc() ---
+	stroke :: proc() ---
+}
+
+//------------------------------------------------------------------------------------------
+// shapes helpers
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	rectangle_fill :: proc(x, y, w, h: f32) ---
+	rectangle_stroke :: proc(x, y, w, h: f32) ---
+	rounded_rectangle_fill :: proc(x, y, w, h, r: f32) ---
+	rounded_rectangle_stroke :: proc(x, y, w, h, r: f32) ---
+	ellipse_fill :: proc(x, y, rx, ry: f32) ---
+	ellipse_stroke :: proc(x, y, rx, ry: f32) ---
+	circle_fill :: proc(x, y, r: f32) ---
+	circle_stroke :: proc(x, y, r: f32) ---
+	arc :: proc(x, y, r, arcAngle, startAngle: f32) ---
+	image_draw :: proc(image: image, rect: rect) ---
+	image_draw_region :: proc(image: image, srcRegion, dstRegion: rect) ---
+
+	text_fill :: proc(x, y: f32, text: str8) ---
+}
+
+//------------------------------------------------------------------------------------------
+// fonts
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	font_nil :: proc() -> font ---
+	font_is_nil :: proc(font: font) -> c.bool ---
+
+	font_create_from_memory :: proc(mem: str8, rangeCount: u32, ranges: [^]unicode_range) -> font ---
+	font_create_from_file :: proc(file: file, rangeCount: u32, ranges: [^]unicode_range) -> font ---
+	font_create_from_path :: proc(path: str8, rangeCount: u32, ranges: [^]unicode_range) -> font ---
+
+	font_destroy :: proc(font: font) ---
+
+	font_get_glyph_indices :: proc(font: font, codePoints: str32, backing: str32) -> str32 ---
+	font_push_glyph_indices :: proc(arena: ^arena, font: font, codePoints: str32) -> str32 ---
+	font_get_glyph_index :: proc(font: font, codePoint: utf32) -> u32 ---
+
+	font_get_metrics :: proc(font: font, emSize: f32) -> font_metrics ---
+	font_get_metrics_unscaled :: proc(font: font) -> font_metrics ---
+	font_get_scale_for_em_pixels :: proc(font: font, emSize: f32) -> f32 ---
+
+	font_text_metrics_utf32 :: proc(font: font, fontSize: f32, codepoints: str32) -> text_metrics ---
+	font_text_metrics :: proc(font: font, fontSize: f32, text: str8) -> text_metrics ---
+}
+
+//------------------------------------------------------------------------------------------
+// images
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	image_nil :: proc() -> image ---
+	image_is_nil :: proc(a: image) -> c.bool ---
+
+	image_create :: proc(surface: surface, width, height: u32) -> image ---
+	image_create_from_rgba8 :: proc(surface: surface, width, height: u32, pixels: [^]u8) -> image ---
+	image_create_from_memory :: proc(surface: surface, mem: str8, flip: c.bool) -> image ---
+	image_create_from_file :: proc(surface: surface, file: file, flip: c.bool) -> image ---
+	image_create_from_path :: proc(surface: surface, path: str8, flip: c.bool) -> image ---
+
+	image_destroy :: proc(image: image) ---
+
+	image_upload_region_rgba8 :: proc(image: image, region: rect, pixels: [^]u8) ---
+	image_size :: proc(image: image) -> vec2 ---
+}
+
+//------------------------------------------------------------------------------------------
+// image atlas
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	rect_atlas_create :: proc(arena: ^arena, width, height: i32) -> ^rect_atlas ---
+	rect_atlas_alloc :: proc(atlas: ^rect_atlas, width, height: i32) -> rect ---
+	rect_atlas_recycle :: proc(atlas: ^rect_atlas, rect: rect) ---
+
+	image_atlas_allfrom_rgba8 :: proc(atlas: ^rect_atlas, backingImage: image, width, height: u32, pixels: [^]u8) -> image_region ---
+	image_atlas_allfrom_memory :: proc(atlas: ^rect_atlas, backingImage: image, mem: str8, flip: c.bool) -> image_region ---
+	image_atlas_allfrom_file :: proc(atlas: ^rect_atlas, backingImage: image, file: file, flip: c.bool) -> image_region ---
+	image_atlas_allfrom_path :: proc(atlas: ^rect_atlas, backingImage: image, path: str8, flip: c.bool) -> image_region ---
+
+	image_atlas_recycle :: proc(atlas: ^rect_atlas, imageRgn: image_region) ---
+}

+ 59 - 0
core/sys/orca/input_state.odin

@@ -0,0 +1,59 @@
+package orca
+
+import "core:c"
+
+key_state :: struct {
+	lastUpdate: u64,
+	transitionCount: u32,
+	repeatCount: u32,
+	down: c.bool,
+	sysClicked: c.bool,
+	sysDoubleClicked: c.bool,
+	sysTripleClicked: c.bool,
+}
+
+keyboard_state :: struct {
+	keys: [len(key_code)]key_state,
+	mods: keymod_flags,
+}
+
+mouse_state :: struct {
+	lastUpdate: u64,
+	posValid: c.bool,
+	pos: vec2,
+	delta: vec2,
+	wheel: vec2,
+
+	_: struct #raw_union {
+		buttons: [len(mouse_button)]key_state,
+
+		_: struct {
+			left: key_state,
+			right: key_state,
+			middle: key_state,
+			ext1: key_state,
+			ext2: key_state,
+		}
+	}
+}
+
+INPUT_TEXT_BACKING_SIZE :: 64
+
+text_state :: struct {
+	lastUpdate: u64,
+	backing: [INPUT_TEXT_BACKING_SIZE]utf32,
+	codePoints: str32,
+}
+
+clipboard_state :: struct {
+	lastUpdate: u64,
+	pastedText: str8,
+}
+
+input_state :: struct {
+	frameCounter: u64,
+	keyboard: keyboard_state,
+	mouse: mouse_state,
+	text: text_state,
+	clipboard: clipboard_state,
+}

+ 159 - 0
core/sys/orca/io.odin

@@ -0,0 +1,159 @@
+package orca
+
+import "core:c"
+
+file :: distinct u64 // handle
+
+file_access :: enum u16 {
+	NONE = 0,
+	READ = 1 << 1,
+	WRITE = 1 << 2,
+}
+
+file_open_flags :: enum u16 {
+	NONE = 0,
+	APPEND = 1 << 1,
+	TRUNCATE = 1 << 2,
+	CREATE = 1 << 3,
+
+	SYMLINK = 1 << 4,
+	NO_FOLLOW = 1 << 5,
+	RESTRICT = 1 << 6,
+}
+
+file_whence :: enum c.int {
+	SEEK_SET,
+	SEEK_END,
+	SEEK_CURRENT,
+}
+
+io_error :: enum i32 {
+	OK = 0,
+	ERR_UNKNOWN,
+	ERR_OP,          // unsupported operation
+	ERR_HANDLE,      // invalid handle
+	ERR_PREV,        // previously had a fatal error (last error stored on handle)
+	ERR_ARG,         // invalid argument or argument combination
+	ERR_PERM,        // access denied
+	ERR_SPACE,       // no space left
+	ERR_NO_ENTRY,    // file or directory does not exist
+	ERR_EXISTS,      // file already exists
+	ERR_NOT_DIR,     // path element is not a directory
+	ERR_DIR,         // attempted to write directory
+	ERR_MAX_FILES,   // max open files reached
+	ERR_MAX_LINKS,   // too many symbolic links in path
+	ERR_PATH_LENGTH, // path too long
+	ERR_FILE_SIZE,   // file too big
+	ERR_OVERFLOW,    // offset too big
+	ERR_NOT_READY,   // no data ready to be read/written
+	ERR_MEM,         // failed to allocate memory
+	ERR_INTERRUPT,   // operation interrupted by a signal
+	ERR_PHYSICAL,    // physical IO error
+	ERR_NO_DEVICE,   // device not found
+	ERR_WALKOUT,     // attempted to walk out of root directory
+}
+
+//----------------------------------------------------------------
+// File System wrapper API
+//----------------------------------------------------------------
+file_type :: enum c.int {
+	UNKNOWN,
+	REGULAR,
+	DIRECTORY,
+	SYMLINK,
+	BLOCK,
+	CHARACTER,
+	FIFO,
+	SOCKET,
+}
+
+file_perm :: enum u16 {
+	OTHER_EXEC = 1 << 0,
+	OTHER_WRITE = 1 << 1,
+	OTHER_READ = 1 << 2,
+
+	GROUP_EXEC = 1 << 3,
+	GROUP_WRITE = 1 << 4,
+	GROUP_READ = 1 << 5,
+
+	OWNER_EXEC = 1 << 6,
+	OWNER_WRITE = 1 << 7,
+	OWNER_READ = 1 << 8,
+
+	STICKY_BIT = 1 << 9,
+	SET_GID = 1 << 10,
+	SET_UID = 1 << 11,
+}
+
+datestamp :: struct {
+	seconds: i64,  // seconds relative to NTP epoch.
+	fraction: u64, // fraction of seconds elapsed since the time specified by seconds.
+}
+
+file_status :: struct {
+	 uid: u64,
+	 type: file_type,
+	 perm: file_perm,
+	 size: u64,
+
+	 creationDate: datestamp,
+	 accessDate: datestamp,
+	 modificationDate: datestamp,
+}
+
+// TODO file dialogs
+
+// typedef struct oc_file_open_with_dialog_elt
+// {
+//     oc_list_elt listElt;
+//     oc_file file;
+// } oc_file_open_with_dialog_elt;
+
+// typedef struct oc_file_open_with_dialog_result
+// {
+//     oc_file_dialog_button button;
+//     oc_file file;
+//     oc_list selection;
+// } oc_file_open_with_dialog_result;
+
+// file_open_with_dialog_result :: u64 // TODO
+// file_dialog_desc :: u64 // TODO
+
+//----------------------------------------------------------------
+// Low-level File IO API
+//----------------------------------------------------------------
+// @(default_calling_convention="c", link_prefix="oc_")
+// foreign {
+// oc_io_cmp oc_io_wait_single_req(oc_io_req* req);
+// }
+
+//----------------------------------------------------------------
+// High-level File IO API
+//----------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	file_nil :: proc() -> file ---
+	file_is_nil :: proc(handle: file) -> c.bool ---
+
+	file_open :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file ---
+	file_open_at :: proc(dir: file, path: str8, rights: file_access, flags: file_open_flags) -> file ---
+	file_close :: proc(file: file) ---
+	file_last_error :: proc(handle: file) -> io_error ---
+
+	file_pos :: proc(file: file) -> i64 ---
+	file_seek :: proc(file: file, offset: i64, whence: file_whence) -> i64 ---
+	file_write :: proc(file: file, size: u64, buffer: [^]byte) -> u64 ---
+	file_read :: proc(file: file, size: u64, buffer: [^]byte) -> u64 ---
+
+	file_get_status :: proc(file: file) -> file_status ---
+	file_size :: proc(file: file) -> u64 ---
+}
+
+//----------------------------------------------------------------
+// Asking users for file capabilities
+//----------------------------------------------------------------
+// @(default_calling_convention="c", link_prefix="oc_")
+// foreign {
+// 	file_open_with_request :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file ---
+// 	file_open_with_dialog :: proc(arena: ^arena, rights: file_access, flags: file_open_flags, desc: ^file_dialog_desc) -> file_open_with_dialog_result ---
+// }

+ 117 - 0
core/sys/orca/lists.odin

@@ -0,0 +1,117 @@
+package orca
+
+// TODO could check if container/intrusive/list/intrusive_list.odin can be used
+
+//----------------------------------------------------------------
+// Lists
+//----------------------------------------------------------------
+list_elt :: struct {
+	prev: ^list_elt,
+	next: ^list_elt,
+}
+
+list :: struct {
+	first: ^list_elt,
+	last: ^list_elt,
+}
+
+list_init :: proc "c" (list: ^list) {
+	list.first = nil
+	list.last = nil
+}
+
+list_insert :: proc "c" (list: ^list, afterElt, elt: ^list_elt) {
+	elt.prev = afterElt
+	elt.next = afterElt.next
+	if afterElt.next != nil {
+		afterElt.next.prev = elt
+	} else {
+		list.last = elt
+	}
+	afterElt.next = elt
+
+	// OC_DEBUG_ASSERT(elt.next != elt, "list_insert(): can't insert an element into itself")
+}
+
+list_insert_before :: proc "c" (list: ^list, beforeElt, elt: ^list_elt) {
+	elt.next = beforeElt
+	elt.prev = beforeElt.prev
+
+	if beforeElt.prev != nil {
+		beforeElt.prev.next = elt
+	} else {
+		list.first = elt
+	}
+	beforeElt.prev = elt
+
+	// OC_DEBUG_ASSERT(elt.next != elt, "list_insert_before(): can't insert an element into itself")
+}
+
+list_remove :: proc "c" (list: ^list, elt: ^list_elt) {
+	if elt.prev != nil {
+		elt.prev.next = elt.next
+	} else {
+		// OC_DEBUG_ASSERT(list.first == elt)
+		list.first = elt.next
+	}
+
+	if elt.next != nil {
+		elt.next.prev = elt.prev
+	} else {
+		// OC_DEBUG_ASSERT(list.last == elt)
+		list.last = elt.prev
+	}
+
+	elt.prev = nil
+	elt.next = nil
+}
+
+list_push :: proc "c" (list: ^list, elt: ^list_elt) {
+	elt.next = list.first
+	elt.prev = nil
+	if list.first != nil {
+		list.first.prev = elt
+	} else {
+		list.last = elt
+	}
+	list.first = elt
+}
+
+list_pop :: proc "c" (list: ^list) -> ^list_elt {
+	elt := list.first
+	if elt != list.last {
+		list_remove(list, elt)
+		return elt
+	} else {
+		return nil
+	}
+}
+
+list_push_back :: proc "c" (list: ^list, elt: ^list_elt) {
+	elt.prev = list.last
+	elt.next = nil
+	
+	if list.last != nil {
+		list.last.next = elt
+	} else {
+		list.first = elt
+	}
+
+	list.last = elt
+}
+
+list_append :: list_push_back
+
+list_pop_back :: proc "c" (list: ^list) -> ^list_elt {
+	elt := list.last
+	if elt != nil {
+		list_remove(list, elt)
+		return elt
+	} else {
+		return nil
+	}
+}
+
+list_empty :: proc "c" (list: ^list) -> bool {
+	return list.first == nil || list.last == nil
+}

+ 32 - 0
core/sys/orca/orca.odin

@@ -0,0 +1,32 @@
+package orca
+
+import "core:c"
+
+vec2 :: [2]f32
+vec3 :: [3]f32
+vec4 :: [4]f32
+vec2i :: [2]i32
+// mat2x3 :: [6]f32
+mat2x3 :: matrix[2, 3]f32
+rect :: [4]f32
+
+//------------------------------------------------------------------------------------------
+// window
+//------------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	window_set_title :: proc(title: str8) ---
+	window_set_size :: proc(size: vec2) ---
+	request_quit :: proc() ---
+}
+
+clock_kind :: enum c.int {
+	MONOTONIC,
+	UPTIME,
+	DATE,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	clock_time :: proc(clock: clock_kind) -> f64 ---
+}

+ 685 - 0
core/sys/orca/ui.odin

@@ -0,0 +1,685 @@
+package orca
+
+import "core:c"
+
+ui_key :: struct {
+	hash: u64
+}
+
+ui_axis :: enum c.int {
+	X,
+	Y,
+}
+
+ui_align :: enum c.int {
+	START,
+	END,
+	CENTER,
+}
+
+ui_layout_align :: [2]ui_align
+
+ui_layout :: struct {
+	axis: ui_axis,
+	spacing: f32,
+	margin: [2]f32,
+	align: ui_layout_align,
+}
+
+ui_size_kind :: enum c.int {
+	TEXT,
+	PIXELS,
+	CHILDREN,
+	PARENT,
+	PARENT_MINUS_PIXELS,
+}
+
+ui_size :: struct {
+	kind: ui_size_kind,
+	value: f32,
+	relax: f32,
+	minSize: f32,
+}
+
+ui_box_size :: [2]ui_size
+ui_box_floating :: [2]c.bool
+
+//NOTE: flags for axis-dependent properties (e.g. UI_STYLE_FLOAT_X/Y) need to be consecutive bits
+//      in order to play well with axis agnostic functions
+ui_style_mask :: enum u64 {
+	NONE = 0,
+	SIZE_WIDTH = 1 << 1,
+	SIZE_HEIGHT = 1 << 2,
+	LAYOUT_AXIS = 1 << 3,
+	LAYOUT_ALIGN_X = 1 << 4,
+	LAYOUT_ALIGN_Y = 1 << 5,
+	LAYOUT_SPACING = 1 << 6,
+	LAYOUT_MARGIN_X = 1 << 7,
+	LAYOUT_MARGIN_Y = 1 << 8,
+	FLOAT_X = 1 << 9,
+	FLOAT_Y = 1 << 10,
+	COLOR = 1 << 11,
+	BG_COLOR = 1 << 12,
+	BORDER_COLOR = 1 << 13,
+	BORDER_SIZE = 1 << 14,
+	ROUNDNESS = 1 << 15,
+	FONT = 1 << 16,
+	FONT_SIZE = 1 << 17,
+	ANIMATION_TIME = 1 << 18,
+	ANIMATION_MASK = 1 << 19,
+
+	//masks
+	SIZE = SIZE_WIDTH | SIZE_HEIGHT,
+
+	LAYOUT_MARGINS = LAYOUT_MARGIN_X | LAYOUT_MARGIN_Y,
+
+	LAYOUT = LAYOUT_AXIS | LAYOUT_ALIGN_X | LAYOUT_ALIGN_Y | LAYOUT_SPACING | LAYOUT_MARGIN_X | LAYOUT_MARGIN_Y,
+
+	FLOAT = FLOAT_X | FLOAT_Y,
+
+	MASK_INHERITED = COLOR | FONT | FONT_SIZE | ANIMATION_TIME | ANIMATION_MASK,
+}
+
+ui_style :: struct {
+	size: ui_box_size,
+	layout: ui_layout,
+	floating: ui_box_floating,
+	floatTarget: vec2,
+	_color: color,
+	bgColor: color,
+	borderColor: color,
+	font: font,
+	fontSize: f32,
+	borderSize: f32,
+	roundness: f32,
+	animationTime: f32,
+	animationMask: ui_style_mask,
+}
+
+ui_palette :: struct {
+	red0: color,
+	red1: color,
+	red2: color,
+	red3: color,
+	red4: color,
+	red5: color,
+	red6: color,
+	red7: color,
+	red8: color,
+	red9: color,
+	orange0: color,
+	orange1: color,
+	orange2: color,
+	orange3: color,
+	orange4: color,
+	orange5: color,
+	orange6: color,
+	orange7: color,
+	orange8: color,
+	orange9: color,
+	amber0: color,
+	amber1: color,
+	amber2: color,
+	amber3: color,
+	amber4: color,
+	amber5: color,
+	amber6: color,
+	amber7: color,
+	amber8: color,
+	amber9: color,
+	yellow0: color,
+	yellow1: color,
+	yellow2: color,
+	yellow3: color,
+	yellow4: color,
+	yellow5: color,
+	yellow6: color,
+	yellow7: color,
+	yellow8: color,
+	yellow9: color,
+	lime0: color,
+	lime1: color,
+	lime2: color,
+	lime3: color,
+	lime4: color,
+	lime5: color,
+	lime6: color,
+	lime7: color,
+	lime8: color,
+	lime9: color,
+	lightGreen0: color,
+	lightGreen1: color,
+	lightGreen2: color,
+	lightGreen3: color,
+	lightGreen4: color,
+	lightGreen5: color,
+	lightGreen6: color,
+	lightGreen7: color,
+	lightGreen8: color,
+	lightGreen9: color,
+	green0: color,
+	green1: color,
+	green2: color,
+	green3: color,
+	green4: color,
+	green5: color,
+	green6: color,
+	green7: color,
+	green8: color,
+	green9: color,
+	teal0: color,
+	teal1: color,
+	teal2: color,
+	teal3: color,
+	teal4: color,
+	teal5: color,
+	teal6: color,
+	teal7: color,
+	teal8: color,
+	teal9: color,
+	cyan0: color,
+	cyan1: color,
+	cyan2: color,
+	cyan3: color,
+	cyan4: color,
+	cyan5: color,
+	cyan6: color,
+	cyan7: color,
+	cyan8: color,
+	cyan9: color,
+	lightBlue0: color,
+	lightBlue1: color,
+	lightBlue2: color,
+	lightBlue3: color,
+	lightBlue4: color,
+	lightBlue5: color,
+	lightBlue6: color,
+	lightBlue7: color,
+	lightBlue8: color,
+	lightBlue9: color,
+	blue0: color,
+	blue1: color,
+	blue2: color,
+	blue3: color,
+	blue4: color,
+	blue5: color,
+	blue6: color,
+	blue7: color,
+	blue8: color,
+	blue9: color,
+	indigo0: color,
+	indigo1: color,
+	indigo2: color,
+	indigo3: color,
+	indigo4: color,
+	indigo5: color,
+	indigo6: color,
+	indigo7: color,
+	indigo8: color,
+	indigo9: color,
+	violet0: color,
+	violet1: color,
+	violet2: color,
+	violet3: color,
+	violet4: color,
+	violet5: color,
+	violet6: color,
+	violet7: color,
+	violet8: color,
+	violet9: color,
+	purple0: color,
+	purple1: color,
+	purple2: color,
+	purple3: color,
+	purple4: color,
+	purple5: color,
+	purple6: color,
+	purple7: color,
+	purple8: color,
+	purple9: color,
+	pink0: color,
+	pink1: color,
+	pink2: color,
+	pink3: color,
+	pink4: color,
+	pink5: color,
+	pink6: color,
+	pink7: color,
+	pink8: color,
+	pink9: color,
+	grey0: color,
+	grey1: color,
+	grey2: color,
+	grey3: color,
+	grey4: color,
+	grey5: color,
+	grey6: color,
+	grey7: color,
+	grey8: color,
+	grey9: color,
+	black: color,
+	white: color,
+}
+
+// TODO exteern
+// ui_palette: UI_DARK_PALETTE
+// ui_palette: UI_LIGHT_PALETTE
+
+ui_theme :: struct {
+	white: color,
+	primary: color,
+	primaryHover: color,
+	primaryActive: color,
+	border: color,
+	fill0: color,
+	fill1: color,
+	fill2: color,
+	bg0: color,
+	bg1: color,
+	bg2: color,
+	bg3: color,
+	bg4: color,
+	text0: color,
+	text1: color,
+	text2: color,
+	text3: color,
+	sliderThumbBorder: color,
+	elevatedBorder: color,
+
+	roundnessSmall: f32,
+	roundnessMedium: f32,
+	roundnessLarge: f32,
+
+	palette: ^ui_palette,
+}
+
+@export UI_DARK_THEME: ui_theme
+@export UI_LIGHT_THEME: ui_theme
+
+ui_tag :: struct {
+	hash: u64,
+}
+
+ui_selector_kind :: enum c.int {
+	ANY,
+	OWNER,
+	TEXT,
+	TAG,
+	STATUS,
+	KEY,
+}
+
+ui_status :: enum u8 {
+	NONE = 0,
+	HOVER = 1 << 1,
+	HOT = 1 << 2,
+	ACTIVE = 1 << 3,
+	DRAGGING = 1 << 4,
+}
+
+ui_selector_op :: enum c.int {
+	DESCENDANT = 0,
+	AND = 1,
+}
+
+ui_selector :: struct {
+	listElt: list_elt,
+	kind: ui_selector_kind,
+	op: ui_selector_op,
+
+	type: struct #raw_union {
+		text: str8,
+		key: ui_key,
+		tag: ui_tag,
+		status: ui_status,
+	}
+}
+
+ui_pattern :: struct {
+	l: list,
+}
+
+ui_style_rule :: struct {
+	boxElt: list_elt,
+	buildElt: list_elt,
+	tmpElt: list_elt,
+
+	owner: ^ui_box,
+	pattern: ui_pattern,
+	mask: ui_style_mask,
+	style: ^ui_style,
+}
+
+ui_sig :: struct {
+	box: ^ui_box,
+
+	mouse: vec2,
+	delta: vec2,
+	wheel: vec2,
+
+	pressed: c.bool,
+	released: c.bool,
+	clicked: c.bool,
+	doubleClicked: c.bool,
+	tripleClicked: c.bool,
+	rightPressed: c.bool,
+
+	dragging: c.bool,
+	hovering: c.bool,
+
+	pasted: c.bool,
+
+}
+
+ui_box_draw_proc :: proc "c" (box: ^ui_box, data: rawptr)
+
+ui_flags :: enum c.int {
+	NONE = 0,
+	CLICKABLE = (1 << 0),
+	SCROLL_WHEEL_X = (1 << 1),
+	SCROLL_WHEEL_Y = (1 << 2),
+	BLOCK_MOUSE = (1 << 3),
+	HOT_ANIMATION = (1 << 4),
+	ACTIVE_ANIMATION = (1 << 5),
+	//WARN: these two following flags need to be kept as consecutive bits to
+	//      play well with axis-agnostic functions
+	OVERFLOW_ALLOW_X = (1 << 6),
+	OVERFLOW_ALLOW_Y = (1 << 7),
+	CLIP = (1 << 8),
+	DRAW_BACKGROUND = (1 << 9),
+	DRAW_FOREGROUND = (1 << 10),
+	DRAW_BORDER = (1 << 11),
+	DRAW_TEXT = (1 << 12),
+	DRAW_PROC = (1 << 13),
+
+	OVERLAY = (1 << 16),
+}
+
+ui_box :: struct {
+	// hierarchy
+	listElt: list_elt,
+	children: list,
+	parent: ^ui_box,
+
+	overlayElt: list_elt,
+
+	// keying and caching
+	bucketElt: list_elt,
+	key: ui_key,
+	frameCounter: u64,
+
+	// builder-provided info
+	flags: ui_flags,
+	string: str8,
+	tags: list,
+
+	drawProc: ui_box_draw_proc,
+	drawData: rawptr,
+
+	// styling
+	beforeRules: list,
+	afterRules: list,
+
+	//ui_style_tag tag
+	targetStyle: ^ui_style,
+	style: ui_style,
+	z: u32,
+
+	floatPos: vec2,
+	childrenSum: [2]f32,
+	spacing: [2]f32,
+	minSize: [2]f32,
+	rect: rect,
+
+	// signals
+	sig: ^ui_sig,
+
+	// stateful behaviour
+	fresh: c.bool,
+	closed: c.bool,
+	parentClosed: c.bool,
+	dragging: c.bool,
+	hot: c.bool,
+	active: c.bool,
+	scroll: vec2,
+	pressedMouse: vec2,
+
+	// animation data
+	hotTransition: f32,
+	activeTransition: f32,
+}
+
+UI_MAX_INPUT_CHAR_PER_FRAME :: 64
+
+ui_input_text :: struct {
+	count: u8,
+	codePoints: [UI_MAX_INPUT_CHAR_PER_FRAME]utf32,
+}
+
+ui_stack_elt :: struct {
+	parent: ^ui_stack_elt,
+
+	_: struct #raw_union {
+		 box: ^ui_box,
+		 size: ui_size,
+		 clip: rect,
+	}
+}
+
+ui_tag_elt :: struct {
+	listElt: list_elt,
+	tag: ui_tag,
+}
+
+UI_BOX_MAP_BUCKET_COUNT :: 1024
+
+ui_edit_move :: enum c.int {
+	NONE,
+	CHAR,
+	WORD,
+	LINE,
+}
+
+ui_context :: struct {
+	init: c.bool,
+
+	input: input_state,
+
+	frameCounter: u64,
+	frameTime: f64,
+	lastFrameDuration: f64,
+
+	frameArena: arena,
+	boxPool: pool,
+	boxMap: [UI_BOX_MAP_BUCKET_COUNT]list,
+
+	root: ^ui_box,
+	overlay: ^ui_box,
+	overlayList: list,
+	boxStack: ^ui_stack_elt,
+	clipStack: ^ui_stack_elt,
+
+	nextBoxBeforeRules: list,
+	nextBoxAfterRules: list,
+	nextBoxTags: list,
+
+	z: u32,
+	hovered: ^ui_box,
+
+	focus: ^ui_box,
+	editCursor: i32,
+	editMark: i32,
+	editFirstDisplayedChar: i32,
+	editCursorBlinkStart: f64,
+	editSelectionMode: ui_edit_move,
+	editWordSelectionInitialCursor: i32,
+	editWordSelectionInitialMark: i32,
+
+	clipboardRegistered: c.bool,
+
+	theme: ^ui_theme,
+}
+
+ui_text_box_result :: struct {
+	changed: c.bool,
+	accepted: c.bool,
+	text: str8,
+}
+
+ui_select_popup_info :: struct {
+	changed: bool,
+	selectedIndex: int, // -1 if nothing is selected
+	optionCount: int,
+	options: [^]str8,
+	placeholder: str8,
+}
+
+ui_radio_group_info :: struct {
+	changed: bool,
+	selectedIndex: int, // -1 if nothing is selected
+	optionCount: int,
+	options: [^]str8,
+}
+
+//----------------------------------------------------------------
+// Context and frame lifecycle
+//----------------------------------------------------------------
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	ui_init :: proc(ctx: ^ui_context) ---
+	ui_get_context :: proc() -> ^ui_context ---
+	ui_set_context :: proc(ctx: ^ui_context) ---
+
+	ui_process_event :: proc(event: ^event) ---
+	ui_begin_frame :: proc(size: vec2, defaultStyle: ^ui_style, mask: ui_style_mask) ---
+	ui_end_frame :: proc() ---
+	ui_draw :: proc() ---
+}
+
+@(deferred_none=ui_end_frame)
+ui_frame :: proc "c" (size: vec2, defaultStyle: ^ui_style, mask: ui_style_mask) {
+	ui_begin_frame(size, defaultStyle, mask)
+}
+ui_frame_scoped :: ui_frame
+
+//----------------------------------------------------------------
+// Common widget helpers
+//----------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	ui_label :: proc(label: cstring) -> ui_sig ---
+	ui_label_str8 :: proc(label: str8) -> ui_sig ---
+	ui_button :: proc(label: cstring) -> ui_sig ---
+	ui_checkbox :: proc(name: cstring, checked: ^c.bool) -> ui_sig ---
+	ui_slider :: proc(label: cstring, value: ^f32) -> ^ui_box ---
+	ui_scrollbar :: proc(label: cstring, thumbRatio: f32, scrollValue: ^f32) -> ^ui_box ---
+	ui_text_box :: proc(name: cstring, arena: ^arena, text: str8) -> ui_text_box_result ---
+	ui_select_popup :: proc(name: cstring, info: ^ui_select_popup_info) -> ui_select_popup_info ---
+	ui_radio_group :: proc(name: cstring, info: ^ui_radio_group_info) -> ui_radio_group_info ---
+
+	ui_panel_begin :: proc(name: cstring, flags: ui_flags) ---
+	ui_panel_end :: proc() ---
+
+	ui_menu_bar_begin :: proc(label: cstring) ---
+	ui_menu_bar_end :: proc() ---
+
+	ui_menu_begin :: proc(label: cstring) ---
+	ui_menu_end :: proc() ---
+
+	ui_menu_button :: proc(name: cstring) -> ui_sig ---
+
+	ui_tooltip_begin :: proc(name: cstring) -> ui_sig ---
+	ui_tooltip_end :: proc() ---
+}
+
+@(deferred_none=ui_panel_end)
+ui_panel :: proc "c" (name: cstring, flags: ui_flags) {
+	ui_panel_begin(name, flags)
+}
+ui_panel_scoped :: ui_panel
+
+@(deferred_none=ui_menu_bar_end)
+ui_menu_bar :: proc "c" (label: cstring) {
+	ui_menu_bar_begin(label)
+}
+ui_menu_bar_scoped :: ui_menu_bar
+
+@(deferred_none=ui_menu_end)
+ui_menu :: proc "c" (label: cstring) {
+	ui_menu_begin(label)
+}
+ui_menu_scoped :: ui_menu
+
+@(deferred_none=ui_tooltip_end)
+ui_tooltip :: proc "c" (label: cstring) -> ui_sig {
+	return ui_tooltip_begin(label)
+}
+ui_tooltip_scoped :: ui_menu
+
+//-------------------------------------------------------------------------------------
+// Styling
+//-------------------------------------------------------------------------------------
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	ui_style_next :: proc(style: ^ui_style, mask: ui_style_mask) ---
+
+	ui_pattern_push :: proc(arena: ^arena, pattern: ^ui_pattern, selector: ui_selector) ---
+	ui_pattern_all :: proc() -> ui_pattern ---
+	ui_pattern_owner :: proc() -> ui_pattern ---
+
+	ui_style_match_before :: proc(pattern: ui_pattern, style: ^ui_style, mask: ui_style_mask) ---
+	ui_style_match_after :: proc(pattern: ui_pattern, style: ^ui_style, mask: ui_style_mask) ---
+}
+
+//-------------------------------------------------------------------------------------
+// BOX
+//-------------------------------------------------------------------------------------
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	ui_box_make_str8 :: proc(str: str8, flags: ui_flags) -> ^ui_box ---
+	ui_box_begin_str8 :: proc(str: str8, flags: ui_flags) -> ^ui_box ---
+	ui_box_end :: proc() -> ^ui_box ---
+}
+
+@(deferred_none=ui_box_end)
+ui_container :: proc "c" (str: string, flags: ui_flags) -> ^ui_box {
+	return ui_box_begin_str8(str, flags)
+}
+
+@(deferred_none=ui_box_end)
+ui_container_str8 :: proc "c" (str: str8, flags: ui_flags) -> ^ui_box {
+	return ui_box_begin_str8(str, flags)
+}
+
+ui_box_make :: proc "c" (str: string, flags: ui_flags) -> ^ui_box {
+	return ui_box_make_str8(str, flags)
+}
+
+ui_box_begin :: proc "c" (str: string, flags: ui_flags) -> ^ui_box {
+	return ui_box_begin_str8(str, flags)
+}
+
+//-------------------------------------------------------------------------------------
+// BOX
+//-------------------------------------------------------------------------------------
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	ui_tag_make_str8 :: proc(str: str8) -> ui_tag ---
+	ui_tag_box_str8 :: proc(box: ^ui_box, str: str8) ---
+	ui_tag_next_str8 :: proc(str: str8) ---
+}
+
+ui_tag_make :: proc "c" (s: string) -> ui_tag {
+	return ui_tag_make_str8(s)
+}
+
+ui_tag_box :: proc "c" (b: ^ui_box, s: string) {
+	ui_tag_box_str8(b, s)
+}
+
+ui_tag_next :: proc "c" (s: string) {
+	ui_tag_next_str8(s)
+}

+ 131 - 0
core/sys/orca/utf8.odin

@@ -0,0 +1,131 @@
+package orca
+
+unicode_range :: struct {
+	firstCodePoint: utf32,
+	count: u32,
+}
+
+UNICODE_BASIC_LATIN:: unicode_range { 0x0000, 127 }
+UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT:: unicode_range { 0x0080, 127 }
+UNICODE_LATIN_EXTENDED_A:: unicode_range { 0x0100, 127 }
+UNICODE_LATIN_EXTENDED_B:: unicode_range { 0x0180, 207 }
+UNICODE_IPA_EXTENSIONS:: unicode_range { 0x0250, 95 }
+UNICODE_SPACING_MODIFIER_LETTERS:: unicode_range { 0x02b0, 79 }
+UNICODE_COMBINING_DIACRITICAL_MARKS:: unicode_range { 0x0300, 111 }
+UNICODE_GREEK_COPTIC:: unicode_range { 0x0370, 143 }
+UNICODE_CYRILLIC:: unicode_range { 0x0400, 255 }
+UNICODE_CYRILLIC_SUPPLEMENT:: unicode_range { 0x0500, 47 }
+UNICODE_ARMENIAN:: unicode_range { 0x0530, 95 }
+UNICODE_HEBREW:: unicode_range { 0x0590, 111 }
+UNICODE_ARABIC:: unicode_range { 0x0600, 255 }
+UNICODE_SYRIAC:: unicode_range { 0x0700, 79 }
+UNICODE_THAANA:: unicode_range { 0x0780, 63 }
+UNICODE_DEVANAGARI:: unicode_range { 0x0900, 127 }
+UNICODE_BENGALI_ASSAMESE:: unicode_range { 0x0980, 127 }
+UNICODE_GURMUKHI:: unicode_range { 0x0a00, 127 }
+UNICODE_GUJARATI:: unicode_range { 0x0a80, 127 }
+UNICODE_ORIYA:: unicode_range { 0x0b00, 127 }
+UNICODE_TAMIL:: unicode_range { 0x0b80, 127 }
+UNICODE_TELUGU:: unicode_range { 0x0c00, 127 }
+UNICODE_KANNADA:: unicode_range { 0x0c80, 127 }
+UNICODE_MALAYALAM:: unicode_range { 0x0d00, 255 }
+UNICODE_SINHALA:: unicode_range { 0x0d80, 127 }
+UNICODE_THAI:: unicode_range { 0x0e00, 127 }
+UNICODE_LAO:: unicode_range { 0x0e80, 127 }
+UNICODE_TIBETAN:: unicode_range { 0x0f00, 255 }
+UNICODE_MYANMAR:: unicode_range { 0x1000, 159 }
+UNICODE_GEORGIAN:: unicode_range { 0x10a0, 95 }
+UNICODE_HANGUL_JAMO:: unicode_range { 0x1100, 255 }
+UNICODE_ETHIOPIC:: unicode_range { 0x1200, 383 }
+UNICODE_CHEROKEE:: unicode_range { 0x13a0, 95 }
+UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS:: unicode_range { 0x1400, 639 }
+UNICODE_OGHAM:: unicode_range { 0x1680, 31 }
+UNICODE_RUNIC:: unicode_range { 0x16a0, 95 }
+UNICODE_TAGALOG:: unicode_range { 0x1700, 31 }
+UNICODE_HANUNOO:: unicode_range { 0x1720, 31 }
+UNICODE_BUHID:: unicode_range { 0x1740, 31 }
+UNICODE_TAGBANWA:: unicode_range { 0x1760, 31 }
+UNICODE_KHMER:: unicode_range { 0x1780, 127 }
+UNICODE_MONGOLIAN:: unicode_range { 0x1800, 175 }
+UNICODE_LIMBU:: unicode_range { 0x1900, 79 }
+UNICODE_TAI_LE:: unicode_range { 0x1950, 47 }
+UNICODE_KHMER_SYMBOLS:: unicode_range { 0x19e0, 31 }
+UNICODE_PHONETIC_EXTENSIONS:: unicode_range { 0x1d00, 127 }
+UNICODE_LATIN_EXTENDED_ADDITIONAL:: unicode_range { 0x1e00, 255 }
+UNICODE_GREEK_EXTENDED:: unicode_range { 0x1f00, 255 }
+UNICODE_GENERAL_PUNCTUATION:: unicode_range { 0x2000, 111 }
+UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS:: unicode_range { 0x2070, 47 }
+UNICODE_CURRENCY_SYMBOLS:: unicode_range { 0x20a0, 47 }
+UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS:: unicode_range { 0x20d0, 47 }
+UNICODE_LETTERLIKE_SYMBOLS:: unicode_range { 0x2100, 79 }
+UNICODE_NUMBER_FORMS:: unicode_range { 0x2150, 63 }
+UNICODE_ARROWS:: unicode_range { 0x2190, 111 }
+UNICODE_MATHEMATICAL_OPERATORS:: unicode_range { 0x2200, 255 }
+UNICODE_MISCELLANEOUS_TECHNICAL:: unicode_range { 0x2300, 255 }
+UNICODE_CONTROL_PICTURES:: unicode_range { 0x2400, 63 }
+UNICODE_OPTICAL_CHARACTER_RECOGNITION:: unicode_range { 0x2440, 31 }
+UNICODE_ENCLOSED_ALPHANUMERICS:: unicode_range { 0x2460, 159 }
+UNICODE_BOX_DRAWING:: unicode_range { 0x2500, 127 }
+UNICODE_BLOCK_ELEMENTS:: unicode_range { 0x2580, 31 }
+UNICODE_GEOMETRIC_SHAPES:: unicode_range { 0x25a0, 95 }
+UNICODE_MISCELLANEOUS_SYMBOLS:: unicode_range { 0x2600, 255 }
+UNICODE_DINGBATS:: unicode_range { 0x2700, 191 }
+UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A:: unicode_range { 0x27c0, 47 }
+UNICODE_SUPPLEMENTAL_ARROWS_A:: unicode_range { 0x27f0, 15 }
+UNICODE_BRAILLE_PATTERNS:: unicode_range { 0x2800, 255 }
+UNICODE_SUPPLEMENTAL_ARROWS_B:: unicode_range { 0x2900, 127 }
+UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B:: unicode_range { 0x2980, 127 }
+UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS:: unicode_range { 0x2a00, 255 }
+UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS:: unicode_range { 0x2b00, 255 }
+UNICODE_CJK_RADICALS_SUPPLEMENT:: unicode_range { 0x2e80, 127 }
+UNICODE_KANGXI_RADICALS:: unicode_range { 0x2f00, 223 }
+UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS:: unicode_range { 0x2ff0, 15 }
+UNICODE_CJK_SYMBOLS_AND_PUNCTUATION:: unicode_range { 0x3000, 63 }
+UNICODE_HIRAGANA:: unicode_range { 0x3040, 95 }
+UNICODE_KATAKANA:: unicode_range { 0x30a0, 95 }
+UNICODE_BOPOMOFO:: unicode_range { 0x3100, 47 }
+UNICODE_HANGUL_COMPATIBILITY_JAMO:: unicode_range { 0x3130, 95 }
+UNICODE_KANBUN_KUNTEN:: unicode_range { 0x3190, 15 }
+UNICODE_BOPOMOFO_EXTENDED:: unicode_range { 0x31a0, 31 }
+UNICODE_KATAKANA_PHONETIC_EXTENSIONS:: unicode_range { 0x31f0, 15 }
+UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS:: unicode_range { 0x3200, 255 }
+UNICODE_CJK_COMPATIBILITY:: unicode_range { 0x3300, 255 }
+UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A:: unicode_range { 0x3400, 6591 }
+UNICODE_YIJING_HEXAGRAM_SYMBOLS:: unicode_range { 0x4dc0, 63 }
+UNICODE_CJK_UNIFIED_IDEOGRAPHS:: unicode_range { 0x4e00, 20911 }
+UNICODE_YI_SYLLABLES:: unicode_range { 0xa000, 1167 }
+UNICODE_YI_RADICALS:: unicode_range { 0xa490, 63 }
+UNICODE_HANGUL_SYLLABLES:: unicode_range { 0xac00, 11183 }
+UNICODE_HIGH_SURROGATE_AREA:: unicode_range { 0xd800, 1023 }
+UNICODE_LOW_SURROGATE_AREA:: unicode_range { 0xdc00, 1023 }
+UNICODE_PRIVATE_USE_AREA:: unicode_range { 0xe000, 6399 }
+UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS:: unicode_range { 0xf900, 511 }
+UNICODE_ALPHABETIC_PRESENTATION_FORMS:: unicode_range { 0xfb00, 79 }
+UNICODE_ARABIC_PRESENTATION_FORMS_A:: unicode_range { 0xfb50, 687 }
+UNICODE_VARIATION_SELECTORS:: unicode_range { 0xfe00, 15 }
+UNICODE_COMBINING_HALF_MARKS:: unicode_range { 0xfe20, 15 }
+UNICODE_CJK_COMPATIBILITY_FORMS:: unicode_range { 0xfe30, 31 }
+UNICODE_SMALL_FORM_VARIANTS:: unicode_range { 0xfe50, 31 }
+UNICODE_ARABIC_PRESENTATION_FORMS_B:: unicode_range { 0xfe70, 143 }
+UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS:: unicode_range { 0xff00, 239 }
+UNICODE_SPECIALS:: unicode_range { 0xfff0, 15 }
+UNICODE_LINEAR_B_SYLLABARY:: unicode_range { 0x10000, 127 }
+UNICODE_LINEAR_B_IDEOGRAMS:: unicode_range { 0x10080, 127 }
+UNICODE_AEGEAN_NUMBERS:: unicode_range { 0x10100, 63 }
+UNICODE_OLD_ITALIC:: unicode_range { 0x10300, 47 }
+UNICODE_GOTHIC:: unicode_range { 0x10330, 31 }
+UNICODE_UGARITIC:: unicode_range { 0x10380, 31 }
+UNICODE_DESERET:: unicode_range { 0x10400, 79 }
+UNICODE_SHAVIAN:: unicode_range { 0x10450, 47 }
+UNICODE_OSMANYA:: unicode_range { 0x10480, 47 }
+UNICODE_CYPRIOT_SYLLABARY:: unicode_range { 0x10800, 63 }
+UNICODE_BYZANTINE_MUSICAL_SYMBOLS:: unicode_range { 0x1d000, 255 }
+UNICODE_MUSICAL_SYMBOLS:: unicode_range { 0x1d100, 255 }
+UNICODE_TAI_XUAN_JING_SYMBOLS:: unicode_range { 0x1d300, 95 }
+UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS:: unicode_range { 0x1d400, 1023 }
+UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B:: unicode_range { 0x20000, 42719 }
+UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT:: unicode_range { 0x2f800, 543 }
+UNICODE_TAGS:: unicode_range { 0xe0000, 127 }
+UNICODE_VARIATION_SELECTORS_SUPPLEMENT:: unicode_range { 0xe0100, 239 }
+UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A:: unicode_range { 0xf0000, 65533 }
+UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B :: unicode_range { 0x100000, 65533 }

+ 266 - 0
core/sys/orca/util.odin

@@ -0,0 +1,266 @@
+package orca
+
+import "core:c"
+import "core:fmt"
+import "core:runtime"
+import "core:intrinsics"
+
+//----------------------------------------------------------------
+// Arenas
+//----------------------------------------------------------------
+
+mem_reserve_proc :: proc "c" (ctx: ^base_allocator, size: u64)
+mem_modify_proc :: proc "c" (ctx: ^base_allocator, ptr: rawptr, size: u64) 
+
+base_allocator :: struct {
+	reserve: mem_reserve_proc,
+	commit: mem_modify_proc,
+	decommit: mem_modify_proc,
+	release: mem_modify_proc,
+}
+
+arena_chunk :: struct {
+	listElt: list_elt,
+	ptr: ^c.char,
+	offset: u64,
+	committed: u64,
+	cap: u64,
+}
+
+arena :: struct {
+	base: ^base_allocator,
+	chunks: list,
+	currentChunk: ^arena_chunk,
+}
+
+arena_scope :: struct {
+	arena: ^arena,
+	chunk: ^arena_chunk,
+	offset: u64,
+}
+
+arena_options :: struct {
+	base: ^base_allocator,
+	reserve: u64,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	arena_init :: proc(arena: ^arena) ---
+	arena_init_with_options :: proc(arena: ^arena, options: ^arena_options) ---
+	arena_cleanup :: proc(arena: ^arena) ---
+
+	arena_push :: proc(arena: ^arena, size: u64) -> rawptr ---
+	arena_clear :: proc(arena: ^arena) ---
+
+	arena_scope_begin :: proc(arena: ^arena) -> arena_scope ---
+	arena_scope_end :: proc(scope: arena_scope) ---
+
+	scratch_begin :: proc() -> arena_scope ---
+	scratch_begin_next :: proc(used: ^arena) -> arena_scope ---
+}
+
+arena_push_type :: proc "c" (arena: ^arena, $T: typeid) -> ^T {
+	return cast(^T) arena_push(arena, size_of(T))
+}
+
+arena_push_array :: proc "c" (arena: ^arena, $T: typeid, count: int) -> []T {
+	return ([^]T)(arena_push(arena, size_of(T)))[:count]
+}
+
+scratch_end :: arena_scope_end
+
+//----------------------------------------------------------------
+// Pool
+//----------------------------------------------------------------
+
+pool :: struct {
+	arena: arena,
+	freeList: list,
+	blockSize: u64,
+}
+
+pool_options :: struct {
+	base: ^base_allocator,
+	reserve: u64,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	pool_init :: proc(pool: ^pool, blockSize: u64) ---
+	pool_init_with_options :: proc(pool: ^pool, blockSize: u64, options: ^pool_options) ---
+	pool_cleanup :: proc(pool: ^pool) ---
+
+	pool_alloc :: proc(pool: ^pool) -> rawptr ---
+	pool_recycle :: proc(pool: ^pool, ptr: rawptr) ---
+	pool_clear :: proc(pool: ^pool) ---
+}
+
+pool_alloc_type :: proc "c" (arena: ^arena, $T: typeid) -> ^T {
+	return cast(^T) pool_alloc(arena)
+}
+
+// TODO support list macros?
+// #define list_entry :: proc(ptr, type, member)
+// #define list_next_entry :: proc(list, elt, type, member)
+// #define list_prev_entry :: proc(list, elt, type, member)
+// #define list_first_entry :: proc(list, type, member)
+// #define list_last_entry :: proc(list, type, member)
+// #define list_pop_entry :: proc(list, type, member)
+
+// @(default_calling_convention="c", link_prefix="oc_")
+// foreign {
+// 	list_init :: proc(list: ^list) ---
+// 	list_empty :: proc(list: ^list) -> c.bool ---
+
+// 	list_begin :: proc(list: ^list) -> ^list_elt ---
+// 	list_end :: proc(list: ^list) -> ^list_elt ---
+// 	list_last :: proc(list: ^list) -> ^list_elt ---
+
+// 	list_insert :: proc(list: ^list, afterElt: ^list_elt, elt: ^list_elt) ---
+// 	list_insert_before :: proc(list: ^list, beforeElt: ^list_elt, elt: ^list_elt) ---
+// 	list_remove :: proc(list: ^list, elt: ^list_elt) ---
+// 	list_push :: proc(list: ^list, elt: ^list_elt) ---
+// 	list_pop :: proc(list: ^list) -> ^list_elt ---
+// 	list_push_back :: proc(list: ^list, elt: ^list_elt) ---
+// 	list_pop_back :: proc(list: ^list) -> ^list_elt ---
+// }
+
+//------------------------------------------------------------------------------------------
+// for iterators
+//------------------------------------------------------------------------------------------
+
+List_Iterator :: struct($T: typeid) {
+	iterate: ^list,
+	curr: ^list_elt,
+	index: int,
+	offset: uintptr,
+}
+
+// NOTE(Skytrias): intrusive list iterator
+list_iter_init :: proc "c" (iterate: ^list, $T: typeid, $field_name: string) -> (res: List_Iterator(T))
+	where intrinsics.type_has_field(T, field_name),
+	      intrinsics.type_field_type(T, field_name) == list_elt {
+	res.iterate = iterate
+	res.curr = list_begin(iterate)
+	res.offset = offset_of_by_string(T, field_name)
+	return
+}
+
+list_iterate :: proc "c" (iter: ^List_Iterator($T)) -> (ptr: ^T, ok: bool) {
+	node := iter.curr
+	if node == nil {
+		return nil, false
+	}
+	iter.index += 1
+	iter.curr = node.next
+	return (^T)(uintptr(node) - iter.offset), true
+}
+
+//----------------------------------------------------------------
+// Strings / string lists / path strings
+//----------------------------------------------------------------
+
+// TODO use odin cstring when ^c.char is used?
+
+str8 :: string
+str32 :: []rune
+
+str8_list :: struct {
+	list: list,
+	eltCount: u64,
+	len: u64,
+}
+
+str8_elt :: struct {
+	str: str8,
+	listElt: list_elt,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	str8_push_buffer :: proc(arena: ^arena, len: u64, buffer: ^c.char) -> str8 ---
+	str8_push_cstring :: proc(arena: ^arena, str: ^c.char) -> str8 ---
+	str8_push_copy :: proc(arena: ^arena, s: str8) -> str8 ---
+	str8_push_slice :: proc(arena: ^arena, s: str8, start: u64, end: u64) -> str8 ---
+	
+	// TODO get rid of these or wrap them
+	str8_pushfv :: proc(arena: ^arena, format: cstring, args: c.va_list) -> str8 ---
+	str8_pushf :: proc(arena: ^arena, format: cstring, #c_vararg args: ..any) -> str8 ---
+
+	str8_to_cstring :: proc(arena: ^arena, string: str8) -> ^c.char ---
+
+	str8_list_push :: proc(arena: ^arena, list: ^str8_list, str: str8) ---
+	str8_list_pushf :: proc(arena: ^arena, list: ^str8_list, format: cstring, #c_vararg args: ..any) ---
+
+	str8_list_collate :: proc(arena: ^arena, list: str8_list, prefix: str8, separator: str8, postfix: str8) -> str8 ---
+	str8_list_join :: proc(arena: ^arena, list: str8_list) -> str8 ---
+	str8_split :: proc(arena: ^arena, str: str8, separators: str8_list) -> str8_list ---
+
+	path_slice_directory :: proc(path: str8) -> str8 ---
+	path_slice_filename :: proc(path: str8) -> str8 ---
+	path_split :: proc(arena: ^arena, path: str8) -> str8_list ---
+	path_join :: proc(arena: ^arena, elements: str8_list) -> str8 ---
+	path_append :: proc(arena: ^arena, parent: str8, relPath: str8) -> str8 ---
+	path_is_absolute :: proc(path: str8) -> bool ---
+}
+
+//----------------------------------------------------------------
+// Logging
+//----------------------------------------------------------------
+
+// TODO proper odin formatted strings
+
+log_level :: enum c.int {
+	ERROR,
+	WARNING,
+	INFO,
+}
+
+@(default_calling_convention="c", link_prefix="oc_")
+foreign {
+	log_ext :: proc(
+		level: log_level,
+		function: cstring,
+		file: cstring,
+		line: c.int,
+		fmt: cstring,
+		#c_vararg args: ..any,
+	) ---
+}
+
+log_proc: [1028]u8
+log_file: [1028]u8
+
+log_temp :: proc "c" (loc: runtime.Source_Code_Location) -> (function, file: cstring) {
+	copy(log_proc[:], loc.procedure)
+	log_proc[len(loc.procedure)] = 0
+	function = cstring(&log_proc[0])
+	
+	copy(log_file[:], loc.file_path)
+	log_file[len(loc.file_path)] = 0
+	file = cstring(&log_file[0])
+
+	return
+}
+
+log_info :: proc "c" (format: cstring, args: ..any, loc := #caller_location) {
+	function, file := log_temp(loc)
+	// final := fmt.ctprintf(format, ..args)
+	// log_ext(.INFO, function, file, loc.line, final, {})
+	log_ext(.INFO, function, file, loc.line, format, {})
+}
+
+log_warning :: proc "c" (format: cstring, args: ..any, loc := #caller_location) {
+	function, file := log_temp(loc)
+	// final := fmt.ctprintf(format, ..args)
+	// log_ext(.WARNING, function, file, loc.line, final, {})
+	log_ext(.WARNING, function, file, loc.line, format, {})
+}
+
+log_error :: proc "c" (format: cstring, args: ..any, loc := #caller_location) {
+	function, file := log_temp(loc)
+	// final := fmt.ctprintf(format, ..args)
+	// log_ext(.ERROR, function, file, loc.line, final, {})
+	log_ext(.ERROR, function, file, loc.line, format, {})
+}

+ 5 - 1
core/sys/wasm/wasi/wasi_api.odin

@@ -1,7 +1,11 @@
 //+build wasm32
 package sys_wasi
 
-foreign import wasi "wasi_snapshot_preview1"
+when ODIN_OS == .Orca {
+	foreign import wasi "wasi"
+} else {
+	foreign import wasi "wasi_snapshot_preview1"
+}
 
 DIRCOOKIE_START :: u64(0)
 size_t :: uint

+ 26 - 0
core/time/time_orca.odin

@@ -0,0 +1,26 @@
+//+private
+//+build orca
+package time
+
+import wasi "core:sys/wasm/wasi"
+
+_IS_SUPPORTED :: false
+
+_now :: proc "contextless" () -> Time {
+	return {}
+}
+
+_sleep :: proc "contextless" (d: Duration) {
+}
+
+_tick_now :: proc "contextless" () -> Tick {
+	// mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 {
+	// 	q := val / den
+	// 	r := val % den
+	// 	return q * num + r * num / den
+	// }
+	return {}
+}
+
+_yield :: proc "contextless" () {
+}

+ 16 - 2
src/build_settings.cpp

@@ -22,6 +22,7 @@ enum TargetOsKind : u16 {
 	
 	TargetOs_wasi,
 	TargetOs_js,
+	TargetOs_orca,
 
 	TargetOs_freestanding,
 
@@ -83,6 +84,7 @@ gb_global String target_os_names[TargetOs_COUNT] = {
 	
 	str_lit("wasi"),
 	str_lit("js"),
+	str_lit("orca"),
 
 	str_lit("freestanding"),
 };
@@ -583,6 +585,14 @@ gb_global TargetMetrics target_wasi_wasm32 = {
 	str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
 };
 
+gb_global TargetMetrics target_orca_wasm32 = {
+	TargetOs_orca,
+	TargetArch_wasm32,
+	4, 4, 8, 16,
+	str_lit("wasm32-wasi-js"),
+	str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"),
+};
+
 
 gb_global TargetMetrics target_freestanding_wasm64p32 = {
 	TargetOs_freestanding,
@@ -655,6 +665,7 @@ gb_global NamedTargetMetrics named_targets[] = {
 	{ str_lit("freestanding_wasm32"), &target_freestanding_wasm32 },
 	{ str_lit("wasi_wasm32"),         &target_wasi_wasm32 },
 	{ str_lit("js_wasm32"),           &target_js_wasm32 },
+	{ str_lit("orca_wasm32"),         &target_orca_wasm32 },
 
 	{ str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 },
 	{ str_lit("js_wasm64p32"),           &target_js_wasm64p32 },
@@ -1513,9 +1524,12 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
 		// if (bc->metrics.arch == TargetArch_wasm64) {
 		// 	link_flags = gb_string_appendc(link_flags, "-mwasm64 ");
 		// }
-		if (bc->no_entry_point) {
+		if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) {
 			link_flags = gb_string_appendc(link_flags, "--no-entry ");
-		}
+			
+			// in case orca target was
+			bc->no_entry_point = true; 
+		} 
 		
 		bc->link_flags = make_string_c(link_flags);
 		

+ 1 - 0
src/checker.cpp

@@ -1015,6 +1015,7 @@ gb_internal void init_universal(void) {
 			{"WASI",         TargetOs_wasi},
 			{"JS",           TargetOs_js},
 			{"Freestanding", TargetOs_freestanding},
+			{"Orca",         TargetOs_orca},
 		};
 
 		auto fields = add_global_enum_type(str_lit("Odin_OS_Type"), values, gb_count_of(values));

+ 9 - 4
src/linker.cpp

@@ -69,15 +69,20 @@ gb_internal i32 linker_stage(LinkerData *gen) {
 	if (is_arch_wasm()) {
 		timings_start_section(timings, str_lit("wasm-ld"));
 
+		String extra_orca_flags = {};
+		if (build_context.metrics.os == TargetOs_orca) {
+			extra_orca_flags = str_lit(" -L . -lorca --export-dynamic");
+		}
+
 	#if defined(GB_SYSTEM_WINDOWS)
 		result = system_exec_command_line_app("wasm-ld",
-			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
+			"\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
 			LIT(build_context.ODIN_ROOT),
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), LIT(extra_orca_flags));
 	#else
 		result = system_exec_command_line_app("wasm-ld",
-			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s",
-			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags));
+			"wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s",
+			LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), LIT(extra_orca_flags));
 	#endif
 		return result;
 	}