Compare commits
8 commits
1d7c2c6f92
...
023c876641
Author | SHA1 | Date | |
---|---|---|---|
|
023c876641 | ||
|
ce685968b2 | ||
|
98288a60cd | ||
|
ae80e1776f | ||
|
f4fcca122b | ||
|
8b56dcd3dd | ||
|
fd482ff455 | ||
|
2a07dc1061 |
11 changed files with 2870 additions and 21 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
compile_flags.txt
|
||||
build/
|
||||
example/libraven.h
|
||||
example/main
|
||||
libraven.h
|
||||
|
|
5
example/build.sh
Normal file
5
example/build.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -xe
|
||||
|
||||
gcc -Wall -Wextra -std=gnu99 $(pkg-config --cflags --libs xcb xcb-xkb cairo pangocairo xkbcommon xkbcommon-x11) -lm main.c -o main
|
58
example/main.c
Normal file
58
example/main.c
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include "pango/pango-font.h"
|
||||
#define RAVEN_IMPLEMENTATION
|
||||
#include "libraven.h"
|
||||
|
||||
typedef struct AppState {
|
||||
PangoFontDescription *desc;
|
||||
} AppState;
|
||||
|
||||
UINode *main_view_scaffold(UINode *root, AppState *state)
|
||||
{
|
||||
{
|
||||
background_node_new(root, UISlate50, 0);
|
||||
UIBoxLayoutNode *layout = box_layout_new(root, UI_DIRECTION_VERTICAL);
|
||||
box_layout_set_margins(layout, 36);
|
||||
layout->gap = 12;
|
||||
layout->justify_secondary_dimension = UI_BOX_LAYOUT_JUSTIFY_CENTER;
|
||||
}
|
||||
|
||||
{
|
||||
UINode *input = (UINode*)text_input_new(root);
|
||||
background_node_new(input, UISlate200, 10);
|
||||
input->width_policy = UI_SIZE_POLICY_STATIC;
|
||||
input->height_policy = UI_SIZE_POLICY_GROW;
|
||||
input->rect.w = 38;
|
||||
|
||||
UIBoxLayoutNode *layout = box_layout_new(input, UI_DIRECTION_HORIZONTAL);
|
||||
layout->justify_secondary_dimension = UI_BOX_LAYOUT_JUSTIFY_CENTER;
|
||||
layout->margin_left = 0;
|
||||
|
||||
UINode *input_text_container = node_new(input, "input_text_container");
|
||||
UITextNode *input_text = text_node_new(input_text_container, state->desc, UINeutral950, NULL);
|
||||
((UITextInputNode*)input)->text_node = input_text;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UIWindow *window = window_new(800, 600);
|
||||
if (!window) {
|
||||
fprintf(stderr, "err: failed to create window\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
AppState state ={
|
||||
.desc = pango_font_description_from_string("Roboto:size=16")
|
||||
};
|
||||
|
||||
UINode *root = node_new(NULL, "[root]");
|
||||
window_attach_root(window, root);
|
||||
main_view_scaffold(root, &state);
|
||||
|
||||
window_turn(window);
|
||||
window_free(window);
|
||||
|
||||
return 0;
|
||||
}
|
145
headerify.py
Normal file
145
headerify.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
from pathlib import Path, PurePath
|
||||
from collections import OrderedDict
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
bundle_header = """
|
||||
// This is an automatically generated bundle of the Raven library. Do not edit this file directly.
|
||||
// Please see the end of the file for licensing information.
|
||||
//
|
||||
// This is a header-only bundle of the Raven library. Add this file to your project and
|
||||
// use `#define RAVEN_IMPLEMENTATION` before including to create the implementation.
|
||||
// Raven depends on these libraries, please make sure you include them before building:
|
||||
// xcb xcb-xkb cairo pangocairo xkbcommon xkbcommon-x11
|
||||
// Compile command example:
|
||||
// gcc -Wall -Wextra -std=gnu99 $(pkg-config --cflags --libs xcb xcb-xkb cairo pangocairo xkbcommon xkbcommon-x11) -lm main.c -o main
|
||||
//
|
||||
// For more information, please see: https://git.hippoz.xyz/hippoz/raven
|
||||
//
|
||||
|
||||
"""
|
||||
|
||||
bundle_footer = """
|
||||
|
||||
// MIT License
|
||||
// Copyright (c) 2023 hippoz
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
def get_local_includes(lines):
|
||||
includes = []
|
||||
for line_index, line_content in enumerate(lines):
|
||||
line = line_content.strip()
|
||||
if line.startswith("#include"):
|
||||
inside_string = False
|
||||
token = ""
|
||||
for char in line:
|
||||
if inside_string:
|
||||
if char == '"':
|
||||
break
|
||||
else:
|
||||
token += char
|
||||
else:
|
||||
if char == '"':
|
||||
inside_string = True
|
||||
if len(token):
|
||||
includes.append({
|
||||
"line_index": line_index,
|
||||
"include_path": token
|
||||
})
|
||||
return includes
|
||||
|
||||
|
||||
def recurse_header_dependencies(header_to_deps, header_name):
|
||||
result = []
|
||||
header_deps = header_to_deps[header_name]
|
||||
for dep in header_deps:
|
||||
result += recurse_header_dependencies(header_to_deps, dep)
|
||||
result.append(header_name)
|
||||
return result
|
||||
|
||||
|
||||
def main(argc, argv) -> int:
|
||||
if argc != 2:
|
||||
print("usage: headerify source_dir")
|
||||
return 1
|
||||
|
||||
source_directory = Path(argv[1])
|
||||
|
||||
header_files = list(source_directory.glob("*.h"))
|
||||
source_files = list(source_directory.glob("*.c"))
|
||||
|
||||
# We need a mapping between the name of a header file and a list of header files that it depends on,
|
||||
# since we will compute the order in which the header files need to be appended afterwards. We
|
||||
# also need a mapping between the name of a header file and the content of that header file,
|
||||
# because we need to comment out the lines that have `#include`s relative to the project, since
|
||||
# we are bundling all of them together.
|
||||
header_to_deps = {}
|
||||
header_to_content = {}
|
||||
for header_file in header_files:
|
||||
header_file_name = header_file.name
|
||||
with header_file.open() as f:
|
||||
lines = f.readlines()
|
||||
local_includes = get_local_includes(lines)
|
||||
|
||||
header_to_deps[header_file_name] = []
|
||||
for local_include in local_includes:
|
||||
# We keep track of the header files that this header file depends on
|
||||
header_to_deps[header_file_name].append(local_include["include_path"])
|
||||
|
||||
# We'll comment out the includes because they're not needed in a single-header bundle
|
||||
lines[local_include["line_index"]] = f"// {lines[local_include['line_index']].strip()} // -- commented because of single-header bundle\n"
|
||||
|
||||
header_to_content[header_file_name] = "".join(lines)
|
||||
|
||||
# Find the order we need to append the header files in based on their dependencies
|
||||
recursed_deps = []
|
||||
for header_file in header_files:
|
||||
recursed_deps += recurse_header_dependencies(header_to_deps, header_file.name)
|
||||
header_file_order = list(OrderedDict.fromkeys(recursed_deps))
|
||||
|
||||
# Find the order we need to append the source files in, based on their respective header files, for consistency
|
||||
source_file_order = []
|
||||
for header_file_name in header_file_order:
|
||||
for source_file in source_files:
|
||||
if source_file.stem == PurePath(header_file_name).stem:
|
||||
source_file_order.append(source_file)
|
||||
break
|
||||
|
||||
# Make sure any source files without a corresponding header are added at the start
|
||||
for source_file in source_files:
|
||||
if source_file not in source_file_order and source_file.stem != "main":
|
||||
source_file_order.insert(0, source_file)
|
||||
|
||||
# We're now ready to stitch the final bundle together
|
||||
bundle = ""
|
||||
bundle += bundle_header
|
||||
|
||||
# Header files
|
||||
bundle += "// ----- header files -----"
|
||||
for header_file_name in header_file_order:
|
||||
bundle += f"\n\n\n// --- {header_file_name} ---\n\n" + header_to_content[header_file_name]
|
||||
bundle += "\n\n// ----- end header files -----\n\n\n\n"
|
||||
|
||||
# Source files
|
||||
bundle += "// ----- source files -----\n#ifdef RAVEN_IMPLEMENTATION\n#define RAVEN_IMPLEMENTATION\n\n"
|
||||
for source_file in source_file_order:
|
||||
with source_file.open() as f:
|
||||
lines = f.readlines()
|
||||
local_includes = get_local_includes(lines)
|
||||
for inc in local_includes:
|
||||
# We'll comment out the includes because they're not needed in a single-header bundle
|
||||
lines[inc["line_index"]] = f"// {lines[inc['line_index']].strip()} // -- commented because of single-header bundle\n"
|
||||
|
||||
bundle += f"\n\n\n// --- {source_file.name} ---\n\n" + "".join(lines)
|
||||
bundle += "\n\n#endif\n// ----- end source files -----\n\n"
|
||||
|
||||
bundle += bundle_footer
|
||||
|
||||
print(bundle)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(len(sys.argv), sys.argv))
|
2613
libraven.h
Normal file
2613
libraven.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -118,7 +118,11 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
|
|||
x += gap;
|
||||
}
|
||||
current->rect.x = x;
|
||||
current->rect.y = y;
|
||||
if (box_layout_node->justify_secondary_dimension == UI_BOX_LAYOUT_JUSTIFY_CENTER) {
|
||||
current->rect.y = margin_top + (maximum_secondary_position - current->rect.h) / 2;
|
||||
} else {
|
||||
current->rect.y = y;
|
||||
}
|
||||
x += current->rect.w;
|
||||
} else {
|
||||
if (current->height_policy == UI_SIZE_POLICY_GROW) {
|
||||
|
@ -130,7 +134,11 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
|
|||
if (current_index) {
|
||||
y += gap;
|
||||
}
|
||||
current->rect.x = x;
|
||||
if (box_layout_node->justify_secondary_dimension == UI_BOX_LAYOUT_JUSTIFY_CENTER) {
|
||||
current->rect.x = margin_left + (maximum_secondary_position - current->rect.w) / 2;
|
||||
} else {
|
||||
current->rect.x = x;
|
||||
}
|
||||
current->rect.y = y;
|
||||
y += current->rect.h;
|
||||
}
|
||||
|
@ -143,6 +151,14 @@ int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void box_layout_set_margins(UIBoxLayoutNode *layout, double margin)
|
||||
{
|
||||
layout->margin_top = margin;
|
||||
layout->margin_left = margin;
|
||||
layout->margin_bottom = margin;
|
||||
layout->margin_right = margin;
|
||||
}
|
||||
|
||||
UIBoxLayoutNode *box_layout_new(UINode *parent, enum UIDirection direction)
|
||||
{
|
||||
UIBoxLayoutNode *n = malloc(sizeof(UIBoxLayoutNode));
|
||||
|
|
|
@ -3,13 +3,20 @@
|
|||
|
||||
#include "node.h"
|
||||
|
||||
enum UIBoxLayoutJustify {
|
||||
UI_BOX_LAYOUT_JUSTIFY_START,
|
||||
UI_BOX_LAYOUT_JUSTIFY_CENTER
|
||||
};
|
||||
|
||||
typedef struct UIBoxLayoutNode {
|
||||
UINode node;
|
||||
enum UIDirection direction;
|
||||
enum UIBoxLayoutJustify justify_secondary_dimension;
|
||||
double margin_top, margin_left, margin_bottom, margin_right, gap;
|
||||
} UIBoxLayoutNode;
|
||||
|
||||
UIBoxLayoutNode *box_layout_new(UINode *parent, enum UIDirection direction);
|
||||
void box_layout_set_margins(UIBoxLayoutNode *layout, double margin);
|
||||
int box_layout_handle(UINode *component, enum UIEvent ev, size_t d, void *p);
|
||||
|
||||
#endif // _UI__BOX_LAYOUT_NODE_H
|
||||
|
|
|
@ -183,7 +183,7 @@ int app_handle(struct UINode *node, void *data, int event_type, size_t d, void *
|
|||
case UPDATE_COUNT: {
|
||||
state->count = d;
|
||||
snprintf(state->counter_text, sizeof(state->counter_text), "%d", state->count);
|
||||
state->counter_text_node->pending_text = state->counter_text;
|
||||
text_node_set_text(state->counter_text_node, state->counter_text);
|
||||
node_request_relayout((UINode*)state->counter_text_node);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -26,10 +26,6 @@ int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
UITextInputNode *n = (UITextInputNode *)node;
|
||||
|
||||
switch (ev) {
|
||||
case UI_EVENT_ATTACHED: {
|
||||
node_notify_subscribe(node->parent, node, (enum UIEvent[]){ UI_EVENT_KEY_DOWN, UI_EVENT_PRESSED, UI_EVENT_UNPRESSED, 0 });
|
||||
break;
|
||||
}
|
||||
case UI_EVENT_KEY_DOWN: {
|
||||
xkb_keycode_t keycode = d;
|
||||
struct xkb_state *xkb_state = (struct xkb_state*)p;
|
||||
|
@ -43,10 +39,10 @@ int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
break;
|
||||
}
|
||||
case XKB_KEY_Delete: {
|
||||
if (n->text_cursor_index > 0 && n->text_cursor_index + 1 <= n->text.size) {
|
||||
ui_string_delete(&n->text, n->text_cursor_index + 1, 1);
|
||||
if (n->text.size > 0 && n->text_cursor_index < n->text.size) {
|
||||
ui_string_delete(&n->text, n->text_cursor_index, 1);
|
||||
}
|
||||
if (n->text_cursor_index > n->text.size) {
|
||||
if (n->text_cursor_index > 0 && n->text_cursor_index > n->text.size) {
|
||||
n->text_cursor_index = n->text.size - 1;
|
||||
}
|
||||
break;
|
||||
|
@ -87,7 +83,7 @@ int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
}
|
||||
|
||||
if (n->text_node) {
|
||||
n->text_node->pending_text = n->text.data;
|
||||
text_node_set_text(n->text_node, n->text.data);
|
||||
n->text_node->caret_index = n->text_cursor_index;
|
||||
n->text_node->wrap = true;
|
||||
node_request_relayout(&n->text_node->node);
|
||||
|
|
|
@ -12,7 +12,7 @@ UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA col
|
|||
UITextNode *n = malloc(sizeof(UITextNode));
|
||||
node_init(&n->node);
|
||||
n->node.flags = UI_NODE_COMPONENT;
|
||||
n->pending_text = text;
|
||||
n->_pending_text = text;
|
||||
n->desc = desc;
|
||||
n->layout = NULL;
|
||||
n->color = color;
|
||||
|
@ -25,6 +25,12 @@ UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA col
|
|||
return n;
|
||||
}
|
||||
|
||||
void text_node_set_text(UITextNode *text_node, char *text)
|
||||
{
|
||||
text_node->_pending_text = text;
|
||||
text_node->node.cached_computed_extents = false;
|
||||
}
|
||||
|
||||
int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
||||
{
|
||||
(void)d;
|
||||
|
@ -36,11 +42,11 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
|
||||
UITextNode *n = (UITextNode*)node;
|
||||
|
||||
if (n->pending_text) {
|
||||
if (n->_pending_text) {
|
||||
if (n->layout) {
|
||||
pango_layout_set_text(n->layout, n->pending_text, -1);
|
||||
pango_layout_set_text(n->layout, n->_pending_text, -1);
|
||||
pango_cairo_update_layout(node->drw, n->layout);
|
||||
n->pending_text = NULL;
|
||||
n->_pending_text = NULL;
|
||||
} else {
|
||||
PangoLayout *layout = pango_cairo_create_layout(n->node.drw);
|
||||
pango_layout_set_font_description(layout, n->desc);
|
||||
|
@ -49,11 +55,10 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
} else {
|
||||
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
|
||||
}
|
||||
pango_layout_set_text(layout, n->pending_text, -1);
|
||||
pango_layout_set_text(layout, n->_pending_text, -1);
|
||||
n->layout = layout;
|
||||
n->pending_text = NULL;
|
||||
n->_pending_text = NULL;
|
||||
}
|
||||
node->parent->cached_computed_extents = false;
|
||||
}
|
||||
|
||||
switch (ev) {
|
||||
|
@ -118,7 +123,7 @@ int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p)
|
|||
g_object_unref(n->layout);
|
||||
}
|
||||
n->layout = NULL;
|
||||
n->pending_text = NULL;
|
||||
n->_pending_text = NULL;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
|
|
@ -11,7 +11,7 @@ typedef struct UITextNode {
|
|||
UINode node;
|
||||
PangoFontDescription *desc;
|
||||
PangoLayout *layout;
|
||||
char *pending_text;
|
||||
char *_pending_text;
|
||||
UIRGBA color;
|
||||
ssize_t caret_index;
|
||||
UINode *caret_node;
|
||||
|
@ -19,6 +19,7 @@ typedef struct UITextNode {
|
|||
} UITextNode;
|
||||
|
||||
int text_node_handle(UINode *node, enum UIEvent ev, size_t d, void *p);
|
||||
void text_node_set_text(UITextNode *text_node, char *text);
|
||||
UITextNode *text_node_new(UINode *parent, PangoFontDescription *desc, UIRGBA color, char *text);
|
||||
|
||||
#endif // _UI__TEXT_NODE_H
|
||||
|
|
Loading…
Reference in a new issue