major project restructure

This commit is contained in:
Alessandro Mauri 2025-10-25 17:32:41 +02:00
parent bd31f562fc
commit 4f7fa7d50c
67 changed files with 25 additions and 2427 deletions

View File

@ -1,43 +0,0 @@
{
"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": ""
}
]
}
}

15
.gitmodules vendored
View File

@ -1,15 +0,0 @@
[submodule "lib/schrift.c3l/thirdparty/libschrift"]
path = lib/schrift.c3l/thirdparty/libschrift
url = https://github.com/tomolt/libschrift
ignore = dirty
[submodule "lib/sdl3.c3l"]
path = lib/sdl3.c3l
url = https://git.alemauri.eu/alema/sdl3.c3l
ignore = dirty
[submodule "lib/vendor"]
path = lib/vendor
url = https://github.com/c3lang/vendor
ignore = dirty
[submodule "lib/ugui_sdl.c3l"]
path = lib/ugui_sdl.c3l
url = https://git.alemauri.eu/alema/ugui_sdl.c3l.git

View File

@ -1,184 +0,0 @@
## 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

View File

@ -1,5 +0,0 @@
C3FLAGS = -g
main: src/main.c3 $(wildcard lib/ugui.c3l/src/*.c3) $(wildcard lib/ugui_sdl.c3l/)
make -C resources/shaders
c3c build ${C3FLAGS}

View File

@ -1,43 +1 @@
# UGUI Welcome to the ugui library.
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
```

View File

View File

@ -1,4 +0,0 @@
all:
make -C thirdparty/libschrift
mkdir -p linux-x64
cp thirdparty/libschrift/libschrift.a linux-x64/libschrift.a

View File

@ -1,58 +0,0 @@
module schrift;
alias SftFont = void*;
alias SftUChar = uint;
alias SftGlyph = uint;
const int SFT_DOWNWARD_Y = 0x01;
struct Sft
{
SftFont font;
double xScale;
double yScale;
double xOffset;
double yOffset;
int flags;
}
struct SftLMetrics
{
double ascender;
double descender;
double lineGap;
}
struct SftGMetrics
{
double advanceWidth;
double leftSideBearing;
int yOffset;
int minWidth;
int minHeight;
}
struct SftKerning
{
double xShift;
double yShift;
}
struct SftImage
{
void *pixels;
int width;
int height;
}
extern fn ZString sft_version() @extern("sft_version");
extern fn SftFont loadmem(void* mem, usz size) @extern("sft_loadmem");
extern fn SftFont loadfile(ZString filename) @extern("sft_loadfile");
extern fn void freefont(SftFont font) @extern("sft_freefont");
extern fn int lmetrics(Sft* sft, SftLMetrics* metrics) @extern("sft_lmetrics");
extern fn int lookup(Sft* sft, SftUChar codepoint, SftGlyph* glyph) @extern("sft_lookup");
extern fn int gmetrics(Sft* sft, SftGlyph glyph, SftGMetrics* metrics) @extern("sft_gmetrics");
extern fn int kerning(Sft* sft, SftGlyph leftGlyph, SftGlyph rightGlyph, SftKerning* kerning) @extern("sft_kerning");
extern fn int render(Sft* sft, SftGlyph glyph, SftImage image) @extern("sft_render");

View File

@ -1,9 +0,0 @@
{
"provides" : "schrift",
"targets" : {
"linux-x64" : {
"dependencies" : [],
"linked-libraries" : ["schrift", "c"]
}
}
}

View File

@ -1,14 +0,0 @@
{
"langrev": "1",
"warnings": [ "no-unused" ],
"dependency-search-paths": [ ".." ],
"dependencies": [ "schrift" ],
"authors": [ "Alessandro Mauri <alemauri001@gmail.com>", "Thomas Oltmann <thomas.oltmann.hhg@gmail.com>" ],
"version": "0.1.0",
"sources": [ ],
"output": "build",
"target": "linux-x64",
"features": [],
"cpu": "generic",
"opt": "O0",
}

@ -1 +0,0 @@
Subproject commit 24737d2922b23df4a5692014f5ba03da0c296112

@ -1 +0,0 @@
Subproject commit e7356df5d1d0c22a6bba822bda6994062b0b75d7

View File

View File

@ -1 +0,0 @@
Welcome to the ugui library.

@ -1 +0,0 @@
Subproject commit 050624fd67c2d80190114c7774ccfa64b0f449b9

@ -1 +0,0 @@
Subproject commit db006221a8af625630fdb8b56707f3d07d4314a2

View File

@ -1,20 +0,0 @@
{
"langrev": "1",
"warnings": ["no-unused"],
"dependency-search-paths": ["lib", "lib/vendor/libraries"],
"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"
}

View File

Binary file not shown.

View File

@ -1,33 +0,0 @@
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

View File

@ -1,118 +0,0 @@
#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);
}
}

View File

@ -1,47 +0,0 @@
#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;
}

View File

@ -1,86 +0,0 @@
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;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View File

@ -1,420 +0,0 @@
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(&times)
{
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(&times)
{
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!");
}!!;
}

View File

@ -90,7 +90,7 @@ fn int Ctx.import_style_from_file(&ctx, String path)
* <style name> { * <style name> {
* padding: left right top bottom; * padding: left right top bottom;
* border: left right top bottom; * border: left right top bottom;
* margin: left right top bottoms; * margin: left right top bottom;
* radius: uint; * radius: uint;
* size: uint; * size: uint;
* Color: #RRGGBBAA; * Color: #RRGGBBAA;
@ -104,6 +104,8 @@ fn int Ctx.import_style_from_file(&ctx, String path)
* The default unit is pixels, but millimeters is also available. The parser function accepts a scale * The default unit is pixels, but millimeters is also available. The parser function accepts a scale
* factor that has to be obtained with the window manager functions. * factor that has to be obtained with the window manager functions.
*/ */
// TODO: implement <style name> : <another style> to easily inherit all properties
// of a previously defined style
module ugui::css; module ugui::css;
@ -151,6 +153,12 @@ fn short Token.to_px(&t, float mm_to_px)
return (short)(t.value * mm_to_px); return (short)(t.value * mm_to_px);
} }
// ---------------------------------------------------------------------------------- //
// LEXER //
// ---------------------------------------------------------------------------------- //
struct Lexer { struct Lexer {
String text; String text;
usz line, col, off; usz line, col, off;
@ -280,6 +288,11 @@ fn Token Lexer.peep_token(&lex)
} }
// ---------------------------------------------------------------------------------- //
// PARSER //
// ---------------------------------------------------------------------------------- //
struct Parser { struct Parser {
Lexer lex; Lexer lex;
Style style; Style style;
@ -295,6 +308,8 @@ macro bool Parser.expect(&p, Token* t, TokenType type)
return false; return false;
} }
// style := ( IDENTIFIER "{" property_list "}" )
// property_list := property property_list
fn bool Parser.parse_style(&p) fn bool Parser.parse_style(&p)
{ {
Token t; Token t;
@ -319,6 +334,10 @@ fn bool Parser.parse_style(&p)
return true; return true;
} }
// property := ( number_property | color_property | size_property ) ";"
// number_property := ( "radius" | "size" ) "=" number
// color_property := ( "bg" | "fg" | "primary" | "secondary" | "accent" ) "=" color
// size_property := ( "padding" | "border" | "margin" ) "=" size
fn bool Parser.parse_property(&p) fn bool Parser.parse_property(&p)
{ {
Token t, prop; Token t, prop;
@ -393,6 +412,7 @@ fn bool Parser.parse_property(&p)
return true; return true;
} }
// number := NUMBER
fn bool Parser.parse_number(&p, short* n) fn bool Parser.parse_number(&p, short* n)
{ {
Token t; Token t;
@ -401,6 +421,7 @@ fn bool Parser.parse_number(&p, short* n)
return true; return true;
} }
// color := COLOR
// FIXME: since '#' is punctuation this cannot be done in parsing but it has to be done in lexing // FIXME: since '#' is punctuation this cannot be done in parsing but it has to be done in lexing
fn bool Parser.parse_color(&p, Color* c) fn bool Parser.parse_color(&p, Color* c)
{ {
@ -410,6 +431,8 @@ fn bool Parser.parse_color(&p, Color* c)
return true; return true;
} }
// size := number | number_list
// number_list := number number number number
fn bool Parser.parse_size(&p, Rect* r) fn bool Parser.parse_size(&p, Rect* r)
{ {
short x; short x;

View File

View File

@ -1,14 +0,0 @@
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;
}

View File

@ -1,10 +0,0 @@
import std::io;
import std::collections::bitset;
def Bits = bitset::BitSet(<128>);
fn void main()
{
Bits b;
io::printn($typeof(b.data[0]).sizeof);
}

View File

@ -1,19 +0,0 @@
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());
}

View File

@ -1,14 +0,0 @@
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;
}

View File

@ -1,72 +0,0 @@
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);
}

View File

@ -1,6 +0,0 @@
import rl;
fn int main(void)
{
return 0;
}

View File

@ -1,30 +0,0 @@
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;
}

View File

@ -1,26 +0,0 @@
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;
}

View File

@ -1,342 +0,0 @@
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;
}

View File

@ -1,94 +0,0 @@
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;
}

View File

@ -1,436 +0,0 @@
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++;
}
}

View File

@ -1,26 +0,0 @@
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;
}

View File

@ -1,7 +0,0 @@
import std::io;
import vtree;
fn int main()
{
return 0;
}

View File

@ -1,226 +0,0 @@
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);
}