diff --git a/bearlibterminal.cabal b/bearlibterminal.cabal
new file mode 100644
--- /dev/null
+++ b/bearlibterminal.cabal
@@ -0,0 +1,86 @@
+cabal-version: 3.6
+name:          bearlibterminal
+version:       0.1.0.0
+synopsis:      Low-level Haskell bindings to the BearLibTerminal graphics library.
+description:
+  A Haskell wrapper for a graphics library for making roguelike-style games.
+  From the [BearLibTerminal documentation](http://foo.wyrd.name/en:bearlibterminal):
+    BearLibTerminal is a library that creates a terminal-like window facilitating flexible textual output and uncomplicated input processing.
+    A lot of roguelike games intentionally use aesthetic textual or pseudographic visual style. However, native output via the command line
+    interface usually has a few annoying shortcomings such as low speed or palette and font restrictions.
+    Using an extended character set (several languages at once or complicated pseudographics) may also be tricky. BearLibTerminal solves
+    that by providing its own window with a grid of character cells and simple yet powerful API for configuration and textual output.
+
+homepage:      https://github.com/PPKFS/bearlibterminal-hs
+bug-reports:   https://github.com/PPKFS/bearlibterminal-hs/issues
+license:       MIT
+author:        Avery
+maintainer:    Avery <ppkfs@outlook.com>
+copyright:     2024-2025 Avery
+category:      Game Development
+build-type:    Simple
+tested-with:   GHC == 9.8.2
+
+source-repository head
+  type:     git
+  location: https://github.com/PPKFS/bearlibterminal-hs.git
+
+common common-options
+  build-depends:
+      base >= 4.17.2 && < 5
+    , text >= 2.1.1 && < 3
+    , bytestring >= 0.12.1 && < 2
+    , mtl >= 2.3.1 && < 3
+
+  ghc-options:
+    -Wall -Wcompat -Widentities -Wredundant-constraints
+    -Wno-unused-packages -Wno-deprecations -fhide-source-paths
+    -Wno-unused-top-binds -Wmissing-deriving-strategies
+
+  default-language: GHC2021
+  default-extensions:
+    DerivingStrategies
+    OverloadedStrings
+    MultiWayIf
+    BlockArguments
+    LambdaCase
+
+library
+  import: common-options
+  hs-source-dirs:  src
+  exposed-modules:
+    BearLibTerminal
+    BearLibTerminal.Raw
+    BearLibTerminal.Keycodes
+    BearLibTerminal.Terminal.CString
+    BearLibTerminal.Terminal.Color
+    BearLibTerminal.Terminal.Print
+    BearLibTerminal.Terminal.Set
+    BearLibTerminal.Terminal.String
+  extra-libraries: stdc++ BearLibTerminal
+  include-dirs:
+    cbits
+  includes:
+    cbits/BearLibTerminal.h
+    cbits/BearLibTerminalExtras.h
+  install-includes:
+    cbits/BearLibTerminal.h
+    cbits/BearLibTerminalExtras.h
+
+executable omni
+  import: common-options
+  build-depends:
+    bearlibterminal >= 0.1.0.0 && <= 0.2.0.0,
+    containers >= 0.6.8 && < 1,
+    formatting >= 7.2.0 && < 8,
+    time >= 1.12.2 && < 2,
+    random >= 1.3.0 && < 2,
+    vector >= 0.13.2 && < 1,
+    word8 >= 0.1.3 && < 1,
+  hs-source-dirs:  omni
+  main-is: Main.hs
+  other-modules:
+    Omni.BasicOutput
+    Omni.DefaultFont
+    Omni.Tilesets
+    Omni.ManualCellsize
diff --git a/cbits/BearLibTerminal.h b/cbits/BearLibTerminal.h
new file mode 100644
--- /dev/null
+++ b/cbits/BearLibTerminal.h
@@ -0,0 +1,766 @@
+/*
+* BearLibTerminal
+* Copyright (C) 2013-2017 Cfyz
+*
+* 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.
+*/
+
+#ifndef BEARLIBTERMINAL_H
+#define BEARLIBTERMINAL_H
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#ifdef __GNUC__
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)
+#pragma GCC diagnostic ignored "-Wformat-nonliteral" /* False-positive when wrapping vsnprintf. */
+#endif /* __GNUC__ >= 4.1 */
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <wchar.h>
+#if defined(__cplusplus)
+#include <sstream>
+#endif
+
+/*
+ * Keyboard scancodes for events/states
+ */
+#define TK_A                0x04
+#define TK_B                0x05
+#define TK_C                0x06
+#define TK_D                0x07
+#define TK_E                0x08
+#define TK_F                0x09
+#define TK_G                0x0A
+#define TK_H                0x0B
+#define TK_I                0x0C
+#define TK_J                0x0D
+#define TK_K                0x0E
+#define TK_L                0x0F
+#define TK_M                0x10
+#define TK_N                0x11
+#define TK_O                0x12
+#define TK_P                0x13
+#define TK_Q                0x14
+#define TK_R                0x15
+#define TK_S                0x16
+#define TK_T                0x17
+#define TK_U                0x18
+#define TK_V                0x19
+#define TK_W                0x1A
+#define TK_X                0x1B
+#define TK_Y                0x1C
+#define TK_Z                0x1D
+#define TK_1                0x1E
+#define TK_2                0x1F
+#define TK_3                0x20
+#define TK_4                0x21
+#define TK_5                0x22
+#define TK_6                0x23
+#define TK_7                0x24
+#define TK_8                0x25
+#define TK_9                0x26
+#define TK_0                0x27
+#define TK_RETURN           0x28
+#define TK_ENTER            0x28
+#define TK_ESCAPE           0x29
+#define TK_BACKSPACE        0x2A
+#define TK_TAB              0x2B
+#define TK_SPACE            0x2C
+#define TK_MINUS            0x2D /*  -  */
+#define TK_EQUALS           0x2E /*  =  */
+#define TK_LBRACKET         0x2F /*  [  */
+#define TK_RBRACKET         0x30 /*  ]  */
+#define TK_BACKSLASH        0x31 /*  \  */
+#define TK_SEMICOLON        0x33 /*  ;  */
+#define TK_APOSTROPHE       0x34 /*  '  */
+#define TK_GRAVE            0x35 /*  `  */
+#define TK_COMMA            0x36 /*  ,  */
+#define TK_PERIOD           0x37 /*  .  */
+#define TK_SLASH            0x38 /*  /  */
+#define TK_F1               0x3A
+#define TK_F2               0x3B
+#define TK_F3               0x3C
+#define TK_F4               0x3D
+#define TK_F5               0x3E
+#define TK_F6               0x3F
+#define TK_F7               0x40
+#define TK_F8               0x41
+#define TK_F9               0x42
+#define TK_F10              0x43
+#define TK_F11              0x44
+#define TK_F12              0x45
+#define TK_PAUSE            0x48 /* Pause/Break */
+#define TK_INSERT           0x49
+#define TK_HOME             0x4A
+#define TK_PAGEUP           0x4B
+#define TK_DELETE           0x4C
+#define TK_END              0x4D
+#define TK_PAGEDOWN         0x4E
+#define TK_RIGHT            0x4F /* Right arrow */
+#define TK_LEFT             0x50 /* Left arrow */
+#define TK_DOWN             0x51 /* Down arrow */
+#define TK_UP               0x52 /* Up arrow */
+#define TK_KP_DIVIDE        0x54 /* '/' on numpad */
+#define TK_KP_MULTIPLY      0x55 /* '*' on numpad */
+#define TK_KP_MINUS         0x56 /* '-' on numpad */
+#define TK_KP_PLUS          0x57 /* '+' on numpad */
+#define TK_KP_ENTER         0x58
+#define TK_KP_1             0x59
+#define TK_KP_2             0x5A
+#define TK_KP_3             0x5B
+#define TK_KP_4             0x5C
+#define TK_KP_5             0x5D
+#define TK_KP_6             0x5E
+#define TK_KP_7             0x5F
+#define TK_KP_8             0x60
+#define TK_KP_9             0x61
+#define TK_KP_0             0x62
+#define TK_KP_PERIOD        0x63 /* '.' on numpad */
+#define TK_SHIFT            0x70
+#define TK_CONTROL          0x71
+#define TK_ALT              0x72
+
+/*
+ * Mouse events/states
+ */
+#define TK_MOUSE_LEFT       0x80 /* Buttons */
+#define TK_MOUSE_RIGHT      0x81
+#define TK_MOUSE_MIDDLE     0x82
+#define TK_MOUSE_X1         0x83
+#define TK_MOUSE_X2         0x84
+#define TK_MOUSE_MOVE       0x85 /* Movement event */
+#define TK_MOUSE_SCROLL     0x86 /* Mouse scroll event */
+#define TK_MOUSE_X          0x87 /* Cusor position in cells */
+#define TK_MOUSE_Y          0x88
+#define TK_MOUSE_PIXEL_X    0x89 /* Cursor position in pixels */
+#define TK_MOUSE_PIXEL_Y    0x8A
+#define TK_MOUSE_WHEEL      0x8B /* Scroll direction and amount */
+#define TK_MOUSE_CLICKS     0x8C /* Number of consecutive clicks */
+
+/*
+ * If key was released instead of pressed, it's code will be OR'ed with TK_KEY_RELEASED:
+ * a) pressed 'A': 0x04
+ * b) released 'A': 0x04|VK_KEY_RELEASED = 0x104
+ */
+#define TK_KEY_RELEASED     0x100
+
+/*
+ * Virtual key-codes for internal terminal states/variables.
+ * These can be accessed via terminal_state function.
+ */
+#define TK_WIDTH            0xC0 /* Terminal window size in cells */
+#define TK_HEIGHT           0xC1
+#define TK_CELL_WIDTH       0xC2 /* Character cell size in pixels */
+#define TK_CELL_HEIGHT      0xC3
+#define TK_COLOR            0xC4 /* Current foregroung color */
+#define TK_BKCOLOR          0xC5 /* Current background color */
+#define TK_LAYER            0xC6 /* Current layer */
+#define TK_COMPOSITION      0xC7 /* Current composition state */
+#define TK_CHAR             0xC8 /* Translated ANSI code of last produced character */
+#define TK_WCHAR            0xC9 /* Unicode codepoint of last produced character */
+#define TK_EVENT            0xCA /* Last dequeued event */
+#define TK_FULLSCREEN       0xCB /* Fullscreen state */
+
+/*
+ * Other events
+ */
+#define TK_CLOSE            0xE0
+#define TK_RESIZED          0xE1
+
+/*
+ * Generic mode enum.
+ * Right now it is used for composition option only.
+ */
+#define TK_OFF                 0
+#define TK_ON                  1
+
+/*
+ * Input result codes for terminal_read function.
+ */
+#define TK_INPUT_NONE          0
+#define TK_INPUT_CANCELLED    -1
+
+/*
+ * Text printing alignment.
+ */
+#define TK_ALIGN_DEFAULT       0
+#define TK_ALIGN_LEFT          1
+#define TK_ALIGN_RIGHT         2
+#define TK_ALIGN_CENTER        3
+#define TK_ALIGN_TOP           4
+#define TK_ALIGN_BOTTOM        8
+#define TK_ALIGN_MIDDLE       12
+
+/*
+ * Terminal uses unsigned 32-bit value for color representation in ARGB order (0xAARRGGBB), e. g.
+ * a) solid red is 0xFFFF0000
+ * b) half-transparent green is 0x8000FF00
+ */
+typedef uint32_t color_t;
+
+typedef struct dimensions_t_
+{
+	int width;
+	int height;
+}
+dimensions_t;
+
+#if defined(BEARLIBTERMINAL_STATIC_BUILD)
+#  define TERMINAL_API
+#elif defined(_WIN32)
+#  if defined(BEARLIBTERMINAL_BUILDING_LIBRARY)
+#    define TERMINAL_API __declspec(dllexport)
+#  else
+#    define TERMINAL_API __declspec(dllimport)
+#  endif
+#else
+#  if defined(BEARLIBTERMINAL_BUILDING_LIBRARY) && __GNUC__ >= 4
+#    define TERMINAL_API __attribute__ ((visibility ("default")))
+#  else
+#    define TERMINAL_API
+#  endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TERMINAL_API int terminal_open();
+TERMINAL_API void terminal_close();
+TERMINAL_API int terminal_set8(const int8_t* value);
+TERMINAL_API int terminal_set16(const int16_t* value);
+TERMINAL_API int terminal_set32(const int32_t* value);
+TERMINAL_API void terminal_refresh();
+TERMINAL_API void terminal_clear();
+TERMINAL_API void terminal_clear_area(int x, int y, int w, int h);
+TERMINAL_API void terminal_crop(int x, int y, int w, int h);
+TERMINAL_API void terminal_layer(int index);
+TERMINAL_API void terminal_color(color_t color);
+TERMINAL_API void terminal_bkcolor(color_t color);
+TERMINAL_API void terminal_composition(int mode);
+TERMINAL_API void terminal_font8(const int8_t* name);
+TERMINAL_API void terminal_font16(const int16_t* name);
+TERMINAL_API void terminal_font32(const int32_t* name);
+TERMINAL_API void terminal_put(int x, int y, int code);
+TERMINAL_API void terminal_put_ext(int x, int y, int dx, int dy, int code, color_t* corners);
+TERMINAL_API int terminal_pick(int x, int y, int index);
+TERMINAL_API color_t terminal_pick_color(int x, int y, int index);
+TERMINAL_API color_t terminal_pick_bkcolor(int x, int y);
+TERMINAL_API void terminal_print_ext8(int x, int y, int w, int h, int align, const int8_t* s, int* out_w, int* out_h);
+TERMINAL_API void terminal_print_ext16(int x, int y, int w, int h, int align, const int16_t* s, int* out_w, int* out_h);
+TERMINAL_API void terminal_print_ext32(int x, int y, int w, int h, int align, const int32_t* s, int* out_w, int* out_h);
+TERMINAL_API void terminal_measure_ext8(int w, int h, const int8_t* s, int* out_w, int* out_h);
+TERMINAL_API void terminal_measure_ext16(int w, int h, const int16_t* s, int* out_w, int* out_h);
+TERMINAL_API void terminal_measure_ext32(int w, int h, const int32_t* s, int* out_w, int* out_h);
+TERMINAL_API int terminal_has_input();
+TERMINAL_API int terminal_state(int code);
+TERMINAL_API int terminal_read();
+TERMINAL_API int terminal_read_str8(int x, int y, int8_t* buffer, int max);
+TERMINAL_API int terminal_read_str16(int x, int y, int16_t* buffer, int max);
+TERMINAL_API int terminal_read_str32(int x, int y, int32_t* buffer, int max);
+TERMINAL_API int terminal_peek();
+TERMINAL_API void terminal_delay(int period);
+TERMINAL_API const int8_t* terminal_get8(const int8_t* key, const int8_t* default_);
+TERMINAL_API const int16_t* terminal_get16(const int16_t* key, const int16_t* default_);
+TERMINAL_API const int32_t* terminal_get32(const int32_t* key, const int32_t* default_);
+TERMINAL_API color_t color_from_name8(const int8_t* name);
+TERMINAL_API color_t color_from_name16(const int16_t* name);
+TERMINAL_API color_t color_from_name32(const int32_t* name);
+TERMINAL_API int terminal_put_array(int x, int y, int w, int h, const uint8_t* data, int row_stride, int column_stride, const void* layout, int char_size);
+
+#ifdef __cplusplus
+} /* End of extern "C" */
+#endif
+
+/*
+ * Utility macro trick which allows macro-in-macro expansion
+ */
+#define TERMINAL_CAT(a, b) TERMINAL_PRIMITIVE_CAT(a, b)
+#define TERMINAL_PRIMITIVE_CAT(a, b) a ## b
+
+/*
+ * wchar_t has different sized depending on platform. Furthermore, it's size
+ * can be changed for GCC compiler.
+ */
+#if !defined(__SIZEOF_WCHAR_T__)
+#  if defined(_WIN32)
+#    define __SIZEOF_WCHAR_T__ 2
+#  else
+#    define __SIZEOF_WCHAR_T__ 4
+#  endif
+#endif
+
+#if __SIZEOF_WCHAR_T__ == 2
+#define TERMINAL_WCHAR_SUFFIX 16
+#define TERMINAL_WCHAR_TYPE int16_t
+#else // 4
+#define TERMINAL_WCHAR_SUFFIX 32
+#define TERMINAL_WCHAR_TYPE int32_t
+#endif
+
+#if defined(__cplusplus)
+#define TERMINAL_INLINE inline
+#define TERMINAL_DEFAULT(value) = value
+#else
+#define TERMINAL_INLINE static inline
+#define TERMINAL_DEFAULT(value)
+#endif
+
+/*
+ * These functions provide inline string formatting support
+ * for terminal_setf, terminal_printf, etc.
+ *
+ * Using static termporary buffer is okay because terminal API is not
+ * required to be multiple-thread safe by design.
+ */
+
+#define TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE 65536
+
+TERMINAL_INLINE const char* terminal_vsprintf(const char* s, va_list args)
+{
+	static int buffer_size = 512;
+	static char* buffer = NULL;
+	int rc = 0;
+
+	if (!s)
+		return NULL;
+	else if (!buffer)
+		buffer = (char*)malloc(buffer_size);
+
+	while (1)
+	{
+		buffer[buffer_size-1] = '\0';
+		rc = vsnprintf(buffer, buffer_size, s, args);
+		if (rc >= buffer_size || buffer[buffer_size-1] != '\0')
+		{
+			if (buffer_size >= TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE)
+				return NULL;
+
+			buffer_size *= 2;
+			buffer = (char*)realloc(buffer, buffer_size);
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	return rc >= 0? buffer: NULL;
+}
+
+TERMINAL_INLINE const wchar_t* terminal_vswprintf(const wchar_t* s, va_list args)
+{
+	static int buffer_size = 512;
+	static wchar_t* buffer = NULL;
+	int rc = 0;
+
+	if (!s)
+		return NULL;
+	else if (!buffer)
+		buffer = (wchar_t*)malloc(buffer_size * sizeof(wchar_t));
+
+	while (1)
+	{
+		buffer[buffer_size-1] = L'\0';
+#if defined(_WIN32)
+		rc = _vsnwprintf(buffer, buffer_size, s, args);
+#else
+		rc = vswprintf(buffer, buffer_size, s, args);
+#endif
+		if (rc >= buffer_size || buffer[buffer_size-1] != L'\0')
+		{
+			if (buffer_size >= TERMINAL_VSPRINTF_MAXIMUM_BUFFER_SIZE)
+				return NULL;
+
+			buffer_size *= 2;
+			buffer = (wchar_t*)realloc(buffer, buffer_size * sizeof(wchar_t));
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	return rc >= 0? buffer: NULL;
+}
+
+#define TERMINAL_FORMATTED_WRAP(type, call) \
+	type ret; \
+	va_list args; \
+	va_start(args, s); \
+	ret = call; \
+	va_end(args); \
+	return ret;
+
+#define TERMINAL_FORMATTED_WRAP_V(call) \
+	va_list args; \
+	va_start(args, s); \
+	call; \
+	va_end(args);
+
+/*
+ * This set of inline functions define basic name substitution + type cast:
+ * terminal_[w]xxxx -> terminal_xxxx{8|16|32}
+ */
+
+TERMINAL_INLINE int terminal_set(const char* s)
+{
+	return terminal_set8((const int8_t*)s);
+}
+
+TERMINAL_INLINE int terminal_setf(const char* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(int, terminal_set(terminal_vsprintf(s, args)))
+}
+
+TERMINAL_INLINE int terminal_wset(const wchar_t* s)
+{
+	return TERMINAL_CAT(terminal_set, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)s);
+}
+
+TERMINAL_INLINE int terminal_wsetf(const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(int, terminal_wset(terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE void terminal_font(const char* name)
+{
+	terminal_font8((const int8_t*)name);
+}
+
+TERMINAL_INLINE void terminal_wfont(const wchar_t* name)
+{
+	TERMINAL_CAT(terminal_font, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)name);
+}
+
+TERMINAL_INLINE dimensions_t terminal_print(int x, int y, const char* s)
+{
+	dimensions_t ret;
+	terminal_print_ext8(x, y, 0, 0, TK_ALIGN_DEFAULT, (const int8_t*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_printf(int x, int y, const char* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_print(x, y, terminal_vsprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_wprint(int x, int y, const wchar_t* s)
+{
+	dimensions_t ret;
+	TERMINAL_CAT(terminal_print_ext, TERMINAL_WCHAR_SUFFIX)(x, y, 0, 0, TK_ALIGN_DEFAULT, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_wprintf(int x, int y, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint(x, y, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_print_ext(int x, int y, int w, int h, int align, const char* s)
+{
+	dimensions_t ret;
+	terminal_print_ext8(x, y, w, h, align, (const int8_t*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_printf_ext(int x, int y, int w, int h, int align, const char* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_print_ext(x, y, w, h, align, terminal_vsprintf(s, args)));
+}
+
+TERMINAL_INLINE dimensions_t terminal_wprint_ext(int x, int y, int w, int h, int align, const wchar_t* s)
+{
+	dimensions_t ret;
+	TERMINAL_CAT(terminal_print_ext, TERMINAL_WCHAR_SUFFIX)(x, y, w, h, align, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_wprintf_ext(int x, int y, int w, int h, int align, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint_ext(x, y, w, h, align, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_measure(const char* s)
+{
+	dimensions_t ret;
+	terminal_measure_ext8(0, 0, (const int8_t*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_measuref(const char* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_measure(terminal_vsprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_wmeasure(const wchar_t* s)
+{
+	dimensions_t ret;
+	TERMINAL_CAT(terminal_measure_ext, TERMINAL_WCHAR_SUFFIX)(0, 0, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_wmeasuref(const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure(terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_measure_ext(int w, int h, const char* s)
+{
+	dimensions_t ret;
+	terminal_measure_ext8(w, h, (const int8_t*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_measuref_ext(int w, int h, const char* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_measure_ext(w, h, terminal_vsprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_wmeasure_ext(int w, int h, const wchar_t* s)
+{
+	dimensions_t ret;
+	TERMINAL_CAT(terminal_measure_ext, TERMINAL_WCHAR_SUFFIX)(w, h, (const TERMINAL_WCHAR_TYPE*)s, &ret.width, &ret.height);
+	return ret;
+}
+
+TERMINAL_INLINE dimensions_t terminal_wmeasuref_ext(int w, int h, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure_ext(w, h, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE int terminal_read_str(int x, int y, char* buffer, int max)
+{
+	return terminal_read_str8(x, y, (int8_t*)buffer, max);
+}
+
+TERMINAL_INLINE int terminal_read_wstr(int x, int y, wchar_t* buffer, int max)
+{
+	return TERMINAL_CAT(terminal_read_str, TERMINAL_WCHAR_SUFFIX)(x, y, (TERMINAL_WCHAR_TYPE*)buffer, max);
+}
+
+TERMINAL_INLINE const char* terminal_get(const char* key, const char* default_ TERMINAL_DEFAULT((const char*)0))
+{
+	return (const char*)terminal_get8((const int8_t*)key, (const int8_t*)default_);
+}
+
+TERMINAL_INLINE const wchar_t* terminal_wget(const wchar_t* key, const wchar_t* default_ TERMINAL_DEFAULT((const wchar_t*)0))
+{
+	return (const wchar_t*)TERMINAL_CAT(terminal_get, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)key, (const TERMINAL_WCHAR_TYPE*)default_);
+}
+
+TERMINAL_INLINE color_t color_from_name(const char* name)
+{
+	return color_from_name8((const int8_t*)name);
+}
+
+TERMINAL_INLINE color_t color_from_wname(const wchar_t* name)
+{
+	return TERMINAL_CAT(color_from_name, TERMINAL_WCHAR_SUFFIX)((const TERMINAL_WCHAR_TYPE*)name);
+}
+
+#ifdef __cplusplus
+/*
+ * C++ supports function overloading, should take advantage of it.
+ */
+
+TERMINAL_INLINE int terminal_set(const wchar_t* s)
+{
+	return terminal_wset(s);
+}
+
+TERMINAL_INLINE int terminal_setf(const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(int, terminal_wset(terminal_vswprintf(s, args)));
+}
+
+TERMINAL_INLINE void terminal_color(const char* name)
+{
+	terminal_color(color_from_name(name));
+}
+
+TERMINAL_INLINE void terminal_color(const wchar_t* name)
+{
+	terminal_color(color_from_wname(name));
+}
+
+TERMINAL_INLINE void terminal_bkcolor(const char* name)
+{
+	terminal_bkcolor(color_from_name(name));
+}
+
+TERMINAL_INLINE void terminal_bkcolor(const wchar_t* name)
+{
+	terminal_bkcolor(color_from_wname(name));
+}
+
+TERMINAL_INLINE void terminal_font(const wchar_t* name)
+{
+	terminal_wfont(name);
+}
+
+TERMINAL_INLINE void terminal_put_ext(int x, int y, int dx, int dy, int code)
+{
+	terminal_put_ext(x, y, dx, dy, code, 0);
+}
+
+TERMINAL_INLINE dimensions_t terminal_print(int x, int y, const wchar_t* s)
+{
+	return terminal_wprint(x, y, s);
+}
+
+TERMINAL_INLINE dimensions_t terminal_printf(int x, int y, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint(x, y, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_print_ext(int x, int y, int w, int h, int align, const wchar_t* s)
+{
+	return terminal_wprint_ext(x, y, w, h, align, s);
+}
+
+TERMINAL_INLINE dimensions_t terminal_printf_ext(int x, int y, int w, int h, int align, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wprint_ext(x, y, w, h, align, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_measure(const wchar_t* s)
+{
+	return terminal_wmeasure(s);
+}
+
+TERMINAL_INLINE dimensions_t terminal_measuref(const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure(terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE dimensions_t terminal_measure_ext(int w, int h, const wchar_t* s)
+{
+	return terminal_wmeasure_ext(w, h, s);
+}
+
+TERMINAL_INLINE dimensions_t terminal_measuref_ext(int w, int h, const wchar_t* s, ...)
+{
+	TERMINAL_FORMATTED_WRAP(dimensions_t, terminal_wmeasure_ext(w, h, terminal_vswprintf(s, args)))
+}
+
+TERMINAL_INLINE int terminal_read_str(int x, int y, wchar_t* buffer, int max)
+{
+	return terminal_read_wstr(x, y, buffer, max);
+}
+
+TERMINAL_INLINE color_t color_from_name(const wchar_t* name)
+{
+	return color_from_wname(name);
+}
+
+TERMINAL_INLINE int terminal_pick(int x, int y)
+{
+	return terminal_pick(x, y, 0);
+}
+
+TERMINAL_INLINE color_t terminal_pick_color(int x, int y)
+{
+	return terminal_pick_color(x, y, 0);
+}
+
+TERMINAL_INLINE const wchar_t* terminal_get(const wchar_t* key, const wchar_t* default_ = (const wchar_t*)0)
+{
+	return terminal_wget(key, default_);
+}
+
+template<typename T, typename C> T terminal_get(const C* key, const T& default_ = T())
+{
+	const C* result_str = terminal_get(key, (const C*)0);
+	if (result_str[0] == C(0))
+		return default_;
+	T result;
+	return (bool)(std::basic_istringstream<C>(result_str) >> result)? result: default_;
+}
+
+#endif /* __cplusplus */
+
+/*
+ * Color routines
+ */
+TERMINAL_INLINE color_t color_from_argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b)
+{
+	return ((color_t)a << 24) | (r << 16) | (g << 8) | b;
+}
+
+/*
+ * Other functional sugar
+ */
+TERMINAL_INLINE int terminal_check(int code)
+{
+	return terminal_state(code) > 0;
+}
+
+/*
+ * WinMain entry point handling macro. This allows easier entry point definition.
+ * The macro will expand to proper WinMain stub regardless of header include order.
+ */
+#if defined(_WIN32)
+
+/*
+ * WinMain probe macro. It will expand to either X or X_WINDOWS_ depending on
+ * Windows.h header inclusion.
+ */
+#define TERMINAL_TAKE_CARE_OF_WINMAIN TERMINAL_WINMAIN_PROBE_IMPL(_WINDOWS_)
+#define TERMINAL_WINMAIN_PROBE_IMPL(DEF) TERMINAL_PRIMITIVE_CAT(TERMINAL_WINMAIN_IMPL, DEF)
+
+/*
+ * Trivial no-arguments WinMain implementation. It just calls main.
+ */
+#define TERMINAL_WINMAIN_IMPL_BASE(INSTANCE_T, STRING_T)\
+	extern "C" int main();\
+	extern "C" int __stdcall WinMain(INSTANCE_T hInstance, INSTANCE_T hPrevInstance, STRING_T lpCmdLine, int nCmdShow)\
+	{\
+		return main();\
+	}
+
+/*
+ * Macro expands to empty string. Windows.h is included thus code MUST use
+ * predefined types or else MSVC will complain.
+ */
+#define TERMINAL_WINMAIN_IMPL TERMINAL_WINMAIN_IMPL_BASE(HINSTANCE, LPSTR)
+
+/*
+ * Macro expands to macro name. Windows.h wasn't included, so WinMain will be
+ * defined with fundamental types (enough for linker to find it).
+ */
+#define TERMINAL_WINMAIN_IMPL_WINDOWS_ TERMINAL_WINMAIN_IMPL_BASE(void*, char*)
+
+#else
+
+/*
+ * Only Windows has WinMain but macro still must be defined for cross-platform
+ * applications.
+ */
+#define TERMINAL_TAKE_CARE_OF_WINMAIN
+
+#endif
+
+#endif // BEARLIBTERMINAL_H
diff --git a/cbits/BearLibTerminalExtras.h b/cbits/BearLibTerminalExtras.h
new file mode 100644
--- /dev/null
+++ b/cbits/BearLibTerminalExtras.h
@@ -0,0 +1,41 @@
+#ifndef BEARLIBTERMINALEXTRAS_H
+#define BEARLIBTERMINALEXTRAS_H
+
+#include "BearLibTerminal.h"
+#include <string.h>
+
+void terminal_color_from_name(const char* name)
+{
+	terminal_color(color_from_name(name));
+}
+
+void terminal_bkcolor_from_name(const char* name)
+{
+	terminal_bkcolor(color_from_name(name));
+}
+
+void terminal_print_ptr(int x, int y, const char* s, dimensions_t* dim)
+{
+  dimensions_t d = terminal_print(x, y, s);
+	memcpy(dim, &d, sizeof(*dim));
+}
+
+void terminal_print_ext_ptr(int x, int y, int w, int h, int align, const char* s, dimensions_t* dim)
+{
+	dimensions_t d = terminal_print_ext(x, y, w, h, align, s);
+	memcpy(dim, &d, sizeof(*dim));
+}
+
+void terminal_measure_ptr(const char* s, dimensions_t* dim)
+{
+	dimensions_t d = terminal_measure(s);
+	memcpy(dim, &d, sizeof(*dim));
+}
+
+void terminal_measure_ext_ptr(int w, int h, const char* s, dimensions_t* dim)
+{
+	dimensions_t d = terminal_measure_ext(w, h, s);
+	memcpy(dim, &d, sizeof(*dim));
+}
+
+#endif
diff --git a/omni/Main.hs b/omni/Main.hs
new file mode 100644
--- /dev/null
+++ b/omni/Main.hs
@@ -0,0 +1,82 @@
+module Main
+  ( main
+  ) where
+
+import BearLibTerminal
+import Control.Monad
+import Control.Exception
+import Data.Text ( Text )
+import qualified Data.Text as T
+import Omni.BasicOutput
+import qualified Data.Map as M
+import Omni.DefaultFont (defaultFont)
+import Omni.Tilesets
+import Omni.ManualCellsize
+
+main :: IO ()
+main = do
+  bracket_
+    (terminalOpen_ >> resetTerminal)
+    terminalClose
+    runLoop
+
+runLoop :: IO ()
+runLoop = do
+  terminalClear
+  printEntries
+  void $ terminalPrint 2 23 "[color=orange]ESC.[/color] Exit"
+  void $ terminalPrintExt 77 22 0 0 (Just AlignRight) "library version 0.1.0.0"
+  void $ terminalPrintExt 77 23 0 0 (Just AlignRight) "BearLibTerminal (Hello from Haskell!)"
+  terminalRefresh
+  c <- terminalRead
+  case c of
+    TkEscape ->return ()
+    TkClose -> return ()
+    x -> maybe runLoop (>> runLoop) (M.lookup x entryMap )
+
+entryPresses :: [Keycode]
+entryPresses = [ Tk1 .. Tk9 ] <> [TkA .. TkK]
+
+entryMap :: M.Map Keycode (IO ())
+entryMap = M.fromList $ zip entryPresses (map snd entries)
+
+entries :: [(Text, IO ())]
+entries =
+  [ ("Basic output", basicOutput)
+  , ("Default font", defaultFont)
+  , ("Tilesets", tilesets)
+  , ("Sprites", return ()) -- TODO
+  , ("Manual cellsize", manualCellsize)
+  , ("Auto-generated tileset", return ())  -- TODO
+  , ("Multiple fonts", return ())  -- TODO
+  , ("Text alignment", return ())  -- TODO
+  , ("Formatted log", return ()) -- TODO
+  , ("Layers", return ()) -- TODO
+  , ("Extended 1: basics", return ()) -- TODO
+  , ("Extended 2: smooth scroll", return ()) -- TODO
+  , ("Dynamic sprites", return ()) -- TODO
+  , ("Speed", return ()) -- TODO
+  , ("Input 1: keyboard", return ()) -- TODO
+  , ("Input 2: mouse", return ()) -- TODO
+  , ("Input 3: text input", return ()) -- TODO
+  , ("Input 4: filtering", return ()) -- TODO
+  , ("Window resizing", return ()) -- TODO
+  , ("Examining cell contents", return ()) -- TODO
+  ]
+
+printEntry :: (Int, (Text, IO ())) -> IO ()
+printEntry (i, (n, _)) = do
+  let shortcut = toEnum $ if i < 9 then fromEnum '1' + i else fromEnum 'a' + (i-9)
+  let col = if i `elem` ([0..2] <> [4]) then "white" else "gray"
+  void $ terminalPrint 2 (1+i) (mconcat ["[color=orange]", T.singleton shortcut, ".[/color][color="<>col<>"]", n])
+
+printEntries :: IO ()
+printEntries = mapM_ printEntry (zip [0..] entries)
+
+
+resetTerminal :: IO ()
+resetTerminal = do
+  -- TODO: I moved all the actual helper stuff to roguefunctor..
+  -- todo: font:default, input filter to keyboard
+  --void $ terminalSetText "" defaultWindowOptions { title = Just "Omni: menu" }
+  terminalColorName "white"
diff --git a/omni/Omni/BasicOutput.hs b/omni/Omni/BasicOutput.hs
new file mode 100644
--- /dev/null
+++ b/omni/Omni/BasicOutput.hs
@@ -0,0 +1,49 @@
+module Omni.BasicOutput where
+import BearLibTerminal.Terminal.Set
+import BearLibTerminal
+import Control.Monad (forM_)
+import Data.Text as T hiding (zip)
+
+orangeText :: Text -> Text
+orangeText = textColor "orange"
+basicOutput :: IO ()
+basicOutput = do
+  terminalSetTitle "Omni: basic output"
+  terminalClear
+  terminalColorName "white"
+
+  n <- width <$> terminalPrint 2 1 (textColor "orange" "1. " <> "Wide color range: ")
+  let longWord :: String
+      longWord = "antidisestablishmentarianism."
+
+  forM_ (zip @Int [0..] longWord) $ \(i, c) -> do
+    let factor :: Double
+        factor = fromIntegral i / fromIntegral n
+        red = round @_ @Int $ (1.0 - factor) * 255
+        green = round $ factor * 255
+    terminalColorUInt (colorFromRGB red green 0)
+    terminalPut (2+n+i) 1 c
+
+  terminalColorName "white"
+  terminalPrint_ 2 3 (mconcat [orangeText "2. ", "Backgrounds: ", textColor "black" (textBkColor "gray" "grey" <> " " <> textBkColor "red" "red")])
+
+  terminalPrint_ 2 5 (orangeText "3. " <> "Unicode support: Кириллица Ελληνικά α=β²±2°")
+
+  terminalPrint_ 2 7 (orangeText "4. " <> "Tile composition: a + [color=red]/[/color] = a[+][color=red]/[/color], a vs. a[+][color=red]¨[/color]")
+
+  terminalPrint_ 2 9 "[color=orange]5. [/color]Box drawing symbols:"
+  terminalPrint_ 5 11 $ T.unlines
+    [ "   ┌────────┐  "
+    , "   │!......s└─┐"
+    , "┌──┘........s.│"
+    , "│............>│"
+    , "│...........┌─┘"
+    , "│<.@..┌─────┘  "
+    , "└─────┘        "
+    ]
+  terminalRefresh
+  c <- terminalRead
+  case c of
+    TkEscape -> return ()
+    TkClose -> return ()
+    _ -> basicOutput
diff --git a/omni/Omni/DefaultFont.hs b/omni/Omni/DefaultFont.hs
new file mode 100644
--- /dev/null
+++ b/omni/Omni/DefaultFont.hs
@@ -0,0 +1,125 @@
+module Omni.DefaultFont where
+import BearLibTerminal
+import BearLibTerminal.Terminal.Print
+import Data.Text hiding (zipWith, length, elem, map)
+import Control.Monad
+import qualified Data.Vector as V
+import Formatting
+import Control.Monad.State
+import BearLibTerminal.Keycodes
+
+data Range = Range
+  { name :: Text
+  , rangeStart :: Int
+  , rangeEnd :: Int
+  , codes :: [Int]
+  , rangeId :: Int
+  }
+
+ranges :: V.Vector Range
+ranges = V.fromList $ zipWith (\i x -> x i) [0..]
+  [ Range "C0 Controls and Basic Latin" 0x0020 0x007F [0x0020 .. 0x007F]
+  , Range "C1 Controls and Latin-1 Supplement" 0x0080 0x00FF [0x00A0 .. 0x00FF]
+  , Range "Latin Extended-A" 0x0100 0x017F [0x0100 .. 0x017F]
+  , Range "Latin Extended-B" 0x0180 0x024F (0x0192 : [0x01FA .. 0x01FF])
+  , Range "Spacing Modifier Letters" 0x02B0 0x02FF ([0x02C6, 0x02C7, 0x02C9] <> [0x02D8 .. 0x02DD])
+  , Range "Greek and Coptic" 0x0370 0x03FF $ mconcat [ 0x037E : [0x0384 .. 0x038A], 0x038C : [0x038E .. 0x03A1], [0x03A3 .. 0x03CE]]
+
+  , Range "Cyrillic" 0x0400 0x04FF $ [0x0400 .. 0x045F] <> [0x0490 .. 0x0491]
+  , Range "Latin Extended Additional" 0x1E00 0x1EFF $ [0x1E80 .. 0x1E85] <> [0x1EF2 .. 0x1EF3]
+  , Range "General Punctuation" 0x2000 0x206F $ mconcat $
+        [ [0x2013 .. 0x2015]
+        , [0x2017 .. 0x201E]
+        , [0x2020 .. 0x2022]
+        , [0x2026, 0x2030]
+        , [0x2032 .. 0x2033]
+        , [0x2039 .. 0x203A]
+        , [0x203C, 0x203E, 0x2044]
+        ]
+  , Range "Superscripts and Subscript" 0x2070 0x209F [0x207F]
+
+  , Range "Currency Symbols" 0x20A0 0x20CF $ [0x20A3 .. 0x20A4] <> [0x20A7, 0x20AC]
+
+  , Range "Letterlike Symbols" 0x2100 0x214F [0x2105, 0x2113, 0x2116, 0x2122, 0x2126, 0x212E]
+
+  , Range "Number Forms" 0x2150 0x218F [0x215B, 0x215E]
+
+  , Range "Arrows" 0x2190 0x21FF $ [0x2190 .. 0x2195] <> [0x21A8]
+
+  , Range "Mathematical Operators" 0x2200 0x22FF $ mconcat
+    [ [0x2202, 0x2206, 0x220F]
+    , [0x2211 .. 0x2212]
+    , [0x2215]
+    , [0x2219 .. 0x221A]
+    , [0x221E .. 0x221F]
+    , [0x2229, 0x222B, 0x2248]
+    , [0x2260 .. 0x2261]
+    , [0x2264 .. 0x2265]
+    ]
+
+  , Range "Miscellaneous Technical" 0x2300 0x23FF $ [0x2302, 0x2310] <> [0x2320 .. 0x2321]
+
+  , Range "Box Drawing" 0x2500 0x257F [0x2500 .. 0x257F]
+
+  , Range "Block Elements" 0x2580 0x259F [0x2580 .. 0x259F]
+
+  , Range "Geometric Shapes" 0x25A0 0x25FF $ mconcat
+    [ [0x25A0 .. 0x25A1]
+    , [0x25AA .. 0x25AC]
+    , [0x25B2, 0x25BA, 0x25BC, 0x25C4]
+    , [0x25CA .. 0x25CB]
+    , [0x25CF]
+    , [0x25D8 .. 0x25D9]
+    , [0x25E6]
+    ]
+
+  , Range "Miscellaneous Symbols" 0x2600 0x26FF $ mconcat
+    [ [0x263A .. 0x263C]
+    , [0x2640, 0x2642, 0x2660, 0x2663]
+    , [0x2665 .. 0x2666]
+    , [0x266A .. 0x266B]
+    ]
+
+  , Range "Private Use Area" 0xF000 0xF00F [0xF001 .. 0xF002]
+
+  , Range "Alphabet presentation form" 0xFB00 0xFB0F [0xFB01 .. 0xFB02]
+  ]
+
+hOffset :: Int
+hOffset = 40
+
+defaultFont :: IO ()
+defaultFont = do
+  terminalSet_ "window: size=80x25, cellsize=auto, title='Omni: WGL4'; font=default"
+  loop 0
+
+loop :: Int -> IO ()
+loop currentSelection = do
+  terminalClear
+  terminalPrint_ 2 1 "[color=white]Select unicode character range:"
+  flip V.mapM_ ranges $ \range -> do
+    terminalColorName (if rangeId range == currentSelection then "orange" else "light gray")
+    terminalPrint_ 1 (2 + rangeId range) (sformat (stext % stext) (if rangeId range == currentSelection then "[\x203A]" else " ") (name range))
+  let currRange = ranges V.! currentSelection
+  forM_ [0..15] $ \j -> terminalPrint (hOffset+ 6 + j*2) 1 (sformat ("[color=orange]" % hex) j)
+
+  flip evalStateT 0 $ forM_ [rangeStart currRange .. rangeEnd currRange] $ \code -> do
+    y <- get
+    when (code `mod` 16 == 0) $ terminalPrint_ hOffset (2+y) (sformat ("[color=orange]" % hexPrefix 4 % ":") code)
+    let included = code `elem` codes currRange
+    terminalColorName (if included then "white" else "dark gray")
+    terminalPutInt (hOffset + 6 + (code `mod` 16) *2) (2+y) code
+    when ((code+1) `mod` 16 == 0) $ modify (+ 1)
+
+  terminalColorName "white"
+  terminalPrint_ hOffset 20 "[color=orange]TIP:[/color] Use ↑/↓ keys to select range"
+  terminalPrint_ hOffset 22 "[color=orange]NOTE:[/color] Character code points printed in\ngray are not included in the WGL4 set."
+
+  terminalRefresh
+  c <- terminalRead
+  case c of
+    x
+      | x `elem` [TkEscape, TkClose] -> return ()
+    TkUp -> when (currentSelection > 0) $ loop (currentSelection - 1)
+    TkDown -> when (currentSelection < length ranges - 1) $ loop (currentSelection + 1)
+    _ -> loop currentSelection
diff --git a/omni/Omni/ManualCellsize.hs b/omni/Omni/ManualCellsize.hs
new file mode 100644
--- /dev/null
+++ b/omni/Omni/ManualCellsize.hs
@@ -0,0 +1,70 @@
+module Omni.ManualCellsize where
+
+import BearLibTerminal
+import Control.Monad (when)
+import Control.Monad.State
+import Data.Functor (void)
+import Formatting
+import qualified Data.Vector as V
+
+data GuiState = GuiState
+  { hinting :: Int
+  , size :: Int
+  , cellWidth :: Int
+  , cellHeight :: Int
+  }
+
+initialGui :: GuiState
+initialGui = GuiState 0 12 8 16
+
+manualCellsize :: IO ()
+manualCellsize = do
+  terminalSet_ "window.title='Omni: manual cellsize'"
+
+  let font = "Media/VeraMono.ttf"
+      fontHintings = V.fromList ["normal", "autohint", "none"]
+      setupCellsize = do
+        s <- get
+        terminalSet_ (sformat ("window: cellsize=" % int % "x" % int) (cellWidth s) (cellHeight s))
+      setupFont = do
+        s <- get
+        terminalSet_ $ sformat ("font: " % stext % ", size=" % int % ", hinting=" % stext) font (size s) (fontHintings V.! hinting s)
+      runLoop = do
+        s <- get
+        terminalClear
+        terminalColorName "white"
+        terminalPrint_ 2 1 "Hello, world!"
+        void $ terminalPrintString 2 3 $ "[color=orange]Font size:[/color] " <> show (size s)
+        terminalPrint_ 2 4 $ "[color=orange]Font hinting:[/color] " <> (fontHintings V.! hinting s)
+        terminalPrint_ 2 5 $ sformat ("[color=orange]Cell size:[/color] " % int % "x" % int) (cellWidth s) (cellHeight s)
+        terminalPrint_ 2 7 "[color=orange]TIP:[/color] Use arrow keys to change cell size"
+        terminalPrint_ 2 8 "[color=orange]TIP:[/color] Use Shift+Up/Down arrow keys to change font size"
+        terminalPrint_ 2 9 "[color=orange]TIP:[/color] Use TAB to switch font hinting mode"
+
+        terminalRefresh
+        k <- terminalRead
+        case k of
+          _
+            | k == TkClose || k == TkEscape -> return ()
+          TkLeft
+            | cellWidth s > 4 -> ifShift (return ()) (modify (\s' -> s' { cellWidth = cellWidth s - 1}) >> setupCellsize)
+          TkRight
+            | cellWidth s < 24 -> ifShift (return ()) (modify (\s' -> s' { cellWidth = cellWidth s + 1}) >> setupCellsize)
+          TkDown -> ifShift
+                      (when (size s > 4) $ modify (\s' -> s' { size = size s - 1}) >> setupFont )
+                      (when (cellHeight s < 24) $ modify (\s' -> s' { cellHeight = cellHeight s + 1}) >> setupCellsize)
+          TkUp -> ifShift
+                      (when (size s < 64) $ modify (\s' -> s' { size = size s + 1}) >> setupFont  )
+                      (when (cellHeight s > 4) $ modify (\s' -> s' { cellHeight = cellHeight s - 1}) >> setupCellsize)
+          TkTab -> modify (\s' -> s' { hinting = (hinting s + 1) `mod` V.length fontHintings}) >> setupFont >> runLoop
+          _ -> runLoop
+      ifShift yes no = do
+        shiftPressed <- terminalKeyState TkShift
+        if shiftPressed then yes else no
+        runLoop
+
+  flip evalStateT initialGui $ do
+    setupCellsize
+    setupFont
+    runLoop
+  terminalSet_ "font: default; window.cellsize=auto"
diff --git a/omni/Omni/Tilesets.hs b/omni/Omni/Tilesets.hs
new file mode 100644
--- /dev/null
+++ b/omni/Omni/Tilesets.hs
@@ -0,0 +1,59 @@
+module Omni.Tilesets where
+
+import BearLibTerminal
+import Control.Monad
+
+tilesets :: IO ()
+tilesets = do
+  terminalComposition CompositionOn
+  terminalSet_ "window.title='Omni: tilesets"
+  terminalSet_ "U+E100: Media/Runic.png, size=8x16"
+  terminalSet_ "U+E200: Media/Tiles.png, size=32x32, align=top-left"
+  terminalSet_ "U+E300: Media/fontawesome-webfont.ttf, size=24x24, spacing=3x2, codepage=Media/fontawesome-codepage.txt"
+  terminalSet_ "zodiac font: Media/Zodiac-S.ttf, size=24x36, spacing=3x3, codepage=437"
+
+  terminalClear
+  terminalColorName "white"
+
+  terminalPrint_ 2 1 "[color=orange]1.[/color] Of course, there is default font tileset."
+
+  terminalPrint_ 2 3 "[color=orange]2.[/color] You can load some arbitrary tiles and use them as glyphs:"
+  terminalPrint_ (2+3) 4 $ "Fire rune ([color=red][U+E102][/color]), " <> "water rune ([color=lighter blue][U+E103][/color]), " <> "earth rune ([color=darker green][U+E104][/color])"
+
+  terminalPrint_ 2 6 "[color=orange]3.[/color] Tiles are not required to be of the same size as font symbols:"
+  terminalPutInt (2+3+0) 7 (0xE200+7)
+  terminalPutInt (2+3+5) 7 (0xE200+8)
+  terminalPutInt (2+3+10) 7 (0xE200+9)
+
+  terminalPrint_ 2 10 "[color=orange]4.[/color] Like font characters, tiles may be freely colored and combined:"
+  terminalPutInt (2+3+0) 11 (0xE200+8)
+  terminalColorName "lighter orange"
+  terminalPutInt (2+3+5) 11 (0xE200+8)
+  terminalColorName "orange"
+  terminalPutInt (2+3+10) 11 (0xE200+8)
+  terminalColorName "dark orange"
+  terminalPutInt (2+3+15) 11 (0xE200+8)
+
+  terminalColorName "white"
+  let order = zip [0..] [11, 10, 14, 12, 13]
+  forM_ order $ \(i, o) -> do
+    terminalPutInt (2+3+25+i*4) 11 (0xE200+o)
+    terminalPutInt (2+3+25+(length order +1)*4) 11 (0xE200+o)
+  terminalPutInt (2+3+25+length order*4) 11 (0xE200+15)
+
+  terminalPrint_ 2 14 "[color=orange]5.[/color] And tiles can even come from TrueType fonts like this:"
+  forM_ [0..5] $ \i -> terminalPutInt (2+3+i*5) 15 (0xE300+i)
+
+  terminalPrint_ (2+3) 18 "...or like this:\n[font=zodiac]D F G S C"
+
+  terminalRefresh
+
+  c <- terminalRead
+  case c of
+    TkClose -> finish
+    TkEscape -> finish
+    _ -> tilesets
+  where
+    finish = do
+      terminalSet_ "U+E100: none; U+E200: none; U+E300: none; zodiac font: none"
+      terminalComposition CompositionOff
diff --git a/src/BearLibTerminal.hs b/src/BearLibTerminal.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal.hs
@@ -0,0 +1,407 @@
+{-|
+Module      : BearLibTerminal.Keycodes
+Description : String functions taking `CString`s.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+  A low-ish level binding to BearLibTerminal. For the most part this is 1-to-1 to the original C/C++ API
+    and the raw bindings in `BearLibTerminal.Raw` are almost identical (the only differences being that intcode
+    return types are wrapped into Booleans where relevant).
+  For functions that expect strings, 3 variants exist:
+
+    - `Text`. This one is strongly preferred because of the performance benefits of `Text` over `String`. These versions
+    are available without a suffix - e.g. `terminalSet`, `terminalPrint_`.
+    - `String`. Available with a `String` suffix - e.g. `terminalSetString`, `terminalPrintString_`.
+    - `CString`. These aren't exported from this module but are available in `BearLibTerminal.Terminal.CString` if you
+      do need them. They also do not have `m ()` variants.
+
+  As this library is a low-level wrapper, the original BearLibTerminal documentation is an excellent reference!
+  http://foo.wyrd.name/en:bearlibterminal:reference
+-}
+
+module BearLibTerminal
+  (
+  -- * Initialisation and configuration
+  -- ** Open
+  -- | For initialising a BearLibTerminal instance and opening a window.
+    terminalOpen, terminalOpen_
+  -- ** Close
+  -- | For deinitialising a BearLibTerminal instance and closing the window.
+  , terminalClose
+  -- ** Set
+
+  -- | For setting configuration options via configuration strings.
+  , terminalSet, terminalSetString
+  , terminalSet_, terminalSetString_
+  -- * Output state
+
+  -- ** Color
+  , colorFromARGB
+  , colorFromRGB
+  -- *** Foreground
+  , terminalColorUInt
+  , terminalColorName
+  , terminalColorNameString
+  -- *** Background
+  , terminalBkColorUInt
+  , terminalBkColorName
+  , terminalBkColorNameString
+
+  -- ** Composition
+  , TerminalCompositionMode(..)
+  , terminalComposition
+  -- ** Layer
+  , terminalLayer
+
+  -- * Output
+  -- ** Print
+
+  -- | These wrap @terminal_print@ and @terminal_print_ext@ in various string flavours.
+  -- These *do* support the inline formatting options that BearLibTerminal itself supports, such as using @[color=red]@,
+  -- pixel offsets, different fonts, and more. For more information check the BearLibTerminal documentation for
+  -- [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+  , PrintAlignment(..)
+  , Dimensions(..)
+  , textColor
+  , textBkColor
+  , terminalPrint
+  , terminalPrint_
+  , terminalPrintExt
+  , terminalPrintExt_
+
+  , terminalPrintString
+  , terminalPrintString_
+  , terminalPrintExtString
+  , terminalPrintExtString_
+
+  -- ** Put
+  , terminalPut
+  , terminalPutInt
+  , terminalPutExt
+
+  -- ** Measure
+  , terminalMeasureText
+  , terminalMeasureString
+  , terminalMeasureExtText
+  , terminalMeasureExtString
+
+  -- ** Refresh
+  , terminalRefresh
+  -- ** Clear
+  , terminalClear
+  , terminalClearArea
+  , terminalCrop
+  -- * Input
+  -- ** Events
+  , module BearLibTerminal.Keycodes
+  , Keycode(..)
+
+  , terminalPeek
+  , terminalPeekCode
+  , terminalHasInput
+  , terminalRead
+  , terminalReadCode
+  -- ** Reading strings
+  , terminalReadString
+  -- ** State
+  , terminalState
+  , terminalKeyState
+  -- ** Picking
+  , terminalPick
+  , terminalPickColor
+  -- * Utility
+  , terminalDelay
+  ) where
+
+
+
+import BearLibTerminal.Keycodes
+import BearLibTerminal.Raw
+import BearLibTerminal.Terminal.CString
+import BearLibTerminal.Terminal.Color
+import BearLibTerminal.Terminal.Print
+import BearLibTerminal.Terminal.Set
+import BearLibTerminal.Terminal.String
+import Control.Exception
+import Control.Monad.IO.Class
+import Data.Char (ord)
+import Data.Coerce (coerce)
+import Data.Text (Text)
+import Foreign
+import Foreign.C (CUInt)
+import GHC.Generics
+import qualified Data.Text.Foreign as T
+maxStringReadSizeInBytes :: Int
+maxStringReadSizeInBytes = 8192
+
+-- | Create a new window with the default parameters. Does not display the window until the first call to `terminalRefresh`.
+--
+-- Wrapper around [@terminal_open@](http://foo.wyrd.name/en:bearlibterminal:reference#open).
+terminalOpen :: MonadIO m
+  => m Bool -- ^ whether the window creation was successful.
+terminalOpen = asBool <$> liftIO c_terminal_open
+
+-- | Create a new window with the default parameters. Does not display the window until the first call to `terminalRefresh`.
+-- Ignore the success return value.
+-- Wrapper around [@terminal_open@](http://foo.wyrd.name/en:bearlibterminal:reference#open).
+terminalOpen_ :: MonadIO m
+  => m ()
+terminalOpen_ = liftIO $ void c_terminal_open
+
+-- | Close the window and cleanup the BearLibTerminal instance.
+--
+-- Wrapper around [@terminal_close@](http://foo.wyrd.name/en:bearlibterminal:reference#close).
+terminalClose ::
+  MonadIO m
+  => m ()
+terminalClose = liftIO c_terminal_close
+
+-- | Draw a single character (given by its code point) onto the screen on the currently selected layer
+-- with the currently selected colors. This takes an `Int` rather than `Char` to avoid possible headaches
+-- with mismatching character codes over string conversion. If you know you are wanting to render single
+-- *characters* (rather than *code points*), consider using `terminalPrintText` (or similar) instead.
+--
+-- Wrapper around [@terminal_put@](http://foo.wyrd.name/en:bearlibterminal:reference#put)
+terminalPutInt ::
+  MonadIO m
+  => Int -- ^ x-coordinate to print the character to.
+  -> Int -- ^ y-coordinate to print the character to.
+  -> Int -- ^ Unicode code point of the character to print.
+  -> m ()
+terminalPutInt x y c = liftIO $ c_terminal_put (fromIntegral x) (fromIntegral y) (fromIntegral c)
+
+-- | Draw a single character (given by its code point) onto the screen on the currently selected layer
+-- with the currently selected colors. This does take `Char` over `Int`, but possible problems with
+-- encodings, unicode, all that jazz - you're on your own. If you know your way around string encodings
+-- and can advise, please do!
+--
+-- Wrapper around [@terminal_put@](http://foo.wyrd.name/en:bearlibterminal:reference#put)
+terminalPut ::
+  MonadIO m
+  => Int -- ^ x-coordinate to print the character to.
+  -> Int -- ^ y-coordinate to print the character to.
+  -> Char -- ^ Unicode code point of the character to print.
+  -> m ()
+terminalPut x y c = liftIO $ c_terminal_put (fromIntegral x) (fromIntegral y) (fromIntegral $ ord c)
+
+-- | Data type to represent whether cell composition should be turned on or off.
+data TerminalCompositionMode =
+  CompositionOn -- ^ Turn cell composition on; i.e. repeated prints to the same location on the same layer will stack the tiles.
+  | CompositionOff-- ^ Turn cell composition off; i.e. repeated prints to the same location on the same layer will overwrite the existing tiles.
+  deriving stock (Eq, Ord, Show, Generic, Read, Enum, Bounded)
+
+-- | Enable or disable terminal composition.
+--
+-- Wrapper around [@terminal_composition@](http://foo.wyrd.name/en:bearlibterminal:reference#composition)
+terminalComposition ::
+  MonadIO m
+  => TerminalCompositionMode -- ^ composition mode to set.
+  -> m ()
+terminalComposition = liftIO . c_terminal_composition .
+  (\case
+    CompositionOff -> 0
+    CompositionOn -> 1
+  )
+
+-- | Set the currently selected layer to be used by following output functions (e.g. `terminalPrintText`).
+--
+-- Wrapper around [@terminal_layer@](http://foo.wyrd.name/en:bearlibterminal:reference#layer).
+terminalLayer ::
+  MonadIO m
+  => Int -- ^ layer to select (0-255 inclusive).
+  -> m ()
+terminalLayer = liftIO . c_terminal_layer . fromIntegral
+
+-- | Clear the entire screen (all layers) with the currently selected background color (`terminalBkColorUInt` and family).
+--
+-- Wrapper around [@terminal_clear@](http://foo.wyrd.name/en:bearlibterminal:reference#clear).
+terminalClear :: MonadIO m => m ()
+terminalClear = liftIO c_terminal_clear
+
+-- | Clear a rectangular area on the currently selected layer. This only sets the background color
+-- on layer 0.
+--
+-- Wrapper around [@terminal_clear_area@](http://foo.wyrd.name/en:bearlibterminal:reference#clear_area).
+terminalClearArea ::
+  MonadIO m
+  => Int -- ^ x-coordinate of the top left of the rectangular area.
+  -> Int -- ^ y-coordinate of the top left of the rectangular area.
+  -> Int -- ^ width of the rectangular area.
+  -> Int  -- ^ height of the rectangular area.
+  -> m ()
+terminalClearArea x y w h = liftIO (c_terminal_clear_area (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h))
+
+-- | Set a cropping area for the currently selected layer. Anything printed outside of this region
+-- will be ignored. Set the width or height to zero or use `terminalClear` to reset this.
+--
+-- Wrapper around [@terminal_crop@](http://foo.wyrd.name/en:bearlibterminal:reference#crop).
+terminalCrop ::
+  MonadIO m
+  => Int -- ^ x-coordinate of the top left of the rectangular area.
+  -> Int -- ^ y-coordinate of the top left of the rectangular area.
+  -> Int -- ^ width of the rectangular area.
+  -> Int  -- ^ height of the rectangular area.
+  -> m ()
+terminalCrop x y w h = liftIO (c_terminal_crop (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h))
+
+-- | Commit the scene to be rendered and redraw the window. This needs to be called once after `terminalOpen`
+-- before the window will be displayed.
+--
+-- Wrapper around [@terminal_refresh@](http://foo.wyrd.name/en:bearlibterminal:reference#crop).
+terminalRefresh :: MonadIO m => m ()
+terminalRefresh = liftIO c_terminal_refresh
+
+-- | Return the code of the tile in the specified cell on the current layer at the current index
+-- in the tile stack.
+--
+-- Wrapper around [@terminal_pick@](http://foo.wyrd.name/en:bearlibterminal:reference#pick).
+terminalPick ::
+  MonadIO m
+  => Int -- ^ x-coordinate of the tile to be queried.
+  -> Int -- ^ y-coordinate of the tile to be queried.
+  -> Int  -- ^ index of the symbol in the cell to be returned.
+  -> m (Maybe Int) -- ^ `Just codepoint` if the symbol exists and `Nothing` otherwise.
+terminalPick x y i = liftIO $ (\x' -> if x' == 0 then Nothing else Just $ fromIntegral x') <$> c_terminal_pick (fromIntegral x) (fromIntegral y) (fromIntegral i)
+
+-- | Return the foreground color of the tile in the specified cell on the current layer at the current index
+-- in the tile stack.
+--
+-- Wrapper around [@terminal_pick_color@](http://foo.wyrd.name/en:bearlibterminal:reference#pick_color).
+terminalPickColor ::
+  MonadIO m
+  => Int -- ^ x-coordinate of the tile to be queried.
+  -> Int -- ^ y-coordinate of the tile to be queried.
+  -> Int  -- ^ index of the symbol in the cell to be returned.
+  -> m Integer -- ^ Color of the foreground if the symbol exists.
+terminalPickColor x y i = liftIO $ fromIntegral <$> c_terminal_pick_color (fromIntegral x) (fromIntegral y) (fromIntegral i)
+
+-- | Return the background color of the tile in the specified cell.
+--
+-- Wrapper around [@terminal_pick_bkcolor@](http://foo.wyrd.name/en:bearlibterminal:reference#pick_bkcolor).
+terminalPickBkColor ::
+  MonadIO m
+  => Int -- ^ x-coordinate of the tile to be queried.
+  -> Int -- ^ y-coordinate of the tile to be queried.
+  -> m Integer -- ^ Color of the background of the cell.
+terminalPickBkColor x y = liftIO $ fromIntegral <$> c_terminal_pick_bkcolor (fromIntegral x) (fromIntegral y)
+
+-- | Draw a single character (given by its code point) onto the screen on the currently selected layer
+-- with the currently selected colors and with additional options.
+-- This takes an `Int` rather than `Char` to avoid possible headaches
+-- with mismatching character codes over string conversion. If you know you are wanting to render single
+-- *characters* (rather than *code points*), consider using `terminalPrintText` (or similar) instead.
+--
+-- Wrapper around [@terminal_put@](http://foo.wyrd.name/en:bearlibterminal:reference#put_ext)
+terminalPutExt ::
+  MonadIO m
+  => Int -- ^ x-coordinate to print the character to.
+  -> Int -- ^ y-coordinate to print the character to.
+  -> Int -- ^ Internal x offset relative to the normal position of a tile in the cell, in pixels.
+  -> Int -- ^ Internal y offset relative to the normal position of a tile in the cell, in pixels.
+  -> Int -- ^ Unicode code point of the character to print.
+  -> Maybe (Integer, Integer, Integer, Integer) -- ^ individual colours specified per-corner for this
+  -- tile (in order: top-left, bottom-left, bottom-right, top-right)
+  -> m ()
+terminalPutExt x y dx dy code mbColors = liftIO $ colorsToArr $ c_terminal_put_ext (fromIntegral x) (fromIntegral y) (fromIntegral dx) (fromIntegral dy) (fromIntegral code)
+  where
+    colorsToArr :: (Ptr CUInt -> IO a) -> IO a
+    colorsToArr f = case mbColors of
+      Nothing -> f nullPtr
+      Just (tl, bl, br, tr) -> withArray (map fromIntegral [tl, bl, br, tr]) f
+
+-- | Measure the size of a string *if it were to be printed to the screen*, given as a `Text`.
+-- Wrapper around [@terminal_measure@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureText ::
+  MonadIO m
+  => Text -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureText = textToCString terminalMeasureCString
+
+-- | Measure the size of a string *if it were to be printed to the screen*, autowrapped in a bounding box, given as a `Text`.
+-- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureExtText ::
+  MonadIO m
+  => Int -- ^ the width of the bounding box.
+  -> Int -- ^ the height of the bounding box.
+  -> Text  -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureExtText w h = textToCString (terminalMeasureExtCString w h)
+
+-- | This function queries the current numeric value of a library property; e.g. mouse position or clicks, or key presses/releases.
+-- When looking to query for key presses or releases, `terminalKeyState` provides a better interface.
+-- Wrapper around [@terminal_state@]. For more information, check the table in the original documentation:
+-- http://foo.wyrd.name/en:bearlibterminal:reference:input#event_and_state_constants
+terminalState :: MonadIO m => Keycode -> m Int
+terminalState = liftIO . fmap fromIntegral . c_terminal_state . fromIntegral
+
+-- | Returns true if there are currently input events in the input queue.
+  -- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#has_input)
+terminalHasInput ::
+  MonadIO m
+  => m Bool -- ^ if there are currently input events in the input queue.
+terminalHasInput = liftIO $ asBool <$> c_terminal_has_input
+
+-- | Read an event from the input queue and remove it. If there are no pending events in the input queue, block
+-- until there is. If you don't want to block until an event comes, check for input with `terminalHasInput` first.
+-- This will return a raw integer - prefer `terminalPeek` to be able to use the pattern synonyms of named keycodes.
+-- Wrapper around [@terminal_read@](http://foo.wyrd.name/en:bearlibterminal:reference#read).
+-- For more information on the input queue, see the original documentation: http://foo.wyrd.name/en:bearlibterminal:reference:input#input_queue
+terminalReadCode :: MonadIO m => m Int
+terminalReadCode = liftIO $ fromIntegral <$> c_terminal_read
+
+-- | Read an event from the input queue but **do not** remove it. If there are no pending events in the input queue, this will return `Nothing`.
+-- This is non-blocking. This will return a raw integer - prefer `terminalPeek` to be able to use the pattern synonyms of named keycodes.
+-- Wrapper around [@terminal_peek@](http://foo.wyrd.name/en:bearlibterminal:reference#peek).
+-- For more information on the input queue, see the original documentation: http://foo.wyrd.name/en:bearlibterminal:reference:input#input_queue
+terminalPeekCode :: MonadIO m => m Int
+terminalPeekCode = liftIO $ fromIntegral <$> c_terminal_peek
+
+-- | Read an inputted string and print a text input prompt at the given location.
+-- This will block until the string is submitted (with Enter) or cancelled out (with Esc).
+-- The string can be up to `maxStringReadSizeInBytes` bytes long.
+-- Retyrns nothing if there was an error, or if the user cancelled out.
+  -- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#read_str)
+terminalReadString ::
+  MonadIO m
+  => Int -- ^ x-coord of where the user input is displayed.
+  -> Int -- ^ y-coord of where the user input is displayed.
+  -> Int -- ^ max string length allowed (for the user, not for technical reasons).
+  -> m (Maybe Text)
+terminalReadString x y m = liftIO $ bracket
+  (callocBytes maxStringReadSizeInBytes)
+  free
+  (\p -> do
+    res <- c_read_str (fromIntegral x) (fromIntegral y) p (fromIntegral m)
+    if res == -1 || res == 0 then return Nothing else
+        (do
+          t <- T.peekCStringLen (p, fromIntegral res)
+          return (Just t)) `catch` (\(SomeException _) -> return Nothing)
+  )
+-- | Pause execution for a number of milliseconds.
+-- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#delay)
+terminalDelay ::
+  MonadIO m
+  => Int -- ^ amount of time to suspend program execution for, in milliseconds.
+  -> m ()
+terminalDelay = liftIO . c_terminal_delay . fromIntegral
+
+-- | Read an event from the input queue and remove it. If there are no pending events in the input queue, block
+-- until there is. If you don't want to block until an event comes, check for input with `terminalHasInput` first.
+-- Wrapper around [@terminal_read@](http://foo.wyrd.name/en:bearlibterminal:reference#read).
+-- For more information on the input queue, see the original documentation: http://foo.wyrd.name/en:bearlibterminal:reference:input#input_queue
+terminalRead :: MonadIO m => m Keycode
+terminalRead = Keycode <$> terminalReadCode
+
+-- | Read an event from the input queue but **do not** remove it. If there are no pending events in the input queue, this will return `Nothing`.
+-- This is non-blocking.
+-- Wrapper around [@terminal_peek@](http://foo.wyrd.name/en:bearlibterminal:reference#peek).
+-- For more information on the input queue, see the original documentation: http://foo.wyrd.name/en:bearlibterminal:reference:input#input_queue
+terminalPeek :: MonadIO m => m (Maybe Keycode)
+terminalPeek = (\x -> if x == 0 then Nothing else Just (Keycode x)) <$> terminalPeekCode
+
+-- | Get the current status of a key (e.g. pressed or released). This is a more specific version of `terminalState` for when the `Keycode` is
+-- a keyboard key. The behaviour will be strange if you use this for e.g. querying the mouse position. Use `terminalState` for that.
+-- Wrapper around [@terminal_state@](http://foo.wyrd.name/en:bearlibterminal:reference:input#state).
+terminalKeyState :: MonadIO m => Keycode -> m Bool
+terminalKeyState = fmap (== 1) . terminalState . coerce
diff --git a/src/BearLibTerminal/Keycodes.hs b/src/BearLibTerminal/Keycodes.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Keycodes.hs
@@ -0,0 +1,406 @@
+{-|
+Module      : BearLibTerminal.Keycodes
+Description : Keycodes and events.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+All events and keycodes that can be received by polling or queried for in the window state.
+-}
+
+{-# LANGUAGE PatternSynonyms #-}
+
+module BearLibTerminal.Keycodes where
+
+import GHC.Generics
+
+-- | Events and states are defined in the original library as integer constants, which are all called keycodes. This is a very liberal
+-- use of a "virtual keycode", because many of them are not keys.
+-- These are defined as bidirectional pattern synonyms so that they still can be treated as bitwise operable (e.g. checking for key release
+-- events).
+-- These are used for:
+-- - `BearLibTerminal.terminalRead` - polling for events (key presses, key releases, window events, mouse movements)
+-- - `BearLibTerminal.terminalState` and `BearLibTerminal.terminalKeyState` - for querying current window state (e.g. mouse position) and
+-- the status of whether keys are currently pressed respectively.
+-- For full reference of the input queue, see http://foo.wyrd.name/en:bearlibterminal:reference:input.
+newtype Keycode = Keycode Int
+  deriving newtype (Eq, Ord, Show, Enum, Bounded, Num, Real, Integral)
+  deriving stock (Generic)
+
+pattern TkA :: Keycode
+pattern TkA= Keycode 0x04
+
+pattern TkB :: Keycode
+pattern TkB= Keycode 0x05
+
+pattern TkC :: Keycode
+pattern TkC= Keycode 0x06
+
+pattern TkD :: Keycode
+pattern TkD= Keycode 0x07
+
+pattern TkE :: Keycode
+pattern TkE= Keycode 0x08
+
+pattern TkF :: Keycode
+pattern TkF= Keycode 0x09
+
+pattern TkG :: Keycode
+pattern TkG= Keycode 0x0A
+
+pattern TkH :: Keycode
+pattern TkH= Keycode 0x0B
+
+pattern TkI :: Keycode
+pattern TkI= Keycode 0x0C
+
+pattern TkJ :: Keycode
+pattern TkJ= Keycode 0x0D
+
+pattern TkK :: Keycode
+pattern TkK= Keycode 0x0E
+
+pattern TkL :: Keycode
+pattern TkL= Keycode 0x0F
+
+pattern TkM :: Keycode
+pattern TkM= Keycode 0x10
+
+pattern TkN :: Keycode
+pattern TkN= Keycode 0x11
+
+pattern TkO :: Keycode
+pattern TkO= Keycode 0x12
+
+pattern TkP :: Keycode
+pattern TkP= Keycode 0x13
+
+pattern TkQ :: Keycode
+pattern TkQ= Keycode 0x14
+
+pattern TkR :: Keycode
+pattern TkR= Keycode 0x15
+
+pattern TkS :: Keycode
+pattern TkS= Keycode 0x16
+
+pattern TkT :: Keycode
+pattern TkT= Keycode 0x17
+
+pattern TkU :: Keycode
+pattern TkU= Keycode 0x18
+
+pattern TkV :: Keycode
+pattern TkV= Keycode 0x19
+
+pattern TkW :: Keycode
+pattern TkW= Keycode 0x1A
+
+pattern TkX :: Keycode
+pattern TkX= Keycode 0x1B
+
+pattern TkY :: Keycode
+pattern TkY= Keycode 0x1C
+
+pattern TkZ :: Keycode
+pattern TkZ= Keycode 0x1D
+
+pattern Tk1 :: Keycode
+pattern Tk1= Keycode 0x1E
+
+pattern Tk2 :: Keycode
+pattern Tk2= Keycode 0x1F
+
+pattern Tk3 :: Keycode
+pattern Tk3= Keycode 0x20
+
+pattern Tk4 :: Keycode
+pattern Tk4= Keycode 0x21
+
+pattern Tk5 :: Keycode
+pattern Tk5= Keycode 0x22
+
+pattern Tk6 :: Keycode
+pattern Tk6= Keycode 0x23
+
+pattern Tk7 :: Keycode
+pattern Tk7= Keycode 0x24
+
+pattern Tk8 :: Keycode
+pattern Tk8= Keycode 0x25
+
+pattern Tk9 :: Keycode
+pattern Tk9= Keycode 0x26
+
+pattern Tk0 :: Keycode
+pattern Tk0= Keycode 0x27
+
+pattern TkReturn :: Keycode
+pattern TkReturn= Keycode 0x28
+
+pattern TkEnter :: Keycode
+pattern TkEnter= Keycode 0x28
+
+pattern TkEscape :: Keycode
+pattern TkEscape= Keycode 0x29
+
+pattern TkBackspace :: Keycode
+pattern TkBackspace= Keycode 0x2A
+
+pattern TkTab :: Keycode
+pattern TkTab= Keycode 0x2B
+
+pattern TkSpace :: Keycode
+pattern TkSpace= Keycode 0x2C
+
+pattern TkMinus :: Keycode
+pattern TkMinus= Keycode 0x2D
+
+pattern TkEquals :: Keycode
+pattern TkEquals= Keycode 0x2E
+
+pattern TkLbracket :: Keycode
+pattern TkLbracket= Keycode 0x2F
+
+pattern TkRbracket :: Keycode
+pattern TkRbracket= Keycode 0x30
+
+pattern TkBackslash :: Keycode
+pattern TkBackslash= Keycode 0x31
+
+pattern TkSemicolon :: Keycode
+pattern TkSemicolon= Keycode 0x33
+
+pattern TkApostrophe :: Keycode
+pattern TkApostrophe= Keycode 0x34
+
+pattern TkGrave :: Keycode
+pattern TkGrave= Keycode 0x35
+
+pattern TkComma :: Keycode
+pattern TkComma= Keycode 0x36
+
+pattern TkPeriod :: Keycode
+pattern TkPeriod= Keycode 0x37
+
+pattern TkSlash :: Keycode
+pattern TkSlash= Keycode 0x38
+
+pattern TkF1 :: Keycode
+pattern TkF1= Keycode 0x3A
+
+pattern TkF2 :: Keycode
+pattern TkF2= Keycode 0x3B
+
+pattern TkF3 :: Keycode
+pattern TkF3= Keycode 0x3C
+
+pattern TkF4 :: Keycode
+pattern TkF4= Keycode 0x3D
+
+pattern TkF5 :: Keycode
+pattern TkF5= Keycode 0x3E
+
+pattern TkF6 :: Keycode
+pattern TkF6= Keycode 0x3F
+
+pattern TkF7 :: Keycode
+pattern TkF7= Keycode 0x40
+
+pattern TkF8 :: Keycode
+pattern TkF8= Keycode 0x41
+
+pattern TkF9 :: Keycode
+pattern TkF9= Keycode 0x42
+
+pattern TkF10 :: Keycode
+pattern TkF10= Keycode 0x43
+
+pattern TkF11 :: Keycode
+pattern TkF11= Keycode 0x44
+
+pattern TkF12 :: Keycode
+pattern TkF12= Keycode 0x45
+
+pattern TkPause :: Keycode
+pattern TkPause= Keycode 0x48
+
+pattern TkInsert :: Keycode
+pattern TkInsert= Keycode 0x49
+
+pattern TkHome :: Keycode
+pattern TkHome= Keycode 0x4A
+
+pattern TkPageup :: Keycode
+pattern TkPageup= Keycode 0x4B
+
+pattern TkDelete :: Keycode
+pattern TkDelete= Keycode 0x4C
+
+pattern TkEnd :: Keycode
+pattern TkEnd= Keycode 0x4D
+
+pattern TkPagedown :: Keycode
+pattern TkPagedown= Keycode 0x4E
+
+pattern TkRight :: Keycode
+pattern TkRight= Keycode 0x4F
+
+pattern TkLeft :: Keycode
+pattern TkLeft= Keycode 0x50
+
+pattern TkDown :: Keycode
+pattern TkDown= Keycode 0x51
+
+pattern TkUp :: Keycode
+pattern TkUp= Keycode 0x52
+
+pattern TkKpDivide :: Keycode
+pattern TkKpDivide= Keycode 0x54
+
+pattern TkKpMultiply :: Keycode
+pattern TkKpMultiply= Keycode 0x55
+
+pattern TkKpMinus :: Keycode
+pattern TkKpMinus= Keycode 0x56
+
+pattern TkKpPlus :: Keycode
+pattern TkKpPlus= Keycode 0x57
+
+pattern TkKpEnter :: Keycode
+pattern TkKpEnter= Keycode 0x58
+
+pattern TkKp1 :: Keycode
+pattern TkKp1= Keycode 0x59
+
+pattern TkKp2 :: Keycode
+pattern TkKp2= Keycode 0x5A
+
+pattern TkKp3 :: Keycode
+pattern TkKp3= Keycode 0x5B
+
+pattern TkKp4 :: Keycode
+pattern TkKp4= Keycode 0x5C
+
+pattern TkKp5 :: Keycode
+pattern TkKp5= Keycode 0x5D
+
+pattern TkKp6 :: Keycode
+pattern TkKp6= Keycode 0x5E
+
+pattern TkKp7 :: Keycode
+pattern TkKp7= Keycode 0x5F
+
+pattern TkKp8 :: Keycode
+pattern TkKp8= Keycode 0x60
+
+pattern TkKp9 :: Keycode
+pattern TkKp9= Keycode 0x61
+
+pattern TkKp0 :: Keycode
+pattern TkKp0= Keycode 0x62
+
+pattern TkKpPeriod :: Keycode
+pattern TkKpPeriod= Keycode 0x63
+
+pattern TkShift :: Keycode
+pattern TkShift= Keycode 0x70
+
+pattern TkControl :: Keycode
+pattern TkControl= Keycode 0x71
+
+pattern TkAlt :: Keycode
+pattern TkAlt= Keycode 0x72
+
+pattern TkMouseLeft :: Keycode
+pattern TkMouseLeft= Keycode 0x80
+
+pattern TkMouseRight :: Keycode
+pattern TkMouseRight= Keycode 0x81
+
+pattern TkMouseMiddle :: Keycode
+pattern TkMouseMiddle= Keycode 0x82
+
+pattern TkMouseX1 :: Keycode
+pattern TkMouseX1= Keycode 0x83
+
+pattern TkMouseX2 :: Keycode
+pattern TkMouseX2= Keycode 0x84
+
+pattern TkMouseMove :: Keycode
+pattern TkMouseMove= Keycode 0x85
+
+pattern TkMouseScroll :: Keycode
+pattern TkMouseScroll= Keycode 0x86
+
+pattern TkMouseX :: Keycode
+pattern TkMouseX= Keycode 0x87
+
+pattern TkMouseY :: Keycode
+pattern TkMouseY= Keycode 0x88
+
+pattern TkMousePixelX :: Keycode
+pattern TkMousePixelX= Keycode 0x89
+
+pattern TkMousePixelY :: Keycode
+pattern TkMousePixelY= Keycode 0x8A
+
+pattern TkMouseWheel :: Keycode
+pattern TkMouseWheel= Keycode 0x8B
+
+pattern TkMouseClicks :: Keycode
+pattern TkMouseClicks= Keycode 0x8C
+
+{-
+ * If key was released instead of pressed, it's code will be OR'ed with TkKEYRELEASED:
+ * a) pressed 'A' :: 0x04
+ * b) released 'A' :: 0x04|VKKEYRELEASED == Keycode 0x104
+-}
+pattern TkKeyReleased :: Keycode
+pattern TkKeyReleased = Keycode 0x100
+
+{-
+ * Virtual key-codes for internal terminal states/variables.
+ * These can be accessed via terminalstate function.
+-}
+pattern TkWidth :: Keycode
+pattern TkWidth= Keycode 0xC0
+
+pattern TkHeight :: Keycode
+pattern TkHeight= Keycode 0xC1
+
+pattern TkCellWidth :: Keycode
+pattern TkCellWidth= Keycode 0xC2
+
+pattern TkCellHeight :: Keycode
+pattern TkCellHeight= Keycode 0xC3
+
+pattern TkColor :: Keycode
+pattern TkColor= Keycode 0xC4
+
+pattern TkBkcolor :: Keycode
+pattern TkBkcolor= Keycode 0xC5
+
+pattern TkLayer :: Keycode
+pattern TkLayer= Keycode 0xC6
+
+pattern TkComposition :: Keycode
+pattern TkComposition= Keycode 0xC7
+
+pattern TkChar :: Keycode
+pattern TkChar= Keycode 0xC8
+
+pattern TkWchar :: Keycode
+pattern TkWchar= Keycode 0xC9
+
+pattern TkEvent :: Keycode
+pattern TkEvent= Keycode 0xCA
+
+pattern TkFullscreen :: Keycode
+pattern TkFullscreen= Keycode 0xCB
+
+pattern TkClose :: Keycode
+pattern TkClose= Keycode 0xE0
+
+pattern TkResized :: Keycode
+pattern TkResized= Keycode 0xE1
diff --git a/src/BearLibTerminal/Raw.hs b/src/BearLibTerminal/Raw.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Raw.hs
@@ -0,0 +1,121 @@
+{-|
+Module      : BearLibTerminal.Raw
+Description : Raw bindings to the BearLibTerminal C graphics library.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+These are the raw bindings to the C library. Not recommended to use them unless you really want to.
+-}
+
+{-# LANGUAGE CApiFFI #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+
+module BearLibTerminal.Raw where
+
+import Foreign.C.Types
+import Foreign.C.String
+import Foreign.Ptr
+import Foreign.Storable
+import Control.Monad.IO.Class
+import Data.Text (Text)
+import qualified Data.Text.Foreign as TF
+import GHC.Generics
+
+-- | A 2D vector representing the dimensions of a string when printed to the screen (optionally with some auto string-wrapping).
+data Dimensions = Dimensions
+  { width :: Int
+  , height :: Int
+  } deriving stock (Show, Generic, Eq, Ord, Read)
+
+instance Storable Dimensions where
+  sizeOf _ = 8
+  alignment _ = 4
+  poke p Dimensions{..} = do
+    pokeByteOff p 0 width
+    pokeByteOff p 4 height
+  peek p = do
+    (width :: CUInt) <- peekByteOff p 0
+    (height :: CUInt) <- peekByteOff p 4
+    return $ Dimensions (fromIntegral width) (fromIntegral height)
+
+-- | Alignment of glyphs within a cell. Primarily useful for alignment of multi-cell characters.
+data PrintAlignment = AlignDefault | AlignLeft | AlignRight | AlignCenter | AlignTop | AlignBottom | AlignMiddle
+  deriving stock (Eq, Ord, Bounded, Enum, Generic, Show, Read)
+
+-- | Wrap a C TRUE return value into a Haskell boolean.
+asBool :: CInt -> Bool
+asBool = (== 1)
+
+foreign import capi safe "BearLibTerminal.h terminal_open" c_terminal_open :: IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_close" c_terminal_close :: IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_set" c_terminal_set :: CString -> IO CInt
+
+textToCString :: MonadIO m => (CString -> IO a) -> Text -> m a
+textToCString f = liftIO . flip TF.withCString f
+
+stringToCString :: MonadIO m => (CString -> IO a) -> String -> m a
+stringToCString f = liftIO . flip withCString f
+
+-- | prepend and amend semigroups.
+surround :: Semigroup a => a -> a -> a -> a
+surround p s t = p <> t <> s
+
+foreign import capi safe "BearLibTerminal.h terminal_color" c_terminal_color_uint :: CUInt -> IO ()
+foreign import capi safe "BearLibTerminalExtras.h terminal_color_from_name" c_terminal_color_from_name :: CString -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_bkcolor" c_terminal_bkcolor_uint :: CUInt -> IO ()
+foreign import capi safe "BearLibTerminalExtras.h terminal_bkcolor_from_name" c_terminal_bkcolor_from_name :: CString -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_composition" c_terminal_composition :: CInt -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_layer" c_terminal_layer :: CInt -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_clear" c_terminal_clear :: IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_clear_area" c_terminal_clear_area :: CInt -> CInt -> CInt -> CInt -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_crop" c_terminal_crop :: CInt -> CInt -> CInt -> CInt -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_refresh" c_terminal_refresh :: IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_put" c_terminal_put :: CInt -> CInt -> CInt -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_pick" c_terminal_pick :: CInt -> CInt -> CInt -> IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_pick_color" c_terminal_pick_color :: CInt -> CInt -> CInt -> IO CUInt
+
+foreign import capi safe "BearLibTerminal.h terminal_pick_bkcolor" c_terminal_pick_bkcolor :: CInt -> CInt -> IO CUInt
+
+foreign import capi safe "BearLibTerminal.h terminal_put_ext" c_terminal_put_ext :: CInt -> CInt -> CInt -> CInt -> CInt -> Ptr CUInt -> IO ()
+
+foreign import capi safe "BearLibTerminalExtras.h terminal_print_ptr" c_terminal_print_ptr :: CInt -> CInt -> CString -> Ptr Dimensions -> IO ()
+
+foreign import capi safe "BearLibTerminalExtras.h terminal_print_ext_ptr" c_terminal_print_ext_ptr :: CInt -> CInt -> CInt -> CInt -> CInt -> CString -> Ptr Dimensions -> IO ()
+
+-- I don't know if wchar is actually useful here.
+-- I don't care enough to try and wrap va_list around the printf variants.
+-- so that's printf, printf_ext, wprint, wprintf, wprint_ext, wprintf_ext, measuref, wmeasure, measuref_ext, wmeasuref_ext
+-- check is unnecessary
+-- foreign import capi unsafe "BearLibTerminal.h terminal_check" c_terminal_check :: CInt -> IO CInt
+-- also read_wstr
+-- not bothering with: color_from_name, color_from_argb
+
+foreign import capi safe "BearLibTerminalExtras.h terminal_measure_ptr" c_terminal_measure_ptr :: CString -> Ptr Dimensions -> IO ()
+
+foreign import capi safe "BearLibTerminalExtras.h terminal_measure_ext_ptr" c_terminal_measure_ext_ptr :: CInt -> CInt -> CString -> Ptr Dimensions -> IO ()
+
+foreign import capi safe "BearLibTerminal.h terminal_state" c_terminal_state :: CInt -> IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_has_input" c_terminal_has_input :: IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_read" c_terminal_read :: IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_peek" c_terminal_peek :: IO CInt
+
+foreign import capi safe "BearLibTerminal.h terminal_read_str" c_read_str :: CInt -> CInt -> Ptr CChar -> CInt -> IO CUInt
+
+foreign import capi safe "BearLibTerminal.h terminal_delay" c_terminal_delay :: CInt -> IO ()
diff --git a/src/BearLibTerminal/Terminal/CString.hs b/src/BearLibTerminal/Terminal/CString.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Terminal/CString.hs
@@ -0,0 +1,131 @@
+{-|
+Module      : BearLibTerminal.Terminal.CString
+Description : String functions taking `CString`s.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+Functions that take strings as `CString`s. Unless you are wanting to marshall across the foreign boundary yourself,
+you probably don't want to use these (prefer `Data.Text.Text` from `BearLibTerminal` or `String` from `BearLibTerminal.Terminal.String`).
+-}
+
+module BearLibTerminal.Terminal.CString
+  ( terminalSetCString
+  , terminalSetCString_
+  , terminalColorNameCString
+  , terminalBkColorNameCString
+
+  , terminalPrintCString
+  , terminalPrintExtCString
+
+  , terminalMeasureCString
+  , terminalMeasureExtCString
+
+  ) where
+
+import BearLibTerminal.Raw
+import Control.Monad.IO.Class
+import Data.Function ((&))
+import Data.Maybe (fromMaybe)
+import Foreign
+import Foreign.C.String
+import Foreign.C.Types (CInt)
+
+-- | Set one or more of the configuration options, given as a `CString`.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSetCString :: MonadIO m =>
+  CString -- ^ Configuration string.
+  -> m Bool -- ^ whether the configuration was successful.
+terminalSetCString = liftIO . (fmap asBool . c_terminal_set)
+
+-- | Set one or more of the configuration options, given as a `CString`. Ignore if it was successful.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSetCString_ :: MonadIO m =>
+  CString -- ^ Configuration string.
+  -> m ()
+terminalSetCString_ = liftIO . (void . c_terminal_set)
+
+-- | Set the currently selected foreground color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `CString`.
+--
+-- Wrapper around [@terminal_color@](http://foo.wyrd.name/en:bearlibterminal:reference#color) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalColorNameCString ::
+  MonadIO m
+  => CString  -- ^ the color name to be selected, in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalColorNameCString = liftIO . c_terminal_color_from_name
+
+-- | Set the currently selected foreground color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `CString`.
+--
+-- Wrapper around [@terminal_bkcolor@](http://foo.wyrd.name/en:bearlibterminal:reference#bkcolor) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalBkColorNameCString ::
+  MonadIO m
+  => CString  -- ^ the color name to be selected, in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalBkColorNameCString = liftIO . c_terminal_bkcolor_from_name
+
+-- | Print a string to the screen, given as a `CString`.
+--
+-- Wrapper around [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+terminalPrintCString ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> CString -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintCString x y c = liftIO $ alloca (\dim -> c_terminal_print_ptr (fromIntegral x) (fromIntegral y) c dim >> peek dim)
+
+-- | Print a string to the screen, given as a `CString`, with (optional) auto-wrapping and alignment.
+-- Wrapper around [@terminal_print_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#print_ext)
+terminalPrintExtCString ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Int -- ^ width of the bounding box for auto-wrapping and alignment.
+  -> Int -- ^ height of the bounding box for auto-wrapping and alignment.
+  -> Maybe PrintAlignment -- ^ alignment of the string within the bounding box.
+  -> CString -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintExtCString x y w h mbAlign c =
+  let align :: CInt
+      align = fromMaybe AlignDefault mbAlign & \case
+        AlignDefault -> 0
+        AlignLeft -> 1
+        AlignRight -> 2
+        AlignCenter -> 3
+        AlignTop -> 4
+        AlignBottom -> 8
+        AlignMiddle -> 12
+  in
+  liftIO $ alloca (\dim -> c_terminal_print_ext_ptr (fromIntegral x) (fromIntegral y) (fromIntegral w) (fromIntegral h) align c dim >> peek dim)
+
+-- | Measure the size of a string *if it were to be printed to the screen*, given as a `CString`.
+-- Wrapper around [@terminal_measure@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureCString ::
+  MonadIO m
+  => CString -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureCString c = liftIO $ alloca (\dim -> c_terminal_measure_ptr c dim >> peek dim)
+
+-- | Measure the size of a string *if it were to be printed to the screen*, autowrapped in a bounding box, given as a `CString`.
+-- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureExtCString ::
+  MonadIO m
+  => Int -- ^ the width of the bounding box.
+  -> Int -- ^ the height of the bounding box.
+  -> CString  -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureExtCString w h c = liftIO $ alloca (\dim -> c_terminal_measure_ext_ptr (fromIntegral w) (fromIntegral h) c dim >> peek dim)
diff --git a/src/BearLibTerminal/Terminal/Color.hs b/src/BearLibTerminal/Terminal/Color.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Terminal/Color.hs
@@ -0,0 +1,90 @@
+{-|
+Module      : BearLibTerminal.Terminal.Color
+Description : Color functions.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+Setting the active foreground/background colors and a couple of helper functions for making colors from (A)RGB quads/triples.
+-}
+
+module BearLibTerminal.Terminal.Color
+  ( colorFromARGB
+  , colorFromRGB
+  , terminalColorUInt
+  , terminalColorName
+  , terminalBkColorUInt
+  , terminalBkColorName
+  ) where
+
+import Control.Monad.IO.Class
+import Data.Text
+import BearLibTerminal.Raw
+import BearLibTerminal.Terminal.CString
+import Data.Bits
+
+-- | Convert a color given as 4 integer values (0-255) into an unsigned integer for use with `terminalColorUInt`. No bounds checking is performed.
+colorFromARGB ::
+  Integral a
+  => a -- ^ alpha channel value.
+  -> a -- ^ red channel value.
+  -> a -- ^ green channel value.
+  -> a -- ^ blue channel value.
+  -> Int -- ^ color in 32-bit integer form.
+colorFromARGB a r g b = (fromIntegral a `shiftL` 24) .|. (fromIntegral r `shiftL` 16) .|. (fromIntegral g `shiftL` 8) .|. fromIntegral b
+
+-- | Convert a color given as 3 integer values (0-255) into an unsigned integer for use with `terminalColorUInt`. No bounds checking is performed.
+-- The alpha channel is fully opaque (0xFF).
+colorFromRGB ::
+  Integral a
+  => a -- ^ red channel value.
+  -> a -- ^ green channel value.
+  -> a -- ^ blue channel value.
+  -> Int -- ^ color in 32-bit integer form.
+colorFromRGB = colorFromARGB 255
+
+-- | Set the currently selected foreground color to be used by following output functions (e.g. print).
+-- Takes a color as a 32-bit unsigned integer in ARGB format.
+-- Wrapper around [@terminal_color@](http://foo.wyrd.name/en:bearlibterminal:reference#color).
+terminalColorUInt ::
+  MonadIO m
+  => Int -- ^ the color to set in 32-bit ARGB format.
+  -> m ()
+terminalColorUInt = liftIO . c_terminal_color_uint . fromIntegral
+
+-- | Set the currently selected foreground color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `Text`.
+--
+-- Wrapper around [@terminal_color@](http://foo.wyrd.name/en:bearlibterminal:reference#color) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalColorName ::
+  MonadIO m
+  => Text -- ^ the color name to be selected,in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalColorName = textToCString terminalColorNameCString
+
+-- | Set the currently selected background color to be used by following output functions (e.g. print).
+-- Takes a color as a 32-bit unsigned integer in ARGB format.
+-- Wrapper around [@terminal_bkcolor@](http://foo.wyrd.name/en:bearlibterminal:reference#bkcolor).
+terminalBkColorUInt ::
+  MonadIO m
+  => Int -- ^ the color to set in 32-bit ARGB format.
+  -> m ()
+terminalBkColorUInt = liftIO . c_terminal_bkcolor_uint . fromIntegral
+
+-- | Set the currently selected background color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `Text`.
+--
+-- Wrapper around [@terminal_bkcolor@](http://foo.wyrd.name/en:bearlibterminal:reference#bkcolor) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalBkColorName ::
+  MonadIO m
+  => Text -- ^ the color name to be selected, in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalBkColorName = textToCString terminalBkColorNameCString
diff --git a/src/BearLibTerminal/Terminal/Print.hs b/src/BearLibTerminal/Terminal/Print.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Terminal/Print.hs
@@ -0,0 +1,94 @@
+{-|
+Module      : BearLibTerminal.Terminal.Print
+Description : Printing text.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+Functions for printing text to the screen (given as `Data.Text.Text`), and a couple of helper functions for wrapping
+strings with inline color strings.
+-}
+
+module BearLibTerminal.Terminal.Print
+  ( textColor
+  , textBkColor
+  , terminalPrint
+  , terminalPrint_
+  , terminalPrintExt
+  , terminalPrintExt_
+  ) where
+
+import BearLibTerminal.Raw
+import Control.Monad.IO.Class
+
+import Data.Text (Text)
+import BearLibTerminal.Terminal.CString
+import Data.String (IsString)
+import Control.Monad (void)
+
+-- | Wrap a string (in any `IsString` format) with color formatting tags.
+textColor ::
+  IsString a
+  => Semigroup a
+  => a -- ^ the color, from the list of valid color identifiers - http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name.
+  -> a -- ^ the text to wrap.
+  -> a
+textColor col = surround ("[color="<>col<>"]") "[/color]"
+
+-- | Wrap a string (in any `IsString` format) with background color formatting tags.
+textBkColor ::
+  IsString a
+  => Semigroup a
+  => a -- ^ the background color, from the list of valid color identifiers - http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name.
+  -> a -- ^ the text to wrap.
+  -> a
+textBkColor col = surround ("[bkcolor="<>col<>"]") "[/bkcolor]"
+
+-- | Print a string to the screen, given as a `Text`.
+--
+-- Wrapper around [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+terminalPrint ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Text -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrint x y = textToCString (terminalPrintCString x y)
+
+-- | Print a string to the screen, given as a `Text`, with (optional) auto-wrapping and alignment.
+-- Wrapper around [@terminal_print_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#print_ext)
+terminalPrintExt ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Int -- ^ width of the bounding box for auto-wrapping and alignment.
+  -> Int -- ^ height of the bounding box for auto-wrapping and alignment.
+  -> Maybe PrintAlignment -- ^ alignment of the string within the bounding box.
+  -> Text -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintExt x y w h align = textToCString (terminalPrintExtCString x y w h align)
+
+-- | Print a string to the screen, given as a `Text`. Ignore the dimensions of the printed string.
+--
+-- Wrapper around [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+terminalPrint_ ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Text -- ^ the string to print.
+  -> m ()
+terminalPrint_ x y t = void $ terminalPrint x y t
+
+-- | Print a string to the screen, given as a `Text`, with (optional) auto-wrapping and alignment.
+-- gnore the dimensions of the printed string.
+-- Wrapper around [@terminal_print_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#print_ext)
+terminalPrintExt_ ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Int -- ^ width of the bounding box for auto-wrapping and alignment.
+  -> Int -- ^ height of the bounding box for auto-wrapping and alignment.
+  -> Maybe PrintAlignment -- ^ alignment of the string within the bounding box.
+  -> Text -- ^ the string to print.
+  -> m ()
+terminalPrintExt_ x y w h align t = void $ terminalPrintExt x y w h align t
diff --git a/src/BearLibTerminal/Terminal/Set.hs b/src/BearLibTerminal/Terminal/Set.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Terminal/Set.hs
@@ -0,0 +1,58 @@
+{-|
+Module      : BearLibTerminal.Terminal.Set
+Description : Setting configuration options.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+Setting configuration options - e.g. cell size, fonts, window title, etc etc etc. A full list of everything settable is at
+http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+
+There are some helper functions specifically for setting a title, but it is recommended to use a @printf@ style package
+to build longer configuration strings.
+-}
+
+module BearLibTerminal.Terminal.Set
+  ( terminalSet
+  , terminalSet_
+
+  , terminalSetTitle
+  , terminalSetMany
+  ) where
+
+import BearLibTerminal.Raw
+import BearLibTerminal.Terminal.CString
+import Control.Monad.IO.Class
+import Data.Text (Text)
+import qualified Data.Text as T
+
+-- | Set one or more of the configuration options, given as a `Text`.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSet :: MonadIO m =>
+  Text -- ^ Configuration string.
+  -> m Bool -- ^ whether the configuration was successful.
+terminalSet = textToCString terminalSetCString
+
+-- | Set one or more of the configuration options, given as a `Text`. Ignore if it was successful.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSet_ ::
+  MonadIO m
+  => Text -- ^ Configuration string.
+  -> m ()
+terminalSet_ = textToCString terminalSetCString_
+
+-- | Set the title of the window.
+terminalSetTitle :: MonadIO m => Text -> m ()
+terminalSetTitle = terminalSet_ . ("window: " <>) . surround "title='" "'"
+
+-- | Set multiple properties at once under a specific super-heading.
+terminalSetMany ::
+  MonadIO m
+  => Text -- ^ the super-heading to set things under (e.g. `window`)
+  -> [Text] -- ^ the list of key:value properties to be set.
+  -> m ()
+terminalSetMany super rest = terminalSet_ $ super <> ": " <> T.intercalate ", " rest
diff --git a/src/BearLibTerminal/Terminal/String.hs b/src/BearLibTerminal/Terminal/String.hs
new file mode 100644
--- /dev/null
+++ b/src/BearLibTerminal/Terminal/String.hs
@@ -0,0 +1,144 @@
+{-|
+Module      : BearLibTerminal.Terminal.String
+Description : String functions taking `String`s.
+License     : MIT
+Stability   : experimental
+Portability : POSIX
+
+Functions that take strings as `String`s. It is preferable to use the `Data.Text.Text` versions if possible, but these are fine
+too.
+-}
+
+module BearLibTerminal.Terminal.String
+  ( terminalSetString
+  , terminalSetString_
+  , terminalColorNameString
+  , terminalBkColorNameString
+  , terminalPrintString
+  , terminalPrintString_
+  , terminalPrintExtString
+  , terminalPrintExtString_
+
+  , terminalMeasureString
+  , terminalMeasureExtString
+
+  ) where
+
+import BearLibTerminal.Raw
+import BearLibTerminal.Terminal.CString
+import Control.Monad (void)
+import Control.Monad.IO.Class
+
+-- | Set one or more of the configuration options, given as a `String`.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSetString :: MonadIO m =>
+  String -- ^ Configuration string.
+  -> m Bool -- ^ whether the configuration was successful.
+terminalSetString = stringToCString terminalSetCString
+
+-- | Set one or more of the configuration options, given as a `String`. Ignore if it was successful.
+--
+-- Wrapper around [@terminal_set@](http://foo.wyrd.name/en:bearlibterminal:reference).
+-- More details are available at the BearLibTerminal docs: http://foo.wyrd.name/en:bearlibterminal:reference:configuration.
+terminalSetString_ :: MonadIO m =>
+  String -- ^ Configuration string.
+  -> m ()
+terminalSetString_ = stringToCString terminalSetCString_
+
+-- | Set the currently selected foreground color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `String`.
+--
+-- Wrapper around [@terminal_color@](http://foo.wyrd.name/en:bearlibterminal:reference#color) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalColorNameString ::
+  MonadIO m
+  => String  -- ^ the color name to be selected, in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalColorNameString = stringToCString terminalColorNameCString
+
+-- | Set the currently selected background color to be used by following output functions (e.g. `terminalPrintText`).
+-- Takes a color as a name in a variety of formats; see [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name)
+-- for a full list.
+--
+-- Takes the color name as a `String`.
+--
+-- Wrapper around [@terminal_bkcolor@](http://foo.wyrd.name/en:bearlibterminal:reference#bkcolor) and
+-- [@color_from_name@](http://foo.wyrd.name/en:bearlibterminal:reference#color_from_name).
+terminalBkColorNameString ::
+  MonadIO m
+  => String  -- ^ the color name to be selected, in @"[brightness]hue"@ format as specified in @color_from_name@.
+  -> m ()
+terminalBkColorNameString = stringToCString terminalBkColorNameCString
+
+-- | Print a string to the screen, given as a `String`.
+--
+-- Wrapper around [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+terminalPrintString ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> String -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintString x y = stringToCString (terminalPrintCString x y)
+
+-- | Print a string to the screen, given as a `String`. Ignore the returned dimensions.
+--
+-- Wrapper around [@terminal_print@](http://foo.wyrd.name/en:bearlibterminal:reference#print).
+terminalPrintString_ ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> String -- ^ the string to print.
+  -> m () -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintString_ x y = void . terminalPrintString x y
+
+-- | Print a string to the screen, given as a `String`, with (optional) auto-wrapping and alignment.
+-- Wrapper around [@terminal_print_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#print_ext)
+terminalPrintExtString ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Int -- ^ width of the bounding box for auto-wrapping and alignment.
+  -> Int -- ^ height of the bounding box for auto-wrapping and alignment.
+  -> Maybe PrintAlignment -- ^ alignment of the string within the bounding box.
+  -> String -- ^ the string to print.
+  -> m Dimensions -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintExtString x y w h align = stringToCString (terminalPrintExtCString x y w h align)
+
+-- | Print a string to the screen, given as a `String`, with (optional) auto-wrapping and alignment.
+-- Ignore the returned dimensions.
+-- Wrapper around [@terminal_print_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#print_ext)
+terminalPrintExtString_ ::
+  MonadIO m
+  => Int  -- ^ x-coordinate to start printing the string at.
+  -> Int -- ^ y-coordinate to start printing the string at.
+  -> Int -- ^ width of the bounding box for auto-wrapping and alignment.
+  -> Int -- ^ height of the bounding box for auto-wrapping and alignment.
+  -> Maybe PrintAlignment -- ^ alignment of the string within the bounding box.
+  -> String -- ^ the string to print.
+  -> m () -- ^ the `Dimensions` of the string as printed on screen.
+terminalPrintExtString_ x y w h align = void . terminalPrintExtString x y w h align
+
+
+-- | Measure the size of a string *if it were to be printed to the screen*, given as a `String`.
+-- Wrapper around [@terminal_measure@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureString ::
+  MonadIO m
+  => String -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureString = stringToCString terminalMeasureCString
+
+-- | Measure the size of a string *if it were to be printed to the screen*, autowrapped in a bounding box, given as a `String`.
+-- Wrapper around [@terminal_measure_ext@](http://foo.wyrd.name/en:bearlibterminal:reference#measure)
+terminalMeasureExtString ::
+  MonadIO m
+  => Int -- ^ the width of the bounding box.
+  -> Int -- ^ the height of the bounding box.
+  -> String  -- ^ the string to measure the print for.
+  -> m Dimensions -- ^ the size of the string if it were printed to the screen.
+terminalMeasureExtString w h = stringToCString (terminalMeasureExtCString w h)
