initial commit
This commit is contained in:
commit
0b3af7375e
43
.ecode/project_build.json
Normal file
43
.ecode/project_build.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"Debug": {
|
||||||
|
"build": [
|
||||||
|
{
|
||||||
|
"args": "-C resources/shaders",
|
||||||
|
"command": "make",
|
||||||
|
"working_dir": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "build -g --threads 8",
|
||||||
|
"command": "c3c",
|
||||||
|
"working_dir": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"build_types": [],
|
||||||
|
"clean": [
|
||||||
|
{
|
||||||
|
"args": "clean",
|
||||||
|
"command": "c3c",
|
||||||
|
"working_dir": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"config": {
|
||||||
|
"clear_sys_env": false
|
||||||
|
},
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"output_parser": {
|
||||||
|
"config": {
|
||||||
|
"relative_file_paths": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"run": [
|
||||||
|
{
|
||||||
|
"args": "",
|
||||||
|
"command": "build/ugui",
|
||||||
|
"name": "Custom Executable",
|
||||||
|
"working_dir": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resources/shaders/compiled/**
|
||||||
|
build
|
||||||
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[submodule "lib/ugui_sdl.c3l"]
|
||||||
|
path = lib/ugui_sdl.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/ugui_sdl.c3l.git
|
||||||
|
[submodule "lib/sdl3.c3l"]
|
||||||
|
path = lib/sdl3.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/sdl3.c3l.git
|
||||||
|
[submodule "lib/schrift.c3l"]
|
||||||
|
path = lib/schrift.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/schrift.c3l.git
|
||||||
|
[submodule "lib/ugui.c3l"]
|
||||||
|
path = lib/ugui.c3l
|
||||||
|
url = https://git.alemauri.eu/alema/ugui.c3l.git
|
||||||
184
ARCHITECTURE.md
Normal file
184
ARCHITECTURE.md
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
## High level overview
|
||||||
|
|
||||||
|
Under the hood every element has an id, this id allows the library to store state
|
||||||
|
between frames.
|
||||||
|
Elements are also cached such that when the ui tree is rebuilt at the beginning of
|
||||||
|
every frame the element data structure doesn't have to be rebuilt.
|
||||||
|
|
||||||
|
Elements are arranged in a tree, nodes are container elements that can contain other
|
||||||
|
elements, leafs are elements that cannot contain other elements.
|
||||||
|
|
||||||
|
Every element has a size and a position, containers also have to keep track of their
|
||||||
|
layout information and some other state.
|
||||||
|
|
||||||
|
Elements can push commands into the draw stack, which is a structure that contains
|
||||||
|
all the draw commands that the user application has to perform do display the ui
|
||||||
|
correctly, such commands include drawing lines, rectangles, sprites, text, etc.
|
||||||
|
|
||||||
|
|
||||||
|
```text
|
||||||
|
+-----------+
|
||||||
|
| ug_init() |
|
||||||
|
+-----+-----+
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
+---------v----------+
|
||||||
|
|ug_input_keyboard() |
|
||||||
|
|ug_input_mouse() <----+
|
||||||
|
|ug_input_clipboard()| |
|
||||||
|
| ... | |
|
||||||
|
+---------+----------+ |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+-------v--------+ |
|
||||||
|
|ug_frame_begin()| |
|
||||||
|
+-------+--------+ |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+---------v----------+ |
|
||||||
|
|ug_window_start() | |
|
||||||
|
+---->ug_container_start()| |
|
||||||
|
| |ug_div_start() | |
|
||||||
|
| | ... | |
|
||||||
|
| +---------+----------+ |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
multiple +--------v---------+ |
|
||||||
|
times |ug_layout_row() | |
|
||||||
|
| |ug_layout_column()| |
|
||||||
|
| |ug_layout_float() | |
|
||||||
|
| | ... | |
|
||||||
|
| +--------+---------+ |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| +------v------+ |
|
||||||
|
| |ug_button() | |
|
||||||
|
| |ug_text_box()| |
|
||||||
|
| |ug_slider() | |
|
||||||
|
| | ... | |
|
||||||
|
| +------+------+ |
|
||||||
|
| | |
|
||||||
|
+--------------+ |
|
||||||
|
| |
|
||||||
|
+--------v---------+ |
|
||||||
|
|ug_window_end() | |
|
||||||
|
|ug_container_end()| |
|
||||||
|
|ug_div_end() | |
|
||||||
|
| ... | |
|
||||||
|
+--------+---------+ |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------v-------+ |
|
||||||
|
|ug_frame_end()| |
|
||||||
|
+------+-------+ |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+------v-------+ |
|
||||||
|
|user draws the| |
|
||||||
|
| ui +-------+
|
||||||
|
+------+-------+
|
||||||
|
|
|
||||||
|
|quit
|
||||||
|
|
|
||||||
|
+------v-------+
|
||||||
|
| ug_destroy() |
|
||||||
|
+--------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layouting
|
||||||
|
|
||||||
|
Layouting happens in a dynamic grid, when a new element is inserted in a non-floating
|
||||||
|
manner it reserves a space in the grid, new elements are placed following this grid.
|
||||||
|
|
||||||
|
Every div has two points of origin, one for the row layout and one for the column
|
||||||
|
layout, named origin_r and origin_c respectively
|
||||||
|
|
||||||
|
origin_r is used when the row layout is used and it is used to position the child
|
||||||
|
elements one next to the other, as such it always points to the top-right edge
|
||||||
|
of the last row element
|
||||||
|
|
||||||
|
```text
|
||||||
|
Layout: row
|
||||||
|
#: lost space
|
||||||
|
Parent div
|
||||||
|
x---------------------------------+
|
||||||
|
|[origin_c] |
|
||||||
|
|[origin_r] |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
+---------------------------------+
|
||||||
|
|
||||||
|
Parent div
|
||||||
|
+-----x---------------------------+
|
||||||
|
| |[origin_r] |
|
||||||
|
| E1 | |
|
||||||
|
| | |
|
||||||
|
x-----+---------------------------+
|
||||||
|
|[origin_c] |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
+-----+---------------------------+
|
||||||
|
|
||||||
|
Parent div
|
||||||
|
+-----+----------+-----x----------+
|
||||||
|
| | E2 | |[origin_r]|
|
||||||
|
| E1 +----------+ | |
|
||||||
|
| |##########| E3 | |
|
||||||
|
+-----+##########| | |
|
||||||
|
|################| | |
|
||||||
|
+----------------x-----+----------+
|
||||||
|
| [origin_c] |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
| | |
|
||||||
|
+----------------+----------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO: handle when the content overflows the div
|
||||||
|
- Use a different concept, like a view or relative space, for example the child
|
||||||
|
div could have position `[0,0]` but in reality it is relative to the origin of the
|
||||||
|
parent div
|
||||||
|
- each div could have a view and a total area of the content, when drawing everything
|
||||||
|
is clipped to the view and scrollbars are shown
|
||||||
|
- individual elements accept dimensions and the x/y coordinates could be interpreted
|
||||||
|
as offset if the layout is row/column or absolute coordinates if the leayout is floating
|
||||||
|
|
||||||
|
A div can be marked resizeable or fixed, and static or dynamic. The difference being
|
||||||
|
that resizeable adds a resize handle to the div and dynamic lets the content overflow
|
||||||
|
causing scrollbars to be drawn
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
How elements determine if they have focus or not
|
||||||
|
|
||||||
|
```C
|
||||||
|
// in begin_{container} code
|
||||||
|
calculate focus
|
||||||
|
set has_focus property
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// in the element code
|
||||||
|
if(PARENT_HAS_FOCUS()) {
|
||||||
|
update stuff
|
||||||
|
} else {
|
||||||
|
fast path to return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
How to get ids:
|
||||||
|
1. use a name for each element
|
||||||
|
2. supply an id for each element
|
||||||
|
3. use a macro and the line position as id and then hash it
|
||||||
|
4. use a macro, get the code line and hash it
|
||||||
5
Makefile
Normal file
5
Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
C3FLAGS = -g
|
||||||
|
|
||||||
|
main: src/main.c3 $(wildcard lib/ugui.c3l/src/*.c3) $(wildcard lib/ugui_sdl.c3l/)
|
||||||
|
make -C resources/shaders
|
||||||
|
c3c build ${C3FLAGS}
|
||||||
43
README.md
Normal file
43
README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# UGUI
|
||||||
|
|
||||||
|
Immediate mode ui library for C3. (Name subject to change).
|
||||||
|
|
||||||
|
This repo contains the library itself, a renderer based on SLD3's new GPU API and a test program.
|
||||||
|
The library is located at `lib/ugui.c3l`.
|
||||||
|
|
||||||
|
The layout algorithm is similar to flexbox. A graphical description of the layout algorithm is located in the [LAYOUT](lib/ugui.c3l/LAYOUT) file. The structure of the library is (was) described in the [ARCHITECTURE](ARCHITECTURE.md) file, most of it is now irrelevant and will be updated in the future.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
* `shaderc` for `glslc`
|
||||||
|
* `c3c` version >0.7.5
|
||||||
|
* `SDL3`
|
||||||
|
* A C compiler
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
1. Clone the repo and all dependencies
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/ma-ale/ugui
|
||||||
|
cd ugui
|
||||||
|
git submodule update --recursive --init
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build libgrapheme
|
||||||
|
```sh
|
||||||
|
cd lib/grapheme.c3l
|
||||||
|
make
|
||||||
|
cd ../..
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Compile libschrift
|
||||||
|
```sh
|
||||||
|
cd lib/schrift.c3l
|
||||||
|
make
|
||||||
|
cd ../..
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Build and run the project
|
||||||
|
```sh
|
||||||
|
make && build/ugui
|
||||||
|
```
|
||||||
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
1
lib/schrift.c3l
Submodule
1
lib/schrift.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 64d168f53b0fe70bda77e2ed7bec35f3f0f1af78
|
||||||
1
lib/sdl3.c3l
Submodule
1
lib/sdl3.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e7356df5d1d0c22a6bba822bda6994062b0b75d7
|
||||||
1
lib/ugui.c3l
Submodule
1
lib/ugui.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 4f7fa7d50c16db3acac020cdd7204e28d42efdf2
|
||||||
1
lib/ugui_sdl.c3l
Submodule
1
lib/ugui_sdl.c3l
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 050624fd67c2d80190114c7774ccfa64b0f449b9
|
||||||
20
project.json
Normal file
20
project.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"langrev": "1",
|
||||||
|
"warnings": ["no-unused"],
|
||||||
|
"dependency-search-paths": ["lib"],
|
||||||
|
"dependencies": ["sdl3", "ugui", "ugui_sdl3"],
|
||||||
|
"features": ["DEBUG_POINTER"],
|
||||||
|
"authors": ["Alessandro Mauri <ale@shitposting.expert>"],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"sources": ["src/**"],
|
||||||
|
"output": "build",
|
||||||
|
"target": "linux-x64",
|
||||||
|
"targets": {
|
||||||
|
"ugui": {
|
||||||
|
"type": "executable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe": true,
|
||||||
|
"opt": "O1",
|
||||||
|
"debug-info": "full"
|
||||||
|
}
|
||||||
0
resources/.gitkeep
Normal file
0
resources/.gitkeep
Normal file
BIN
resources/hack-nerd.ttf
Normal file
BIN
resources/hack-nerd.ttf
Normal file
Binary file not shown.
33
resources/shaders/Makefile
Normal file
33
resources/shaders/Makefile
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
SOURCE_DIR := ./source
|
||||||
|
COMPILED_DIR := ./compiled
|
||||||
|
|
||||||
|
SOURCE_FILES := $(wildcard $(SOURCE_DIR)/*.glsl)
|
||||||
|
|
||||||
|
COMPILED_FILES := $(patsubst $(SOURCE_DIR)/%.glsl,$(COMPILED_DIR)/%.spv,$(SOURCE_FILES))
|
||||||
|
|
||||||
|
all: $(COMPILED_FILES)
|
||||||
|
@echo "Compiling shaders from $(SOURCE_DIR) -> $(COMPILED_DIR)"
|
||||||
|
|
||||||
|
$(COMPILED_DIR)/%.spv: $(SOURCE_DIR)/%.glsl
|
||||||
|
@mkdir -p $(COMPILED_DIR)
|
||||||
|
@stage=$$(basename $< .glsl | cut -d. -f2); \
|
||||||
|
if [ "$$stage" = "frag" ] || [ "$$stage" = "vert" ]; then \
|
||||||
|
echo "$$stage $(notdir $<) > $(notdir $@)"; \
|
||||||
|
glslc -O0 -g -fshader-stage=$$stage $< -o $@; \
|
||||||
|
else \
|
||||||
|
echo "Skipping $<: unsupported stage $$stage"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
$(COMPILED_DIR):
|
||||||
|
mkdir -p $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: tree
|
||||||
|
tree:
|
||||||
|
tree $(COMPILED_DIR)
|
||||||
|
|
||||||
|
.PHONY: compile_all
|
||||||
|
compile_all: clean all tree
|
||||||
118
resources/shaders/source/ugui.frag.glsl
Normal file
118
resources/shaders/source/ugui.frag.glsl
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
/* Combined fragment shader to render UGUI commands */
|
||||||
|
|
||||||
|
// type values, these are the same as in renderer.c3
|
||||||
|
const uint TYPE_RECT = 0;
|
||||||
|
const uint TYPE_FONT = 1;
|
||||||
|
const uint TYPE_SPRITE = 2;
|
||||||
|
const uint TYPE_MSDF = 3;
|
||||||
|
|
||||||
|
// viewport size
|
||||||
|
layout(set = 3, binding = 0) uniform Viewport {
|
||||||
|
ivec2 view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// textures
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D font_atlas;
|
||||||
|
layout(set = 2, binding = 1) uniform sampler2D sprite_atlas;
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
layout(location = 0) in vec4 in_color;
|
||||||
|
layout(location = 1) in vec2 in_uv;
|
||||||
|
layout(location = 2) in vec4 in_quad_size;
|
||||||
|
layout(location = 3) in float in_radius;
|
||||||
|
layout(location = 4) flat in uint in_type;
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
|
||||||
|
// SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels
|
||||||
|
float sdf_rr(vec2 p, vec2 half_size, float radius) {
|
||||||
|
vec2 q = abs(p) - half_size + radius;
|
||||||
|
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const float PX_RANGE = 4.0f;
|
||||||
|
float screen_px_range(vec2 uv, sampler2D tx) {
|
||||||
|
vec2 unit_range = vec2(PX_RANGE)/vec2(textureSize(tx, 0));
|
||||||
|
vec2 texel_size = vec2(1.0)/fwidth(uv);
|
||||||
|
return max(0.5*dot(unit_range, texel_size), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float median(float r, float g, float b) {
|
||||||
|
return max(min(r, g), min(max(r, g), b));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_RECT, draw a rouded rectangle with a SDF
|
||||||
|
void rect_main()
|
||||||
|
{
|
||||||
|
vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5;
|
||||||
|
vec2 half_size = in_quad_size.zw * 0.5;
|
||||||
|
float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius);
|
||||||
|
float alpha = 1.0 - smoothstep(0.0, 1.5, distance);
|
||||||
|
fragColor = vec4(in_color.rgb, in_color.a * alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_SPRITE, draws a sprite sampled from an atlas
|
||||||
|
void sprite_main()
|
||||||
|
{
|
||||||
|
ivec2 ts = textureSize(sprite_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
fragColor = texture(sprite_atlas, real_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_FONT, draws a character sampled from an atlas that contains only the alpha channel
|
||||||
|
void font_main()
|
||||||
|
{
|
||||||
|
ivec2 ts = textureSize(font_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
|
||||||
|
vec4 opacity = texture(font_atlas, real_uv);
|
||||||
|
fragColor = vec4(in_color.rgb, in_color.a*opacity.r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// main for TYPE_MSDF, draws a sprite that is stored as a multi-channel SDF
|
||||||
|
void msdf_main() {
|
||||||
|
ivec2 ts = textureSize(sprite_atlas, 0);
|
||||||
|
vec2 fts = vec2(ts);
|
||||||
|
vec2 real_uv = in_uv.xy / fts;
|
||||||
|
|
||||||
|
vec3 msd = texture(sprite_atlas, real_uv).rgb;
|
||||||
|
float sd = median(msd.r, msd.g, msd.b);
|
||||||
|
float distance = screen_px_range(real_uv, sprite_atlas)*(sd - 0.5);
|
||||||
|
float opacity = clamp(distance + 0.5, 0.0, 1.0);
|
||||||
|
fragColor = in_color * opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// shader main
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
switch (in_type) {
|
||||||
|
case TYPE_RECT:
|
||||||
|
rect_main();
|
||||||
|
break;
|
||||||
|
case TYPE_FONT:
|
||||||
|
font_main();
|
||||||
|
break;
|
||||||
|
case TYPE_SPRITE:
|
||||||
|
sprite_main();
|
||||||
|
break;
|
||||||
|
case TYPE_MSDF:
|
||||||
|
msdf_main();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ERROR, invalid type, return magenta
|
||||||
|
fragColor = vec4(1.0, 0.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
resources/shaders/source/ugui.vert.glsl
Normal file
47
resources/shaders/source/ugui.vert.glsl
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
/* Combined vertex shader to render UGUI commands */
|
||||||
|
|
||||||
|
// Viewport size in pixels
|
||||||
|
layout(set = 1, binding = 0) uniform Viewport {
|
||||||
|
ivec2 view;
|
||||||
|
};
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
layout(location = 0) in ivec2 in_position;
|
||||||
|
layout(location = 1) in ivec4 in_attr; // quad x,y,w,h
|
||||||
|
layout(location = 2) in ivec4 in_uv;
|
||||||
|
layout(location = 3) in uvec4 in_color;
|
||||||
|
layout(location = 4) in uint in_type;
|
||||||
|
|
||||||
|
// outputs
|
||||||
|
layout(location = 0) out vec4 out_color;
|
||||||
|
layout(location = 1) out vec2 out_uv;
|
||||||
|
layout(location = 2) out vec4 out_quad_size;
|
||||||
|
layout(location = 3) out float out_radius;
|
||||||
|
layout(location = 4) out uint out_type;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// vertex position
|
||||||
|
ivec2 px_pos = in_attr.xy + in_position.xy * in_attr.zw;
|
||||||
|
vec2 clip_pos;
|
||||||
|
clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0;
|
||||||
|
clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0);
|
||||||
|
gl_Position = vec4(clip_pos, 0.0, 1.0);
|
||||||
|
|
||||||
|
// color output
|
||||||
|
out_color = vec4(in_color) / 255.0;
|
||||||
|
|
||||||
|
// uv output. only useful if the type is SPRITE
|
||||||
|
vec2 px_uv = in_uv.xy + in_position.xy * in_uv.zw;
|
||||||
|
out_uv = vec2(px_uv);
|
||||||
|
|
||||||
|
// quad size and radius output, only useful if type is RECT
|
||||||
|
out_quad_size = vec4(in_attr);
|
||||||
|
out_radius = float(abs(in_uv.x));
|
||||||
|
|
||||||
|
// type output
|
||||||
|
out_type = in_type;
|
||||||
|
}
|
||||||
86
resources/style.css
Normal file
86
resources/style.css
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
div {
|
||||||
|
bg: #282828;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #6c19ca8f;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
border: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 2;
|
||||||
|
radius: 10;
|
||||||
|
size: 32;
|
||||||
|
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #504945ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 1;
|
||||||
|
radius: 10;
|
||||||
|
size: 20;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle {
|
||||||
|
margin: 2;
|
||||||
|
border: 2;
|
||||||
|
padding: 1;
|
||||||
|
radius: 10;
|
||||||
|
size: 20;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider {
|
||||||
|
margin: 2;
|
||||||
|
padding: 2;
|
||||||
|
border: 1;
|
||||||
|
radius: 4;
|
||||||
|
size: 8;
|
||||||
|
bg: #3c3836ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar {
|
||||||
|
padding: 2;
|
||||||
|
size: 8;
|
||||||
|
bg: #45858842;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #fabd2fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
text-box {
|
||||||
|
bg: #4a4543ff;
|
||||||
|
fg: #fbf1c7ff;
|
||||||
|
primary: #cc241dff;
|
||||||
|
secondary: #458588ff;
|
||||||
|
accent: #8f3f61a8;
|
||||||
|
border: 1;
|
||||||
|
padding: 4;
|
||||||
|
margin: 2;
|
||||||
|
}
|
||||||
BIN
resources/tick_sdf.qoi
Normal file
BIN
resources/tick_sdf.qoi
Normal file
Binary file not shown.
BIN
resources/tux.qoi
Normal file
BIN
resources/tux.qoi
Normal file
Binary file not shown.
BIN
resources/tux_inv.qoi
Normal file
BIN
resources/tux_inv.qoi
Normal file
Binary file not shown.
0
scripts/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
420
src/main.c3
Normal file
420
src/main.c3
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
import std::io;
|
||||||
|
import ugui;
|
||||||
|
import std::time;
|
||||||
|
import std::collections::ringbuffer;
|
||||||
|
import std::core::string;
|
||||||
|
import std::core::mem::allocator;
|
||||||
|
import ugui::sdl::ren;
|
||||||
|
|
||||||
|
|
||||||
|
alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]};
|
||||||
|
|
||||||
|
fn void Times.print_stats(×)
|
||||||
|
{
|
||||||
|
time::NanoDuration min, max, avg, x;
|
||||||
|
min = times.get(0);
|
||||||
|
for (usz i = 0; i < times.written; i++) {
|
||||||
|
x = times.get(i);
|
||||||
|
if (x < min) { min = x; }
|
||||||
|
if (x > max) { max = x; }
|
||||||
|
avg += x;
|
||||||
|
}
|
||||||
|
avg = (NanoDuration)((ulong)avg/128.0);
|
||||||
|
|
||||||
|
io::printfn("min=%s, max=%s, avg=%s", min, max, avg);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimeStats {
|
||||||
|
time::NanoDuration min, max, avg;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn TimeStats Times.get_stats(×)
|
||||||
|
{
|
||||||
|
time::NanoDuration min, max, avg, x;
|
||||||
|
min = times.get(0);
|
||||||
|
for (usz i = 0; i < times.written; i++) {
|
||||||
|
x = times.get(i);
|
||||||
|
if (x < min) { min = x; }
|
||||||
|
if (x > max) { max = x; }
|
||||||
|
avg += x;
|
||||||
|
}
|
||||||
|
avg = (NanoDuration)((ulong)avg/128.0);
|
||||||
|
|
||||||
|
return {.min = min, .max = max, .avg = avg};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv";
|
||||||
|
const char[*] FS_PATH = "resources/shaders/compiled/ugui.frag.spv";
|
||||||
|
|
||||||
|
const char[*] STYLESHEET_PATH = "resources/style.css";
|
||||||
|
|
||||||
|
const bool LIMIT_FPS = true;
|
||||||
|
const bool VSYNC = true;
|
||||||
|
|
||||||
|
fn int main(String[] args)
|
||||||
|
{
|
||||||
|
ArenaAllocator arena;
|
||||||
|
char[] mem = mem::new_array(char, 1024*1024);
|
||||||
|
defer (void)mem::free(mem);
|
||||||
|
arena.init(mem);
|
||||||
|
|
||||||
|
ugui::Ctx ui;
|
||||||
|
ui.init(&arena)!!;
|
||||||
|
defer ui.free();
|
||||||
|
|
||||||
|
ren::Renderer ren;
|
||||||
|
ren.init("Ugui Test", 800, 600, VSYNC);
|
||||||
|
defer ren.free();
|
||||||
|
ui.input_window_size(800, 600)!!;
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// FONT LOADING //
|
||||||
|
// ========================================================================================== //
|
||||||
|
// import font in the ui context
|
||||||
|
ui.load_font(&arena, "font1", "resources/hack-nerd.ttf", 16)!!;
|
||||||
|
|
||||||
|
// set the renderer's font atlas
|
||||||
|
ren.font_atlas_id = ui.get_font_id("font1");
|
||||||
|
|
||||||
|
// send the atlas to the gpu
|
||||||
|
Atlas* font_atlas = ui.get_font_atlas("font1")!!;
|
||||||
|
ren.new_texture("font1", JUST_ALPHA, font_atlas.buffer, font_atlas.width, font_atlas.height);
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// ICON LOADING //
|
||||||
|
// ========================================================================================== //
|
||||||
|
// create the atlas and upload some icons
|
||||||
|
ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!;
|
||||||
|
ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!;
|
||||||
|
ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!;
|
||||||
|
|
||||||
|
// set the renderer's sprite atlas
|
||||||
|
ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons");
|
||||||
|
|
||||||
|
// upload the atlas to the gpu
|
||||||
|
Atlas atlas = ui.sprite_atlas.atlas;
|
||||||
|
ren.new_texture("icons", FULL_COLOR, atlas.buffer, atlas.width, atlas.height);
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// PIPELINE SETUP //
|
||||||
|
// ========================================================================================== //
|
||||||
|
ren.load_spirv_shader_from_file("UGUI_PIPELINE", VS_PATH, FS_PATH, 2, 0);
|
||||||
|
ren.create_pipeline("UGUI_PIPELINE", RECT);
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// CSS INPUT //
|
||||||
|
// ========================================================================================== //
|
||||||
|
io::printfn("imported %d styles", ui.import_style_from_file(STYLESHEET_PATH));
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// OTHER VARIABLES //
|
||||||
|
// ========================================================================================== //
|
||||||
|
TextEdit te;
|
||||||
|
te.buffer = mem::new_array(char, 256);
|
||||||
|
defer mem::free(te.buffer);
|
||||||
|
|
||||||
|
isz frame;
|
||||||
|
double fps;
|
||||||
|
time::Clock clock;
|
||||||
|
time::Clock fps_clock;
|
||||||
|
time::Clock sleep_clock;
|
||||||
|
Times ui_times;
|
||||||
|
Times draw_times;
|
||||||
|
|
||||||
|
// ========================================================================================== //
|
||||||
|
// MAIN LOOP //
|
||||||
|
// ========================================================================================== //
|
||||||
|
ren::pre(ren.win);
|
||||||
|
|
||||||
|
bool quit;
|
||||||
|
while (!quit) {
|
||||||
|
clock.mark();
|
||||||
|
fps_clock.mark();
|
||||||
|
sleep_clock.mark();
|
||||||
|
|
||||||
|
quit = ui.handle_events()!!;
|
||||||
|
|
||||||
|
/* End Input Handling */
|
||||||
|
|
||||||
|
/* Start UI Handling */
|
||||||
|
ui.frame_begin()!!;
|
||||||
|
|
||||||
|
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true;
|
||||||
|
|
||||||
|
const String APPLICATION = "calculator";
|
||||||
|
$switch APPLICATION:
|
||||||
|
$case "debug":
|
||||||
|
debug_app(&ui);
|
||||||
|
$case "calculator":
|
||||||
|
calculator(&ui, &te);
|
||||||
|
$case "popup":
|
||||||
|
popup(&ui);
|
||||||
|
$endswitch
|
||||||
|
|
||||||
|
// Timings counter
|
||||||
|
TimeStats dts = draw_times.get_stats();
|
||||||
|
TimeStats uts = ui_times.get_stats();
|
||||||
|
|
||||||
|
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT, absolute: true, off: {10, 10}) {
|
||||||
|
ui.text(string::tformat("frame %d, fps = %.2f", frame, fps))!!;
|
||||||
|
ui.text(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
||||||
|
}!!;
|
||||||
|
|
||||||
|
ui.frame_end()!!;
|
||||||
|
/* End UI Handling */
|
||||||
|
ui_times.push(clock.mark());
|
||||||
|
//ui_times.print_stats();
|
||||||
|
|
||||||
|
/* Start UI Drawing */
|
||||||
|
ren.begin_render(true);
|
||||||
|
|
||||||
|
ren.render_ugui(&ui.cmd_queue);
|
||||||
|
|
||||||
|
ren.end_render();
|
||||||
|
|
||||||
|
draw_times.push(clock.mark());
|
||||||
|
//draw_times.print_stats();
|
||||||
|
/* End Drawing */
|
||||||
|
|
||||||
|
// wait for the next event, timeout after 100ms
|
||||||
|
int timeout = LIMIT_FPS ? (int)(100.0-sleep_clock.mark().to_ms()-0.5) : 0;
|
||||||
|
if (ui.skip_frame) timeout = 0;
|
||||||
|
ren::wait_events(timeout);
|
||||||
|
|
||||||
|
fps = 1.0 / fps_clock.mark().to_sec();
|
||||||
|
frame++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void debug_app(ugui::Ctx* ui)
|
||||||
|
{
|
||||||
|
static LayoutDirection d = ROW;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@grow(), anchor: CENTER) {
|
||||||
|
ui.@div(ugui::@exact(300), ugui::@exact(300), d, scroll_x: true, scroll_y: true) {
|
||||||
|
if (ui.button("one")!!.mouse_release) d = COLUMN;
|
||||||
|
if (ui.button("two")!!.mouse_release) d = ROW;
|
||||||
|
ui.button("three")!!;
|
||||||
|
ui.button("four")!!;
|
||||||
|
ui.button("five")!!;
|
||||||
|
ui.button("six")!!;
|
||||||
|
ui.button("seven")!!;
|
||||||
|
ui.button("eight")!!;
|
||||||
|
ui.button("nine")!!;
|
||||||
|
ui.button("ten")!!;
|
||||||
|
}!!;
|
||||||
|
}!!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn void debug_app(ugui::Ctx* ui)
|
||||||
|
{
|
||||||
|
static bool toggle;
|
||||||
|
ui.div_begin({.w=-100})!!;
|
||||||
|
{
|
||||||
|
ui.layout_set_column()!!;
|
||||||
|
if (ui.button(icon:"tux")!!.mouse_press) {
|
||||||
|
io::printn("press button0");
|
||||||
|
toggle = !toggle;
|
||||||
|
}
|
||||||
|
//ui.layout_next_column()!!;
|
||||||
|
if (ui.button(label: "ciao", icon: "tick")!!.mouse_press) {
|
||||||
|
io::printn("press button1");
|
||||||
|
}
|
||||||
|
//ui.layout_next_column()!!;
|
||||||
|
if (ui.button()!!.mouse_release) {
|
||||||
|
io::printn("release button2");
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.layout_set_row()!!;
|
||||||
|
ui.layout_next_row()!!;
|
||||||
|
static float rf, gf, bf, af;
|
||||||
|
ui.slider_ver({0,0,30,100}, &rf)!!;
|
||||||
|
ui.slider_ver({0,0,30,100}, &gf)!!;
|
||||||
|
ui.slider_ver({0,0,30,100}, &bf)!!;
|
||||||
|
ui.slider_ver({0,0,30,100}, &af)!!;
|
||||||
|
|
||||||
|
ui.layout_next_column()!!;
|
||||||
|
ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
|
||||||
|
|
||||||
|
ui.layout_next_column()!!;
|
||||||
|
ui.button("Continua!")!!;
|
||||||
|
|
||||||
|
ui.layout_next_row()!!;
|
||||||
|
static bool check;
|
||||||
|
ui.checkbox("", {}, &check, "tick")!!;
|
||||||
|
ui.checkbox("", {}, &check)!!;
|
||||||
|
ui.toggle("", {}, &toggle)!!;
|
||||||
|
|
||||||
|
ui.sprite("tux")!!;
|
||||||
|
static char[128] text_box = "ciao mamma";
|
||||||
|
static usz text_len = "ciao mamma".len;
|
||||||
|
ui.text_box({0,0,200,200}, text_box[..], &text_len)!!;
|
||||||
|
};
|
||||||
|
ui.div_end()!!;
|
||||||
|
|
||||||
|
ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
|
||||||
|
{
|
||||||
|
ui.layout_set_column()!!;
|
||||||
|
static float slider2 = 0.5;
|
||||||
|
if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) {
|
||||||
|
io::printfn("other slider: %f", slider2);
|
||||||
|
}
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
if (toggle) {
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
ui.button()!!;
|
||||||
|
}
|
||||||
|
ui.layout_next_column()!!;
|
||||||
|
ui.layout_set_row()!!;
|
||||||
|
static float f1, f2;
|
||||||
|
ui.slider_hor({0,0,100,30}, &f1)!!;
|
||||||
|
ui.slider_hor({0,0,100,30}, &f2)!!;
|
||||||
|
};
|
||||||
|
ui.div_end()!!;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
import std::os::process;
|
||||||
|
|
||||||
|
fn void calculator(ugui::Ctx* ui, TextEdit* te)
|
||||||
|
{
|
||||||
|
static char[128] buffer;
|
||||||
|
static usz len;
|
||||||
|
bool eval;
|
||||||
|
|
||||||
|
// keyboard input
|
||||||
|
switch(ui.get_keys()) {
|
||||||
|
case "+": nextcase;
|
||||||
|
case "-": nextcase;
|
||||||
|
case "*": nextcase;
|
||||||
|
case "/": nextcase;
|
||||||
|
case "(": nextcase;
|
||||||
|
case ")": nextcase;
|
||||||
|
case ".": nextcase;
|
||||||
|
case "0": nextcase;
|
||||||
|
case "1": nextcase;
|
||||||
|
case "2": nextcase;
|
||||||
|
case "3": nextcase;
|
||||||
|
case "4": nextcase;
|
||||||
|
case "5": nextcase;
|
||||||
|
case "6": nextcase;
|
||||||
|
case "7": nextcase;
|
||||||
|
case "8": nextcase;
|
||||||
|
case "9":
|
||||||
|
buffer[len++] = ui.get_keys()[0];
|
||||||
|
case "\n":
|
||||||
|
eval = len != 0;
|
||||||
|
case "c":
|
||||||
|
len = 0;
|
||||||
|
case "d":
|
||||||
|
if (len > 0) len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool toggle;
|
||||||
|
static ugui::Point pos;
|
||||||
|
if (ui.is_mouse_released(ugui::BTN_RIGHT)) {
|
||||||
|
pos = ui.input.mouse.pos;
|
||||||
|
toggle = !toggle;
|
||||||
|
}
|
||||||
|
if (toggle) {
|
||||||
|
toggle = ui.popup_begin(pos, ugui::@fit(50), ugui::@fit(100), COLUMN)!!;
|
||||||
|
ui.button("Uno")!!;
|
||||||
|
ui.button("Due")!!;
|
||||||
|
ui.button("Tre")!!;
|
||||||
|
ui.button("Quattro")!!;
|
||||||
|
ui.div_end()!!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ui input/output
|
||||||
|
ui.@div(ugui::@grow(), ugui::@grow(), ROW, CENTER) { // center everything on the screen
|
||||||
|
ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT) {
|
||||||
|
|
||||||
|
ui.@div(ugui::@grow(), ugui::@fit(), ROW, CENTER) {ui.text("SHITTY AHH CALCULATOR")!!;}!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@exact(100), ROW, RIGHT) {
|
||||||
|
ui.text((String)buffer[:len])!!;
|
||||||
|
}!!;
|
||||||
|
ui.@row() {
|
||||||
|
ui.@column() {
|
||||||
|
ui.button("7")!!.mouse_press ? buffer[len++] = '7' : 0;
|
||||||
|
ui.button("4")!!.mouse_press ? buffer[len++] = '4' : 0;
|
||||||
|
ui.button("1")!!.mouse_press ? buffer[len++] = '1' : 0;
|
||||||
|
ui.button("0")!!.mouse_press ? buffer[len++] = '0' : 0;
|
||||||
|
}!!;
|
||||||
|
ui.@column() {
|
||||||
|
ui.button("8")!!.mouse_press ? buffer[len++] = '8' : 0;
|
||||||
|
ui.button("5")!!.mouse_press ? buffer[len++] = '5' : 0;
|
||||||
|
ui.button("2")!!.mouse_press ? buffer[len++] = '2' : 0;
|
||||||
|
ui.button(".")!!.mouse_press ? buffer[len++] = '.' : 0;
|
||||||
|
}!!;
|
||||||
|
ui.@column() {
|
||||||
|
ui.button("9")!!.mouse_press ? buffer[len++] = '9' : 0;
|
||||||
|
ui.button("6")!!.mouse_press ? buffer[len++] = '6' : 0;
|
||||||
|
ui.button("3")!!.mouse_press ? buffer[len++] = '3' : 0;
|
||||||
|
ui.button("(")!!.mouse_press ? buffer[len++] = '(' : 0;
|
||||||
|
}!!;
|
||||||
|
ui.@div(ugui::@exact(10), ugui::@exact(10)) {}!!;
|
||||||
|
ui.@column() {
|
||||||
|
ui.button("x")!!.mouse_press ? buffer[len++] = '*' : 0;
|
||||||
|
ui.button("/")!!.mouse_press ? buffer[len++] = '/' : 0;
|
||||||
|
ui.button("+")!!.mouse_press ? buffer[len++] = '+' : 0;
|
||||||
|
ui.button(")")!!.mouse_press ? buffer[len++] = ')' : 0;
|
||||||
|
}!!;
|
||||||
|
ui.@column() {
|
||||||
|
ui.button("C")!!.mouse_press ? len = 0 : 0;
|
||||||
|
ui.button("D")!!.mouse_press ? len > 0 ? len-- : 0 : 0;
|
||||||
|
ui.button("-")!!.mouse_press ? buffer[len++] = '-' : 0;
|
||||||
|
|
||||||
|
// eval the expression with 'bc'
|
||||||
|
if (ui.button("=")!!.mouse_press || eval) {
|
||||||
|
char[128] out;
|
||||||
|
String y = string::tformat("echo '%s' | bc", (String)buffer[:len]);
|
||||||
|
String x = process::execute_stdout_to_buffer(out[:128], (String[]){"sh", "-c", y}) ?? "";
|
||||||
|
buffer[:x.len] = x[..];
|
||||||
|
len = x.len;
|
||||||
|
}
|
||||||
|
}!!;
|
||||||
|
}!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER) {
|
||||||
|
static bool state;
|
||||||
|
ui.checkbox("boolean", &state, "tick")!!;
|
||||||
|
ui.sprite("tux", 32)!!;
|
||||||
|
ui.toggle("lmao", &state)!!;
|
||||||
|
}!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@exact(50), anchor: CENTER, scroll_y: true) {
|
||||||
|
static float f;
|
||||||
|
ui.slider_hor(ugui::@exact(100), ugui::@exact(20), &f)!!;
|
||||||
|
ui.slider_ver(ugui::@exact(20), ugui::@exact(100), &f)!!;
|
||||||
|
}!!;
|
||||||
|
ui.@div(ugui::@grow(), ugui::@fit(), anchor: CENTER) {
|
||||||
|
ui.text_box(ugui::@grow(), ugui::@exact(100), te, TOP_LEFT)!!;
|
||||||
|
}!!;
|
||||||
|
}!!; }!!;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void popup(ugui::Ctx* ui)
|
||||||
|
{
|
||||||
|
static bool toggle;
|
||||||
|
static ugui::Point pos;
|
||||||
|
|
||||||
|
if (toggle) {
|
||||||
|
toggle = ui.popup_begin(pos, ugui::@fit(), ugui::@fit(), COLUMN)!!;
|
||||||
|
ui.button("POP")!!;
|
||||||
|
ui.button("UP")!!;
|
||||||
|
ui.div_end()!!;
|
||||||
|
}
|
||||||
|
ui.@center(COLUMN) {
|
||||||
|
if (ui.button("ciao")!!.mouse_release) {
|
||||||
|
pos = ui.input.mouse.pos;
|
||||||
|
toggle = ~toggle;
|
||||||
|
}
|
||||||
|
if (ui.button("mamma")!!.mouse_press) ugui::println("pressed!");
|
||||||
|
}!!;
|
||||||
|
}
|
||||||
14
test/test_bitsruct.c3
Normal file
14
test/test_bitsruct.c3
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
bitstruct Bits : uint {
|
||||||
|
bool a : 0;
|
||||||
|
bool b : 1;
|
||||||
|
bool c : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
Bits a = {false, true, false};
|
||||||
|
Bits b = {true, true, false};
|
||||||
|
Bits c = a | b;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
test/test_bittype.c3
Normal file
10
test/test_bittype.c3
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import std::io;
|
||||||
|
import std::collections::bitset;
|
||||||
|
|
||||||
|
def Bits = bitset::BitSet(<128>);
|
||||||
|
|
||||||
|
fn void main()
|
||||||
|
{
|
||||||
|
Bits b;
|
||||||
|
io::printn($typeof(b.data[0]).sizeof);
|
||||||
|
}
|
||||||
19
test/test_color.c3
Normal file
19
test/test_color.c3
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import std::io;
|
||||||
|
|
||||||
|
struct Color { short r,g,b,a; }
|
||||||
|
|
||||||
|
macro Color uint.to_rgba(uint u)
|
||||||
|
{
|
||||||
|
return Color{
|
||||||
|
.r = (char)((u >> 24) & 0xff),
|
||||||
|
.g = (char)((u >> 16) & 0xff),
|
||||||
|
.b = (char)((u >> 8) & 0xff),
|
||||||
|
.a = (char)((u >> 0) & 0xff)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void main(String[] args)
|
||||||
|
{
|
||||||
|
uint col = args[1].to_uint()!!;
|
||||||
|
io::printn(col.to_rgba());
|
||||||
|
}
|
||||||
14
test/test_custom_hash.c3
Normal file
14
test/test_custom_hash.c3
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import std::collections::map;
|
||||||
|
|
||||||
|
def Codepoint = uint;
|
||||||
|
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();
|
||||||
|
def CodeMap = map::HashMap(<Codepoint, Codepoint>);
|
||||||
|
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
CodeMap m;
|
||||||
|
m.new_init();
|
||||||
|
m.free();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
72
test/test_error.c3
Normal file
72
test/test_error.c3
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import std::io;
|
||||||
|
|
||||||
|
struct FaultStack {
|
||||||
|
usz elem;
|
||||||
|
anyfault[16] v;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void FaultStack.push(&fs, anyfault f)
|
||||||
|
{
|
||||||
|
if (fs.elem < fs.v.len) {
|
||||||
|
fs.v[fs.elem++] = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn anyfault FaultStack.pop(&fs)
|
||||||
|
{
|
||||||
|
return fs.elem > 0 ? fs.v[fs.elem-- - 1] : anyfault{};
|
||||||
|
}
|
||||||
|
|
||||||
|
FaultStack fs;
|
||||||
|
|
||||||
|
fn int! err1()
|
||||||
|
{
|
||||||
|
return IoError.OUT_OF_SPACE?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void! err2()
|
||||||
|
{
|
||||||
|
return IoError.EOF?;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
macro @unwrap(#f)
|
||||||
|
{
|
||||||
|
$if ($typeof(#f).typeid == void!.typeid) {
|
||||||
|
if (catch err = #f) { fs.push(err); }
|
||||||
|
return;
|
||||||
|
} $else {
|
||||||
|
$typeof(#f) x = #f;
|
||||||
|
if (catch err = x) {
|
||||||
|
fs.push(err);
|
||||||
|
return $typeof(#f!!){};
|
||||||
|
} else {return x;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
<*
|
||||||
|
@require @typekind(#func) == OPTIONAL : `@unwrap requires an optional value`
|
||||||
|
*>
|
||||||
|
macro @unwrap(#func)
|
||||||
|
{
|
||||||
|
anyfault exc = @catch(#func);
|
||||||
|
if (exc != anyfault{}) {
|
||||||
|
fs.push(exc);
|
||||||
|
$if $typeof(#func!!).typeid != void.typeid:
|
||||||
|
return $typeof(#func!!){};
|
||||||
|
$else
|
||||||
|
return;
|
||||||
|
$endif
|
||||||
|
} else {
|
||||||
|
return #func!!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void main()
|
||||||
|
{
|
||||||
|
@unwrap(err1());
|
||||||
|
@unwrap(err2());
|
||||||
|
|
||||||
|
io::printfn("%s", fs.v);
|
||||||
|
}
|
||||||
6
test/test_font.c3
Normal file
6
test/test_font.c3
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import rl;
|
||||||
|
|
||||||
|
fn int main(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
30
test/test_idgen.c3
Normal file
30
test/test_idgen.c3
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import std::io;
|
||||||
|
|
||||||
|
alias Id = uint;
|
||||||
|
|
||||||
|
fn void foo_ex(Id id)
|
||||||
|
{
|
||||||
|
io::printfn("id = %d", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro Id @compute_id(...)
|
||||||
|
{
|
||||||
|
Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE);
|
||||||
|
$for var $i = 0; $i < $vacount; $i++:
|
||||||
|
id ^= (Id)$vaconst[$i].hash();
|
||||||
|
$endfor
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro foo(...) => foo_ex(@compute_id($vasplat));
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
foo_ex(1234);
|
||||||
|
foo();
|
||||||
|
foo();
|
||||||
|
foo();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
26
test/test_keyboard.c3
Normal file
26
test/test_keyboard.c3
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import rl;
|
||||||
|
import std::io;
|
||||||
|
|
||||||
|
fn int main(String[] args)
|
||||||
|
{
|
||||||
|
short width = 800;
|
||||||
|
short height = 450;
|
||||||
|
rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE);
|
||||||
|
rl::init_window(width, height, "Ugui Test");
|
||||||
|
rl::set_target_fps(60);
|
||||||
|
rl::enable_event_waiting();
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
KeyboardKey k;
|
||||||
|
while (!rl::window_should_close()) {
|
||||||
|
do {
|
||||||
|
k = rl::get_char_pressed();
|
||||||
|
io::printfn("%s", k);
|
||||||
|
} while (k != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
rl::close_window();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
342
test/test_mtree.c3
Normal file
342
test/test_mtree.c3
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
module mtree{Type};
|
||||||
|
|
||||||
|
import std::core::mem;
|
||||||
|
import std::core::mem::allocator;
|
||||||
|
import std::io;
|
||||||
|
import std::bits;
|
||||||
|
import std::collections::list;
|
||||||
|
|
||||||
|
|
||||||
|
alias Bitmap = ulong;
|
||||||
|
const BITS = Bitmap.sizeof*8;
|
||||||
|
|
||||||
|
alias IdxList = list::List{int};
|
||||||
|
|
||||||
|
// more: if positive it contains the index of the next node that contains the children information
|
||||||
|
struct Node {
|
||||||
|
int more;
|
||||||
|
int parent;
|
||||||
|
Bitmap children;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MTree {
|
||||||
|
usz elements;
|
||||||
|
Allocator allocator;
|
||||||
|
IdxList queue;
|
||||||
|
Bitmap[] used;
|
||||||
|
Type[] elem_mat; // element matrix
|
||||||
|
Node[] refs_mat; // relationship matrix
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn void MTree.init(&tree, usz size, Allocator allocator = mem)
|
||||||
|
{
|
||||||
|
// round size to the nearest multiple of BITS
|
||||||
|
size = size + size%BITS;
|
||||||
|
|
||||||
|
tree.elements = 0;
|
||||||
|
tree.allocator = allocator;
|
||||||
|
tree.queue.init(allocator, size);
|
||||||
|
tree.used = allocator::new_array(tree.allocator, Bitmap, size/BITS);
|
||||||
|
tree.elem_mat = allocator::new_array(tree.allocator, Type, size);
|
||||||
|
tree.refs_mat = allocator::new_array(tree.allocator, Node, size);
|
||||||
|
|
||||||
|
foreach (&r: tree.refs_mat) {
|
||||||
|
r.more = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn void MTree.free(&tree)
|
||||||
|
{
|
||||||
|
tree.elements = 0;
|
||||||
|
tree.queue.free();
|
||||||
|
(void)allocator::free(tree.allocator, tree.used);
|
||||||
|
(void)allocator::free(tree.allocator, tree.elem_mat);
|
||||||
|
(void)allocator::free(tree.allocator, tree.refs_mat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn int MTree.get_free_spot(&tree)
|
||||||
|
{
|
||||||
|
foreach (idx, d: tree.used) {
|
||||||
|
if (d != $typeof(d).max) {
|
||||||
|
int spot = (int)idx*BITS + BITS-(int)d.clz();
|
||||||
|
return spot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable("no free spots left");
|
||||||
|
}
|
||||||
|
|
||||||
|
<* @require idx >= 0 *>
|
||||||
|
fn void MTree.set_used(&tree, int idx)
|
||||||
|
{
|
||||||
|
int r = idx % BITS;
|
||||||
|
int q = idx / BITS;
|
||||||
|
tree.used[q] |= (1l << r);
|
||||||
|
}
|
||||||
|
|
||||||
|
<* @require idx >= 0 *>
|
||||||
|
fn void MTree.unset_used(&tree, int idx)
|
||||||
|
{
|
||||||
|
int r = idx % BITS;
|
||||||
|
int q = idx / BITS;
|
||||||
|
tree.used[q] &= ~(1l << r);
|
||||||
|
}
|
||||||
|
|
||||||
|
<* @require idx >= 0 *>
|
||||||
|
fn bool MTree.is_used(&tree, int idx)
|
||||||
|
{
|
||||||
|
int r = idx % BITS;
|
||||||
|
int q = idx / BITS;
|
||||||
|
return !!(tree.used[q] & (1l << r));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get the last node in the "more" chain
|
||||||
|
<* @require tree.is_used(parent) == true *>
|
||||||
|
fn int MTree.last_node(&tree, int parent)
|
||||||
|
{
|
||||||
|
while(tree.refs_mat[parent].more >= 0) {
|
||||||
|
parent = tree.refs_mat[parent].more;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<* @require tree.elements == 0 || tree.is_used(parent) == true *>
|
||||||
|
fn int MTree.add(&tree, int parent, Type t)
|
||||||
|
{
|
||||||
|
int idx = tree.get_free_spot();
|
||||||
|
int subtree = idx / BITS;
|
||||||
|
|
||||||
|
tree.set_used(idx);
|
||||||
|
tree.elem_mat[idx] = t;
|
||||||
|
tree.refs_mat[idx] = (Node){
|
||||||
|
.parent = parent,
|
||||||
|
.more = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.elements++;
|
||||||
|
// root element, has no parent
|
||||||
|
if (tree.elements == 1) {
|
||||||
|
tree.refs_mat[idx].parent = -1;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if the parent already has a node in the same subtree as the child then update that node's
|
||||||
|
// children bitmap
|
||||||
|
bool done;
|
||||||
|
for (int p = parent; p >= 0; p = tree.refs_mat[p].more) {
|
||||||
|
int ps = p/BITS;
|
||||||
|
if (ps == subtree) {
|
||||||
|
tree.refs_mat[p].children |= (1l << (idx%BITS));
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// on fail we need to create another parent node
|
||||||
|
if (!done) {
|
||||||
|
int new_more = tree.get_free_spot();
|
||||||
|
// if the new node does not land in the same subtree as the child we cannot do
|
||||||
|
// anything since the references are immutable
|
||||||
|
if (new_more/BITS != subtree) {
|
||||||
|
unreachable("cannot allocate new child for parent");
|
||||||
|
}
|
||||||
|
tree.set_used(new_more);
|
||||||
|
tree.elements++;
|
||||||
|
// update the "more" chain
|
||||||
|
int last_link = tree.last_node(parent);
|
||||||
|
tree.refs_mat[last_link].more = new_more;
|
||||||
|
tree.refs_mat[new_more].more = -1;
|
||||||
|
tree.refs_mat[new_more].children |= (long)(1 << (idx%BITS));
|
||||||
|
tree.refs_mat[new_more].parent = last_link;
|
||||||
|
// FIXME: the elem_mat is not updated, do we need to?
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get the index of the n-th children of parent, -1 otherwise
|
||||||
|
// usage: for (int i, c; (c = tree.children_it(parent, i)) >= 0; i++) { ... }
|
||||||
|
fn int MTree.children_it(&tree, int parent, int n)
|
||||||
|
{
|
||||||
|
int tot_children;
|
||||||
|
int child;
|
||||||
|
for (int p = parent; p >= 0; p = tree.refs_mat[p].more) {
|
||||||
|
int cn = (int)tree.refs_mat[p].children.popcount();
|
||||||
|
tot_children += cn;
|
||||||
|
|
||||||
|
// we are in the right subtree
|
||||||
|
if (tot_children > n) {
|
||||||
|
child = (p/BITS) * BITS; // start at the parent's subtree index
|
||||||
|
int j = cn - (tot_children - n); // we need the j-th children of this node
|
||||||
|
Bitmap u = tree.refs_mat[p].children;
|
||||||
|
|
||||||
|
child += j; // add the children number
|
||||||
|
do {
|
||||||
|
child += (int)u.ctz(); // increment by the skipped zeroes
|
||||||
|
u >>= u.ctz() + 1;
|
||||||
|
j--;
|
||||||
|
} while (j >= 0);
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int MTree.children_num(&tree, int parent)
|
||||||
|
{
|
||||||
|
int n;
|
||||||
|
for (int p = parent; p >= 0; p = tree.refs_mat[p].more) {
|
||||||
|
n += (int)tree.refs_mat[p].children.popcount();
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int MTree.subtree_size(&tree, int parent)
|
||||||
|
{
|
||||||
|
int x = tree.children_num(parent);
|
||||||
|
int c;
|
||||||
|
for (int n; (c = tree.children_it(parent, n)) >= 0; n++) {
|
||||||
|
x += tree.subtree_size(c);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn int MTree.level_order_it(&tree, int parent, int i)
|
||||||
|
{
|
||||||
|
if (i == 0) {
|
||||||
|
tree.queue.clear();
|
||||||
|
tree.queue.push(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tree.queue.len() == 0) return -1;
|
||||||
|
|
||||||
|
int p = tree.queue.pop_first()!!;
|
||||||
|
int c;
|
||||||
|
for (int n; (c = tree.children_it(p, n)) >= 0; n++) {
|
||||||
|
tree.queue.push(c);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void MTree.prune(&tree, int parent)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
for (int i = 0; (c = tree.children_it(parent, i)) >= 0; i++) {
|
||||||
|
tree.prune(c); // prune the subtree
|
||||||
|
|
||||||
|
// delete all children including their more chain
|
||||||
|
for (int p = c; p >= 0;) {
|
||||||
|
int next = tree.refs_mat[p].more;
|
||||||
|
tree.unset_used(p);
|
||||||
|
tree.refs_mat[p] = {.more = -1};
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally delete the parent
|
||||||
|
for (int p = parent; p >= 0;) {
|
||||||
|
int next = tree.refs_mat[p].more;
|
||||||
|
tree.unset_used(p);
|
||||||
|
tree.elements--;
|
||||||
|
tree.refs_mat[p] = {.more = -1};
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro bool MTree.is_root(&t, int i) => t.refs_mat[i].parent == -1;
|
||||||
|
|
||||||
|
fn void MTree.print(&tree)
|
||||||
|
{
|
||||||
|
foreach (idx, c: tree.elem_mat) {
|
||||||
|
if (tree.is_used((int)idx)) {
|
||||||
|
io::printfn("[%d](%s) parent:%d more:%d children:%b",
|
||||||
|
idx, c, tree.refs_mat[idx].parent, tree.refs_mat[idx].more,
|
||||||
|
tree.refs_mat[idx].children
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module foo;
|
||||||
|
|
||||||
|
import std::io;
|
||||||
|
import mtree;
|
||||||
|
|
||||||
|
alias Tree = mtree::MTree{int};
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
Tree t;
|
||||||
|
t.init(256);
|
||||||
|
defer t.free();
|
||||||
|
/*
|
||||||
|
int root = t.add(0, 0);
|
||||||
|
int c1 = t.add(root, 1);
|
||||||
|
int c2 = t.add(root, 2);
|
||||||
|
|
||||||
|
int c11 = t.add(c1, 11);
|
||||||
|
int c12 = t.add(c1, 12);
|
||||||
|
|
||||||
|
int c3 = t.add(root, 3);
|
||||||
|
|
||||||
|
for (int x = 0; x < 70; x++) {
|
||||||
|
t.add(c2, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int c31 = t.add(c3, 31);
|
||||||
|
int c32 = t.add(c3, 32);
|
||||||
|
|
||||||
|
int c4 = t.add(root, 4);
|
||||||
|
|
||||||
|
int c13 = t.add(c1, 13);
|
||||||
|
int c14 = t.add(c1, 14);
|
||||||
|
int c15 = t.add(c1, 15);
|
||||||
|
|
||||||
|
t.prune(c2);
|
||||||
|
|
||||||
|
io::printn("printing tree");
|
||||||
|
t.print();
|
||||||
|
|
||||||
|
usz x;
|
||||||
|
foreach_r (u: t.used) {
|
||||||
|
x += u.popcount();
|
||||||
|
io::printf("%b ", u);
|
||||||
|
}
|
||||||
|
io::printfn("TOT:%d/%d",x,t.elements);
|
||||||
|
|
||||||
|
io::printn(t.subtree_size(root));
|
||||||
|
|
||||||
|
io::printn();
|
||||||
|
*/
|
||||||
|
|
||||||
|
int root = t.add(0, 0);
|
||||||
|
int c1 = t.add(root, 1);
|
||||||
|
int c2 = t.add(root, 2);
|
||||||
|
int c3 = t.add(root, 3);
|
||||||
|
|
||||||
|
int c11 = t.add(c1, 11);
|
||||||
|
int c12 = t.add(c1, 12);
|
||||||
|
|
||||||
|
int c111 = t.add(c11, 111);
|
||||||
|
int c121 = t.add(c12, 121);
|
||||||
|
|
||||||
|
int c31 = t.add(c3, 31);
|
||||||
|
|
||||||
|
int c;
|
||||||
|
for (int i; (c = t.level_order_it(root, i)) >= 0; i++) {
|
||||||
|
io::printfn("%d-th: [%d](%d)", i, c, t.elem_mat[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
94
test/test_renderer.c3
Normal file
94
test/test_renderer.c3
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import sdlrenderer::ren;
|
||||||
|
import std::io;
|
||||||
|
import std::thread;
|
||||||
|
import sdl3::sdl;
|
||||||
|
import std::compression::qoi;
|
||||||
|
import std::core::mem::allocator;
|
||||||
|
|
||||||
|
char[*] shader_rect_vert = $embed("resources/shaders/compiled/rect.vert.spv");
|
||||||
|
char[*] shader_rect_frag = $embed("resources/shaders/compiled/rect.frag.spv");
|
||||||
|
|
||||||
|
char[*] shader_sprite_vert = $embed("resources/shaders/compiled/sprite.vert.spv");
|
||||||
|
char[*] shader_sprite_frag = $embed("resources/shaders/compiled/sprite.frag.spv");
|
||||||
|
|
||||||
|
const uint WINDOW_WIDTH = 640;
|
||||||
|
const uint WINDOW_HEIGHT = 480;
|
||||||
|
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
ren::Renderer ren;
|
||||||
|
ren.init("test window", WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
|
||||||
|
// TODO: these could be the same function
|
||||||
|
ren.load_spirv_shader_from_mem("rect shader", &shader_rect_vert, &shader_rect_frag, 0, 0);
|
||||||
|
ren.create_pipeline("rect shader", RECT);
|
||||||
|
|
||||||
|
// load the tux qoi image
|
||||||
|
QOIDesc img_desc;
|
||||||
|
char[] img_pixels = qoi::read(allocator::temp(), "resources/tux.qoi", &img_desc)!!;
|
||||||
|
// and put it in a texture
|
||||||
|
ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height);
|
||||||
|
// create a new pipeline to use the texture
|
||||||
|
ren.load_spirv_shader_from_mem("sprite shader", &shader_sprite_vert, &shader_sprite_frag, 1, 0);
|
||||||
|
ren.create_pipeline("sprite shader", SPRITE);
|
||||||
|
|
||||||
|
|
||||||
|
sdl::Event e;
|
||||||
|
bool quit = false;
|
||||||
|
for (usz i = 0; !quit; i++) {
|
||||||
|
|
||||||
|
if (sdl::poll_event(&e)) {
|
||||||
|
if (e.type == EVENT_QUIT) {
|
||||||
|
quit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 300) {
|
||||||
|
io::printn("ciao!");
|
||||||
|
img_pixels = qoi::read(allocator::temp(), "resources/tux_inv.qoi", &img_desc)!!;
|
||||||
|
ren.update_texture("tux", img_pixels, img_desc.width, img_desc.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
ren.begin_render(true);
|
||||||
|
|
||||||
|
// Colored Rectangles Render Pass
|
||||||
|
ren.start_render_pass("rect shader");
|
||||||
|
|
||||||
|
// rect 1
|
||||||
|
ren.push_quad(100,100,100,100,0xff00ff00, 20);
|
||||||
|
// rect 2
|
||||||
|
ren.push_quad(0,0,20,20,0xff0000ff);
|
||||||
|
// rect 3
|
||||||
|
ren.push_quad(200,300,50,50,0xffff0000);
|
||||||
|
|
||||||
|
//ren.set_scissor(0,50,200,300);
|
||||||
|
|
||||||
|
ren.draw_quads();
|
||||||
|
|
||||||
|
ren.end_render_pass();
|
||||||
|
// End Rectangle Render Pass
|
||||||
|
|
||||||
|
|
||||||
|
// Textured Rectangles Render Pass
|
||||||
|
ren.start_render_pass("sprite shader");
|
||||||
|
|
||||||
|
// bind the pipeline's sampler
|
||||||
|
ren.bind_texture("tux");
|
||||||
|
|
||||||
|
// tux
|
||||||
|
ren.push_sprite(300, 0, 54, 64, 0, 0);
|
||||||
|
|
||||||
|
ren.reset_scissor();
|
||||||
|
|
||||||
|
ren.draw_quads();
|
||||||
|
|
||||||
|
ren.end_render_pass();
|
||||||
|
// End Textured Rectangle Render Pass
|
||||||
|
|
||||||
|
ren.end_render();
|
||||||
|
}
|
||||||
|
|
||||||
|
ren.free();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
436
test/test_tree_layout.c3
Normal file
436
test/test_tree_layout.c3
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
import vtree;
|
||||||
|
import std::io;
|
||||||
|
import std::math;
|
||||||
|
import std::thread;
|
||||||
|
|
||||||
|
const short WIDTH = 128;
|
||||||
|
const short HEIGHT = 64;
|
||||||
|
|
||||||
|
struct Size {
|
||||||
|
short min, max;
|
||||||
|
}
|
||||||
|
macro Size @grow() => {.min = 0, .max = 0};
|
||||||
|
macro Size @exact(short s) => {.min = s, .max = s};
|
||||||
|
macro Size @fit(short min = 0, short max = short.max) => {.min = min, .max = max};
|
||||||
|
macro bool Size.@is_grow(s) => (s.min == 0 && s.max == 0);
|
||||||
|
macro bool Size.@is_exact(s) => (s.min == s.max && s.min != 0);
|
||||||
|
macro bool Size.@is_fit(s) => (s.min != s.max);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
short x, y, w, h;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LayoutDirection {
|
||||||
|
ROW,
|
||||||
|
COLUMN
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ElemType {
|
||||||
|
DIV,
|
||||||
|
ELEM
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Anchor {
|
||||||
|
TOP_LEFT,
|
||||||
|
LEFT,
|
||||||
|
BOTTOM_LEFT,
|
||||||
|
BOTTOM,
|
||||||
|
BOTTOM_RIGHT,
|
||||||
|
RIGHT,
|
||||||
|
TOP_RIGHT,
|
||||||
|
TOP,
|
||||||
|
CENTER
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Elem {
|
||||||
|
ElemType type;
|
||||||
|
Size w, h;
|
||||||
|
Rect bounds;
|
||||||
|
Size ch_w, ch_h; // children width / height
|
||||||
|
uint grow_children; // how many children want to grow, decreased once a child has grown
|
||||||
|
short orig_x, orig_y;
|
||||||
|
short occupied; // occupied space in the layout direction
|
||||||
|
LayoutDirection layout_dir;
|
||||||
|
Anchor anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias ElemTree = vtree::VTree{Elem*};
|
||||||
|
|
||||||
|
|
||||||
|
char[HEIGHT][WIDTH] screen;
|
||||||
|
fn void paint(Rect bounds, char c)
|
||||||
|
{
|
||||||
|
for (short x = bounds.x; x < WIDTH && x < bounds.x + bounds.w; x++) {
|
||||||
|
for (short y = bounds.y; y < HEIGHT && y < bounds.y + bounds.h; y++) {
|
||||||
|
screen[x][y] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isz Elem.div_start(&e, ElemTree* tree, isz parent, Size w, Size h, LayoutDirection dir = ROW, Anchor anchor = TOP_LEFT, char c = ' ')
|
||||||
|
{
|
||||||
|
e.type = DIV;
|
||||||
|
e.w = w;
|
||||||
|
e.h = h;
|
||||||
|
e.layout_dir = dir;
|
||||||
|
e.anchor = anchor;
|
||||||
|
|
||||||
|
e.grow_children = 0;
|
||||||
|
e.occupied = 0;
|
||||||
|
e.ch_w = e.ch_h = {};
|
||||||
|
e.orig_x = e.orig_y = 0;
|
||||||
|
|
||||||
|
// update grow children if necessary
|
||||||
|
Elem* p = tree.get(parent) ?? &&{};
|
||||||
|
if ((p.layout_dir == ROW && e.w.@is_grow()) || ((p.layout_dir == COLUMN && e.h.@is_grow()))) {
|
||||||
|
p.grow_children++;
|
||||||
|
}
|
||||||
|
|
||||||
|
paint(e.bounds, c);
|
||||||
|
return tree.add(e, parent)!!;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void update_parent_size(Elem* parent, Elem* child)
|
||||||
|
{
|
||||||
|
// update the parent children size
|
||||||
|
switch (parent.layout_dir) {
|
||||||
|
case ROW: // on rows grow the ch width by the child width and only grow ch height if it exceeds
|
||||||
|
parent.ch_w.min += child.w.min;
|
||||||
|
parent.ch_w.max += child.w.max;
|
||||||
|
parent.ch_h.min = math::max(child.h.min, parent.ch_h.min);
|
||||||
|
parent.ch_h.max = math::max(child.h.max, parent.ch_h.max);
|
||||||
|
case COLUMN: // do the opposite on column
|
||||||
|
parent.ch_w.min = math::max(child.w.min, parent.ch_w.min);
|
||||||
|
parent.ch_w.max = math::max(child.w.max, parent.ch_w.max);
|
||||||
|
parent.ch_h.min += child.h.min;
|
||||||
|
parent.ch_h.max += child.h.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isz Elem.div_end(&e, ElemTree* tree, isz node)
|
||||||
|
{
|
||||||
|
isz parent = tree.parentof(node) ?? -1;
|
||||||
|
if (parent > 0) {
|
||||||
|
Elem* p = tree.get(parent)!!;
|
||||||
|
update_parent_size(p, e);
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void resolve_dimensions(Elem* e, Elem* p)
|
||||||
|
{
|
||||||
|
// ASSIGN WIDTH
|
||||||
|
switch {
|
||||||
|
case e.w.@is_exact():
|
||||||
|
e.bounds.w = e.w.min;
|
||||||
|
case e.w.@is_grow():
|
||||||
|
break;
|
||||||
|
// done in another pass
|
||||||
|
case e.w.@is_fit(): // fit the element's children
|
||||||
|
short min = math::max(e.ch_w.min, e.w.min);
|
||||||
|
short max = math::min(e.ch_w.max, e.w.max);
|
||||||
|
if (max >= min) { // OK!
|
||||||
|
e.bounds.w = max;
|
||||||
|
} else {
|
||||||
|
unreachable("cannot fit children");
|
||||||
|
}
|
||||||
|
default: unreachable("width is not exact, grow or fit");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN HEIGHT
|
||||||
|
switch {
|
||||||
|
case e.h.@is_exact():
|
||||||
|
e.bounds.h = e.h.min;
|
||||||
|
case e.h.@is_grow():
|
||||||
|
break;
|
||||||
|
// done in another pass
|
||||||
|
case e.h.@is_fit(): // fit the element's children
|
||||||
|
short min = math::max(e.ch_h.min, e.h.min);
|
||||||
|
short max = math::min(e.ch_h.max, e.h.max);
|
||||||
|
if (max >= min) { // OK!
|
||||||
|
e.bounds.h = max;
|
||||||
|
} else {
|
||||||
|
unreachable("cannot fit children");
|
||||||
|
}
|
||||||
|
default: unreachable("width is not exact, grow or fit");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (p.layout_dir) {
|
||||||
|
case ROW:
|
||||||
|
if (!e.w.@is_grow()) p.occupied += e.bounds.w;
|
||||||
|
case COLUMN:
|
||||||
|
if (!e.h.@is_grow()) p.occupied += e.bounds.h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void resolve_grow_elements(Elem* e, Elem* p)
|
||||||
|
{
|
||||||
|
// WIDTH
|
||||||
|
if (e.w.@is_grow()) {
|
||||||
|
if (p.layout_dir == ROW) { // grow along the axis, divide the parent size
|
||||||
|
e.bounds.w = (short)((int)(p.bounds.w - p.occupied) / (int)p.grow_children);
|
||||||
|
p.grow_children--;
|
||||||
|
p.occupied += e.bounds.w;
|
||||||
|
} else if (p.layout_dir == COLUMN) { // grow across the layout axis, inherit width of the parent
|
||||||
|
e.bounds.w = p.bounds.w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEIGHT
|
||||||
|
if (e.h.@is_grow()) {
|
||||||
|
if (p.layout_dir == COLUMN) { // grow along the axis, divide the parent size
|
||||||
|
e.bounds.h = (short)((int)(p.bounds.h - p.occupied) / (int)p.grow_children);
|
||||||
|
p.grow_children--;
|
||||||
|
p.occupied += e.bounds.h;
|
||||||
|
} else if (p.layout_dir == ROW) { // grow across the layout axis, inherit width of the parent
|
||||||
|
e.bounds.h = p.bounds.h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void resolve_placement(Elem* e, Elem* p)
|
||||||
|
{
|
||||||
|
switch (p.anchor) {
|
||||||
|
case TOP_LEFT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y;
|
||||||
|
case LEFT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.y -= p.occupied/2;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.y -= e.bounds.h/2;
|
||||||
|
}
|
||||||
|
case BOTTOM_LEFT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x;
|
||||||
|
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.y -= p.occupied;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.y -= e.bounds.h;
|
||||||
|
}
|
||||||
|
case BOTTOM:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||||
|
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.y -= p.occupied;
|
||||||
|
e.bounds.x -= e.bounds.w/2;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.y -= e.bounds.h;
|
||||||
|
e.bounds.x -= p.occupied/2;
|
||||||
|
}
|
||||||
|
case BOTTOM_RIGHT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||||
|
e.bounds.y = p.bounds.y + p.bounds.h + p.orig_y;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.y -= p.occupied;
|
||||||
|
e.bounds.x -= e.bounds.w;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.y -= e.bounds.h;
|
||||||
|
e.bounds.x -= p.occupied;
|
||||||
|
}
|
||||||
|
case RIGHT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.y -= p.occupied/2;
|
||||||
|
e.bounds.x -= e.bounds.w;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.y -= e.bounds.h/2;
|
||||||
|
e.bounds.x -= p.occupied;
|
||||||
|
}
|
||||||
|
case TOP_RIGHT:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.x -= e.bounds.w;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.x -= p.occupied;
|
||||||
|
}
|
||||||
|
case TOP:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.x -= e.bounds.w/2;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.x -= p.occupied/2;
|
||||||
|
}
|
||||||
|
case CENTER:
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x + p.bounds.w/2;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y + p.bounds.h/2;
|
||||||
|
if (p.layout_dir == COLUMN) {
|
||||||
|
e.bounds.x -= e.bounds.w/2;
|
||||||
|
e.bounds.y -= p.occupied/2;
|
||||||
|
} else if (p.layout_dir == ROW) {
|
||||||
|
e.bounds.x -= p.occupied/2;
|
||||||
|
e.bounds.y -= e.bounds.h/2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
e.bounds.x = p.bounds.x + p.orig_x;
|
||||||
|
e.bounds.y = p.bounds.y + p.orig_y;
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (p.layout_dir) {
|
||||||
|
case ROW:
|
||||||
|
p.orig_x += e.bounds.w;
|
||||||
|
case COLUMN:
|
||||||
|
p.orig_y += e.bounds.h;
|
||||||
|
default: unreachable("unknown layout direction");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void frame_end(ElemTree* tree, isz root)
|
||||||
|
{
|
||||||
|
// assign the element bounds
|
||||||
|
isz cursor = -1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
// RESOLVE DIMENSIONS
|
||||||
|
isz current = tree.level_order_it(root, &cursor)!!;
|
||||||
|
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||||
|
Elem* e = tree.get(current)!!;
|
||||||
|
isz pi = tree.parentof(current)!!;
|
||||||
|
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||||
|
resolve_dimensions(e, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESOLVE GROW ELEMENTS
|
||||||
|
cursor = -1;
|
||||||
|
current = tree.level_order_it(root, &cursor)!!;
|
||||||
|
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||||
|
Elem* e = tree.get(current)!!;
|
||||||
|
isz pi = tree.parentof(current)!!; if (ch == current) continue;
|
||||||
|
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||||
|
|
||||||
|
resolve_grow_elements(e, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// RESOLVE PLACEMENT
|
||||||
|
cursor = -1;
|
||||||
|
current = tree.level_order_it(root, &cursor)!!;
|
||||||
|
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||||
|
Elem* e = tree.get(current)!!;
|
||||||
|
isz pi = tree.parentof(current)!!;
|
||||||
|
Elem* p = (pi != current) ? tree.get(pi) ?? &&{} : &&{};
|
||||||
|
|
||||||
|
resolve_placement(e, p);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
cursor = -1;
|
||||||
|
isz current = tree.level_order_it(root, &cursor)!!;
|
||||||
|
for (; current >= 0; current = tree.level_order_it(root, &cursor)!!) {
|
||||||
|
Elem* p = tree.get(current)!!;
|
||||||
|
|
||||||
|
// RESOLVE KNOWN DIMENSIONS
|
||||||
|
isz ch_cur = 0;
|
||||||
|
isz ch = tree.children_it(current, &ch_cur)!!;
|
||||||
|
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||||
|
Elem* c = tree.get(ch)!!;
|
||||||
|
if (tree.is_root(ch)!!) {
|
||||||
|
resolve_dimensions(p, &&{});
|
||||||
|
} else {
|
||||||
|
resolve_dimensions(c, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESOLVE GROW CHILDREN
|
||||||
|
ch_cur = 0;
|
||||||
|
ch = tree.children_it(current, &ch_cur)!!;
|
||||||
|
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||||
|
Elem* c = tree.get(ch)!!;
|
||||||
|
if (tree.is_root(ch)!!) {
|
||||||
|
resolve_grow_elements(p, &&{});
|
||||||
|
} else {
|
||||||
|
resolve_grow_elements(c, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESOLVE CHILDREN PLACEMENT
|
||||||
|
ch_cur = 0;
|
||||||
|
ch = tree.children_it(current, &ch_cur)!!;
|
||||||
|
for (; ch >= 0; ch = tree.children_it(current, &ch_cur)!!) {
|
||||||
|
Elem* c = tree.get(ch)!!;
|
||||||
|
if (tree.is_root(ch)!!) {
|
||||||
|
resolve_placement(p, &&{});
|
||||||
|
} else {
|
||||||
|
resolve_placement(c, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void main()
|
||||||
|
{
|
||||||
|
ElemTree tree;
|
||||||
|
tree.init(64, mem)!!;
|
||||||
|
isz parent;
|
||||||
|
defer (void)tree.free();
|
||||||
|
|
||||||
|
Elem root; // root div
|
||||||
|
Elem div1, div2, div3, div4;
|
||||||
|
usz frame;
|
||||||
|
while (true) {
|
||||||
|
parent = root.div_start(&tree, parent, @exact(WIDTH), @exact(HEIGHT), ROW, anchor: RIGHT);
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
parent = div1.div_start(&tree, parent, @grow(), @grow(), dir: ROW, c: '1');
|
||||||
|
{
|
||||||
|
parent = div4.div_start(&tree, parent, @exact(30), @exact(30), dir: ROW, c: '4');
|
||||||
|
parent = div4.div_end(&tree, parent);
|
||||||
|
}
|
||||||
|
parent = div1.div_end(&tree, parent);
|
||||||
|
|
||||||
|
if (frame < 200) {
|
||||||
|
parent = div2.div_start(&tree, parent, @exact(20), @fit(), dir: COLUMN, c: '2');
|
||||||
|
{
|
||||||
|
parent = div3.div_start(&tree, parent, @exact(10), @exact(10), dir: ROW, c: '3');
|
||||||
|
parent = div3.div_end(&tree, parent);
|
||||||
|
}
|
||||||
|
parent = div2.div_end(&tree, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
parent = div3.div_start(&tree, parent, @fit(), @fit(), COLUMN, anchor: CENTER);
|
||||||
|
{
|
||||||
|
parent = div1.div_start(&tree, parent, @exact(20), @exact(20), dir: ROW, c: '1');
|
||||||
|
parent = div1.div_end(&tree, parent);
|
||||||
|
|
||||||
|
parent = div2.div_start(&tree, parent, @exact(10), @exact(10), dir: ROW, c: '2');
|
||||||
|
parent = div2.div_end(&tree, parent);
|
||||||
|
}
|
||||||
|
parent = div3.div_end(&tree, parent);
|
||||||
|
|
||||||
|
parent = root.div_end(&tree, parent);
|
||||||
|
|
||||||
|
frame_end(&tree, parent);
|
||||||
|
tree.nuke();
|
||||||
|
|
||||||
|
|
||||||
|
// draw the screen
|
||||||
|
//io::print("\e[1;1H\e[2J");
|
||||||
|
for (short x = 0; x < WIDTH+2; x++) io::printf("%c", x == 0 || x == WIDTH+1 ? '+' : '-');
|
||||||
|
io::printn();
|
||||||
|
for (short y = 0; y < HEIGHT; y++) {
|
||||||
|
io::print("|");
|
||||||
|
for (short x = 0; x < WIDTH; x++) {
|
||||||
|
char c = screen[x][y] == 0 ? 'x' : screen[x][y];
|
||||||
|
io::printf("%c", c);
|
||||||
|
}
|
||||||
|
io::print("|");
|
||||||
|
io::printn();
|
||||||
|
}
|
||||||
|
for (short x = 0; x < WIDTH+2; x++) io::printf("%c", x == 0 || x == WIDTH+1 ? '+' : '-');
|
||||||
|
io::printn("\n\n");
|
||||||
|
|
||||||
|
thread::sleep_ms(10);
|
||||||
|
frame++;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
test/test_union.c3
Normal file
26
test/test_union.c3
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
struct CmdA {
|
||||||
|
int a, b;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CmdB {
|
||||||
|
float a, b;
|
||||||
|
}
|
||||||
|
|
||||||
|
union AnyCmd {
|
||||||
|
CmdA a;
|
||||||
|
CmdB b;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cmd {
|
||||||
|
int type;
|
||||||
|
AnyCmd cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
Cmd c;
|
||||||
|
c.type = 1;
|
||||||
|
c.cmd.a = {.a = 1, .b = 2};
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
7
test/test_vtree.c3
Normal file
7
test/test_vtree.c3
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import std::io;
|
||||||
|
import vtree;
|
||||||
|
|
||||||
|
fn int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
226
test/ugui_font.c3
Normal file
226
test/ugui_font.c3
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
module ugui;
|
||||||
|
|
||||||
|
import cache;
|
||||||
|
//#include <grapheme.h>
|
||||||
|
//#include <assert.h>
|
||||||
|
|
||||||
|
//#include "stb_truetype.h"
|
||||||
|
//#include "stbimage_write.h"
|
||||||
|
|
||||||
|
// unicode code point, different type for a different hash
|
||||||
|
def Codepoint = uint;
|
||||||
|
|
||||||
|
/* width and height of a glyph contain the kering advance
|
||||||
|
* (u,v)
|
||||||
|
* +-------------*---+ -
|
||||||
|
* | ^ | | ^
|
||||||
|
* | |oy | | |
|
||||||
|
* | v | | |
|
||||||
|
* | .ii. | | |
|
||||||
|
* | @@@@@@. |<->| |
|
||||||
|
* | V@Mio@@o |adv| |h
|
||||||
|
* | :i. V@V | | |
|
||||||
|
* | :oM@@M | | |
|
||||||
|
* | :@@@MM@M | | |
|
||||||
|
* | @@o o@M | | |
|
||||||
|
* |<->:@@. M@M | | |
|
||||||
|
* |ox @@@o@@@@ | | |
|
||||||
|
* | :M@@V:@@.| | v
|
||||||
|
* +-------------*---+ -
|
||||||
|
* |<------------->|
|
||||||
|
* w
|
||||||
|
*/
|
||||||
|
struct Glyph {
|
||||||
|
Codepoint code;
|
||||||
|
uint u, v;
|
||||||
|
ushort w, h, a, x, y;
|
||||||
|
}
|
||||||
|
|
||||||
|
def GlyphCache = cache::Cache(<Codepoint, Glyph, 1024>);
|
||||||
|
|
||||||
|
// identity map the ASCII range
|
||||||
|
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();
|
||||||
|
|
||||||
|
struct FontAtlas {
|
||||||
|
uint width, height;
|
||||||
|
char* atlas;
|
||||||
|
uint glyph_max_w, glyph_max_h;
|
||||||
|
int size;
|
||||||
|
int file_size;
|
||||||
|
char *file;
|
||||||
|
void *priv;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro is_utf8(char c) => c & 0x80;
|
||||||
|
const uint BDEPTH = 1;
|
||||||
|
const uint BORDER = 4;
|
||||||
|
|
||||||
|
// FIXME: as of now only monospaced fonts look decent since no
|
||||||
|
// kerning information is stored
|
||||||
|
|
||||||
|
struct Priv @private {
|
||||||
|
stbtt_fontinfo stb;
|
||||||
|
float scale;
|
||||||
|
int baseline;
|
||||||
|
unsigned char *bitmap;
|
||||||
|
struct cache c;
|
||||||
|
}
|
||||||
|
//#define PRIV(x) ((struct priv *)x->priv)
|
||||||
|
|
||||||
|
|
||||||
|
struct font_atlas * font_init(void)
|
||||||
|
{
|
||||||
|
struct font_atlas *p = emalloc(sizeof(struct font_atlas));
|
||||||
|
memset(p, 0, sizeof(struct font_atlas));
|
||||||
|
p->priv = emalloc(sizeof(struct priv));
|
||||||
|
memset(p->priv, 0, sizeof(struct priv));
|
||||||
|
PRIV(p)->c = cache_init();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// loads a font into memory, storing all the ASCII characters in the atlas, each font
|
||||||
|
// atlas structure holds glyphs of a specific size in pixels
|
||||||
|
// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall)
|
||||||
|
int font_load(struct font_atlas *atlas, const char *path, int size)
|
||||||
|
{
|
||||||
|
if (!atlas || !path)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int err;
|
||||||
|
|
||||||
|
dump_file(path, &(atlas->file), &(atlas->file_size));
|
||||||
|
|
||||||
|
err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0);
|
||||||
|
if (err == 0) return -1;
|
||||||
|
|
||||||
|
int ascent, descent, linegap, baseline;
|
||||||
|
int x0,y0,x1,y1;
|
||||||
|
float scale;
|
||||||
|
stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap);
|
||||||
|
stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1);
|
||||||
|
scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size);
|
||||||
|
baseline = scale * -y0;
|
||||||
|
atlas->glyph_max_w = (scale*x1) - (scale*x0);
|
||||||
|
atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0);
|
||||||
|
atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||||
|
memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||||
|
PRIV(atlas)->baseline = atlas->glyph_max_h - baseline;
|
||||||
|
PRIV(atlas)->scale = scale;
|
||||||
|
PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
|
||||||
|
// FIXME: make this a square atlas
|
||||||
|
atlas->width = atlas->glyph_max_w*CACHE_SIZE/4;
|
||||||
|
atlas->height = atlas->glyph_max_h*4;
|
||||||
|
atlas->size = size;
|
||||||
|
|
||||||
|
// preallocate all ascii characters
|
||||||
|
for (char c = ' '; c <= '~'; c++) {
|
||||||
|
if (!font_get_glyph_texture(atlas, c, NULL))
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int font_free(struct font_atlas *atlas)
|
||||||
|
{
|
||||||
|
efree(atlas->atlas);
|
||||||
|
efree(atlas->file);
|
||||||
|
efree(PRIV(atlas)->bitmap);
|
||||||
|
cache_free(&PRIV(atlas)->c);
|
||||||
|
efree(atlas->priv);
|
||||||
|
efree(atlas);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: time and take the median of the time it takes to generate the cache and
|
||||||
|
// the time it takes to draw the glyph
|
||||||
|
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated)
|
||||||
|
{
|
||||||
|
int _u = 0;
|
||||||
|
if (!updated) updated = &_u;
|
||||||
|
|
||||||
|
const struct font_glyph *r;
|
||||||
|
if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) {
|
||||||
|
*updated = 0;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
*updated = 1;
|
||||||
|
// generate the sdf and put it into the cache
|
||||||
|
// TODO: generate the whole block at once
|
||||||
|
int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code);
|
||||||
|
int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base;
|
||||||
|
base = atlas->glyph_max_h - PRIV(atlas)->baseline;
|
||||||
|
stbtt_GetGlyphBitmapBoxSubpixel(
|
||||||
|
&PRIV(atlas)->stb,
|
||||||
|
idx,
|
||||||
|
PRIV(atlas)->scale,
|
||||||
|
PRIV(atlas)->scale,
|
||||||
|
0,0,
|
||||||
|
&x0,&y0,
|
||||||
|
&x1, &y1);
|
||||||
|
gw = x1 - x0;
|
||||||
|
gh = y1 - y0;
|
||||||
|
stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l);
|
||||||
|
adv *= PRIV(atlas)->scale;
|
||||||
|
off_x = PRIV(atlas)->scale*l;
|
||||||
|
off_y = atlas->glyph_max_h+y0;
|
||||||
|
stbtt_MakeGlyphBitmapSubpixel(
|
||||||
|
&PRIV(atlas)->stb,
|
||||||
|
PRIV(atlas)->bitmap,
|
||||||
|
atlas->glyph_max_w,
|
||||||
|
atlas->glyph_max_h,
|
||||||
|
atlas->glyph_max_w,
|
||||||
|
PRIV(atlas)->scale,
|
||||||
|
PRIV(atlas)->scale,
|
||||||
|
0, 0,
|
||||||
|
idx);
|
||||||
|
|
||||||
|
// TODO: bounds check usign atlas height
|
||||||
|
// TODO: clear spot area in the atlas before writing on it
|
||||||
|
unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c);
|
||||||
|
unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h;
|
||||||
|
unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width;
|
||||||
|
unsigned int w = atlas->width;
|
||||||
|
|
||||||
|
unsigned char *a = (void *)atlas->atlas;
|
||||||
|
|
||||||
|
//printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y);
|
||||||
|
|
||||||
|
for (int y = 0; y < gh; y++) {
|
||||||
|
for (int x = 0; x < gw; x++) {
|
||||||
|
int c, r;
|
||||||
|
r = (ty+y)*w;
|
||||||
|
c = tx+x;
|
||||||
|
a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct font_glyph g = {
|
||||||
|
.codepoint = code,
|
||||||
|
.u = tx,
|
||||||
|
.v = ty,
|
||||||
|
.w = gw,
|
||||||
|
.h = gh,
|
||||||
|
.x = off_x,
|
||||||
|
.y = off_y-base,
|
||||||
|
.a = adv,
|
||||||
|
};
|
||||||
|
return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void font_dump(const struct font_atlas *atlas, const char *path)
|
||||||
|
{
|
||||||
|
stbi_write_png(
|
||||||
|
path,
|
||||||
|
atlas->width,
|
||||||
|
atlas->height,
|
||||||
|
BDEPTH,
|
||||||
|
atlas->atlas,
|
||||||
|
BDEPTH*atlas->width);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user