├── .gitignore
├── README.md
├── TODO.md
├── c
├── Makefile
└── src
│ ├── lhelp.c
│ ├── lhelp.h
│ ├── platform.c
│ ├── sane.h
│ ├── util.c
│ ├── util.h
│ └── xcb
│ ├── context.c
│ ├── context.h
│ ├── event.c
│ ├── event.h
│ ├── scairo.c
│ ├── spixmap.c
│ ├── swin.c
│ ├── terra_xkb.c
│ ├── terra_xkb.h
│ ├── vidata.c
│ ├── vidata.h
│ ├── window.c
│ ├── window.h
│ ├── xcb_ctx.c
│ ├── xcb_ctx.h
│ ├── xlhelp.c
│ ├── xlhelp.h
│ ├── xutil.c
│ └── xutil.h
├── l
├── _retired.lua
├── abonaments
│ └── tcp
│ │ └── client.lua
├── element.lua
├── input
│ ├── click.lua
│ ├── clickbind.lua
│ ├── clickmap.lua
│ ├── key.lua
│ ├── keybind.lua
│ └── keymap.lua
├── oak
│ ├── align.lua
│ ├── border.lua
│ ├── elements
│ │ ├── _retired.lua
│ │ ├── branches
│ │ │ ├── _retired.lua
│ │ │ ├── branch.lua
│ │ │ ├── el.lua
│ │ │ ├── horizontal.lua
│ │ │ ├── internal.lua
│ │ │ ├── root.lua
│ │ │ └── vertical.lua
│ │ ├── element.lua
│ │ ├── internal.lua
│ │ └── leaves
│ │ │ ├── bg.lua
│ │ │ ├── leaf.lua
│ │ │ ├── svg.lua
│ │ │ └── text.lua
│ ├── internal.lua
│ ├── padding.lua
│ ├── shape.lua
│ ├── size.lua
│ └── source.lua
├── object.lua
├── orchard.lua
├── platforms
│ ├── common
│ │ └── window.lua
│ └── xcb
│ │ ├── app.lua
│ │ └── window.lua
├── puv.lua
├── sigtools.lua
└── tools
│ ├── color.lua
│ ├── enum.lua
│ ├── promise.lua
│ ├── shapers.lua
│ ├── sstr.lua
│ ├── table.lua
│ ├── tracker.lua
│ └── urn.lua
├── notes.md
├── run_tests.sh
├── shell.nix
├── showcase
├── green_background_red_ball.png
├── made_with_terra.png
├── mathgraph.png
└── whether.png
├── terra-dev-1.rockspec
└── tests
└── tools
├── sstr_spec.lua
└── urn
├── basics_spec.lua
└── json_spec.lua
/.gitignore:
--------------------------------------------------------------------------------
1 | terra/
2 | *.o
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## IMPORTANT
2 | This project is currently unmaintained.
3 |
4 | # Terra
5 | *What if you didn't have to write electron apps anymore?*
6 |
7 | Terra offers a platform-independent, featureful toolkit that allows you to easily and quickly build your application, and then flexibly go as low-level as you need to optimize your application.
8 | This toolkit was built based on my experience with [AwesomeWM](https://github.com/awesomeWM/awesome), [Elm](https://elm-lang.org/), and [Elm-ui](https://github.com/mdgriffith/elm-ui), and other projects. I hope you find it useful.
9 |
10 |
11 | ## Build gorgeous, high-performance applications
12 | Terra aims to make developing cross platform applications an experience that is both fast and pleasant, without sacrificing on aesthetics or performance.
13 |
14 |
15 | ## Current state
16 | **Warning: Terra is experimental software and very early in development. We push to the main branch and live life day to day. :)**
17 | Also, I'm working on this whenever I have time and whenever I feel like it. I may be slow to solve issues.
18 | Terra currently only works with luajit on linux under Xorg. Support for Wayland, Windows and Mac is planned.
19 | If you experience any issues with installing, bugs, etc., you can open an issue or contact me directly.
20 |
21 | ## Example
22 | This code creates an application, creates a window, creates a UI tree with Oak, terra's built-in UI library, paints a green background, creates a red ball, draws "hello world" on it, and animates the ball to spin in a circle.
23 | ```lua
24 | #!/usr/bin/env luajit
25 |
26 | local t_app = require("terra.app")
27 | local t_window = require("terra.window." .. t_app.get_platform())
28 |
29 | local tt_color = require("terra.tools.color")
30 |
31 | local to_size = require("terra.oak.size")
32 | local to_align = require("terra.oak.align")
33 |
34 | local toeb_root = require("terra.oak.elements.branches.root")
35 | local toeb_el = require("terra.oak.elements.branches.el")
36 |
37 | local toel_bg = require("terra.oak.elements.leaves.bg")
38 | local toel_text = require("terra.oak.elements.leaves.text")
39 |
40 | local function init_app(app)
41 |
42 | local model = {}
43 | app.model = model
44 |
45 | -- create the window
46 | model.main_window = t_window.create(app, 320, 420, 200, 160, {
47 |
48 | tree = toeb_root.new({ -- the root of the UI tree
49 | toeb_el.new({ -- the background of the window
50 | width = to_size.FILL,
51 | height = to_size.FILL,
52 | bg = toel_bg.new({
53 | source = tt_color.rgb(0.17, 0.42, 0.21), -- green
54 | }),
55 | toeb_el.new({ -- the red ball
56 | halign = to_align.CENTER,
57 | valign = to_align.CENTER,
58 | width = 60,
59 | height = 60,
60 | bg = toel_bg.new({
61 | source = tt_color.rgb(0.8, 0.1, 0), -- red
62 | border_radius = 30,
63 | }),
64 | -- declaratively subscribe to signals on elements
65 | subscribe_on_root = {
66 | ["AnimationEvent"] = function(self, time)
67 | local spin_push = 20
68 | self:set_offset_x(math.sin(time) * spin_push)
69 | self:set_offset_y(-math.cos(time) * spin_push)
70 | end
71 | },
72 | toel_text.new({ -- "hello world" text
73 | family = "Roboto",
74 | size = 11,
75 | width = 40, -- constrain the width so the text wraps
76 | halign = to_align.CENTER,
77 | valign = to_align.CENTER,
78 | text = "hello world",
79 | fg = tt_color.rgb(1, 1, 1),
80 | })
81 | }),
82 | }),
83 | }),
84 | })
85 |
86 | t_window.request_raise(model.main_window)
87 | end
88 |
89 | t_app.desktop(init_app, t_app.make_default_event_handler(function(app, event_type, ...)
90 | end))
91 | ```
92 |
93 | The above code produces the following output (without the titlebar):
94 |
95 |
96 | ## Installing
97 | 1. Clone the repo
98 | `git clone https://github.com/chris-montero/terra.git`
99 | 2. Build the project and install the luarock
100 | `sudo luarocks make`
101 | 3. That's it. Now you should be able to successfully run the code in the [Example](#Example) section.
102 |
103 | # Credits
104 | * [Uli Schlachter](https://github.com/psychon), for promptly and elaborately answering my questions about Xorg on stack overflow.
105 | * My mom, for sponsoring this project. Thanks mom.
106 |
107 | # Contributing
108 | You are welcome to contribute by opening issues, comitting code, or through donations.
109 | Any support is sincerely appreciated.
110 |
111 | https://ko-fi.com/chrismontero
112 |
113 | US DOLLAR IBAN: `RO75BTRLUSDCRT0323524101`
114 | EURO IBAN: `RO71BTRLEURCRT0323524101`
115 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | # TODO
3 | [ ] maybe have separate `set_bounding_geometry`, `set_drawing_geometry`, `get_bounding_geometry` and `get_drawing_geometry` functions, instead of just having `set_geometry` and `get_geometry`. This could be very useful in cases where you want to layer children in a container, you want one of the children to be "anchored" to the relative x & y coordinates of the parent "el", you want this child to draw something, but you don't want it to take up any space in the "el" parent. In this case, you could override the `get_bounding_geometry` and make it `return x, y, 0, 0`. This way, the element would still be allowed to be anchored to the parent and draw, but would not take up any space in the layout.
4 |
5 | [ ] we need a good way to set and get the shape of an element. Additionally, we need to find a way to clip the sub-children of the element to its shape so that they can't draw outside of it. Additionally, we need to find a way to "punch holes" into an element, and be able to "see through" that hole AND click through it. I think the way I'm going to make all of this work is by having a "shape" property. I'm pretty sure cairo lets save a shape, and also lets you do "is_mouse_inside" or something like that, which would basically solve all of our issues. I'm also thinking that it would be nice to have a "clip_through_parent" property which would essentially "punch" a hole through the parent in the shape of the child. The reason this might be a good idea is because later on, if I'm going to be optimizing everything to only redraw its before/after geometry, moving the child (and thus moving the "hole") would also be optimized with this system.
6 |
7 | [ ] write documentation. Maybe use [Sphinx](https://www.sphinx-doc.org/en/master/)?
8 |
--------------------------------------------------------------------------------
/c/Makefile:
--------------------------------------------------------------------------------
1 | CC = clang
2 | # CC = gcc
3 |
4 | BUILD_DIR = build
5 |
6 | LIB_NAME = application
7 | LIB_NAME_DEBUG = $(LIB_NAME)
8 |
9 | SRC_DIR = src
10 | APP_SRC_FILE_NAMES = \
11 | app \
12 | application \
13 | lhelp \
14 | terra_xkb \
15 | event \
16 | util \
17 | xdraw
18 |
19 | APP_FULL_C_FILE_PATHS = $(patsubst %, $(SRC_DIR)/%.c, $(APP_SRC_FILE_NAMES))
20 | APP_FULL_O_FILE_PATHS = $(patsubst %, %.o, $(APP_SRC_FILE_NAMES))
21 |
22 | WINDOWS_DIR = $(SRC_DIR)/windows
23 | SWIN_SRC_FILE_NAMES = swin
24 |
25 | SWIN_FULL_C_FILE_PATHS = $(patsubst %, $(SRC_DIR)/%.c, $(SWIN_SRC_FILE_NAMES)) $(WINDOWS_DIR)/xcb.c
26 | SWIN_FULL_O_FILE_PATHS = $(patsubst %, %.o, $(SWIN_SRC_FILE_NAMES)) xcb.o
27 |
28 | SCAIRO = scairo
29 | SCAIRO_FULL_C_FILE_PATHS = $(SRC_DIR)/$(SCAIRO).c
30 | SCAIRO_FULL_O_FILE_PATHS = $(SCAIRO).o
31 |
32 | SPIXMAP = spixmap
33 | SPIXMAP_FULL_C_FILE_PATHS = $(SRC_DIR)/$(SPIXMAP).c
34 | SPIXMAP_FULL_O_FILE_PATHS = $(SPIXMAP).o
35 |
36 | PACKAGES = \
37 | luajit \
38 | xcb xcb-keysyms xcb-cursor \
39 | x11 \
40 | xkbcommon xkbcommon-x11 \
41 | cairo
42 | PKGCONFIG = pkg-config
43 | COMPILER_FLAGS = -std=c99 -Wall -Wextra
44 | CFLAGS = $(shell $(PKGCONFIG) --cflags $(PACKAGES)) -Isrc
45 | LIBS = $(shell $(PKGCONFIG) --libs $(PACKAGES)) -lev
46 |
47 | all: application swin scairo spixmap
48 |
49 | application: compile_application build_application
50 | swin : compile_swin build_swin
51 | scairo : compile_scairo build_scairo
52 | spixmap : compile_spixmap build_spixmap
53 |
54 | compile_application: $(APP_FULL_C_SRC_PATHS)
55 | $(CC) \
56 | $(CFLAGS) \
57 | $(APP_FULL_C_FILE_PATHS) \
58 | $(COMPILER_FLAGS) \
59 | -c -fpic
60 |
61 | build_application: $(FULL_SRC_FILES)
62 | $(CC) \
63 | $(LIBS) \
64 | $(APP_FULL_O_FILE_PATHS) \
65 | $(COMPILER_FLAGS) \
66 | -shared \
67 | -o $(LIB_NAME).so
68 |
69 | compile_swin :
70 | $(CC) \
71 | $(CFLAGS) \
72 | $(SWIN_FULL_C_FILE_PATHS) \
73 | $(COMPILER_FLAGS) \
74 | -c -fpic
75 |
76 | build_swin :
77 | $(CC) \
78 | $(LIBS) \
79 | $(SWIN_FULL_O_FILE_PATHS) \
80 | $(COMPILER_FLAGS) \
81 | -shared \
82 | -o swin.so
83 |
84 | compile_scairo :
85 | $(CC) \
86 | $(CFLAGS) \
87 | $(SCAIRO_FULL_C_FILE_PATHS) \
88 | $(COMPILER_FLAGS) \
89 | -c -fpic
90 |
91 | build_scairo :
92 | $(CC) \
93 | $(LIBS) \
94 | $(SCAIRO_FULL_O_FILE_PATHS) \
95 | $(COMPILER_FLAGS) \
96 | -shared \
97 | -o scairo.so
98 |
99 | compile_spixmap :
100 | $(CC) \
101 | $(CFLAGS) \
102 | $(SPIXMAP_FULL_C_FILE_PATHS) \
103 | $(COMPILER_FLAGS) \
104 | -c -fpic
105 |
106 | build_spixmap :
107 | $(CC) \
108 | $(LIBS) \
109 | $(SPIXMAP_FULL_O_FILE_PATHS) \
110 | $(COMPILER_FLAGS) \
111 | -shared \
112 | -o spixmap.so
113 |
114 | debug: $(FULL_SRC_FILES)
115 | $(CC) \
116 | --verbose -g \
117 | $(FULL_SRC_FILES) \
118 | $(COMPILER_FLAGS) \
119 | $(CFLAGS) \
120 | $(LIBS) \
121 | -o $(LIB_NAME)
122 |
123 | clean:
124 | $(RM) *.o *.so core*
125 |
126 | .PHONY: all application swin scairo spixmap clean
127 |
--------------------------------------------------------------------------------
/c/src/lhelp.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 |
5 | #include "lhelp.h"
6 |
7 | #include "sane.h"
8 |
9 | // from luajit source code. Copyright Mike Pall. (probably copy pasted from lua5.2 lol)
10 | void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
11 | {
12 | luaL_checkstack(L, nup, "too many upvalues");
13 | for (; l->name; l++) {
14 | int i;
15 | for (i = 0; i < nup; i++) /* Copy upvalues to the top. */
16 | lua_pushvalue(L, -nup);
17 | lua_pushcclosure(L, l->func, nup);
18 | lua_setfield(L, -(nup + 2), l->name);
19 | }
20 | lua_pop(L, nup); /* Remove upvalues. */
21 | }
22 |
23 | void lhelp_dump_stack(lua_State *L)
24 | {
25 | uint i = lua_gettop(L);
26 | fprintf(stderr, "LUA STACK DUMP START: \n");
27 | while (i != 0) {
28 | uint t = lua_type(L, i);
29 | switch (t) {
30 | case LUA_TSTRING:
31 | fprintf(stderr, "%d\t string: %s\n", i, lua_tostring(L, i));
32 | break;
33 | case LUA_TBOOLEAN:
34 | fprintf(stderr, "%d\t boolean: %s", i, lua_toboolean(L, i) ? "true\n" : "false\n");
35 | break;
36 | case LUA_TNUMBER:
37 | fprintf(stderr, "%d\t number: %g\n", i, lua_tonumber(L, i));
38 | break;
39 | case LUA_TNIL:
40 | fprintf(stderr, "%d\t nil\n", i);
41 | break;
42 | case LUA_TTABLE:
43 | fprintf(stderr, "%d\t table(%p); length: %lu\n", i, lua_topointer(L, i), lua_objlen(L, i));
44 | break;
45 | default:
46 | fprintf(
47 | stderr,
48 | "%d\t %s(%p); length: %lu\n",
49 | i,
50 | lua_typename(L, t),
51 | lua_topointer(L, i),
52 | lua_objlen(L, i)
53 | );
54 | break;
55 | }
56 | i--;
57 | }
58 | fprintf(stderr, "\nLUA STACK DUMP END \n");
59 | }
60 |
61 | // the default runtime error function
62 | int lhelp_function_on_runtime_error(lua_State *L)
63 | {
64 | // push the `deubg.traceback` function on the stack
65 | lua_getglobal(L, "debug");
66 | lua_pushstring(L, "traceback");
67 | lua_rawget(L, -2);
68 | lua_remove(L, -2); // remove the debug library
69 |
70 | // push the return value of `debug.traceback(, 1)` on the stack
71 | lua_tostring(L, 1); // make sure the error is a string
72 | lua_pushvalue(L, 1);
73 | lua_pushinteger(L, 1);
74 | lua_call(L, 2, 1);
75 |
76 | // remove the original message
77 | lua_remove(L, 1);
78 |
79 | // now create a message like:
80 | // ----------------- RUNTIME ERROR -----------------
81 | //
82 | lua_pushstring(L, "\n----------------- RUNTIME ERROR -----------------\n");
83 | lua_insert(L, 1);
84 | lua_pushstring(L, "\n\n");
85 | lua_concat(L, 3);
86 |
87 | // the error string we created is still on the stack
88 | return 1;
89 | }
90 |
91 | // does t[`name`] = `b` where "t" is the table on top of the stack
92 | void lhelp_set_bool(lua_State *L, char *name, bool b)
93 | {
94 | lua_pushstring(L, name);
95 | lua_pushboolean(L, b);
96 | lua_rawset(L, -3);
97 | }
98 |
99 | // does t[`name`] = `i` where "t" is the table on top of the stack
100 | void lhelp_set_int(lua_State *L, char *name, int i)
101 | {
102 | lua_pushstring(L, name);
103 | lua_pushinteger(L, i);
104 | lua_rawset(L, -3);
105 | }
106 |
107 | // does t[`name`] = `value` where "t" is the table on top of the stack
108 | void lhelp_set_string(lua_State *L, char *name, char *value)
109 | {
110 | lua_pushstring(L, name);
111 | lua_pushstring(L, value);
112 | lua_rawset(L, -3);
113 | }
114 |
--------------------------------------------------------------------------------
/c/src/lhelp.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_LHELP_H
2 | #define TERRA_LHELP_H
3 |
4 | #include
5 | #include
6 |
7 | #include "sane.h"
8 |
9 | // from lua5.2. In spite of the fact that we use luajit, which defines these
10 | // macros, we use luarocks to compile the project, which uses lua5.1's
11 | // headers. What this means is that we have to define these ourselves to
12 | // ensure compatibility with lua5.1.
13 | void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);
14 | #define luaL_newlibtable(L, l) \
15 | lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
16 | #define luaL_newlib(L, l) (luaL_newlibtable(L, l), luaL_setfuncs(L, l, 0))
17 |
18 | void lhelp_dump_stack(lua_State *L);
19 | int lhelp_function_on_runtime_error(lua_State *L);
20 |
21 | void lhelp_set_bool(lua_State *L, char *name, bool b);
22 | void lhelp_set_int(lua_State *L, char *name, int i);
23 | void lhelp_set_string(lua_State *L, char *name, char *value);
24 |
25 | #endif
26 |
--------------------------------------------------------------------------------
/c/src/platform.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | // #define PLATFORM "xcb" // TODO: parameterize this
4 | //
5 | // int terra_get_platform(lua_State *L)
6 | // {
7 | // lua_pushstring(L, PLATFORM);
8 | // return 1;
9 | // }
10 | //
11 | // static const struct luaL_Reg lib_terra_platform[] = {
12 | //
13 | // { "get", terra_get_platform },
14 | // { NULL, NULL },
15 | // };
16 | //
17 | // int luaopen_terra_platform(lua_State *L)
18 | // {
19 | // luaL_newlib(L, lib_terra_platform);
20 | //
21 | // return 1;
22 | // }
23 |
24 | #define PLATFORM "xcb" // TODO: parameterize this
25 |
26 | int luaopen_terra_platform(lua_State *L)
27 | {
28 | lua_pushstring(L, PLATFORM);
29 |
30 | return 1;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/c/src/sane.h:
--------------------------------------------------------------------------------
1 | #ifndef SANE_LIB_H
2 | #define SANE_LIB_H
3 |
4 | #include
5 |
6 | // macros are normally defined in uppercase. why should "true" and "false"
7 | // be different?
8 | #define FALSE 0
9 | #define TRUE 1
10 |
11 | #ifndef NULL
12 | #define NULL ((void *)0)
13 | #endif
14 |
15 | // much easier to type
16 | #define i8 int8_t
17 | #define i16 int16_t
18 | #define i32 int32_t
19 | #define i64 int64_t
20 | #define u8 uint8_t
21 | #define u16 uint16_t
22 | #define u32 uint32_t
23 | #define u64 uint64_t
24 |
25 | // what people should be using as default instead of "int"
26 | #define uint unsigned int
27 |
28 | typedef uint8_t byte8;
29 | typedef uint16_t byte16;
30 | #define bool unsigned int
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/c/src/util.c:
--------------------------------------------------------------------------------
1 |
2 | #include // for `fprintf`, `stderr`
3 | #include
4 | #include // for backtrace
5 |
6 | #include "util.h"
7 |
8 | #define MAX_BACKTRACE_STACK_SIZE 32
9 |
10 | void util_backtrace_print(void)
11 | {
12 | void *stack[MAX_BACKTRACE_STACK_SIZE];
13 | char **symbols;
14 | int stack_size;
15 |
16 | stack_size = backtrace(stack, NUMBEROF(stack));
17 | symbols = backtrace_symbols(stack, stack_size);
18 |
19 | if(symbols == NULL) return; // TODO: maybe I can remove this
20 |
21 | fprintf(stderr, "Dumping backtrace:\n");
22 | for(int i = 0; i < stack_size; i++) {
23 | fprintf(stderr, "\t%s\n", symbols[i]);
24 | }
25 | free(symbols);
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/c/src/util.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_UTIL_H
2 | #define TERRA_UTIL_H
3 |
4 | #define UNUSED(x) (void)(x)
5 | #define NUMBEROF(arr) (sizeof((arr)) / sizeof((arr[0])))
6 |
7 | #ifdef DEBUG
8 | // ##__VA_ARGS__ makes it so it removes the last ',' if there's no more args
9 | #define DLOG(fmt, ...) fprintf(stderr, "%d %s %s : " fmt, __LINE__, __FILE__, __FUNCTION__, ##__VA_ARGS__)
10 | #else
11 | #define DLOG(...)
12 | #endif
13 |
14 | void util_backtrace_print(void);
15 |
16 | #endif
17 |
--------------------------------------------------------------------------------
/c/src/xcb/context.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_CONTEXT_H
2 | #define TERRA_XCB_CONTEXT_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | // #include
9 |
10 | #include
11 |
12 | #include "sane.h"
13 |
14 | // TODO: organize these properly
15 | struct XcbContext {
16 | lua_State *L;
17 |
18 | struct xcb_connection_t *connection;
19 | // TODO: the screen seems to hold information about width in px and mm.
20 | // Use this information to figure out the dpi of each screen
21 | struct xcb_screen_t *screen;
22 |
23 | struct xkb_context *xkb_ctx;
24 | struct xkb_state *xkb_state;
25 |
26 | xcb_visualtype_t *visual;
27 | u8 visual_depth;
28 | i32 default_screen_number;
29 | xcb_colormap_t default_colormap_id;
30 |
31 | // keyboard support
32 | xcb_key_symbols_t *keysyms;
33 |
34 | // cursor support
35 | xcb_cursor_context_t *cursor_ctx;
36 | xcb_cursor_t current_cursor;
37 |
38 | xcb_window_t gc_window;
39 | xcb_gcontext_t default_gc_id;
40 |
41 | #ifdef DEBUG
42 | struct xcb_errors_context_t *xcb_error_ctx;
43 | #endif
44 | };
45 |
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/c/src/xcb/event.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_EVENT_H
2 | #define TERRA_XCB_EVENT_H
3 |
4 | void event_handle(xcb_generic_event_t *e);
5 | #endif
6 |
--------------------------------------------------------------------------------
/c/src/xcb/scairo.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 |
5 | #include
6 |
7 | #include "lhelp.h"
8 |
9 | #include "xcb/xlhelp.h"
10 | #include "xcb/context.h"
11 |
12 | int luaH_scairo_surface_create_from_pixmap(lua_State *L)
13 | {
14 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
15 | xcb_pixmap_t pixmap_id = (xcb_pixmap_t)xlhelp_check_id(L, 2);
16 | u16 width = luaL_checkinteger(L, 3);
17 | u16 height = luaL_checkinteger(L, 4);
18 |
19 | cairo_surface_t *cairo_surface = cairo_xcb_surface_create(
20 | xc->connection,
21 | pixmap_id,
22 | xc->visual,
23 | width,
24 | height
25 | );
26 |
27 | // push the pointer directly as a lua number. TODO: is this safe?
28 | // Also: http://lua-users.org/wiki/LightUserData says
29 | // "Light userdata are intended to store C pointers in Lua (note:
30 | // Lua numbers may or may not be suitable for this purpose depending
31 | // on the data types on the platform)."
32 | // My problem was that I couldn't give lgi a lightuserdata and have
33 | // it understand it was a pointer, but it worked if I just gave it
34 | // a number, hence the use of an integer here.
35 | lua_pushinteger(L, (u64)cairo_surface);
36 | return 1;
37 | }
38 |
39 | int luaH_scairo_surface_set_pixmap(lua_State *L)
40 | {
41 | cairo_surface_t *cairo_surface = (cairo_surface_t *) lua_tointeger(L, 1);
42 | xcb_pixmap_t pixmap_id = (xcb_pixmap_t)xlhelp_check_id(L, 2);
43 | u16 width = luaL_checkinteger(L, 3);
44 | u16 height = luaL_checkinteger(L, 4);
45 |
46 | cairo_xcb_surface_set_drawable(cairo_surface, pixmap_id, width, height);
47 |
48 | return 0;
49 | }
50 |
51 | int luaH_scairo_surface_destroy(lua_State *L)
52 | {
53 | cairo_surface_t *cairo_surface = (cairo_surface_t *) lua_tointeger(L, 1);
54 | cairo_surface_finish(cairo_surface);
55 | cairo_surface_destroy(cairo_surface);
56 | return 0;
57 | }
58 |
59 | static const struct luaL_Reg lib_scairo[] = {
60 | { "create_from_pixmap", luaH_scairo_surface_create_from_pixmap },
61 | { "set_pixmap", luaH_scairo_surface_set_pixmap },
62 | { "destroy", luaH_scairo_surface_destroy },
63 | { NULL, NULL }
64 | };
65 |
66 | int luaopen_terra_platforms_xcb_scairo(lua_State *L)
67 | {
68 | luaL_newlib(L, lib_scairo);
69 | return 1;
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/c/src/xcb/spixmap.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 |
5 | #include
6 |
7 | #include "lhelp.h"
8 |
9 | #include "xcb/xlhelp.h"
10 | #include "xcb/context.h"
11 |
12 | int luaH_spixmap_create(lua_State *L)
13 | {
14 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
15 | u16 width = luaL_checkint(L, 2);
16 | u16 height = luaL_checkint(L, 3);
17 |
18 | xcb_pixmap_t pixmap_id = xcb_generate_id(xc->connection);
19 | xcb_create_pixmap(
20 | xc->connection,
21 | xc->visual_depth,
22 | pixmap_id,
23 | xc->screen->root, // drawable to get the screen from
24 | width,
25 | height
26 | );
27 |
28 | xlhelp_push_id(L, pixmap_id);
29 |
30 | return 1;
31 | }
32 |
33 | int luaH_spixmap_draw_portion_to_window(lua_State *L)
34 | {
35 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
36 | xcb_pixmap_t pix_id = (xcb_pixmap_t)xlhelp_check_id(L, 2);
37 | xcb_window_t win_id = (xcb_window_t)xlhelp_check_id(L, 3);
38 |
39 | u16 x = luaL_checkinteger(L, 4);
40 | u16 y = luaL_checkinteger(L, 5);
41 | u16 width = luaL_checkinteger(L, 6);
42 | u16 height = luaL_checkinteger(L, 7);
43 |
44 | xcb_copy_area(
45 | xc->connection,
46 | pix_id,
47 | win_id,
48 | xc->default_gc_id, // do we even need gcs in x anymore?
49 | x, x,
50 | y, y,
51 | width, height
52 | );
53 |
54 | return 0;
55 | }
56 |
57 | int luaH_spixmap_destroy(lua_State *L)
58 | {
59 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
60 | xcb_pixmap_t pix_id = (xcb_pixmap_t)xlhelp_check_id(L, 2);
61 | xcb_free_pixmap(xc->connection, pix_id);
62 | return 0;
63 | }
64 |
65 | static const struct luaL_Reg lib_spixmap[] = {
66 | { "create", luaH_spixmap_create },
67 | { "draw_portion_to_window", luaH_spixmap_draw_portion_to_window },
68 | { "destroy", luaH_spixmap_destroy },
69 | { NULL, NULL }
70 | };
71 |
72 | int luaopen_terra_platforms_xcb_spixmap(lua_State *L)
73 | {
74 | luaL_newlib(L, lib_spixmap);
75 | return 1;
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/c/src/xcb/swin.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | #include
5 |
6 | #include "lhelp.h"
7 |
8 | #include "xcb/window.h"
9 | #include "xlhelp.h"
10 | // #include "click.h"
11 | // #include "key.h"
12 |
13 | int luaH_swin_create(lua_State *L)
14 | {
15 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
16 | // TODO: maybe check that these don't go over bounds
17 | i16 x = luaL_checkint(L, 2);
18 | i16 y = luaL_checkint(L, 3);
19 | u16 width = luaL_checkint(L, 4);
20 | u16 height = luaL_checkint(L, 5);
21 | luaL_checktype(L, 6, LUA_TBOOLEAN);
22 | u8 override_redirect = lua_toboolean(L, 6);
23 | printf("override_redirect: %b\n", override_redirect);
24 |
25 | // these can never be 0
26 | if (width == 0) width = 1;
27 | if (height == 0) height = 1;
28 |
29 | // TODO: make this platform independent
30 | xcb_window_t x_window_id = terra_xcb_window_create(xc, x, y, width, height, override_redirect);
31 | xlhelp_push_id(L, x_window_id);
32 | xcb_flush(xc->connection);
33 | return 1;
34 | }
35 |
36 | int luaH_swin_map_request(lua_State *L) {
37 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
38 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
39 | terra_xcb_window_map_request(xc, x_win_id);
40 | xcb_flush(xc->connection);
41 | return 0;
42 | }
43 |
44 | int luaH_swin_unmap(lua_State *L) {
45 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
46 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
47 | terra_xcb_window_unmap(xc, x_win_id);
48 | return 0;
49 | }
50 |
51 | int luaH_swin_set_geometry_request(lua_State *L) {
52 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
53 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
54 |
55 | // TODO: maybe check that these don't go over bounds
56 | i16 x = (i16)luaL_checkint(L, 3);
57 | i16 y = (i16)luaL_checkint(L, 4);
58 | u16 width = (u16)luaL_checkint(L, 5);
59 | u16 height = (u16)luaL_checkint(L, 6);
60 |
61 | terra_xcb_window_set_geometry_request(xc, x_win_id, x, y, width, height);
62 | return 0;
63 | }
64 |
65 | int luaH_swin_set_coordinates_request(lua_State *L) {
66 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
67 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
68 |
69 | // TODO: maybe check that these don't go over bounds
70 | i16 x = (i16)luaL_checkint(L, 3);
71 | i16 y = (i16)luaL_checkint(L, 4);
72 |
73 | terra_xcb_window_set_coordinates_request(xc, x_win_id, x, y);
74 | return 0;
75 | }
76 |
77 | int luaH_swin_set_sizes_request(lua_State *L) {
78 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
79 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
80 |
81 | // TODO: maybe check that these don't go over bounds
82 | u16 width = (u16)luaL_checkint(L, 3);
83 | u16 height = (u16)luaL_checkint(L, 4);
84 |
85 | terra_xcb_window_set_sizes_request(xc, x_win_id, width, height);
86 | return 0;
87 | }
88 |
89 | int luaH_swin_destroy(lua_State *L)
90 | {
91 | struct XcbContext *xc = xlhelp_check_xcb_ctx(L, 1);
92 | xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 2);
93 | terra_xcb_window_destroy(xc, x_win_id);
94 | return 0;
95 | }
96 |
97 | // TODO: use properties for this (I think ICCCM had something for this)
98 | // int luaH_swin_focus_request(lua_State *L)
99 | // {
100 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
101 | // window_set_focus(x_win_id);
102 | // return 0;
103 | // }
104 |
105 |
106 | // // TODO: move this documentation somewhere else, or maybe just put a
107 | // // manual together and also include this.
108 | // // you should be able to use this from the lua side as such:
109 | // // `swin.subscribe_key(, )`
110 | // // where is a table of the form:
111 | // // {
112 | // // key: string,
113 | // // is_press: TRUE (for press) or FALSE (for release),
114 | // // modifiers : number (which is a 16 bit bitmask denoting which modifiers have to be pressed for this key to fire)
115 | // // }
116 | // int luaH_swin_subscribe_key(lua_State *L)
117 | // {
118 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
119 | // struct Key k = xlhelp_key_from_table(L); // expects table to be on top
120 | // window_subscribe_key(x_win_id, k);
121 | //
122 | // // printf("Subscribing Key on window %d:\n", win);
123 | // // printf("\tkeycode: %d\n", k.keycode);
124 | // // printf("\tkey_event: %d\n", k.key_event);
125 | // // printf("\tmodifiers: %b\n", k.modifiers);
126 | //
127 | // return 0;
128 | // }
129 | //
130 | // int luaH_swin_unsubscribe_key(lua_State *L)
131 | // {
132 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
133 | // // TODO: maybe rework this into `xlhelp_check_key`
134 | // struct Key k = xlhelp_key_from_table(L); // expects table to be on top
135 | // window_unsubscribe_key(x_win_id, k);
136 | // return 0;
137 | // }
138 | //
139 | // int luaH_swin_subscribe_click(lua_State *L)
140 | // {
141 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
142 | // struct Click c = xlhelp_click_from_table(L); // expects table to be on top
143 | // window_subscribe_click(x_win_id, c);
144 | // return 0;
145 | // }
146 | //
147 | // int luaH_swin_unsubscribe_click(lua_State *L)
148 | // {
149 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
150 | // // TODO: maybe rework this into `xlhelp_check_click`
151 | // struct Click c = xlhelp_click_from_table(L); // expects table to be on top
152 | // window_unsubscribe_click(x_win_id, c);
153 | // return 0;
154 | // }
155 | //
156 | // int luaH_swin_grab_pointer(lua_State *L)
157 | // {
158 | // xcb_window_t x_win_id = (xcb_window_t)xlhelp_check_id(L, 1);
159 | // const xcb_event_mask_t event_mask = (xcb_event_mask_t)luaL_checkinteger(L, 2);
160 | // window_grab_pointer(x_win_id, event_mask);
161 | // return 0;
162 | // }
163 | //
164 | // int luaH_swin_ungrab_pointer(lua_State *L)
165 | // {
166 | // window_ungrab_pointer();
167 | // return 0;
168 | // }
169 | //
170 | // int luaH_swin_window_stack_above(lua_State *L)
171 | // {
172 | // xcb_window_t below = (xcb_window_t)xlhelp_check_id(L, 1);
173 | // xcb_window_t win_id = (xcb_window_t)xlhelp_check_id(L, 2);
174 | //
175 | // window_stack_above(below, win_id);
176 | // return 0;
177 | // }
178 |
179 | static const struct luaL_Reg lib_swin[] = {
180 | { "create", luaH_swin_create },
181 | { "destroy", luaH_swin_destroy },
182 | { "map_request", luaH_swin_map_request },
183 | { "unmap", luaH_swin_unmap },
184 | { "set_geometry_request", luaH_swin_set_geometry_request },
185 | { "set_coordinates_request", luaH_swin_set_coordinates_request },
186 | { "set_sizes_request", luaH_swin_set_sizes_request },
187 | // { "change_event_mask", luaH_swin_change_event_mask },
188 | // { "set_focus", luaH_swin_set_focus },
189 | // { "map_request", luaH_swin_map_request },
190 | // { "subscribe_key", luaH_swin_subscribe_key },
191 | // { "unsubscribe_key", luaH_swin_unsubscribe_key },
192 | // { "subscribe_click", luaH_swin_subscribe_click },
193 | // { "unsubscribe_click", luaH_swin_unsubscribe_click },
194 | // { "grab_pointer", luaH_swin_grab_pointer },
195 | // { "ungrab_pointer", luaH_swin_ungrab_pointer },
196 | // { "stack_above", luaH_swin_window_stack_above },
197 | { NULL, NULL }
198 | };
199 |
200 | int luaopen_terra_platforms_xcb_swin(lua_State *L)
201 | {
202 | luaL_newlib(L, lib_swin);
203 | return 1;
204 | }
205 |
206 |
207 |
--------------------------------------------------------------------------------
/c/src/xcb/terra_xkb.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include // for `memcpy`
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "sane.h"
10 |
11 | #include "xcb/terra_xkb.h"
12 | #include "xcb/context.h"
13 | #include "xcb/xcb_ctx.h"
14 |
15 |
16 | // returns true if the given utf-8 string is a control character such as
17 | // Control or Shift, etc.
18 | bool _is_control_character(char *str)
19 | {
20 | if (str[0] == (0x7f)/*(127)*/) return TRUE;
21 | if (str[0] >= 0 && str[0] < 0x20) return TRUE;
22 | return FALSE;
23 | }
24 |
25 | // NOTE: we use this "keybuffer" struct because for some reason we can't just
26 | // return a simple 64 byte buffer, even though C has ways of specifying how
27 | // large this buffer return value is going to be.
28 | struct Keybuffer terra_xkb_keycode_to_string(xcb_keycode_t keycode)
29 | {
30 | // XXX: for now, we just use 64 bytes which is usually enough. The output
31 | // may get truncated, but its a sacrifice I am willing to make.
32 | // If someone complains, we'll fix this then
33 | static char str[KEY_NAME_BUFFER_LENGTH]; // xkbcommon/xkbcommon.h recommends a
34 | // length of at least 64 bytes, so lets try that.
35 |
36 | // int string_length = xkb_state_key_get_utf8(
37 | xkb_state_key_get_utf8(
38 | xcb_ctx.xkb_state,
39 | keycode,
40 | str,
41 | KEY_NAME_BUFFER_LENGTH
42 | );
43 |
44 | // if the key pressed is a control character, just push the key name,
45 | // like "Control", "Shift", etc.
46 | if (_is_control_character(str)) {
47 | xcb_keysym_t keysym = xcb_key_symbols_get_keysym(
48 | xcb_ctx.keysyms,
49 | keycode,
50 | 0 // col ?? (thats what xcb/xcb_keysyms.h says)
51 | );
52 | // the same truncation rules as above apply here. We'll fix it
53 | // if we need to
54 | xkb_keysym_get_name(keysym, str, KEY_NAME_BUFFER_LENGTH);
55 | }
56 |
57 | struct Keybuffer kbf;
58 | memcpy(&kbf.key_str, str, KEY_NAME_BUFFER_LENGTH);
59 |
60 | return kbf;
61 | }
62 |
63 |
64 | // initialize xkb
65 | void terra_xkb_init(void)
66 | {
67 | // TRUE on success, FALSE on error
68 | int xkb_success = xkb_x11_setup_xkb_extension(
69 | xcb_ctx.connection,
70 | XKB_X11_MIN_MAJOR_XKB_VERSION,
71 | XKB_X11_MIN_MINOR_XKB_VERSION,
72 | 0, // extension flags
73 | NULL, // major_xkb_version_out
74 | NULL, // minor_xkb_version_out
75 | NULL, // base_event_out
76 | NULL // base_error_out
77 | );
78 |
79 | if (xkb_success == FALSE) {
80 | fprintf(stderr, "COULDN'T INITIALIZE XKB. EXITING.\n");
81 | exit(1);
82 | }
83 |
84 | u16 event_mask =
85 | XCB_XKB_EVENT_TYPE_STATE_NOTIFY
86 | | XCB_XKB_EVENT_TYPE_MAP_NOTIFY
87 | | XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY;
88 |
89 | // maps used to allow key remapping in terra
90 | u16 remapping_mask =
91 | XCB_XKB_MAP_PART_KEY_TYPES
92 | | XCB_XKB_MAP_PART_KEY_SYMS
93 | | XCB_XKB_MAP_PART_MODIFIER_MAP
94 | | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS
95 | | XCB_XKB_MAP_PART_KEY_ACTIONS
96 | | XCB_XKB_MAP_PART_KEY_BEHAVIORS
97 | | XCB_XKB_MAP_PART_VIRTUAL_MODS
98 | | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP;
99 |
100 | // enable detectable auto-repeat, but ignore failures // TODO: what exactly does this mean
101 | xcb_xkb_per_client_flags_cookie_t pclient_flags;
102 | pclient_flags = xcb_xkb_per_client_flags(
103 | xcb_ctx.connection,
104 | XCB_XKB_ID_USE_CORE_KBD, // deviceSpec
105 | XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, // change
106 | XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, // value
107 | 0, // ctrlsToChange
108 | 0, // autoCtrls
109 | 0 // autoCtrlsValues
110 | );
111 | xcb_discard_reply(
112 | xcb_ctx.connection,
113 | pclient_flags.sequence
114 | );
115 |
116 | xcb_xkb_select_events(
117 | xcb_ctx.connection,
118 | XCB_XKB_ID_USE_CORE_KBD, // deviceSpec
119 | event_mask, // affectWhich
120 | 0, // clear
121 | event_mask, // selectAll
122 | remapping_mask, // affectMap
123 | remapping_mask, // map
124 | 0 // details // TODO: I think this should be NULL
125 | );
126 |
127 | // Init keymap
128 |
129 | // The steps go as follows:
130 | // (1) you create an xkb_context, which you use to
131 | // (2) create an xkb_keymap, which you use to
132 | // (3) create an xkb_state
133 | // and finally, in the event handling function, you
134 | // (4) use the xkb_state in a function like `xkb_state_key_get_utf8` to
135 | // get the string representation of the key pressed or released, etc.
136 | xcb_ctx.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
137 | if (xcb_ctx.xkb_ctx == NULL) {
138 | fprintf(stderr, "Could not create XKB context used for resolving keypresses. Exiting.\n");
139 | exit(1);
140 | }
141 |
142 | // TODO: learn about xkb concepts like layouts, rules, devices, keymaps, etc.
143 | // And xkb in general, and provide lua APIs for working with them
144 |
145 | // try to get id of the current keyboard
146 | i32 device_id = xkb_x11_get_core_keyboard_device_id(xcb_ctx.connection);
147 | if (device_id == -1) {
148 | fprintf(stderr, "Could not get XKB device id. Exiting.");
149 | exit(1);
150 | }
151 |
152 | struct xkb_keymap *xkb_keymap = xkb_x11_keymap_new_from_device(
153 | xcb_ctx.xkb_ctx,
154 | xcb_ctx.connection,
155 | device_id,
156 | XKB_KEYMAP_COMPILE_NO_FLAGS
157 | );
158 |
159 | if (xkb_keymap == NULL) {
160 | fprintf(stderr, "Could not get XKB keymap from device. Exiting.");
161 | exit(1);
162 | }
163 |
164 | xcb_ctx.xkb_state = xkb_x11_state_new_from_device(
165 | xkb_keymap,
166 | xcb_ctx.connection,
167 | device_id
168 | );
169 |
170 | // we're done using this keymap, so unref it
171 | xkb_keymap_unref(xkb_keymap);
172 |
173 | if (xcb_ctx.xkb_state == NULL) {
174 | fprintf(stderr, "Could not get XKB state from device. Exiting.");
175 | exit(1);
176 | }
177 |
178 | }
179 |
180 |
181 | void terra_xkb_free(void)
182 | {
183 | // unsubscribe from all events
184 | xcb_xkb_select_events(
185 | xcb_ctx.connection,
186 | XCB_XKB_ID_USE_CORE_KBD,
187 | 0,
188 | 0,
189 | 0,
190 | 0,
191 | 0,
192 | 0
193 | );
194 |
195 | // free keymap related data
196 | xkb_state_unref(xcb_ctx.xkb_state);
197 | xkb_context_unref(xcb_ctx.xkb_ctx);
198 | }
199 |
200 |
201 |
--------------------------------------------------------------------------------
/c/src/xcb/terra_xkb.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_TERRA_XKB_H
2 | #define TERRA_XCB_TERRA_XKB_H
3 |
4 | #define KEY_NAME_BUFFER_LENGTH 64
5 |
6 | struct Keybuffer {
7 | char key_str[KEY_NAME_BUFFER_LENGTH];
8 | };
9 |
10 | void terra_xkb_init(void);
11 | void terra_xkb_free(void);
12 |
13 | struct Keybuffer terra_xkb_keycode_to_string(xcb_keycode_t keycode);
14 |
15 | #endif
16 |
--------------------------------------------------------------------------------
/c/src/xcb/vidata.c:
--------------------------------------------------------------------------------
1 |
2 | #include // for `fprintf`, `stderr`
3 | #include // for `exit`
4 |
5 | #include
6 |
7 | #include "sane.h"
8 |
9 | #include "xcb/vidata.h"
10 |
11 | struct VisualData vidata_find_argb(const xcb_screen_t *s)
12 | {
13 | struct VisualData vdata;
14 | vdata.visual = NULL; // used to check for failure
15 | vdata.visual_depth = 0;
16 |
17 | xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
18 |
19 | if(depth_iter.data == NULL) {
20 | return vdata;
21 | }
22 |
23 | while(depth_iter.rem != 0) {
24 |
25 | if (depth_iter.data->depth != 32) {
26 | printf(
27 | "Found depth_iterator with depth %d. Skipping.\n",
28 | depth_iter.data->depth
29 | );
30 | xcb_depth_next(&depth_iter);
31 | continue;
32 | }
33 |
34 | printf(
35 | "Successfully found depth_iterator with depth %d.\n",
36 | depth_iter.data->depth
37 | );
38 |
39 | xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
40 | while (visual_iter.rem != 0) {
41 | vdata.visual = visual_iter.data;
42 | vdata.visual_depth = depth_iter.data->depth;
43 | // TODO: are these any different from each other?
44 | // printf("Found Visual:\n");
45 | // printf("\tbits_per_rgb_value: %d\n", visual_iter.data->bits_per_rgb_value);
46 | // printf("\t_class: %d\n", visual_iter.data->_class);
47 | // printf("\tcolormap_entries: %d\n", visual_iter.data->colormap_entries);
48 | // printf("\tred_mask: %d\n", visual_iter.data->red_mask);
49 | // printf("\tgreen_mask: %d\n", visual_iter.data->green_mask);
50 | // printf("\tblue_mask: %d\n", visual_iter.data->blue_mask);
51 | return vdata;
52 | xcb_visualtype_next(&visual_iter);
53 | }
54 | }
55 | return vdata;
56 | }
57 |
58 | struct VisualData vidata_find_default(const xcb_screen_t *s)
59 | {
60 | struct VisualData vdata;
61 | vdata.visual = NULL; // used to check for failure
62 | vdata.visual_depth = 0;
63 |
64 | xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
65 |
66 | if(depth_iter.data == NULL) {
67 | return vdata;
68 | }
69 |
70 | printf(
71 | "Found depth_iterator with depth %d. Using this one as default.\n",
72 | depth_iter.data->depth
73 | );
74 |
75 | xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
76 | while (visual_iter.rem != 0) {
77 | vdata.visual = visual_iter.data;
78 | vdata.visual_depth = depth_iter.data->depth;
79 | // TODO: are these any different from each other?
80 | // printf("Found Visual:\n");
81 | // printf("\tbits_per_rgb_value: %d\n", visual_iter.data->bits_per_rgb_value);
82 | // printf("\t_class: %d\n", visual_iter.data->_class);
83 | // printf("\tcolormap_entries: %d\n", visual_iter.data->colormap_entries);
84 | // printf("\tred_mask: %d\n", visual_iter.data->red_mask);
85 | // printf("\tgreen_mask: %d\n", visual_iter.data->green_mask);
86 | // printf("\tblue_mask: %d\n", visual_iter.data->blue_mask);
87 | return vdata;
88 | xcb_visualtype_next(&visual_iter);
89 | }
90 |
91 | return vdata;
92 | }
93 |
94 | bool vidata_is_invalid(struct VisualData vidata)
95 | {
96 | if (vidata.visual == NULL) {
97 | return TRUE;
98 | } else {
99 | return FALSE;
100 | }
101 | }
102 |
103 | // u8 vidata_find_visual_depth(const xcb_screen_t *s, xcb_visualid_t vid)
104 | // {
105 | // xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(s);
106 | //
107 | // if(!depth_iter.data) { // TODO: check
108 | // goto abort;
109 | // }
110 | // while (depth_iter.rem) { // TODO: check
111 | // xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
112 | // while (visual_iter.rem) { // TODO: check
113 | // if(vid == visual_iter.data->visual_id) {
114 | // return depth_iter.data->depth;
115 | // }
116 | // xcb_visualtype_next(&visual_iter);
117 | // }
118 | // xcb_depth_next(&depth_iter);
119 | // }
120 | // goto abort; // if everything failed, just close everything
121 | //
122 | // abort:
123 | // fprintf(stderr, "FATAL: Could not find a visual's depth");
124 | // exit(1); // TODO: exit with another code once we have all of them figured out?
125 | // }
126 |
127 |
--------------------------------------------------------------------------------
/c/src/xcb/vidata.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_VIDATA_H
2 | #define TERRA_XCB_VIDATA_H
3 |
4 | #include
5 |
6 | #include "sane.h"
7 |
8 | struct VisualData {
9 | xcb_visualtype_t *visual;
10 | u8 visual_depth;
11 | };
12 |
13 | bool vidata_is_invalid(struct VisualData vidata);
14 | struct VisualData vidata_find_argb(const xcb_screen_t *s);
15 | struct VisualData vidata_find_default(const xcb_screen_t *s);
16 |
17 | #endif
18 |
--------------------------------------------------------------------------------
/c/src/xcb/window.c:
--------------------------------------------------------------------------------
1 |
2 | // #include // for `free` // TODO: remove
3 | #include // for `printf` TODO: remove
4 |
5 | #include
6 |
7 | #include "xcb/context.h"
8 |
9 |
10 | xcb_window_t terra_xcb_window_create(struct XcbContext *xc, i16 x, i16 y, u16 width, u16 height, u8 override_redirect)
11 | {
12 | // lhelp_dump_stack(L);
13 |
14 | xcb_event_mask_t ev_mask =
15 | XCB_EVENT_MASK_KEY_PRESS
16 | | XCB_EVENT_MASK_KEY_RELEASE
17 | | XCB_EVENT_MASK_BUTTON_PRESS
18 | | XCB_EVENT_MASK_BUTTON_RELEASE
19 | | XCB_EVENT_MASK_ENTER_WINDOW
20 | | XCB_EVENT_MASK_LEAVE_WINDOW
21 | | XCB_EVENT_MASK_POINTER_MOTION
22 | | XCB_EVENT_MASK_EXPOSURE
23 | | XCB_EVENT_MASK_VISIBILITY_CHANGE
24 | | XCB_EVENT_MASK_STRUCTURE_NOTIFY
25 | | XCB_EVENT_MASK_FOCUS_CHANGE
26 | | XCB_EVENT_MASK_PROPERTY_CHANGE;
27 |
28 | // | XCB_EVENT_MASK_KEYMAP_STATE
29 | // | XCB_EVENT_MASK_RESIZE_REDIRECT
30 | // | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY
31 | // | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
32 | // | XCB_EVENT_MASK_COLOR_MAP_CHANGE
33 | // | XCB_EVENT_MASK_OWNER_GRAB_BUTTON
34 |
35 | xcb_window_t x_window_id = xcb_generate_id(xc->connection);
36 |
37 | // NOTE: apparently we MUST specify values for BACK_PIXEL and
38 | // BORDER_PIXEL, otherwise the window doesn't show up? I don't know why.
39 | u32 x_window_value_mask =
40 | XCB_CW_BACK_PIXEL
41 | | XCB_CW_BORDER_PIXEL
42 | | XCB_CW_BIT_GRAVITY
43 | | XCB_CW_OVERRIDE_REDIRECT
44 | | XCB_CW_EVENT_MASK
45 | | XCB_CW_COLORMAP;
46 | // XCB_CW_CURSOR; // TODO: select cursor
47 | u32 x_window_value_list[] = {
48 | xc->screen->black_pixel,
49 | xc->screen->black_pixel,
50 | XCB_GRAVITY_NORTH_WEST,
51 | override_redirect,
52 | ev_mask,
53 | xc->default_colormap_id
54 | };
55 | xcb_create_window(
56 | xc->connection,
57 | xc->visual_depth,
58 | x_window_id,
59 | xc->screen->root, // parent id
60 | x,
61 | y,
62 | width,
63 | height,
64 | 0, // border width (the window manager will take care of this)
65 | XCB_WINDOW_CLASS_INPUT_OUTPUT,
66 | // XCB_WINDOW_CLASS_COPY_FROM_PARENT,
67 | xc->visual->visual_id, // TODO: learn more about visuals
68 | x_window_value_mask,
69 | x_window_value_list
70 | );
71 |
72 | return x_window_id;
73 | }
74 |
75 | void terra_xcb_window_change_cursor(struct XcbContext *xc, xcb_window_t x_window_id, char *cursor_str)
76 | {
77 | xcb_cursor_t new_cursor = xcb_cursor_load_cursor(xc->cursor_ctx, cursor_str);
78 | xcb_cursor_t old_cursor = xc->current_cursor;
79 | xcb_change_window_attributes(
80 | xc->connection,
81 | x_window_id,
82 | XCB_CW_CURSOR,
83 | (u32[]){ new_cursor }
84 | );
85 | xcb_free_cursor(xc->connection, old_cursor);
86 | xc->current_cursor = new_cursor;
87 | }
88 |
89 | void terra_xcb_window_set_geometry_request(
90 | struct XcbContext *xc,
91 | xcb_window_t x_window_id,
92 | i16 x,
93 | i16 y,
94 | u16 width,
95 | u16 height
96 | ) {
97 | u32 window_config_mask =
98 | XCB_CONFIG_WINDOW_X
99 | | XCB_CONFIG_WINDOW_Y
100 | | XCB_CONFIG_WINDOW_WIDTH
101 | | XCB_CONFIG_WINDOW_HEIGHT;
102 |
103 | const i32 window_config_values[] = { x, y, width, height };
104 |
105 | xcb_configure_window(
106 | xc->connection,
107 | x_window_id,
108 | window_config_mask,
109 | window_config_values
110 | );
111 | // xcb_flush(xc->connection);
112 | }
113 |
114 | void terra_xcb_window_set_sizes_request(struct XcbContext *xc, xcb_window_t x_window_id, u16 width, u16 height)
115 | {
116 | u32 client_config_mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
117 | const u32 client_config_values[] = { width, height };
118 | xcb_configure_window(
119 | xc->connection,
120 | x_window_id,
121 | client_config_mask,
122 | client_config_values
123 | );
124 | }
125 |
126 | void terra_xcb_window_set_coordinates_request(struct XcbContext *xc, xcb_window_t x_window_id, i16 x, i16 y)
127 | {
128 | u32 client_config_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
129 | const u32 client_config_values[] = { x, y };
130 |
131 | xcb_configure_window(
132 | xc->connection,
133 | x_window_id,
134 | client_config_mask,
135 | client_config_values
136 | );
137 | }
138 |
139 |
140 | // void terra_xcb_window_map_request(struct XcbContext *xc, xcb_window_t x_window_id)
141 | // {
142 | // xcb_map_request_event_t mr;
143 | //
144 | // mr.response_type = XCB_MAP_REQUEST;
145 | // mr.parent = xc->screen->root;
146 | // mr.window = x_window_id;
147 | // xcb_send_event(
148 | // xc->connection,
149 | // FALSE, // override_redirect
150 | // xc->screen->root,
151 | // XCB_EVENT_MASK_STRUCTURE_NOTIFY,
152 | // (char *) &mr
153 | // );
154 | // // xcb_flush(xc->connection);
155 | // }
156 |
157 | void terra_xcb_window_map_request(struct XcbContext *xc, xcb_window_t x_window_id)
158 | {
159 | xcb_map_window(xc->connection, x_window_id);
160 | }
161 |
162 | void terra_xcb_window_unmap(struct XcbContext *xc, xcb_window_t x_window_id)
163 | {
164 | xcb_unmap_window(xc->connection, x_window_id);
165 | }
166 |
167 | void terra_xcb_window_destroy(struct XcbContext *xc, xcb_window_t x_window_id)
168 | {
169 | xcb_destroy_window(xc->connection, x_window_id);
170 | }
171 |
172 | // // TODO: make this work based on properties
173 | // void terra_xcb_window_set_focus_request(struct XcbContext *xc, xcb_window_t window_id) {
174 | // if (window_id == XCB_NONE) return;
175 | // xcb_set_input_focus(
176 | // xc->connection,
177 | // XCB_INPUT_FOCUS_POINTER_ROOT,
178 | // // XCB_INPUT_FOCUS_NONE,
179 | // window_id,
180 | // XCB_CURRENT_TIME
181 | // );
182 | // }
183 |
184 |
185 | // // TODO: maybe I should just let users only rely on automatic grabs?
186 | // void terra_xcb_window_grab_pointer(xcb_window_t x_win_id, xcb_event_mask_t event_mask)
187 | // {
188 | // xcb_grab_pointer(
189 | // xc->connection,
190 | // FALSE, // "owner_events" (if the grab window should still get the events)
191 | // x_win_id,
192 | // event_mask,
193 | // XCB_GRAB_MODE_ASYNC,
194 | // XCB_GRAB_MODE_ASYNC,
195 | // XCB_NONE, // confine_to
196 | // XCB_NONE, // cursor
197 | // XCB_CURRENT_TIME
198 | // );
199 | // }
200 | // void terra_xcb_window_ungrab_pointer()
201 | // {
202 | // xcb_ungrab_pointer(xc->connection, XCB_CURRENT_TIME);
203 | // }
204 | //
205 |
206 | // void terra_xcb_window_subscribe_key(xcb_window_t x_win_id, struct Key key)
207 | // {
208 | // // xcb_keycode_t *keycode = util_xcb_keysym_to_keycode(keybindings[i].keysym); // TODO: dont I have to free this??
209 | // xcb_grab_key(
210 | // xc->connection,
211 | // FALSE, // owner_events. Should the window still get the event for this key?
212 | // x_win_id,
213 | // key.modifiers,
214 | // key.keycode,
215 | // XCB_GRAB_MODE_ASYNC,
216 | // XCB_GRAB_MODE_ASYNC
217 | // );
218 | // }
219 | //
220 | // void terra_xcb_window_unsubscribe_key(xcb_window_t x_win_id, struct Key key)
221 | // {
222 | // xcb_ungrab_key(
223 | // xc->connection,
224 | // key.keycode,
225 | // x_win_id,
226 | // key.modifiers
227 | // );
228 | // }
229 | //
230 | // void terra_xcb_window_subscribe_click(xcb_window_t x_win_id, struct Click click)
231 | // {
232 | // xcb_grab_button(
233 | // xc->connection,
234 | // TRUE, // owner events. should the grabbing window still get events?
235 | // x_win_id,
236 | // XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
237 | // XCB_GRAB_MODE_ASYNC,
238 | // XCB_GRAB_MODE_ASYNC,
239 | // xc->screen->root, // confine_to window
240 | // // x_win_id,
241 | // XCB_NONE, // cursor
242 | // click.button,
243 | // click.modifiers
244 | // );
245 | // }
246 | //
247 | // void terra_xcb_window_unsubscribe_click(xcb_window_t x_win_id, struct Click click)
248 | // {
249 | // xcb_ungrab_button(
250 | // xc->connection,
251 | // click.button,
252 | // x_win_id,
253 | // click.modifiers
254 | // );
255 | // }
256 |
257 |
--------------------------------------------------------------------------------
/c/src/xcb/window.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_WINDOW_H
2 | #define TERRA_XCB_WINDOW_H
3 |
4 | #include
5 |
6 | #include "xcb/context.h"
7 | #include "sane.h"
8 |
9 | xcb_window_t terra_xcb_window_create(struct XcbContext *xc, i16 x, i16 y, u16 width, u16 height, u8 override_redirect);
10 | void terra_xcb_window_change_cursor(struct XcbContext *xc, xcb_window_t x_window_id, char *cursor_str);
11 | void terra_xcb_window_set_geometry_request(struct XcbContext *xc, xcb_window_t x_window_id, i16 x, i16 y, u16 width, u16 height);
12 | void terra_xcb_window_set_sizes_request(struct XcbContext *xc, xcb_window_t x_window_id, u16 width, u16 height);
13 | void terra_xcb_window_set_coordinates_request(struct XcbContext *xc, xcb_window_t x_window_id, i16 x, i16 y);
14 | void terra_xcb_window_map_request(struct XcbContext *xc, xcb_window_t x_window_id);
15 | void terra_xcb_window_unmap(struct XcbContext *xc, xcb_window_t x_window_id);
16 | void terra_xcb_window_destroy(struct XcbContext *xc, xcb_window_t x_window_id);
17 |
18 | #endif
19 |
--------------------------------------------------------------------------------
/c/src/xcb/xcb_ctx.c:
--------------------------------------------------------------------------------
1 |
2 | #include "context.h"
3 |
4 | struct XcbContext xcb_ctx;
5 |
6 |
--------------------------------------------------------------------------------
/c/src/xcb/xcb_ctx.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_XCB_CTX_H
2 | #define TERRA_XCB_XCB_CTX_H
3 |
4 | #include "xcb_ctx.h"
5 |
6 | extern struct XcbContext xcb_ctx;
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/c/src/xcb/xlhelp.c:
--------------------------------------------------------------------------------
1 | #ifndef XCB_XLHELP_H
2 | #define XCB_XLHELP_H
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "lhelp.h"
10 | #include "util.h"
11 | #include "sane.h"
12 |
13 | #include "xcb/xlhelp.h"
14 |
15 | struct XcbContext *xlhelp_check_xcb_ctx(lua_State *L, int ind)
16 | {
17 | struct XcbContext *xc = (struct XcbContext *)lua_touserdata(L, ind);
18 | if (xc == NULL) {
19 | printf("TERRA ERROR - supplied argument is not an 'XcbContext': %s.\n", lua_tostring(L, ind));
20 | util_backtrace_print();
21 | exit(1);
22 | }
23 | return xc;
24 | }
25 |
26 | void xlhelp_push_id(lua_State *L, u32 x_id)
27 | {
28 | lua_pushinteger(L, x_id);
29 | lua_tostring(L, -1); // convert the id to a lua string
30 | }
31 |
32 | u32 xlhelp_check_id(lua_State *L, i32 ind)
33 | {
34 | u32 id = lua_tointeger(L, ind);
35 | if (id == 0) {
36 | // TODO: dostring("debug.traceback")
37 | printf("ERROR - invalid X11 id: %s. (TODO: print a traceback)\n", lua_tostring(L, ind));
38 | util_backtrace_print();
39 | exit(1);
40 | }
41 | return id;
42 | }
43 |
44 | // int lhelp_store_event_handler(lua_State *L)
45 | // {
46 | // // the top function should be the event handler supplied to us by the user.
47 | // // we don't need it now, so put it in the registry.
48 | // lua_setfield(L, LUA_REGISTRYINDEX, TERRA_LUA_REGISTRY_EVENT_HANDLER_KEY);
49 | //
50 | // // by this point, we should only be left with one function on the stack:
51 | // // the model initializing function
52 | //
53 | // // push the on_runtime_error function at the beginning of the stack
54 | // lua_pushcfunction(L, lhelp_function_on_runtime_error);
55 | // lua_insert(L, 1);
56 | //
57 | // // finally, run the model initializing function, which should return
58 | // // to us with the model.
59 | // lua_pushlightuserdata(L, &app);
60 | // int runtime_error = lua_pcall(L, 1, 1, 1);
61 | // if (runtime_error != 0) return runtime_error;
62 | //
63 | // // then keep the model safe by storing it into the lua registry
64 | // lua_setfield(L, LUA_REGISTRYINDEX, TERRA_LUA_REGISTRY_MODEL_KEY);
65 | //
66 | // // remove `lhelp_function_on_runtime_error` function
67 | // lua_remove(L, 1);
68 | // return 0;
69 | // }
70 |
71 | // setup the lua equivalent of `event_handler(xcb_ctx, model, `
72 | // the should be pushed by the user of this function. This is
73 | // usually done in the event handling portion of the C code
74 | void xlhelp_setup_event_handler(lua_State *L)
75 | {
76 | lua_pushcfunction(L, lhelp_function_on_runtime_error);
77 | lua_getfield(L, LUA_REGISTRYINDEX, TERRA_LUA_REGISTRY_EVENT_HANDLER_KEY);
78 | }
79 |
80 | // after calling `lhelp_setup_event_handler`, and pushing onto the stack the
81 | // desired event, you can call this function to have it automatically call
82 | // the event handler properly
83 | void xlhelp_call_event_handler(lua_State *L, uint nr_params)
84 | {
85 | int runtime_error = lua_pcall(L, nr_params, 0, 1);
86 | if (runtime_error != 0) {
87 | fprintf(stderr, "%s\n", lua_tostring(L, -1));
88 | lua_pop(L, 1); // pop error message
89 | }
90 | lua_pop(L, 1); // pop the error function
91 | }
92 |
93 | #endif
94 |
--------------------------------------------------------------------------------
/c/src/xcb/xlhelp.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_XLHELP_H
2 | #define TERRA_XCB_XLHELP_H
3 |
4 | #include
5 | #include
6 |
7 | #include "sane.h"
8 |
9 | #define TERRA_LUA_REGISTRY_EVENT_HANDLER_KEY "terra_lua_event_handler"
10 |
11 | struct XcbContext *xlhelp_check_xcb_ctx(lua_State *L, int ind);
12 | u32 xlhelp_check_id(lua_State *L, i32 ind);
13 | void xlhelp_push_id(lua_State *L, u32 x_id);
14 |
15 | void xlhelp_setup_event_handler(lua_State *L);
16 | void xlhelp_call_event_handler(lua_State *L, uint nr_parameters);
17 |
18 | #endif
19 |
--------------------------------------------------------------------------------
/c/src/xcb/xutil.c:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #ifdef DEBUG
8 | #include
9 | #endif
10 | #include // for `XStringToKeysym()`
11 |
12 | #include "xcb/context.h"
13 | #include "xcb/xcb_ctx.h"
14 |
15 | xcb_keycode_t xutil_string_to_keycode(const char *str)
16 | {
17 | xcb_keysym_t keysym = XStringToKeysym(str);
18 | // TODO: in "/usr/include/xcb/xcb_keysyms.h" it says that this function
19 | // can be slow. Maybe we can do it some other way?
20 | xcb_keycode_t *keycodes = xcb_key_symbols_get_keycode(xcb_ctx.keysyms, keysym);
21 |
22 | if(keycodes == NULL) return 0;
23 |
24 | // TODO: returning only the first is probably not ok
25 | xcb_keycode_t keycode = keycodes[0];
26 | free(keycodes); // we are responsible for freeing the keycodes
27 |
28 | return keycode;
29 | }
30 |
31 | xcb_keycode_t *xutil_xcb_keysym_to_keycode(xcb_keysym_t keysym) {
32 | // FIXME: memory leak. this should be freed
33 | xcb_keycode_t *keycode = xcb_key_symbols_get_keycode(xcb_ctx.keysyms, keysym);
34 | return keycode;
35 | }
36 |
37 | xcb_keysym_t xutil_xcb_keycode_to_keysym(xcb_keycode_t keycode) {
38 | xcb_keysym_t keysym = xcb_key_symbols_get_keysym(xcb_ctx.keysyms, keycode, 0);
39 | return keysym;
40 | }
41 |
42 | void xutil_xerror_handle(xcb_generic_error_t *error)
43 | {
44 | #ifdef DEBUG
45 | // ignore this
46 | // if(e->error_code == XCB_WINDOW
47 | // || (e->error_code == XCB_MATCH
48 | // && e->major_code == XCB_SET_INPUT_FOCUS)
49 | // || (e->error_code == XCB_VALUE
50 | // && e->major_code == XCB_KILL_CLIENT)
51 | // || (e->error_code == XCB_MATCH
52 | // && e->major_code == XCB_CONFIGURE_WINDOW))
53 | // return;
54 |
55 | const char *major = xcb_errors_get_name_for_major_code(
56 | xcb_ctx.xcb_error_ctx,
57 | error->major_code
58 | );
59 | const char *minor = xcb_errors_get_name_for_minor_code(
60 | xcb_ctx.xcb_error_ctx,
61 | error->major_code,
62 | error->minor_code
63 | );
64 | const char *extension = NULL;
65 | const char *error_str = xcb_errors_get_name_for_error(
66 | xcb_ctx.xcb_error_ctx,
67 | error->error_code,
68 | &extension
69 | );
70 |
71 | fprintf(stderr, "X error: request - %s%s%s (major %d, minor %d); \terror - (%d) %s%s%s; \tresource_id - (%d)\n",
72 | major,
73 | minor == NULL ? "" : "-",
74 | minor == NULL ? "" : minor,
75 | error->major_code,
76 | error->minor_code,
77 | error->error_code,
78 | extension == NULL ? "" : extension,
79 | extension == NULL ? "" : "-",
80 | error_str,
81 | error->resource_id
82 | );
83 | #else
84 | fprintf(stderr, "XCB ERROR:\n");
85 | fprintf(stderr, "\terror_code: %d\n", error->error_code);
86 | fprintf(stderr, "\tsequence: %d\n", error->sequence);
87 | fprintf(stderr, "\tresource_id: %d\n", error->resource_id);
88 | fprintf(stderr, "\tminor_code: %d\n", error->minor_code);
89 | fprintf(stderr, "\tmajor_code: %d\n", error->major_code);
90 | #endif
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/c/src/xcb/xutil.h:
--------------------------------------------------------------------------------
1 | #ifndef TERRA_XCB_XUTIL_H
2 | #define TERRA_XCB_XUTIL_H
3 |
4 | #include
5 |
6 | xcb_keycode_t xutil_string_to_keycode(const char *str);
7 | xcb_keycode_t *xutil_xcb_keysym_to_keycode(xcb_keysym_t keysym);
8 | xcb_keysym_t xutil_xcb_keycode_to_keysym(xcb_keycode_t keycode);
9 |
10 | void xutil_xerror_handle(xcb_generic_error_t *error);
11 |
12 | #endif
13 |
--------------------------------------------------------------------------------
/l/_retired.lua:
--------------------------------------------------------------------------------
1 |
2 | -- TODO: make it so that the user has the option to either handle platform specific events directly or abstract them away automatically
3 | -- LEFTOFF: make signals work on windows properly
4 |
5 | local te_xcb = require("terra.events")
6 |
7 | local events = te_xcb.events
8 |
9 | -- -- TODO: this should automatically clean up after itself
10 | -- local function destroy(window)
11 | --
12 | -- twindow.destroy(window)
13 | -- tsoil.destroy(window.soil)
14 | --
15 | -- if window.branch == nil then return end
16 | --
17 | -- -- TODO: maybe the user should manually detach the branch?
18 | -- ou_internal.element_recursively_detach(window.branch)
19 | --
20 | -- _root_unsubscribe_functions(root, root.model)
21 | --
22 | -- end
23 |
24 | local function _handle_configure_notify_event(window, event_type, window_id, x, y, width, height)
25 |
26 | local size_changed = false
27 |
28 | if window.width ~= event.width or window.height ~= event.height then
29 | size_changed = true
30 | end
31 |
32 | window.x = event.x
33 | window.y = event.y
34 | window.width = event.width
35 | window.height = event.height
36 |
37 | local tree = window.tree
38 | if tree == nil then return end
39 |
40 | if size_changed then
41 | tstation.emit(tree.station, tree, t_i_e_tree.ParentWindowSizeChanged, width, height)
42 | end
43 | end
44 |
45 | local function _handle_click_event(window, event_type, window_id, is_press, button, modifiers, x, y)
46 |
47 | local tree = window.tree
48 | if tree == nil then return end
49 | if x > tree.width or y > tree.height then return end
50 |
51 | tstation.emit(tree.station, tree, t_i_e_tree.MouseClickEvent, is_press, button, modifiers, x, y)
52 | end
53 |
54 | local function _handle_create_event(app, ...)
55 | -- this can only happen if someone creates a window with us as the parent.
56 | -- this is currently not supported, so we do nothing.
57 | end
58 |
59 | local function _handle_destroy_event(window, event_type, parent_id, window_id)
60 |
61 | _window_drawing_context_destroy(window)
62 |
63 | local tree = window.tree
64 | if tree == nil then return end
65 |
66 | -- TODO: also let each child element know about this
67 |
68 | tstation.emit(window.station, window, event_type, window_id)
69 | end
70 |
71 | local function _handle_enter_event(app, event_type, window_id, ...)
72 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
73 | tstation.emit(window.station, window, event_type, window_id, ...)
74 | end
75 |
76 | local function _handle_expose_event(app, event_type, window_id, ...)
77 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
78 | tstation.emit(window.station, window, event_type, window_id, ...)
79 | end
80 |
81 | local function _handle_focus_in_event(app, event_type, window_id)
82 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
83 | -- window.focused = true -- TODO: move this out of here
84 | tstation.emit(window.station, window, event_type, window_id)
85 | end
86 |
87 | local function _handle_focus_out_event(app, event_type, window_id)
88 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
89 | -- window.focused = false -- TODO: move this out of here
90 | tstation.emit(window.station, window, event_type, window_id)
91 | end
92 |
93 | local function _handle_key_event(app, event_type, window_id, ...)
94 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
95 | -- TODO: set keybindings on windows directly
96 |
97 | tstation.emit(window.station, window, event_type, window_id, ...)
98 | end
99 |
100 | local function _handle_leave_event(app, event_type, window_id, ...)
101 |
102 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
103 |
104 | tstation.emit(window.station, window, event_type, window_id, ...)
105 | end
106 |
107 | local function _handle_motion_event(app, event_type, window_id, ...)
108 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
109 | tstation.emit(window.station, window, event_type, window_id, ...)
110 |
111 | -- TODO: move this code where it belongs
112 | -- local hit_children = ou_internal.get_approved_mouse_hit_children(
113 | -- window,
114 | -- event_type,
115 | -- event.x,
116 | -- event.y
117 | -- )
118 | --
119 | -- for _, child in ipairs(hit_children) do
120 | -- local geom = child.oak_geometry
121 | -- tstation.emit(child.station, {
122 | -- type = event_type,
123 | -- -- translate the coordinates so the child gets relative coordinates
124 | -- x = event.x - geom.x,
125 | -- y = event.y - geom.y,
126 | -- })
127 | -- end
128 | end
129 |
130 | local function _handle_map_event(app, event_type, window_id, ...)
131 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
132 | -- TODO: move this out of here
133 | -- window.visibility = tw_internal.visibility.RAISED
134 |
135 | tstation.emit(window.station, window, event_type, window_id, ...)
136 | end
137 |
138 | local function _handle_map_request(app, event_type, parent_id, window_id)
139 | -- this could only happen if we had child windows and one of them
140 | -- would request to be mapped. This is currently not supported.
141 | end
142 |
143 | local function _handle_property_event(app, event_type, window_id, ...)
144 | -- TODO: implement this properly
145 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
146 | tstation.emit(window.station, window, event_type, window_id, ...)
147 | end
148 |
149 | local function _handle_reparent_event(app, event)
150 | -- this happens when a window is reparented to us, or when our window
151 | -- is reparented onto another. I'm not sure what to do about this yet,
152 | -- so let's just mark everything for relayout and redraw.
153 | -- ou_internal.element_mark_relayout(window)
154 | -- ou_internal.element_mark_redraw(window)
155 | end
156 |
157 | local function _handle_visibility_event(app, event_type, window_id, visibility)
158 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
159 |
160 | -- TODO: move this out of here
161 | -- window.visibility = tw_internal.visibility.RAISED_AND_SHOWING
162 |
163 | tstation.emit(window.station, window, event_type, window_id, visibility)
164 | end
165 |
166 | local function _handle_unmap_event(app, event_type, window_id)
167 | local window = t_orchard.get_window_by_id(app.orchard, window_id)
168 |
169 | -- TODO: move this out of here
170 | -- window.visibility = tw_internal.visibility.HIDDEN
171 |
172 | tstation.emit(window.station, window, event_type, window_id)
173 | end
174 |
175 |
176 |
177 |
178 | local default_event_handler_map = {
179 |
180 | -- platform specific events
181 | [events.X_ClickEvent] = _handle_click_event,
182 | [events.X_ConfigureNotify] = _handle_configure_notify_event,
183 | [events.X_CreateNotify] = _handle_create_event,
184 | [events.X_DestroyNotify] = _handle_destroy_event,
185 | [events.X_EnterNotify] = _handle_enter_event,
186 | [events.X_ExposeEvent] = _handle_expose_event,
187 | [events.X_FocusIn] = _handle_focus_in_event,
188 | [events.X_FocusOut] = _handle_focus_out_event,
189 | [events.X_KeyEvent] = _handle_key_event,
190 | [events.X_LeaveNotify] = _handle_leave_event,
191 | [events.X_MotionEvent] = _handle_motion_event,
192 | [events.X_MapNotify] = _handle_map_event,
193 | [events.X_MapRequest] = _handle_map_request,
194 | [events.X_PropertyNotify] = _handle_property_event,
195 | [events.X_ReparentNotify] = _handle_reparent_event,
196 | [events.X_VisibilityNotify] = _handle_visibility_event,
197 | [events.X_UnmapNotify] = _handle_unmap_event,
198 | }
199 |
200 |
201 | return {
202 | events = events,
203 | default_event_handler_map = default_event_handler_map,
204 | }
205 |
--------------------------------------------------------------------------------
/l/abonaments/tcp/client.lua:
--------------------------------------------------------------------------------
1 |
2 | local luv = require("luv")
3 | local tstation = require("tstation")
4 |
5 | local tt_promise = require("terra.tools.promise")
6 | local t_puv = require("terra.puv")
7 |
8 | local function _make_id(host, port)
9 | return tostring(host) .. '/' .. tostring(port)
10 | end
11 |
12 | local function _ensure_connection(app, host, port)
13 |
14 | local id = _make_id(host, port)
15 | local conn = app._tcp_conns[id]
16 | if conn == nil then
17 | return t_puv.tcp_new()
18 | :next(function(tcp)
19 | app._tcp_conns[id] = tcp
20 | return t_puv.tcp_connect(tcp, host, port)
21 | end)
22 | else
23 | local p = tt_promise.new()
24 | p:resolve(conn)
25 | return p
26 | end
27 | end
28 |
29 | local function listen_by_line(app, host, port, sig_succ, sig_fail)
30 | _ensure_connection(app, host, port)
31 | :next(function(tcp)
32 | luv.read_start(tcp, function(err, data)
33 | if err ~= nil then
34 | return tstation.emit(app.station, sig_fail, err)
35 | else
36 | return tstation.emit(app.station, sig_succ, data)
37 | end
38 | end)
39 | end)
40 | end
41 |
42 | local function stop_listening(app, host, port)
43 | end
44 |
45 | local function write(app, host, port, data)
46 | _ensure_connection(app, host, port)
47 | :next(function(tcp)
48 | return t_puv.tcp_write(tcp, data)
49 | end)
50 | end
51 |
52 | return {
53 | listen_by_line = listen_by_line,
54 | stop_listening = stop_listening,
55 | write = write,
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/l/element.lua:
--------------------------------------------------------------------------------
1 |
2 | local t_object = require("terra.object")
3 | local tt_table = require("terra.tools.table")
4 | -- local tstation = require("tstation")
5 |
6 | local events = {
7 | MouseEnterEvent = "MouseEnterEvent",
8 | MouseLeaveEvent = "MouseLeaveEvent",
9 | MouseMotionEvent = "MouseMotionEvent",
10 | MouseClickEvent = "MouseClickEvent",
11 | }
12 |
13 | local function default_get_geometry(element)
14 | return element.geometry
15 | end
16 |
17 | local function default_set_geometry(element, x, y, width, height)
18 | element.geometry.x = x
19 | element.geometry.y = y
20 | element.geometry.width = width
21 | element.geometry.height = height
22 | end
23 |
24 | -- returns true if the given point exists inside the geometry of the given element.
25 | local function contains_point(element, point_x, point_y)
26 | local geom = element.geometry
27 | if point_x < geom.x then return false end
28 | if point_x > geom.x + geom.width then return false end
29 | if point_y < geom.y then return false end
30 | if point_y > geom.y + geom.height then return false end
31 | return true
32 | end
33 |
34 | -- geometries can have floating point values. clip areas shouldn't. Given the
35 | -- geometry of an element, this function returns a table with x, y, width, height
36 | -- as integers
37 | local function geometry_to_clip_area(geom) -- TODO: maybe rename this to "get_bounding_box"
38 |
39 | -- note: when clipping, these values should always be integers, in
40 | -- order to have the rectangle be on pixel aligned coordinates. We do
41 | -- this because the cairo docs suggest this would be fastest.
42 | -- https://www.cairographics.org/FAQ/#clipping_performance
43 | local element_x_floor, element_x_fractional_part = math.modf(
44 | geom.x
45 | )
46 | local element_y_floor, element_y_fractional_part = math.modf(
47 | geom.y
48 | )
49 |
50 | return { -- TODO: dont return a table here
51 | x = element_x_floor,
52 | y = element_y_floor,
53 | width = math.ceil(geom.width + element_x_fractional_part),
54 | height = math.ceil(geom.height + element_y_fractional_part)
55 | }
56 | end
57 |
58 | local function new()
59 |
60 | local element_defaults = {
61 | geometry = {}, -- TODO: maybe make this `nil` at first until an element is attached
62 | get_geometry = default_get_geometry,
63 | set_geometry = default_set_geometry,
64 | }
65 |
66 | local element = tt_table.crush(t_object.new(), element_defaults)
67 |
68 | return element
69 | end
70 |
71 |
72 | return {
73 | new = new,
74 |
75 | events = events,
76 |
77 | default_get_geometry = default_get_geometry,
78 | default_set_geometry = default_set_geometry,
79 |
80 | contains_point = contains_point,
81 |
82 | geometry_to_clip_area = geometry_to_clip_area,
83 | }
84 |
--------------------------------------------------------------------------------
/l/input/click.lua:
--------------------------------------------------------------------------------
1 |
2 | local lib = {
3 | ANY = 0,
4 | LEFT = 1,
5 | RIGHT = 2,
6 | MIDDLE = 3,
7 | SCROLL_UP = 4,
8 | SCROLL_DOWN = 5,
9 | }
10 |
11 | return lib
12 |
13 |
--------------------------------------------------------------------------------
/l/input/clickbind.lua:
--------------------------------------------------------------------------------
1 |
2 | -- `on_press` or `on_release` can be nil, but not both
3 | local function new(button, modifiers, on_press, on_release)
4 |
5 | if on_press == nil and on_release == nil then
6 | error("cannot create a clickbind where both `on_press` and `on_release` are nil.")
7 | end
8 |
9 | return {
10 | button = button,
11 | modifiers = modifiers,
12 | on_press = on_press,
13 | on_release = on_release,
14 | }
15 | end
16 |
17 | return {
18 | new = new
19 | }
20 |
--------------------------------------------------------------------------------
/l/input/clickmap.lua:
--------------------------------------------------------------------------------
1 |
2 | local i_click = require("input.click")
3 | local i_key = require("input.key")
4 |
5 | -- Glossary:
6 | -- * click_id:
7 | -- - a table used to identify a specific modifier + click combination
8 | -- - also called a "cid"
9 | -- For example: {
10 | -- "button" : , (the mouse button number. e.g. 1 for left click, 2 for right click, etc.)
11 | -- "modifiers" : (a 16 bit bitmap where each bit represents one modifier. Except for the special "MOD_ANY" modifier)
12 | -- "is_press :
13 | -- }
14 | -- * clickbind:
15 | -- - just like a `click_id` but with a "callback" field to denote the
16 | -- callback to be called when the click combination matches
17 | -- * clickbindings:
18 | -- _ a contiguous array of `clickbind` values
19 | -- * clickmap:
20 | -- - a table of the form { : { : } }.
21 | -- In the first table the index is a modifier mask turned
22 | -- into string form. In the second, nested table, the key is an
23 | -- to denote the "button". The is the callback
24 | -- that exists at the given modifier + button combination.
25 | -- - normally used to index into with a `click_id`, in order to
26 | -- execute a clickbinding
27 |
28 |
29 | -- create a "clickmap" from "clickbindings"
30 | local function from_clickbindings(clickbindings)
31 | local clickmap = {}
32 | for _, clickbind in ipairs(clickbindings) do
33 |
34 | local str_mods = tostring(clickbind.modifiers)
35 | local buttons = clickmap[str_mods]
36 | if buttons == nil then
37 | buttons = {}
38 | clickmap[str_mods] = buttons
39 | end
40 |
41 | buttons[clickbind.button] = {
42 | [true] = clickbind.on_press,
43 | [false] = clickbind.on_release,
44 | }
45 | end
46 | return clickmap
47 | end
48 |
49 | -- indexes the given "clickmap" with the given "click_id", to give back a
50 | -- callback, if there is one
51 | local function index(clickmap, cid)
52 | local buttons = clickmap[tostring(cid.modifiers)]
53 | if buttons == nil then
54 | -- buttons are nil, so try with "any modifiers"
55 | buttons = clickmap[tostring(i_key.MOD_ANY)]
56 | end
57 | if buttons == nil then return end
58 | local press_or_release = buttons[cid.button]
59 | if press_or_release == nil then
60 | press_or_release = buttons[i_click.ANY]
61 | end
62 | if press_or_release == nil then return end
63 | local cb = press_or_release[cid.is_press]
64 | return cb
65 | end
66 |
67 | return {
68 | from_clickbindings = from_clickbindings,
69 | index = index,
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/l/input/key.lua:
--------------------------------------------------------------------------------
1 |
2 | local lib = {
3 | ANY = 0, -- literal keycode to signify "any key"
4 |
5 | MOD_NONE = 0,
6 | MOD_SHIFT = 1, -- 1 << 0
7 | MOD_LOCK = 2, -- 1 << 1
8 | MOD_CONTROL = 4, -- 1 << 2
9 | MOD_1 = 8, -- 1 << 3
10 | MOD_2 = 16, -- 1 << 4
11 | MOD_3 = 32, -- 1 << 5
12 | MOD_4 = 64, -- 1 << 6
13 | MOD_5 = 128, -- 1 << 7
14 | MOD_ANY = 32768 -- 1 << 15
15 | }
16 |
17 | return lib
18 |
19 |
--------------------------------------------------------------------------------
/l/input/keybind.lua:
--------------------------------------------------------------------------------
1 |
2 | -- on_press or on_release can be nil, but not both
3 | local function new(keyname, modifiers, on_press, on_release)
4 |
5 | if on_press == nil and on_release == nil then
6 | error("cannot create a keybind without either `on_press` or `on_release`")
7 | end
8 |
9 | return {
10 | key = keyname,
11 | modifiers = modifiers,
12 | on_press = on_press,
13 | on_release = on_release,
14 | }
15 | end
16 |
17 | return {
18 | new = new
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/l/input/keymap.lua:
--------------------------------------------------------------------------------
1 |
2 | -- Glossary:
3 | -- * key_id:
4 | -- - a table used to identify a specific modifier + key combination
5 | -- - also called a "kid"
6 | -- For example: {
7 | -- "key" : ,
8 | -- "modifiers" : (a 16 bit bitmap where each bit represents one modifier. Except for the special "MOD_ANY" modifier)
9 | -- "is_press" :
10 | -- }
11 | -- * keybind:
12 | -- - just like a `key_id` but with a "callback" field to denote the
13 | -- callback to be called when the key combination matches
14 | -- * keybindings:
15 | -- - a contiguous array of `keybind` values
16 | -- * keymap:
17 | -- - a table of the form { : { | : { : } } }.
18 | -- In the first table the index is a modifier mask turned
19 | -- into string form. In the second, nested table, the key is a
20 | -- to denote the "key" name, or, it can be an integer
21 | -- index which is treated as a literal keycode. The third nested
22 | -- table is the table that uses a boolean to determine whether
23 | -- the callback is for a key press event (true), or for a key
24 | -- release event (false). The is the callback that
25 | -- exists at the given modifier + key + is_press combination.
26 |
27 | -- create a "keymap" from "keybindings".
28 | local function from_keybindings(keybindings)
29 | local keymap = {}
30 | for _, keybind in ipairs(keybindings) do
31 | local str_mods = tostring(keybind.modifiers)
32 | local keys = keymap[str_mods]
33 | if keys == nil then
34 | keys = {}
35 | keymap[str_mods] = keys
36 | end
37 | keys[keybind.key] = {
38 | [true] = keybind.on_press,
39 | [false] = keybind.on_release,
40 | }
41 | end
42 | return keymap
43 | end
44 |
45 | -- indexes the given "keymap" with the given "key_id", to give back a
46 | -- callback, if there is one
47 | local function index(keymap, kid)
48 | local keys = keymap[tostring(kid.modifiers)]
49 | if keys == nil then return end
50 | local key = kid.key
51 |
52 | -- TODO: I don't think this applies anymore
53 | -- FIXME: the "`" or "grave" key doesn't work properly. When using
54 | -- `subscribe_key` to subscribe to events of this key, using "grave"
55 | -- as the `.key` name works to subscribe, but when we get an event
56 | -- from the C side, instead of giving us "grave" back as the `.key`,
57 | -- it gives us '`'. This fixes the issue, but I'd like to fix it
58 | -- properly from the C side.
59 | if key == '`' then key = "grave" end
60 | local press_or_release = keys[key]
61 | local cb = press_or_release[kid.is_press]
62 | return cb
63 | end
64 |
65 | return {
66 | from_keybindings = from_keybindings,
67 | index = index,
68 | }
69 |
--------------------------------------------------------------------------------
/l/oak/align.lua:
--------------------------------------------------------------------------------
1 |
2 | -- TODO: maybe replace these with "ALIGN_START", "ALIGN_CENTER", "ALIGN_END"
3 | local ALIGN_CENTER = 1
4 | local ALIGN_LEFT = 2
5 | local ALIGN_RIGHT = 3
6 | local ALIGN_TOP = 4
7 | local ALIGN_BOTTOM = 5
8 |
9 | local lib = {
10 | CENTER = ALIGN_CENTER,
11 | LEFT = ALIGN_LEFT,
12 | RIGHT = ALIGN_RIGHT,
13 | TOP = ALIGN_TOP,
14 | BOTTOM = ALIGN_BOTTOM,
15 | }
16 |
17 | return lib
18 |
19 |
--------------------------------------------------------------------------------
/l/oak/border.lua:
--------------------------------------------------------------------------------
1 |
2 | local _BORDER_EACH = 1
3 |
4 | -- TODO: I think it'd be better to make the border an element like any other
5 | local function get_border_width(element)
6 | local bg = element.bg
7 | if element.bg == nil then return 0 end
8 | if bg.border_width == nil then return 0 end
9 | return bg.border_width
10 | end
11 |
12 | local function get_border_radius(element)
13 | local bg = element.bg
14 | if element.bg == nil then return 0 end
15 | return bg.border_radius or 0
16 | end
17 |
18 | local function radius_each(args)
19 |
20 | args.top_left = args.top_left or 0
21 | args.top_right = args.top_right or 0
22 | args.bottom_right = args.bottom_right or 0
23 | args.bottom_left = args.bottom_left or 0
24 | args.border_type = _BORDER_EACH
25 |
26 | assert(type(args.top_left) == "number", "'.top_left' should be number, got: " .. tostring(top_left))
27 | assert(type(args.top_right) == "number", "'.top_right' should be number, got: " .. tostring(top_right))
28 | assert(type(args.bottom_right) == "number", "'.bottom_right' should be number, got: " .. tostring(bottom_right))
29 | assert(type(args.bottom_left) == "number", "'.bottom_left' should be number, got: " .. tostring(bottom_left))
30 |
31 | return args
32 | end
33 |
34 | -- TODO: just return numbers here instead of a table
35 | local function standardize_radius(border_radius)
36 |
37 | if border_radius == nil then
38 | return {
39 | top_left = 0,
40 | top_right = 0,
41 | bottom_right = 0,
42 | bottom_left = 0,
43 | }
44 | elseif type(border_radius) == "number" then
45 | return {
46 | top_left = border_radius,
47 | top_right = border_radius,
48 | bottom_right = border_radius,
49 | bottom_left = border_radius,
50 | }
51 | else -- border_each
52 | return {
53 | top_left = border_radius.top_left,
54 | top_right = border_radius.top_right,
55 | bottom_right = border_radius.bottom_right,
56 | bottom_left = border_radius.bottom_left,
57 | }
58 | end
59 | end
60 |
61 | return {
62 | get_width = get_border_width,
63 | get_radius = get_border_radius,
64 |
65 | radius_each = radius_each,
66 | standardize_radius = standardize_radius,
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/l/oak/elements/_retired.lua:
--------------------------------------------------------------------------------
1 |
2 | local function needs_relayout(element)
3 | return element._oak_private.needs_relayout
4 | end
5 |
6 | local function needs_redraw(element)
7 | return element._oak_private.needs_redraw
8 | end
9 |
10 | local function mark_relayout(element)
11 |
12 | -- if this element is not attached to a root, don't mark it because all
13 | -- freshly attached elements automatically get relayouted and redrawn.
14 | local root = element.scope.root
15 | if root == nil then return end
16 |
17 | -- is the element already marked?
18 | if element._oak_private.needs_relayout then return end
19 |
20 | root.nr_of_elements_that_need_relayout = root.nr_of_elements_that_need_relayout + 1
21 |
22 | element._oak_private.needs_relayout = true
23 | end
24 |
25 | local function mark_redraw(element)
26 |
27 | -- if this element is not attached to a root, don't mark it because all
28 | -- freshly attached elements automatically get relayouted and redrawn.
29 | local root = element.scope.root
30 | if root == nil then return end
31 |
32 | -- is the element already marked?
33 | if element._oak_private.needs_redraw then return end
34 |
35 | root.nr_of_elements_that_need_redraw = root.nr_of_elements_that_need_redraw + 1
36 |
37 | element._oak_private.needs_redraw = true
38 | end
39 |
40 | local function mark_dont_relayout(element)
41 |
42 | local root = element.scope.root
43 | if root == nil then return end
44 |
45 | if element._oak_private.needs_relayout == false then return end
46 |
47 | root.nr_of_elements_that_need_relayout = root.nr_of_elements_that_need_relayout - 1
48 |
49 | element._oak_private.needs_relayout = false
50 | end
51 |
52 | local function mark_dont_redraw(element)
53 |
54 | local root = element.scope.root
55 | if root == nil then return end
56 |
57 | if element._oak_private.needs_redraw == false then return end
58 |
59 | root.nr_of_elements_that_need_redraw = root.nr_of_elements_that_need_redraw - 1
60 |
61 | element._oak_private.needs_redraw = false
62 | end
63 |
64 | -- local function element_mark_redraw_all_subchildren(element)
65 | -- element_mark_redraw(element)
66 | -- if element.oak_children_iter == nil then return end -- not a branch
67 | -- for _, c in element:oak_children_iter() do
68 | -- element_mark_redraw_all_subchildren(c)
69 | -- end
70 | -- end
71 |
72 | -- -- returns a list of the fewest elements that need a relayout
73 | -- -- for example: if 'b' is the child of 'a', and they're both marked as requiring
74 | -- -- a relayout, only { a } will be returned because 'b' will automatically be
75 | -- -- relayouted when 'a' will be, since all children of a relayoued element are
76 | -- -- automatically relayouted
77 | -- local function get_least_elements_to_relayout(element)
78 | --
79 | -- local function dig(storage, current_element)
80 | --
81 | -- if current_element.oak_children_iter == nil then return end -- not a branch
82 | -- for _, c in current_element:oak_children_iter() do
83 | --
84 | -- if element_needs_relayout(c) then
85 | -- table.insert(storage, c)
86 | -- else
87 | -- dig(storage, c)
88 | -- end
89 | --
90 | -- end
91 | --
92 | -- end
93 | --
94 | -- local elements_needing_relayout = {}
95 | -- dig(elements_needing_relayout, element)
96 | -- return elements_needing_relayout
97 | -- end
98 | --
99 | -- -- traverses the whole tree and returns a list of all elements that need to
100 | -- -- be redrawn
101 | -- local function get_all_elements_to_redraw(element)
102 | --
103 | -- local function dig(storage, current_element)
104 | --
105 | -- if current_element.oak_children_iter == nil then return end -- not a branch
106 | -- for _, c in current_element:oak_children_iter() do
107 | -- if element_needs_redraw(c) then
108 | -- table.insert(storage, c)
109 | -- dig(storage, c)
110 | -- end
111 | -- end
112 | -- end
113 | --
114 | -- local elements_needing_redraw = {}
115 | -- dig(elements_needing_redraw, element)
116 | -- return elements_needing_redraw
117 | -- end
118 |
119 | -- TODO: TEST
120 | -- TODO: try to turn this an iterator and not create tables anymore.
121 | local function element_get_children_to_relayout_and_redraw(element)
122 |
123 | local function dig(
124 | current,
125 | least_relayout_storage, -- the "least" elements that need to be relayouted
126 | all_relayout_storage, -- all elements that were marked to be relayouted
127 | redraw_storage, -- all elements that need to be redrawn
128 | should_still_check_for_relayout,
129 | should_redraw_from_here_on
130 | )
131 | if element_needs_relayout(current) then
132 | should_redraw_from_here_on = true
133 | if should_still_check_for_relayout then
134 | table.insert(least_relayout_storage, current)
135 | should_still_check_for_relayout = false
136 | end
137 | table.insert(all_relayout_storage, current)
138 | end
139 |
140 | if should_redraw_from_here_on then
141 | table.insert(redraw_storage, current)
142 | else
143 | if element_needs_redraw(current) then
144 | table.insert(redraw_storage, current)
145 | end
146 | end
147 |
148 | if current.oak_children_iter == nil then return end
149 | for _, c in current:oak_children_iter() do
150 | dig(
151 | c,
152 | least_relayout_storage,
153 | all_relayout_storage,
154 | redraw_storage,
155 | should_still_check_for_relayout,
156 | should_redraw_from_here_on
157 | )
158 | end
159 | end
160 |
161 | local least_elements_to_relayout = {}
162 | local all_elements_to_relayout = {}
163 | local elements_to_redraw = {}
164 | dig(
165 | element,
166 | least_elements_to_relayout,
167 | all_elements_to_relayout,
168 | elements_to_redraw,
169 | true,
170 | false
171 | )
172 |
173 | return least_elements_to_relayout, all_elements_to_relayout, elements_to_redraw
174 | end
175 |
176 | -- TODO: either make this work, or add something like a "shape"
177 | -- property to all elements.
178 | local function apply_clip_shape(branch, cr, width, height)
179 |
180 | local clip_shape = branch.clip_shape
181 | if clip_shape ~= nil then
182 | clip_shape(branch, cr, width, height)
183 | return
184 | end
185 |
186 | local bg = branch.bg
187 | if bg == nil then
188 | return
189 | end
190 |
191 | local border_radius = bg.border_radius or 0
192 | if border_radius == 0 then
193 | return
194 | end
195 |
196 | if branch.clip_to_background == true then
197 |
198 | local border_width = bg.border_width or 0
199 | cr:translate(border_width, border_width)
200 | to_shape.rounded_rectangle(
201 | cr,
202 | width - (border_width * 2),
203 | height - (border_width * 2),
204 | border_radius
205 | )
206 |
207 | end
208 |
209 | end
210 |
211 |
--------------------------------------------------------------------------------
/l/oak/elements/branches/_retired.lua:
--------------------------------------------------------------------------------
1 |
2 | -- NOTE: this is a very outdated drawing function that was optimized at the time.
3 | -- local function draw(root, cr)
4 | --
5 | -- local something_was_drawn = true -- TODO: use this properly
6 | --
7 | -- -- if no element needs to be relayouted or redrawn, just return
8 | -- if root.nr_of_elements_that_need_relayout == 0
9 | -- and root.nr_of_elements_that_need_redraw == 0
10 | -- then return false end
11 | --
12 | -- -- TODO: make sure not to return tables here. Just store them in the root.
13 | -- -- This should massively optimize everything since these tables would
14 | -- -- not need to be gc'd.
15 | -- local
16 | -- least_elements_to_relayout,
17 | -- all_elements_to_relayout,
18 | -- elements_to_redraw
19 | -- = toe_internal.element_get_children_to_relayout_and_redraw(root)
20 | --
21 | -- -- TODO: rewrite the draw function to make it start out with a "nil"
22 | -- -- clip region, that gets progressively larger as the regions get marked
23 | -- -- for redraw.
24 | -- -- ALSO: write a "draw_debug" function
25 | -- -- local something_needs_to_be_drawn = elements_to_redraw[1] ~= nil
26 | -- local dirty_region = lgi.cairo.Region.create() -- TODO: replace lgi
27 | --
28 | -- -- mark all regions of the redraw elements as dirty BEFORE relayouting
29 | --
30 | -- print('redraw elements that need clipping before relayout:')
31 | -- for _, element in ipairs(elements_to_redraw) do
32 | -- -- TODO: check for oak_geometry == nil
33 | -- print("element to redraw", element)
34 | -- local elem_geom = element:get_geometry()
35 | -- if elem_geom.x ~= nil then
36 | -- dirty_region:union_rectangle(
37 | -- lgi.cairo.RectangleInt(
38 | -- toe_internal.geometry_to_clip_area(element.geometry)
39 | -- )
40 | -- )
41 | -- end
42 | -- end
43 | --
44 | -- -- relayout all elements that it makes sense to relayout
45 | -- for _, element in ipairs(least_elements_to_relayout) do
46 | -- print("element to relayout", element)
47 | -- local geom = element:get_geometry()
48 | -- toe_internal.element_recursively_process(
49 | -- element,
50 | -- geom.x,
51 | -- geom.y,
52 | -- geom.width,
53 | -- geom.height
54 | -- )
55 | -- end
56 | --
57 | -- -- was something relayouted?
58 | -- if #least_elements_to_relayout > 0 then
59 | -- -- if something was relayouted, mark all regions of the redraw
60 | -- -- elements as dirty AFTER RELAYOUTING AGAIN, because their geometry
61 | -- -- might have changed
62 | --
63 | -- print('redraw elements that need clipping after relayout:')
64 | -- for _, element in ipairs(elements_to_redraw) do
65 | -- dirty_region:union_rectangle(
66 | -- lgi.cairo.RectangleInt(
67 | -- toe_internal.geometry_to_clip_area(element.geometry)
68 | -- )
69 | -- )
70 | -- end
71 | -- end
72 | --
73 | -- -- reset the clip so we can draw anywhere on the window
74 | -- cr:reset_clip()
75 | --
76 | -- -- clip the whole region marked
77 | -- for i=0, dirty_region:num_rectangles() - 1 do
78 | -- local rect = dirty_region:get_rectangle(i)
79 | -- print('CLIPPING THE FOLLOWING RECTS:')
80 | -- print("rect: ", rect.x, rect.y, rect.width, rect.height)
81 | -- cr:rectangle(rect.x, rect.y, rect.width, rect.height)
82 | -- end
83 | -- cr:clip()
84 | --
85 | -- -- draw the background first, so we don't get "solitaire trails" from
86 | -- -- previous drawings if the background is transparent
87 | -- cr:save() -- save because we don't want to use this operator for everything.
88 | -- -- We use the CLEAR operator to make sure that all previous data
89 | -- -- that used to exist in memory where our surface now exists gets cleared.
90 | -- -- Otherwise we can get random artifacts and trash in our drawing.
91 | -- -- This will also automatically draw transparency if a compositor
92 | -- -- is running.
93 | -- cr:set_operator(lgi.cairo.Operator.CLEAR)
94 | -- cr:paint()
95 | -- cr:restore()
96 | --
97 | -- -- draw the whole tree onto the pixmap. We clipped to the dirty region
98 | -- -- earlier, which should ensure that only that portion is redrawn.
99 | -- toe_internal.element_recursively_draw_on_context(root, cr)
100 | --
101 | -- -- cr:rectangle(100, 100, 40, 40)
102 | -- -- cr:set_source_rgb(0.1, 0.9, 0.1)
103 | -- -- cr:fill()
104 | --
105 | -- -- reset the marked changes so that changes dont persist across frames
106 | -- for _, element in ipairs(all_elements_to_relayout) do
107 | -- element._oak_private.needs_relayout = false
108 | -- end
109 | -- for _, element in ipairs(elements_to_redraw) do
110 | -- element._oak_private.needs_redraw = false
111 | -- end
112 | -- root.nr_of_elements_that_need_relayout = 0
113 | -- root.nr_of_elements_that_need_redraw = 0
114 | --
115 | -- print("DRAWING DONE IN OAK ROOT")
116 | --
117 | -- return something_was_drawn
118 | -- end
119 |
120 |
121 |
--------------------------------------------------------------------------------
/l/oak/elements/branches/branch.lua:
--------------------------------------------------------------------------------
1 |
2 | local tt_table = require("terra.tools.table")
3 |
4 | local toe_internal = require("terra.oak.elements.internal")
5 | local toe_element = require("terra.oak.elements.element")
6 |
7 | local function oak_handle_attach_to_parent_element(branch, parent, root, window, app)
8 | toe_element.default_oak_handle_attach_to_parent_element(branch, parent, root, window, app)
9 | for key, child in branch:oak_children_iter() do
10 | branch.oak_private.child_id_to_index[child.oak_private.id] = key
11 | child:oak_handle_attach_to_parent_element(branch, root, window, app)
12 | end
13 | end
14 |
15 | local function oak_handle_detach_from_parent_element(branch, parent, root, window, app)
16 | toe_element.default_oak_handle_detach_from_parent_element(branch, parent, root, window, app)
17 | for _, child in branch:oak_children_iter() do
18 | child:oak_handle_detach_from_parent_element(branch, root, window, app)
19 | end
20 | end
21 |
22 | local function default_oak_children_iter(branch)
23 |
24 | local co = coroutine.create(function()
25 | if branch.bg ~= nil then
26 | coroutine.yield("bg", branch.bg)
27 | end
28 | if branch.shadow ~= nil then
29 | coroutine.yield("shadow", branch.shadow)
30 | end
31 | for i=1, #branch do
32 | coroutine.yield(i, branch[i])
33 | end
34 | end)
35 |
36 | return function()
37 | local is_not_finished, k, v = coroutine.resume(co)
38 | if is_not_finished then
39 | return k, v
40 | else
41 | return nil, nil
42 | end
43 | end
44 | end
45 |
46 |
47 | local function set_halign(branch, halign)
48 | toe_element.default_oak_prop_set(branch, "halign", halign)
49 | end
50 |
51 | local function set_valign(branch, valign)
52 | toe_element.default_oak_prop_set(branch, "valign", valign)
53 | end
54 |
55 | local function set_padding(branch, padding)
56 | toe_element.default_oak_prop_set(branch, "padding", padding)
57 | end
58 |
59 | local function new()
60 |
61 | local branch_defaults = {
62 |
63 | -- part of the interface to be a
64 | oak_children_iter = default_oak_children_iter,
65 |
66 | -- branch specific attach/detach functions
67 | oak_handle_attach_to_parent_element = oak_handle_attach_to_parent_element,
68 | oak_handle_detach_from_parent_element = oak_handle_detach_from_parent_element,
69 |
70 | -- default branch-specific property setters
71 | set_bg = toe_internal.set_bg,
72 | set_shadow = toe_internal.set_shadow,
73 | set_child_n = toe_internal.set_child_n,
74 | insert_child_n = toe_internal.insert_child_n,
75 | remove_child_n = toe_internal.remove_child_n,
76 | remove = toe_internal.element_remove,
77 |
78 | set_valign = set_valign,
79 | set_halign = set_halign,
80 | set_padding = set_padding,
81 | }
82 |
83 | local branch = tt_table.crush(toe_element.new(), branch_defaults)
84 | -- we use this to keep track of the index of each child set. This makes
85 | -- it easier to have a "remove_element" function where you only have to
86 | -- supply the element itself.
87 | branch.oak_private.child_id_to_index = {}
88 | return branch
89 | end
90 |
91 | return {
92 | new = new,
93 |
94 | oak_handle_attach_to_parent_element = oak_handle_attach_to_parent_element,
95 | oak_handle_detach_from_parent_element = oak_handle_detach_from_parent_element,
96 |
97 | default_oak_children_iter = default_oak_children_iter,
98 |
99 | set_bg = toe_internal.set_bg,
100 | set_shadow = toe_internal.set_shadow,
101 | set_child_n = toe_internal.set_child_n,
102 | insert_child_n = toe_internal.insert_child_n,
103 | remove_child_n = toe_internal.remove_child_n,
104 | remove = toe_internal.element_remove,
105 |
106 | set_valign = set_valign,
107 | set_halign = set_halign,
108 | set_padding = set_padding,
109 | }
110 |
111 |
--------------------------------------------------------------------------------
/l/oak/elements/branches/el.lua:
--------------------------------------------------------------------------------
1 |
2 | local tt_table = require("terra.tools.table")
3 |
4 | local to_padding = require("terra.oak.padding")
5 | local to_size = require("terra.oak.size")
6 | local to_align = require("terra.oak.align")
7 | local to_border = require("terra.oak.border")
8 | local to_internal = require("terra.oak.internal")
9 |
10 | local toe_element = require("terra.oak.elements.element")
11 | local toe_internal = require("terra.oak.elements.internal")
12 |
13 | local toeb_internal = require("terra.oak.elements.branches.internal")
14 | local toeb_branch = require("terra.oak.elements.branches.branch")
15 |
16 | local function el_calculate_minimum_dimensions(el, constraint_w, constraint_h)
17 |
18 | local el_bw = to_border.get_width(el)
19 | local standardized_padding = to_padding.standardize(el.padding or 0)
20 |
21 | local min_w = standardized_padding.left + standardized_padding.right + (el_bw * 2)
22 | local min_h = standardized_padding.top + standardized_padding.bottom + (el_bw * 2)
23 |
24 | local max_w = 0
25 | local max_h = 0
26 |
27 | -- NOTE: only go through the children in the array portion of the table because
28 | -- we don't want the shadow or the bg to take up horizontal space
29 | for _, child in ipairs(el) do
30 |
31 | local child_bw = to_border.get_width(child)
32 | local child_standardized_padding = to_padding.standardize(child.padding or 0)
33 | local child_w, child_h = child.width, child.height
34 |
35 | if type(child_w) == "number" and type(child_h) == "number" then
36 | max_w = math.max(
37 | max_w,
38 | child_w
39 | + (child_bw * 2)
40 | + child_standardized_padding.left
41 | + child_standardized_padding.right
42 | )
43 | max_h = math.max(
44 | max_h,
45 | child_h
46 | + (child_bw * 2)
47 | + child_standardized_padding.top
48 | + child_standardized_padding.bottom
49 | )
50 | elseif type(child_w) == "number" and type(child_h) ~= "number" then
51 | local _, min_child_h = child:oak_calculate_minimum_dimensions(constraint_w, constraint_h)
52 | max_w = math.max(
53 | max_w,
54 | child_w
55 | + (child_bw * 2)
56 | + child_standardized_padding.left
57 | + child_standardized_padding.right
58 | )
59 | max_h = math.max(max_h, min_child_h)
60 | elseif type(child_w) ~= "number" and type(child_h) == "number" then
61 | local min_child_w, _ = child:oak_calculate_minimum_dimensions(constraint_w, constraint_h)
62 | max_w = math.max(max_w, min_child_w)
63 | max_h = math.max(
64 | max_h,
65 | child_h
66 | + (child_bw * 2)
67 | + child_standardized_padding.top
68 | + child_standardized_padding.bottom
69 | )
70 | else -- both are not numbers
71 | local min_child_w, min_child_h = child:oak_calculate_minimum_dimensions(constraint_w, constraint_h)
72 | max_w = math.max(max_w, min_child_w)
73 | max_h = math.max(max_h, min_child_h)
74 | end
75 | end
76 |
77 | return min_w + max_w, min_h + max_h
78 | end
79 |
80 |
81 | local function _dimensionate_single_child_el(el, child, avail_w, avail_h)
82 |
83 | local padd = el.padding or 0
84 | local standardized_padding = to_padding.standardize(padd)
85 | local padding_top = standardized_padding.top
86 | local padding_right = standardized_padding.right
87 | local padding_bottom = standardized_padding.bottom
88 | local padding_left = standardized_padding.left
89 | local child_bw = to_border.get_width(child)
90 |
91 | local child_w = 0
92 | local child_h = 0
93 |
94 | local function _calculate_non_shrink_width(c)
95 | if type(c.width) == "number" then
96 | return c.width + (child_bw * 2)
97 | else -- child.width == "fill"
98 | local remaining_w = avail_w - (padding_left + padding_right)
99 | if remaining_w - (child_bw * 2) > 0 then
100 | return remaining_w
101 | else
102 | return child_bw * 2
103 | end
104 | end
105 | end
106 |
107 | local function _calculate_non_shrink_height(c)
108 | if type(c.height) == "number" then
109 | return c.height + (child_bw * 2)
110 | else -- child.height == "fill"
111 | local remaining_h = avail_h - (padding_top + padding_bottom)
112 | if remaining_h - (child_bw * 2) > 0 then
113 | return remaining_h
114 | else
115 | return child_bw * 2
116 | end
117 | end
118 | end
119 |
120 | if to_size.is_shrink(child.width) and to_size.is_shrink(child.height) then
121 | child_w, child_h = child:oak_calculate_minimum_dimensions(nil, nil)
122 |
123 | elseif to_size.is_shrink(child.width) and not to_size.is_shrink(child.height) then
124 | local min_w, _ = child:oak_calculate_minimum_dimensions(nil, child_h)
125 | child_h = _calculate_non_shrink_height(child)
126 | child_w = min_w
127 |
128 | elseif not to_size.is_shrink(child.width) and to_size.is_shrink(child.height) then
129 | child_w = _calculate_non_shrink_width(child)
130 | local _, min_h = child:oak_calculate_minimum_dimensions(child_w, nil)
131 | child_h = min_h
132 |
133 | else -- neither are of type "shrink"
134 | child_w = _calculate_non_shrink_width(child)
135 | child_h = _calculate_non_shrink_height(child)
136 |
137 | end
138 |
139 | return {
140 | element = child,
141 | valign = child.valign or to_align.TOP,
142 | halign = child.halign or to_align.LEFT,
143 | width = child_w,
144 | height = child_h,
145 | offset_x = child.offset_x or 0,
146 | offset_y = child.offset_y or 0,
147 | }
148 | end
149 |
150 | local function el_dimensionate_children(el, avail_w, avail_h)
151 |
152 | local dimensionated_children_data = {
153 | available_width = avail_w,
154 | available_height = avail_h,
155 | standardized_padding = to_padding.standardize(el.padding or 0),
156 | parent_border_width = to_border.get_width(el)
157 | }
158 |
159 | do
160 | local shadow = el.shadow
161 | local bg = el.bg
162 | if shadow ~= nil then dimensionated_children_data.shadow = shadow end
163 | if bg ~= nil then dimensionated_children_data.bg = bg end
164 | end
165 |
166 | for _, child in ipairs(el) do
167 | table.insert(
168 | dimensionated_children_data,
169 | _dimensionate_single_child_el(el, child, avail_w, avail_h)
170 | )
171 | end
172 |
173 | return dimensionated_children_data
174 | end
175 |
176 | local function el_position_children(dimensionated_children_data)
177 | local available_width = dimensionated_children_data.available_width
178 | local available_height = dimensionated_children_data.available_height
179 |
180 | local padding_top, padding_right, padding_bottom, padding_left
181 | do
182 | local standardized_padding = dimensionated_children_data.standardized_padding
183 | padding_top = standardized_padding.top
184 | padding_right = standardized_padding.right
185 | padding_bottom = standardized_padding.bottom
186 | padding_left = standardized_padding.left
187 | end
188 |
189 | local parent_bw = dimensionated_children_data.parent_border_width
190 |
191 | local positioned_children_data = {}
192 |
193 | do -- add shadow and bg elements first
194 |
195 | -- Note: normally, elements should not be dimensionated here, only
196 | -- positioned. but it's such a trivial task that we just dimensionate
197 | -- and position the shadow and bg here
198 | local shadow = dimensionated_children_data.shadow
199 | if shadow ~= nil then
200 | table.insert(positioned_children_data, toe_internal.shadow_dimensionate_and_position(
201 | shadow,
202 | available_width,
203 | available_height
204 | ))
205 | end
206 |
207 | local bg = dimensionated_children_data.bg
208 | if bg ~= nil then
209 | table.insert(positioned_children_data, {
210 | x = bg.offset_x or 0,
211 | y = bg.offset_y or 0,
212 | width = available_width,
213 | height = available_height,
214 | element = bg
215 | })
216 | end
217 | end
218 |
219 | for _, dimensionated_child in ipairs(dimensionated_children_data) do
220 |
221 | local child_w = dimensionated_child.width
222 | local child_h = dimensionated_child.height
223 | table.insert(positioned_children_data, {
224 | element = dimensionated_child.element, -- a reference to the child
225 | x = to_internal.align_on_secondary_axis(
226 | padding_left + parent_bw,
227 | padding_right + parent_bw,
228 | dimensionated_child.halign,
229 | available_width,
230 | child_w
231 | ) + dimensionated_child.offset_x,
232 | y = to_internal.align_on_secondary_axis(
233 | padding_top + parent_bw,
234 | padding_bottom + parent_bw,
235 | dimensionated_child.valign,
236 | available_height,
237 | child_h
238 | ) + dimensionated_child.offset_y,
239 | width = child_w - (parent_bw * 2),
240 | height = child_h - (parent_bw * 2),
241 | })
242 | end
243 |
244 | return positioned_children_data
245 | end
246 |
247 | -- TODO: make this function set the geometries of its subchildren directly
248 | local function el_oak_geometrize_children(el, avail_w, avail_h)
249 |
250 | -- NOTE: this will return nil if this el has no shadow, no bg,
251 | -- and no sub-children
252 | if el.bg == nil and el.shadow == nil and #el == 0 then return nil end
253 |
254 | -- TODO: merge these two functions into one
255 | return el_position_children(
256 | el_dimensionate_children(el, avail_w, avail_h)
257 | )
258 | end
259 |
260 | local function new(args)
261 | if args == nil then args = {} end
262 |
263 | local el_defaults = {
264 | -- part of the interface to be a
265 | oak_geometrize_children = el_geometrize_children,
266 | oak_calculate_minimum_dimensions = el_calculate_minimum_dimensions,
267 | }
268 |
269 | return tt_table.crush(toeb_branch.new(), el_defaults, args)
270 | end
271 |
272 | return {
273 | new = new,
274 |
275 | dimensionate_children = el_dimensionate_children,
276 | position_children = el_position_children,
277 |
278 | oak_geometrize_children = el_oak_geometrize_children,
279 | oak_calculate_minimum_dimensions = oak_el_calculate_minimum_dimensions,
280 | }
281 |
282 |
--------------------------------------------------------------------------------
/l/oak/elements/branches/internal.lua:
--------------------------------------------------------------------------------
1 |
2 | local t_element = require("terra.element")
3 | local toe_element = require("terra.oak.elements.element")
4 |
5 | local function get_spacing_between_children(children_amt, spacing)
6 | -- if we have 0 or 1 children, there's 0 spacing
7 | -- if there's 2 or more, we have (num_children - 1) * spacing
8 | if children_amt >= 2 then
9 | return spacing * (children_amt - 1)
10 | end
11 | return 0
12 | end
13 |
14 | local function shadow_dimensionate_and_position(shadow, avail_w, avail_h)
15 |
16 | local edge_width = shadow.edge_width or 0
17 | local shadow_w = avail_w + (edge_width * 2)
18 | local shadow_h = avail_h + (edge_width * 2)
19 |
20 | -- TODO: make sure the shadow x and y is always on integer coordinates
21 | local shadow_edge_width = shadow.edge_width or 0
22 | -- always place shadow in the center of the parent geometry, regardless of
23 | -- what halign/valign the shadow has
24 | local shadow_x = - shadow_edge_width
25 | local shadow_y = - shadow_edge_width
26 |
27 | if shadow.offset_x ~= nil then shadow_x = shadow_x + shadow.offset_x end
28 | if shadow.offset_y ~= nil then shadow_y = shadow_y + shadow.offset_y end
29 |
30 | return {
31 | x = shadow_x,
32 | y = shadow_y,
33 | width = shadow_w,
34 | height = shadow_h,
35 | element = shadow,
36 | }
37 | end
38 |
39 | local function set_spacing(elem, value)
40 | toe_element.default_oak_prop_set(elem, "spacing", value)
41 | end
42 |
43 | return {
44 | POSITION_START = 1,
45 | POSITION_START_END = 2,
46 | POSITION_START_CENTER_END = 3,
47 |
48 | get_spacing_between_children = get_spacing_between_children,
49 | shadow_dimensionate_and_position = shadow_dimensionate_and_position,
50 |
51 | set_spacing = set_spacing,
52 | }
53 |
--------------------------------------------------------------------------------
/l/oak/elements/element.lua:
--------------------------------------------------------------------------------
1 |
2 | local tt_table = require("terra.tools.table")
3 |
4 | local t_element = require("terra.element")
5 | local t_sigtools = require("terra.sigtools")
6 | local toe_internal = require("terra.oak.elements.internal")
7 |
8 | local function set_offset_x(element, value)
9 | toe_internal.default_oak_prop_set(element, "offset_x", value)
10 | end
11 |
12 | local function set_offset_y(element, value)
13 | toe_internal.default_oak_prop_set(element, "offset_y", value)
14 | end
15 |
16 | local function set_oak_draw(element, value)
17 | toe_internal.default_oak_prop_set(element, "oak_draw", value)
18 | end
19 |
20 | local function set_opacity(element, value)
21 | toe_internal.default_oak_prop_set(element, "opacity", value)
22 | end
23 |
24 | local function new()
25 |
26 | local element_defaults = {
27 | oak_private = {
28 | -- use an empty function as an ID. TODO: check if this is efficient
29 | id = function() end,
30 | needs_redraw = false,
31 | },
32 | scope = {
33 | -- will be set when the element gets attached to an attached parent:
34 | -- root :
35 | -- window :
36 | -- parent :
37 | -- self :
38 | -- app :
39 | },
40 |
41 | -- TODO: make it so that these should only ever be user-defined.
42 | subscribe_on_self = {},
43 | subscribe_on_parent = {},
44 | subscribe_on_root = {},
45 | subscribe_on_window = {},
46 | subscribe_on_app = {},
47 |
48 | -- transform-related property setters
49 | set_offset_x = set_offset_x,
50 | set_offset_y = set_offset_y,
51 | -- TODO: implement scale_x, scale_y, rotate, origin
52 |
53 | -- drawing related property setters
54 | set_oak_draw = set_oak_draw,
55 | set_opacity = set_opacity,
56 | }
57 |
58 | return tt_table.crush(t_element.new(), element_defaults)
59 | end
60 |
61 | local function element_setup_signals(element, parent_element, parent_root, parent_window, parent_app)
62 | t_sigtools.setup_subscribe_on_object_signals(element, "self", element)
63 | t_sigtools.setup_subscribe_on_object_signals(parent_element, "parent", element)
64 | t_sigtools.setup_subscribe_on_object_signals(parent_root, "root", element)
65 | t_sigtools.setup_subscribe_on_object_signals(parent_window, "window", element)
66 | t_sigtools.setup_subscribe_on_object_signals(parent_app, "app", element)
67 | end
68 |
69 | local function element_teardown_signals(element, parent_element, parent_root, parent_window, parent_app)
70 | t_sigtools.teardown_subscribe_on_object_signals(element, "self", element)
71 | t_sigtools.teardown_subscribe_on_object_signals(parent_element, "parent", element)
72 | t_sigtools.teardown_subscribe_on_object_signals(parent_root, "root", element)
73 | t_sigtools.teardown_subscribe_on_object_signals(parent_window, "window", element)
74 | t_sigtools.teardown_subscribe_on_object_signals(parent_app, "app", element)
75 | end
76 |
77 | local function default_oak_handle_attach_to_parent_element(element, parent, root, window, app)
78 |
79 | element.scope.self = element
80 | element.scope.parent = parent
81 | element.scope.root = root
82 | element.scope.window = window
83 | element.scope.app = app
84 |
85 | element_setup_signals(element, parent, root, window, app)
86 | end
87 |
88 | local function default_oak_handle_detach_from_parent_element(element, parent, root, window, app)
89 |
90 | element.scope.self = nil
91 | element.scope.parent = nil
92 | element.scope.root = nil
93 | element.scope.window = nil
94 | element.scope.app = nil
95 |
96 | element_teardown_signals(element, parent, root, window, app)
97 | end
98 |
99 |
100 | return {
101 | -- constructor
102 | new = new,
103 |
104 | set_offset_x = set_offset_x,
105 | set_offset_y = set_offset_y,
106 | set_oak_draw = set_oak_draw,
107 | set_opacity = set_opacity,
108 |
109 | default_oak_handle_attach_to_parent_element = default_oak_handle_attach_to_parent_element,
110 | default_oak_handle_detach_from_parent_element = default_oak_handle_detach_from_parent_element,
111 | }
112 |
113 |
--------------------------------------------------------------------------------
/l/oak/elements/leaves/bg.lua:
--------------------------------------------------------------------------------
1 |
2 | local tt_table = require("terra.tools.table")
3 | local tt_color = require("terra.tools.color")
4 |
5 | local to_border = require("terra.oak.border")
6 | local to_source = require("terra.oak.source")
7 | local to_shape = require("terra.oak.shape")
8 |
9 | local toe_internal = require("terra.oak.elements.internal")
10 | local toe_element = require("terra.oak.elements.element")
11 |
12 | local toel_leaf = require("terra.oak.elements.leaves.leaf")
13 |
14 |
15 | local function draw_background(bg_elem, cr, width, height)
16 | local bg_source = bg_elem.source
17 |
18 | if bg_source == nil then return end
19 |
20 | local br_top_left, br_top_right, br_bottom_right, br_bottom_left
21 | do
22 | local border_radius = to_border.standardize_radius(bg_elem.border_radius or 0)
23 | br_top_left = border_radius.top_left or 0
24 | br_top_right = border_radius.top_right or 0
25 | br_bottom_right = border_radius.bottom_right or 0
26 | br_bottom_left = border_radius.bottom_left or 0
27 | end
28 | local border_width = bg_elem.border_width or 0
29 |
30 | if br_top_left > 0 or
31 | br_top_right > 0 or
32 | br_bottom_right > 0 or
33 | br_bottom_left > 0
34 | then
35 | -- we have rounded borders, so use a rounded rectangle as the path
36 | cr:save()
37 | cr:translate(border_width, border_width)
38 | to_shape.rounded_rectangle_each(
39 | cr,
40 | width - (border_width * 2),
41 | height - (border_width * 2),
42 | br_top_left,
43 | br_top_right,
44 | br_bottom_right,
45 | br_bottom_left
46 | )
47 | cr:restore()
48 | else
49 | cr:rectangle(
50 | border_width,
51 | border_width,
52 | width - (border_width * 2),
53 | height - (border_width * 2)
54 | )
55 | end
56 |
57 | cr:set_source(to_source.to_cairo_source(bg_source))
58 | cr:fill()
59 | end
60 |
61 | local function draw_border(bg_elem, cr, width, height)
62 |
63 | local border_width = bg_elem.border_width
64 |
65 | if border_width == nil then return end
66 |
67 | local br_top_left, br_top_right, br_bottom_right, br_bottom_left
68 | do
69 | local border_radius = to_border.standardize_radius(bg_elem.border_radius or 0)
70 | br_top_left = border_radius.top_left or 0
71 | br_top_right = border_radius.top_right or 0
72 | br_bottom_right = border_radius.bottom_right or 0
73 | br_bottom_left = border_radius.bottom_left or 0
74 | end
75 |
76 | -- local border_radius = bg_elem.border_radius or 0
77 | local border_source = bg_elem.border_source or tt_color.rgb(0, 0, 0) -- black default color for border
78 | cr:push_group_with_content(lgi.cairo.Content.ALPHA)
79 | cr.fill_rule = lgi.cairo.FillRule.EVEN_ODD
80 |
81 | if br_top_left > 0 or
82 | br_top_right > 0 or
83 | br_bottom_right > 0 or
84 | br_bottom_left > 0
85 | then
86 |
87 | to_shape.rounded_rectangle_each(
88 | cr,
89 | width,
90 | height,
91 | br_top_left + border_width,
92 | br_top_right + border_width,
93 | br_bottom_right + border_width,
94 | br_bottom_left + border_width
95 | )
96 |
97 | -- to_shape.rounded_rectangle(cr, width, height, border_radius + border_width)
98 | cr:translate(border_width, border_width)
99 | -- to_shape.rounded_rectangle(cr, width - (border_width * 2), height - (border_width * 2), border_radius)
100 | to_shape.rounded_rectangle_each(
101 | cr,
102 | width - (border_width * 2),
103 | height - (border_width * 2),
104 | br_top_left,
105 | br_top_right,
106 | br_bottom_right,
107 | br_bottom_left
108 | )
109 | cr:fill()
110 | else
111 | cr:rectangle(0, 0, width, height)
112 | cr:rectangle(
113 | border_width,
114 | border_width,
115 | width - (border_width * 2),
116 | height - (border_width * 2)
117 | )
118 | cr:fill()
119 | end
120 | local msk = cr:pop_group()
121 | cr:set_source(to_source.to_cairo_source(border_source))
122 | cr:mask(msk)
123 | finish_pattern_surface(msk)
124 | end
125 |
126 | local function bg_draw(bg, cr, width, height)
127 | cr:save()
128 | draw_border(bg, cr, width, height)
129 | cr:restore()
130 | draw_background(bg, cr, width, height)
131 | end
132 |
133 | local function set_source(bg, source)
134 | toe_element.default_oak_prop_set(bg, "source", source)
135 | end
136 |
137 | local function new(args)
138 |
139 | local bg_defaults = {
140 |
141 | -- TODO: uncomment these
142 | -- width = ou_internal.SIZE_FILL,
143 | -- height = ou_internal.SIZE_FILL,
144 |
145 | -- part of the interface to be a
146 | oak_handle_attach_to_parent_element = toe_internal.element_common_handle_attach_to_parent_element,
147 | oak_handle_detach_from_parent_element = toe_internal.element_common_handle_detach_from_parent_element,
148 |
149 | -- this element happens to draw something
150 | oak_draw = bg_draw,
151 |
152 | -- TODO: implement this
153 | set_source = set_source,
154 |
155 | -- TODO: make bg also work with halign and valign
156 | }
157 |
158 | return tt_table.crush(toel_leaf.new(), bg_defaults, args)
159 | end
160 |
161 | return {
162 | new = new,
163 |
164 | oak_draw = bg_draw,
165 | set_source = set_source,
166 | }
167 |
168 |
--------------------------------------------------------------------------------
/l/oak/elements/leaves/leaf.lua:
--------------------------------------------------------------------------------
1 |
2 | local tt_table = require("terra.tools.table")
3 |
4 | local toe_element = require("terra.oak.elements.element")
5 | local toe_internal = require("terra.oak.elements.internal")
6 |
7 | local function new()
8 |
9 | local leaf_defaults = {
10 | oak_handle_attach_to_parent_element = toe_element.default_oak_handle_attach_to_parent_element,
11 | oak_handle_detach_from_parent_element = toe_element.default_oak_handle_detach_from_parent_element,
12 |
13 | -- convenience element self-removal method
14 | remove = toe_internal.element_remove,
15 | }
16 |
17 | return tt_table.crush(toe_element.new(), leaf_defaults)
18 | end
19 |
20 | return {
21 | new = new,
22 |
23 | remove = toe_internal.element_remove,
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/l/oak/elements/leaves/svg.lua:
--------------------------------------------------------------------------------
1 |
2 | local lgi = require("lgi")
3 |
4 | local tt_color = require("terra.tools.color")
5 | local tt_table = require("terra.tools.table")
6 |
7 | local to_source = require("terra.oak.source")
8 |
9 | local toel_leaf = require("terra.oak.elements.leaves.leaf")
10 |
11 | -- TODO: check if it still makes sense to keep this code this way
12 | local rsvg_handle_cache = setmetatable({}, { __mode = 'k' })
13 |
14 | ---Load rsvg handle form image file
15 | -- @tparam string file Path to svg file or svg content directly as a lua string.
16 | -- @return Rsvg handle
17 | -- @treturn table A table where cached data can be stored.
18 | local function load_rsvg_handle(file)
19 |
20 | local cache = (rsvg_handle_cache[file] or {})["handle"]
21 |
22 | if cache then
23 | return cache, rsvg_handle_cache[file]
24 | end
25 |
26 | local handle, err
27 |
28 | if file:match("<[?]?xml") or file:match("