Compare commits
4 Commits
87de68028a
...
78e2c64da6
Author | SHA1 | Date | |
---|---|---|---|
78e2c64da6 | |||
16adfd7cc5 | |||
1746f7d940 | |||
169b5e1dfd |
29
TODO
29
TODO
@ -7,14 +7,31 @@
|
|||||||
[ ] Write a README.md
|
[ ] Write a README.md
|
||||||
[ ] Use an arena allocator for cache
|
[ ] Use an arena allocator for cache
|
||||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||||
|
[ ] Do command buffer damage tracking based on a context grid (see rxi writeup)
|
||||||
[x] Better handling of the active and focused widgets, try
|
[x] Better handling of the active and focused widgets, try
|
||||||
to maintain focus until mouse release (fix scroll bars)
|
to maintain focus until mouse release (fix scroll bars)
|
||||||
[x] Clip element bounds to parent div, specifically text
|
[x] Clip element bounds to parent div, specifically text
|
||||||
[ ] Keyboard input
|
|
||||||
[ ] Mouse scroll wheel
|
|
||||||
[ ] Touch input
|
|
||||||
[ ] Resizeable divs
|
[ ] Resizeable divs
|
||||||
[ ] Implement a z index
|
[ ] Implement a z index and sort command buffer based on that
|
||||||
|
[ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it
|
||||||
|
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division
|
||||||
|
[ ] Animations, somehow
|
||||||
|
[ ] Maybe cache codepoint converted strings
|
||||||
|
[x] Fix scroll wheel when div is scrolled
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
[ ] Text reflow
|
||||||
|
[x] Flexbox
|
||||||
|
[ ] Center elements to the row/column
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
[x] Keyboard input
|
||||||
|
[x] Mouse scroll wheel
|
||||||
|
[ ] Touch input
|
||||||
|
[x] Do not set input event to true if the movement was zero (like no mouse movement)
|
||||||
|
[ ] Use input event flags, for example to consume the input event
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@ -36,7 +53,7 @@ _ border radius
|
|||||||
|
|
||||||
## Raylib
|
## Raylib
|
||||||
|
|
||||||
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
[x] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
||||||
[x] Implement pixel radius rounding for border radius
|
[x] Implement pixel radius rounding for border radius
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
@ -45,3 +62,5 @@ _ border radius
|
|||||||
[x] Button with label
|
[x] Button with label
|
||||||
[ ] Text Input box
|
[ ] Text Input box
|
||||||
[ ] Icon Buttons
|
[ ] Icon Buttons
|
||||||
|
[ ] Switch
|
||||||
|
|
||||||
|
15
src/cache.c3
15
src/cache.c3
@ -62,8 +62,9 @@ fn Value*! Cache.search(&cache, Key id)
|
|||||||
// get_entry() faults on miss
|
// get_entry() faults on miss
|
||||||
IdTableEntry* entry = cache.table.get_entry(id)!;
|
IdTableEntry* entry = cache.table.get_entry(id)!;
|
||||||
|
|
||||||
/* MISS */
|
/* MISS, wrong key */
|
||||||
if (entry.key != id) {
|
if (entry.key != id) {
|
||||||
|
cache.table.remove(id)!;
|
||||||
return SearchResult.MISSING?;
|
return SearchResult.MISSING?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,18 @@ fn Value*! Cache.search(&cache, Key id)
|
|||||||
return &(cache.pool[entry.value]);
|
return &(cache.pool[entry.value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn void Cache.remove(&cache, Key id)
|
||||||
|
{
|
||||||
|
IdTableEntry*! entry = cache.table.get_entry(id);
|
||||||
|
if (catch entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found, remove it
|
||||||
|
cache.present[entry.value] = false;
|
||||||
|
(void)cache.table.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
/* Look for a free spot in the present bitmap and return its index */
|
/* Look for a free spot in the present bitmap and return its index */
|
||||||
/* If there is no free space left then just return the first position */
|
/* If there is no free space left then just return the first position */
|
||||||
fn usz Cache.get_free_spot(&cache) @private
|
fn usz Cache.get_free_spot(&cache) @private
|
||||||
|
74
src/main.c3
74
src/main.c3
@ -66,6 +66,16 @@ void main()
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
macro rl::Color ugui::Color.conv(color)
|
||||||
|
{
|
||||||
|
return rl::Color{.r = color.r, .g = color.g, .b = color.b, .a = color.a};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro rl::Rectangle ugui::Rect.conv(rect)
|
||||||
|
{
|
||||||
|
return rl::Rectangle{.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h};
|
||||||
|
}
|
||||||
|
|
||||||
fn int main(String[] args)
|
fn int main(String[] args)
|
||||||
{
|
{
|
||||||
ugui::Ctx ui;
|
ugui::Ctx ui;
|
||||||
@ -96,6 +106,23 @@ fn int main(String[] args)
|
|||||||
while (!rl::window_should_close()) {
|
while (!rl::window_should_close()) {
|
||||||
clock.mark();
|
clock.mark();
|
||||||
|
|
||||||
|
/*
|
||||||
|
KeyboardKey k;
|
||||||
|
do {
|
||||||
|
k = rl::get_key_pressed();
|
||||||
|
if (!k) { break; }
|
||||||
|
if (rl::is_key_down(k)) { io::print("down "); }
|
||||||
|
if (rl::is_key_pressed(k)) { io::print("pressed "); }
|
||||||
|
if (rl::is_key_released(k)) { io::print("released "); }
|
||||||
|
io::printfn("%s", k);
|
||||||
|
} while (k != 0);
|
||||||
|
*/
|
||||||
|
for (int c; (c = rl::get_char_pressed()) != 0;) {
|
||||||
|
int[1] ts;
|
||||||
|
ts[0] = c;
|
||||||
|
ui.input_text_unicode(ts[..]);
|
||||||
|
}
|
||||||
|
|
||||||
/* Start Input Handling */
|
/* Start Input Handling */
|
||||||
if (rl::is_window_resized()) {
|
if (rl::is_window_resized()) {
|
||||||
width = (short)rl::get_screen_width();
|
width = (short)rl::get_screen_width();
|
||||||
@ -107,11 +134,14 @@ fn int main(String[] args)
|
|||||||
|
|
||||||
rl::Vector2 mpos = rl::get_mouse_position();
|
rl::Vector2 mpos = rl::get_mouse_position();
|
||||||
ui.input_mouse_abs((short)mpos.x, (short)mpos.y);
|
ui.input_mouse_abs((short)mpos.x, (short)mpos.y);
|
||||||
|
rl::Vector2 mwheel = rl::get_mouse_wheel_move_v();
|
||||||
|
ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y);
|
||||||
|
|
||||||
ugui::MouseButtons buttons;
|
ugui::MouseButtons buttons = {
|
||||||
buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT);
|
.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT),
|
||||||
buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT);
|
.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT),
|
||||||
buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE);
|
.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE),
|
||||||
|
};
|
||||||
ui.input_mouse_button(buttons);
|
ui.input_mouse_button(buttons);
|
||||||
/* End Input Handling */
|
/* End Input Handling */
|
||||||
|
|
||||||
@ -119,7 +149,7 @@ fn int main(String[] args)
|
|||||||
ui.frame_begin()!!;
|
ui.frame_begin()!!;
|
||||||
|
|
||||||
// main div, fill the whole window
|
// main div, fill the whole window
|
||||||
ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!;
|
ui.div_begin("main", ugui::Rect{.w=-100})!!;
|
||||||
{|
|
{|
|
||||||
ui.layout_set_column()!!;
|
ui.layout_set_column()!!;
|
||||||
if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) {
|
if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) {
|
||||||
@ -181,10 +211,11 @@ fn int main(String[] args)
|
|||||||
TimeStats uts = ui_times.get_stats();
|
TimeStats uts = ui_times.get_stats();
|
||||||
|
|
||||||
ui.layout_set_floating()!!;
|
ui.layout_set_floating()!!;
|
||||||
ui.div_begin("fps", ugui::Rect{0, ui.height-60, 200, 60})!!;
|
ui.div_begin("fps", ugui::Rect{0, ui.height-100, -300, 100})!!;
|
||||||
{|
|
{|
|
||||||
ui.layout_set_row()!!;
|
ui.layout_set_column()!!;
|
||||||
ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
|
||||||
|
ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!;
|
||||||
|};
|
|};
|
||||||
ui.div_end()!!;
|
ui.div_end()!!;
|
||||||
|
|
||||||
@ -197,27 +228,14 @@ fn int main(String[] args)
|
|||||||
rl::begin_drawing();
|
rl::begin_drawing();
|
||||||
// ClearBackground(BLACK);
|
// ClearBackground(BLACK);
|
||||||
|
|
||||||
rl::Color c;
|
|
||||||
rl::Rectangle r;
|
|
||||||
for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) {
|
for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) {
|
||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
case ugui::CmdType.CMD_RECT:
|
case ugui::CmdType.CMD_RECT:
|
||||||
c = rl::Color{
|
Rect r = cmd.rect.rect;
|
||||||
.r = cmd.rect.color.r,
|
|
||||||
.g = cmd.rect.color.g,
|
|
||||||
.b = cmd.rect.color.b,
|
|
||||||
.a = cmd.rect.color.a,
|
|
||||||
};
|
|
||||||
r = rl::Rectangle{
|
|
||||||
.x = cmd.rect.rect.x,
|
|
||||||
.y = cmd.rect.rect.y,
|
|
||||||
.height = cmd.rect.rect.h,
|
|
||||||
.width = cmd.rect.rect.w,
|
|
||||||
};
|
|
||||||
float rad = cmd.rect.radius;
|
float rad = cmd.rect.radius;
|
||||||
// for some weird-ass reason the straight forward inverse formula does not work
|
// for some weird-ass reason the straight forward inverse formula does not work
|
||||||
float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width;
|
float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w;
|
||||||
rl::draw_rectangle_rounded(r, roundness, 0, c);
|
rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv());
|
||||||
case ugui::CmdType.CMD_UPDATE_ATLAS:
|
case ugui::CmdType.CMD_UPDATE_ATLAS:
|
||||||
if (cmd.update_atlas.id != font_id) { break; }
|
if (cmd.update_atlas.id != font_id) { break; }
|
||||||
//rl::unload_image(font_atlas);
|
//rl::unload_image(font_atlas);
|
||||||
@ -233,18 +251,12 @@ fn int main(String[] args)
|
|||||||
font_texture = rl::load_texture_from_image(font_atlas);
|
font_texture = rl::load_texture_from_image(font_atlas);
|
||||||
case ugui::CmdType.CMD_SPRITE:
|
case ugui::CmdType.CMD_SPRITE:
|
||||||
if (cmd.sprite.texture_id != font_id) { break; }
|
if (cmd.sprite.texture_id != font_id) { break; }
|
||||||
rl::Rectangle source = {
|
|
||||||
.x = cmd.sprite.texture_rect.x,
|
|
||||||
.y = cmd.sprite.texture_rect.y,
|
|
||||||
.width = cmd.sprite.texture_rect.w,
|
|
||||||
.height = cmd.sprite.texture_rect.h,
|
|
||||||
};
|
|
||||||
rl::Vector2 position = {
|
rl::Vector2 position = {
|
||||||
.x = cmd.sprite.rect.x,
|
.x = cmd.sprite.rect.x,
|
||||||
.y = cmd.sprite.rect.y,
|
.y = cmd.sprite.rect.y,
|
||||||
};
|
};
|
||||||
rl::begin_shader_mode(font_shader);
|
rl::begin_shader_mode(font_shader);
|
||||||
rl::draw_texture_rec(font_texture, source, position, rl::WHITE);
|
rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
|
||||||
rl::end_shader_mode();
|
rl::end_shader_mode();
|
||||||
case ugui::CmdType.CMD_SCISSOR:
|
case ugui::CmdType.CMD_SCISSOR:
|
||||||
if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) {
|
if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) {
|
||||||
|
@ -58,7 +58,7 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m
|
|||||||
|
|
||||||
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
||||||
Rect text_size = ctx.get_text_bounds(label)!;
|
Rect text_size = ctx.get_text_bounds(label)!;
|
||||||
Rect btn_size = rect_add(text_size, Rect{0,0,10,10});
|
Rect btn_size = text_size.add(Rect{0,0,10,10});
|
||||||
|
|
||||||
// 2. Layout
|
// 2. Layout
|
||||||
elem.bounds = ctx.position_element(parent, btn_size, true);
|
elem.bounds = ctx.position_element(parent, btn_size, true);
|
||||||
|
@ -29,6 +29,7 @@ struct CmdUpdateAtlas {
|
|||||||
struct CmdSprite {
|
struct CmdSprite {
|
||||||
Rect rect;
|
Rect rect;
|
||||||
Rect texture_rect;
|
Rect texture_rect;
|
||||||
|
Color hue;
|
||||||
Id texture_id;
|
Id texture_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,15 +49,29 @@ struct Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max})
|
||||||
|
{
|
||||||
|
bool no_area = rect.w <= 0 || rect.h <= 0;
|
||||||
|
return no_area || !rect.collides(clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this whole thing could be done at compile time, maybe
|
||||||
|
macro Ctx.push_cmd(&ctx, Cmd *cmd)
|
||||||
|
{
|
||||||
|
Rect rect;
|
||||||
|
switch (cmd.type) {
|
||||||
|
case CMD_RECT: rect = cmd.rect.rect;
|
||||||
|
case CMD_SPRITE: rect = cmd.sprite.rect;
|
||||||
|
default: return ctx.cmd_queue.enqueue(cmd);
|
||||||
|
}
|
||||||
|
if (cull_rect(rect, ctx.div_scissor)) return;
|
||||||
|
return ctx.cmd_queue.enqueue(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: is this really the best solution?
|
// FIXME: is this really the best solution?
|
||||||
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
|
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
|
||||||
fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
|
fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
|
||||||
{
|
{
|
||||||
// FIXME: this should be culled higher up, maybe
|
|
||||||
if (rect.w <= 0 || rect.h <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect border = ctx.style.border;
|
Rect border = ctx.style.border;
|
||||||
Rect padding = ctx.style.padding;
|
Rect padding = ctx.style.padding;
|
||||||
ushort radius = ctx.style.radius;
|
ushort radius = ctx.style.radius;
|
||||||
@ -69,7 +84,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo
|
|||||||
.rect.color = border_color,
|
.rect.color = border_color,
|
||||||
.rect.radius = do_radius ? radius : 0,
|
.rect.radius = do_radius ? radius : 0,
|
||||||
};
|
};
|
||||||
ctx.cmd_queue.enqueue(&cmd)!;
|
ctx.push_cmd(&cmd)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cmd cmd = {
|
Cmd cmd = {
|
||||||
@ -83,22 +98,24 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo
|
|||||||
.rect.color = color,
|
.rect.color = color,
|
||||||
.rect.radius = do_radius ? radius : 0,
|
.rect.radius = do_radius ? radius : 0,
|
||||||
};
|
};
|
||||||
ctx.cmd_queue.enqueue(&cmd)!;
|
if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return;
|
||||||
|
ctx.push_cmd(&cmd)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add texture id
|
// TODO: add texture id
|
||||||
fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id)
|
fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = uint_to_rgba(0xffffffff))
|
||||||
{
|
{
|
||||||
Cmd cmd = {
|
Cmd cmd = {
|
||||||
.type = CMD_SPRITE,
|
.type = CMD_SPRITE,
|
||||||
.sprite.rect = bounds,
|
.sprite.rect = bounds,
|
||||||
.sprite.texture_rect = texture,
|
.sprite.texture_rect = texture,
|
||||||
.sprite.texture_id = texture_id,
|
.sprite.texture_id = texture_id,
|
||||||
|
.sprite.hue = hue,
|
||||||
};
|
};
|
||||||
ctx.cmd_queue.enqueue(&cmd)!;
|
ctx.push_cmd(&cmd)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void! Ctx.push_string(&ctx, Rect bounds, String text)
|
fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = uint_to_rgba(0xffffffff))
|
||||||
{
|
{
|
||||||
if (text.len == 0) {
|
if (text.len == 0) {
|
||||||
return;
|
return;
|
||||||
@ -136,9 +153,7 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text)
|
|||||||
.h = gp.h,
|
.h = gp.h,
|
||||||
};
|
};
|
||||||
// push the sprite only if it collides with the bounds
|
// push the sprite only if it collides with the bounds
|
||||||
if (gb.collides(bounds)) {
|
if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, hue)!;
|
||||||
ctx.push_sprite(gb, gt, texture_id)!;
|
|
||||||
}
|
|
||||||
line_len += gp.adv;
|
line_len += gp.adv;
|
||||||
} else if (cp == '\n'){
|
} else if (cp == '\n'){
|
||||||
orig.y += line_height + line_gap;
|
orig.y += line_height + line_gap;
|
||||||
@ -164,7 +179,7 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas)
|
|||||||
.bpp = (ushort)atlas.type.bpp(),
|
.bpp = (ushort)atlas.type.bpp(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
ctx.cmd_queue.enqueue(&up)!;
|
ctx.push_cmd(&up)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void! Ctx.push_scissor(&ctx, Rect rect)
|
fn void! Ctx.push_scissor(&ctx, Rect rect)
|
||||||
@ -173,5 +188,5 @@ fn void! Ctx.push_scissor(&ctx, Rect rect)
|
|||||||
.type = CMD_SCISSOR,
|
.type = CMD_SCISSOR,
|
||||||
.scissor.rect = rect.intersection(ctx.div_scissor),
|
.scissor.rect = rect.intersection(ctx.div_scissor),
|
||||||
};
|
};
|
||||||
ctx.cmd_queue.enqueue(&sc)!;
|
ctx.push_cmd(&sc)!;
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,6 @@ import fifo;
|
|||||||
import std::io;
|
import std::io;
|
||||||
import std::core::string;
|
import std::core::string;
|
||||||
|
|
||||||
struct Rect {
|
|
||||||
short x, y, w, h;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Point {
|
|
||||||
short x, y;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Color{
|
|
||||||
char r, g, b, a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// element ids are just long ints
|
// element ids are just long ints
|
||||||
def Id = usz;
|
def Id = usz;
|
||||||
@ -93,6 +82,7 @@ const uint STACK_STEP = 10;
|
|||||||
const uint MAX_ELEMS = 128;
|
const uint MAX_ELEMS = 128;
|
||||||
const uint MAX_CMDS = 256;
|
const uint MAX_CMDS = 256;
|
||||||
const uint ROOT_ID = 1;
|
const uint ROOT_ID = 1;
|
||||||
|
const uint TEXT_MAX = 64;
|
||||||
|
|
||||||
// global style, similar to the css box model
|
// global style, similar to the css box model
|
||||||
struct Style { // css box model
|
struct Style { // css box model
|
||||||
@ -126,6 +116,13 @@ struct Ctx {
|
|||||||
// mouse_pressed = mouse_updated & mouse_down
|
// mouse_pressed = mouse_updated & mouse_down
|
||||||
MouseButtons down;
|
MouseButtons down;
|
||||||
MouseButtons updated;
|
MouseButtons updated;
|
||||||
|
// scroll wheel
|
||||||
|
Point scroll;
|
||||||
|
}
|
||||||
|
struct keyboard {
|
||||||
|
char[TEXT_MAX] text;
|
||||||
|
usz text_len;
|
||||||
|
ModKeys down;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,45 +133,6 @@ struct Ctx {
|
|||||||
isz active_div; // tree node indicating the current active div
|
isz active_div; // tree node indicating the current active div
|
||||||
}
|
}
|
||||||
|
|
||||||
macro point_in_rect(Point p, Rect r)
|
|
||||||
{
|
|
||||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true if rect a contains b
|
|
||||||
macro bool Rect.contains(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro Rect Rect.intersection(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return Rect{
|
|
||||||
.x = (short)max(a.x, b.x),
|
|
||||||
.y = (short)max(a.y, b.y),
|
|
||||||
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
|
||||||
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// rect intersection not null
|
|
||||||
macro bool Rect.collides(Rect a, Rect b)
|
|
||||||
{
|
|
||||||
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
|
||||||
|
|
||||||
macro Rect rect_add(Rect r1, Rect r2)
|
|
||||||
{
|
|
||||||
return Rect{
|
|
||||||
.x = r1.x + r2.x,
|
|
||||||
.y = r1.y + r2.y,
|
|
||||||
.w = r1.w + r2.w,
|
|
||||||
.h = r1.h + r2.h,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// return a pointer to the parent of the current active div
|
// return a pointer to the parent of the current active div
|
||||||
fn Elem*! Ctx.get_parent(&ctx)
|
fn Elem*! Ctx.get_parent(&ctx)
|
||||||
{
|
{
|
||||||
@ -182,18 +140,26 @@ fn Elem*! Ctx.get_parent(&ctx)
|
|||||||
return ctx.cache.search(parent_id);
|
return ctx.cache.search(parent_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro @bits(#a) => $typeof(#a).sizeof*8;
|
||||||
|
macro Id.rotate_left(id, uint $n) => (id << $n) | (id >> (@bits(id) - $n));
|
||||||
|
const uint GOLDEN_RATIO = 0x9E3779B9;
|
||||||
|
|
||||||
// generate an id combining the hashes of the parent id and the label
|
// generate an id combining the hashes of the parent id and the label
|
||||||
// with the Cantor pairing function
|
// with the Cantor pairing function
|
||||||
macro Id! Ctx.gen_id(&ctx, String label)
|
macro Id! Ctx.gen_id(&ctx, String label)
|
||||||
{
|
{
|
||||||
Id a = ctx.tree.get(ctx.active_div)!;
|
Id id1 = ctx.tree.get(ctx.active_div)!;
|
||||||
Id b = label.hash();
|
Id id2 = label.hash();
|
||||||
return (a + b) * (a + b + 1) / 2 + a;
|
// Mix the two IDs non-linearly
|
||||||
|
Id mixed = id1 ^ id2.rotate_left(13);
|
||||||
|
mixed ^= id1.rotate_left(7);
|
||||||
|
mixed += GOLDEN_RATIO;
|
||||||
|
return mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get or push an element from the cache, return a pointer to it
|
// get or push an element from the cache, return a pointer to it
|
||||||
// resets all flags except is_new which is set accordingly
|
// resets all flags except is_new which is set accordingly
|
||||||
macro Ctx.get_elem(&ctx, Id id)
|
fn Elem*! Ctx.get_elem(&ctx, Id id)
|
||||||
{
|
{
|
||||||
Elem empty_elem;
|
Elem empty_elem;
|
||||||
bool is_new;
|
bool is_new;
|
||||||
@ -206,6 +172,18 @@ macro Ctx.get_elem(&ctx, Id id)
|
|||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// find an element, does not allocate a new one in cache
|
||||||
|
// THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE
|
||||||
|
macro Elem* Ctx.find_elem(&ctx, Id id)
|
||||||
|
{
|
||||||
|
Elem*! elem;
|
||||||
|
elem = ctx.cache.search(id);
|
||||||
|
if (catch elem) {
|
||||||
|
return &&Elem{};
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Since ids are now keyed with the element's parent id, this function does not work
|
// FIXME: Since ids are now keyed with the element's parent id, this function does not work
|
||||||
// outside of the element's div block.
|
// outside of the element's div block.
|
||||||
// this searches an element in the cache by label, it does not create a new element
|
// this searches an element in the cache by label, it does not create a new element
|
||||||
@ -305,6 +283,7 @@ fn void! Ctx.frame_end(&ctx)
|
|||||||
|
|
||||||
// 2. clear input fields
|
// 2. clear input fields
|
||||||
ctx.input.events = (InputEvents)0;
|
ctx.input.events = (InputEvents)0;
|
||||||
|
ctx.input.keyboard.text_len = 0;
|
||||||
|
|
||||||
// send atlas updates
|
// send atlas updates
|
||||||
if (ctx.font.should_update) {
|
if (ctx.font.should_update) {
|
||||||
@ -333,7 +312,7 @@ $endif
|
|||||||
*>
|
*>
|
||||||
macro bool Ctx.is_hovered(&ctx, Elem *elem)
|
macro bool Ctx.is_hovered(&ctx, Elem *elem)
|
||||||
{
|
{
|
||||||
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
|
return ctx.input.mouse.pos.in_rect(elem.bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro bool Ctx.elem_focus(&ctx, Elem *elem)
|
macro bool Ctx.elem_focus(&ctx, Elem *elem)
|
||||||
|
@ -23,6 +23,11 @@ struct ElemDiv {
|
|||||||
Point origin_r, origin_c;
|
Point origin_r, origin_c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// begin a widget container, or div, the size determines the offset (x,y) width and height.
|
||||||
|
// if the width or height are zero the width or height are set to the maximum available.
|
||||||
|
// if the width or height are negative the width or height will be calculated based on the children size
|
||||||
|
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height
|
||||||
|
// FIXME: there is a bug if the size.w or size.h == -0
|
||||||
fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false)
|
fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false)
|
||||||
{
|
{
|
||||||
Id id = ctx.gen_id(label)!;
|
Id id = ctx.gen_id(label)!;
|
||||||
@ -43,7 +48,13 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
|
|||||||
elem.div.scroll_y.enabled = scroll_y;
|
elem.div.scroll_y.enabled = scroll_y;
|
||||||
|
|
||||||
// 2. layout the element
|
// 2. layout the element
|
||||||
elem.bounds = ctx.position_element(parent, size);
|
Rect wanted_size = {
|
||||||
|
.x = size.x,
|
||||||
|
.y = size.y,
|
||||||
|
.w = size.w < 0 ? max(elem.div.pcb.w, (short)-size.w) : size.w,
|
||||||
|
.h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h,
|
||||||
|
};
|
||||||
|
elem.bounds = ctx.position_element(parent, wanted_size);
|
||||||
elem.div.children_bounds = elem.bounds;
|
elem.div.children_bounds = elem.bounds;
|
||||||
|
|
||||||
// update the ctx scissor
|
// update the ctx scissor
|
||||||
@ -99,11 +110,14 @@ fn void! Ctx.div_end(&ctx)
|
|||||||
|
|
||||||
Id hsid = ctx.gen_id("div_scrollbar_horizontal")!;
|
Id hsid = ctx.gen_id("div_scrollbar_horizontal")!;
|
||||||
Id vsid = ctx.gen_id("div_scrollbar_vertical")!;
|
Id vsid = ctx.gen_id("div_scrollbar_vertical")!;
|
||||||
|
short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.is_hovered(ctx.find_elem(vsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0;
|
||||||
short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.hover_id == vsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0;
|
short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.is_hovered(ctx.find_elem(hsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0;
|
||||||
short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.hover_id == hsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0;
|
|
||||||
|
|
||||||
if (elem.div.scroll_y.on) {
|
if (elem.div.scroll_y.on) {
|
||||||
|
if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) {
|
||||||
|
elem.div.scroll_y.value += ctx.input.mouse.scroll.y * 0.07f;
|
||||||
|
elem.div.scroll_y.value = math::clamp(elem.div.scroll_y.value, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
Rect vslider = {
|
Rect vslider = {
|
||||||
.x = elem.bounds.x + elem.bounds.w - wdim,
|
.x = elem.bounds.x + elem.bounds.w - wdim,
|
||||||
.y = elem.bounds.y,
|
.y = elem.bounds.y,
|
||||||
@ -117,6 +131,10 @@ fn void! Ctx.div_end(&ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (elem.div.scroll_x.on) {
|
if (elem.div.scroll_x.on) {
|
||||||
|
if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) {
|
||||||
|
elem.div.scroll_x.value += ctx.input.mouse.scroll.x * 0.07f;
|
||||||
|
elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
Rect hslider = {
|
Rect hslider = {
|
||||||
.x = elem.bounds.x,
|
.x = elem.bounds.x,
|
||||||
.y = elem.bounds.y + elem.bounds.h - hdim,
|
.y = elem.bounds.y + elem.bounds.h - hdim,
|
||||||
|
@ -76,6 +76,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale)
|
|||||||
|
|
||||||
font.sft.font = schrift::loadfile(path);
|
font.sft.font = schrift::loadfile(path);
|
||||||
if (font.sft.font == null) {
|
if (font.sft.font == null) {
|
||||||
|
font.table.free();
|
||||||
return UgFontError.TTF_LOAD_FAILED?;
|
return UgFontError.TTF_LOAD_FAILED?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,36 +1,18 @@
|
|||||||
module ugui;
|
module ugui;
|
||||||
|
|
||||||
|
import grapheme;
|
||||||
import std::io;
|
import std::io;
|
||||||
import std::math;
|
import std::math;
|
||||||
|
|
||||||
// TODO: this could be a bitstruct
|
|
||||||
bitstruct InputEvents : uint {
|
bitstruct InputEvents : uint {
|
||||||
bool resize : 0; // window size was changed
|
bool resize : 0; // window size was changed
|
||||||
bool change_focus : 1; // window focus changed
|
bool change_focus : 1; // window focus changed
|
||||||
bool mouse_move : 2; // mouse was moved
|
bool mouse_move : 2; // mouse was moved
|
||||||
bool mouse_btn : 3; // mouse button pressed or released
|
bool mouse_btn : 3; // mouse button pressed or released
|
||||||
}
|
bool mouse_scroll : 4; // mouse scroll wheel. x or y
|
||||||
|
bool text_input : 5;
|
||||||
// Window size was changed
|
bool mod_key : 6;
|
||||||
fn void! Ctx.input_window_size(&ctx, short width, short height)
|
|
||||||
{
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
return UgError.INVALID_SIZE?;
|
|
||||||
}
|
|
||||||
ctx.width = width;
|
|
||||||
ctx.height = height;
|
|
||||||
ctx.input.events.resize = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window gained/lost focus
|
|
||||||
fn void Ctx.input_changefocus(&ctx, bool has_focus)
|
|
||||||
{
|
|
||||||
// FIXME: raylib only has an API to query the focus status so we have to
|
|
||||||
// update the input flag only if the focus changed
|
|
||||||
if (ctx.has_focus != has_focus) {
|
|
||||||
ctx.input.events.change_focus = true;
|
|
||||||
}
|
|
||||||
ctx.has_focus = has_focus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bitstruct MouseButtons : uint {
|
bitstruct MouseButtons : uint {
|
||||||
@ -41,30 +23,77 @@ bitstruct MouseButtons : uint {
|
|||||||
bool btn_5 : 4;
|
bool btn_5 : 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down;
|
// FIXME: all of these names were prefixed with key_ idk if this is better,
|
||||||
macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down;
|
// if it is remove the prefix on MouseButtons as well
|
||||||
macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down;
|
// Modifier Keys, same as SDL
|
||||||
|
bitstruct ModKeys : uint {
|
||||||
|
bool lshift : 0;
|
||||||
|
bool rshift : 1;
|
||||||
|
bool lctrl : 2;
|
||||||
|
bool rctrl : 3;
|
||||||
|
bool lalt : 4;
|
||||||
|
bool ralt : 5;
|
||||||
|
bool lgui : 6;
|
||||||
|
bool rgui : 7;
|
||||||
|
bool num : 8;
|
||||||
|
bool caps : 9;
|
||||||
|
bool mode : 10;
|
||||||
|
bool scroll : 11;
|
||||||
|
}
|
||||||
|
|
||||||
const MouseButtons BTN_NONE = (MouseButtons)0u;
|
const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true};
|
||||||
const MouseButtons BTN_ANY = (MouseButtons)(uint.max);
|
const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true};
|
||||||
|
const ModKeys KMOD_ALT = {.lalt = true, .ralt = true};
|
||||||
|
const ModKeys KMOD_GUI = {.lgui = true, .rgui = true};
|
||||||
|
const ModKeys KMOD_NONE = ModKeys{};
|
||||||
|
const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max);
|
||||||
|
|
||||||
|
const MouseButtons BTN_NONE = MouseButtons{};
|
||||||
|
const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max);
|
||||||
const MouseButtons BTN_LEFT = {.btn_left = true};
|
const MouseButtons BTN_LEFT = {.btn_left = true};
|
||||||
const MouseButtons BTN_MIDDLE = {.btn_middle = true};
|
const MouseButtons BTN_MIDDLE = {.btn_middle = true};
|
||||||
const MouseButtons BTN_RIGHT = {.btn_right = true};
|
const MouseButtons BTN_RIGHT = {.btn_right = true};
|
||||||
const MouseButtons BTN_4 = {.btn_4 = true};
|
const MouseButtons BTN_4 = {.btn_4 = true};
|
||||||
const MouseButtons BTN_5 = {.btn_5 = true};
|
const MouseButtons BTN_5 = {.btn_5 = true};
|
||||||
|
|
||||||
|
const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max);
|
||||||
|
|
||||||
|
// Window size was changed
|
||||||
|
fn void! Ctx.input_window_size(&ctx, short width, short height)
|
||||||
|
{
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
return UgError.INVALID_SIZE?;
|
||||||
|
}
|
||||||
|
ctx.input.events.resize = ctx.width != width || ctx.height != height;
|
||||||
|
ctx.width = width;
|
||||||
|
ctx.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window gained/lost focus
|
||||||
|
fn void Ctx.input_changefocus(&ctx, bool has_focus)
|
||||||
|
{
|
||||||
|
// FIXME: raylib only has an API to query the focus status so we have to
|
||||||
|
// update the input flag only if the focus changed
|
||||||
|
ctx.input.events.change_focus = ctx.has_focus != has_focus;
|
||||||
|
ctx.has_focus = has_focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down;
|
||||||
|
macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down;
|
||||||
|
macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down;
|
||||||
|
|
||||||
// FIXME: hthis compairson could be done with a cast using MouseButtons.inner
|
// FIXME: hthis compairson could be done with a cast using MouseButtons.inner
|
||||||
// property but I could not figure out how
|
// property but I could not figure out how
|
||||||
macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE;
|
macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE;
|
||||||
macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE;
|
macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE;
|
||||||
macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE;
|
macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE;
|
||||||
|
|
||||||
// Mouse Button moved
|
// Mouse Buttons down
|
||||||
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
||||||
{
|
{
|
||||||
ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons;
|
ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons;
|
||||||
ctx.input.mouse.down = buttons;
|
ctx.input.mouse.down = buttons;
|
||||||
ctx.input.events.mouse_btn = true;
|
ctx.input.events.mouse_btn = (uint)ctx.input.mouse.down != 0 || (uint)ctx.input.mouse.updated != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse was moved, report absolute position
|
// Mouse was moved, report absolute position
|
||||||
@ -80,7 +109,7 @@ fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
|||||||
ctx.input.mouse.delta.x = dx;
|
ctx.input.mouse.delta.x = dx;
|
||||||
ctx.input.mouse.delta.y = dy;
|
ctx.input.mouse.delta.y = dy;
|
||||||
|
|
||||||
ctx.input.events.mouse_move = true;
|
ctx.input.events.mouse_move = dx != 0 || dy != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse was moved, report relative motion
|
// Mouse was moved, report relative motion
|
||||||
@ -96,5 +125,52 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
|
|||||||
ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width);
|
ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width);
|
||||||
ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
|
ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
|
||||||
|
|
||||||
ctx.input.events.mouse_move = true;
|
ctx.input.events.mouse_move = dx != 0 || dy != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void Ctx.input_mouse_wheel(&ctx, short x, short y, float scale = 1.0)
|
||||||
|
{
|
||||||
|
ctx.input.mouse.scroll.x = (short)((float)-x*scale);
|
||||||
|
ctx.input.mouse.scroll.y = (short)((float)-y*scale);
|
||||||
|
ctx.input.events.mouse_scroll = x !=0 || y != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append utf-8 encoded text to the context text input
|
||||||
|
fn void Ctx.input_text_utf8(&ctx, char[] text)
|
||||||
|
{
|
||||||
|
if (text.len == 0) { return; }
|
||||||
|
|
||||||
|
usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len;
|
||||||
|
usz len = text.len > remaining ? remaining : text.len;
|
||||||
|
char[] s = ctx.input.keyboard.text[ctx.input.keyboard.text_len ..];
|
||||||
|
s[..len-1] = text[..len-1];
|
||||||
|
ctx.input.keyboard.text_len += len;
|
||||||
|
ctx.input.events.text_input = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn void Ctx.input_text_unicode(&ctx, int[] text)
|
||||||
|
{
|
||||||
|
if (text.len == 0) { return; }
|
||||||
|
|
||||||
|
char[32] tmp;
|
||||||
|
usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len;
|
||||||
|
char[] s = ctx.input.keyboard.text[ctx.input.keyboard.text_len ..];
|
||||||
|
|
||||||
|
usz off;
|
||||||
|
foreach (idx, cp: text) {
|
||||||
|
if (off >= remaining) { break; }
|
||||||
|
usz enc = grapheme::encode_utf8(cp, tmp[..], tmp.len);
|
||||||
|
s[off..off+enc] = tmp[..enc];
|
||||||
|
off += enc;
|
||||||
|
}
|
||||||
|
ctx.input.keyboard.text_len += off;
|
||||||
|
|
||||||
|
ctx.input.events.text_input = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse Buttons down
|
||||||
|
fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys)
|
||||||
|
{
|
||||||
|
ctx.input.keyboard.down = modkeys;
|
||||||
|
ctx.input.events.mod_key = (uint)ctx.input.keyboard.down != 0;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,26 @@ fn void! Ctx.layout_next_column(&ctx)
|
|||||||
parent.div.origin_r = parent.div.origin_c;
|
parent.div.origin_r = parent.div.origin_c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro Rect Elem.get_view(&elem)
|
||||||
|
{
|
||||||
|
Rect off;
|
||||||
|
if (elem.div.scroll_x.enabled && elem.div.scroll_x.on) {
|
||||||
|
off.x = (short)((float)(elem.div.pcb.w - elem.bounds.w) * elem.div.scroll_x.value);
|
||||||
|
off.w = -SCROLLBAR_DIM;
|
||||||
|
}
|
||||||
|
if (elem.div.scroll_y.enabled && elem.div.scroll_y.on) {
|
||||||
|
off.y = (short)((float)(elem.div.pcb.h - elem.bounds.h) * elem.div.scroll_y.value);
|
||||||
|
off.h = -SCROLLBAR_DIM;
|
||||||
|
}
|
||||||
|
return elem.bounds.add(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Elem.get_view_off(&elem)
|
||||||
|
{
|
||||||
|
return elem.get_view().sub(elem.bounds).position();
|
||||||
|
}
|
||||||
|
|
||||||
// position the rectangle inside the parent according to the layout
|
// position the rectangle inside the parent according to the layout
|
||||||
// parent: parent div
|
// parent: parent div
|
||||||
// rect: the requested size
|
// rect: the requested size
|
||||||
@ -90,11 +110,13 @@ fn void! Ctx.layout_next_column(&ctx)
|
|||||||
*>
|
*>
|
||||||
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
|
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
|
||||||
{
|
{
|
||||||
Rect placement;
|
|
||||||
Point origin;
|
|
||||||
ElemDiv* div = &parent.div;
|
ElemDiv* div = &parent.div;
|
||||||
|
|
||||||
|
Rect parent_bounds, parent_view;
|
||||||
|
Rect child_placement, child_occupied;
|
||||||
|
|
||||||
// 1. Select the right origin
|
// 1. Select the right origin
|
||||||
|
Point origin;
|
||||||
switch (div.layout) {
|
switch (div.layout) {
|
||||||
case LAYOUT_ROW:
|
case LAYOUT_ROW:
|
||||||
origin = div.origin_r;
|
origin = div.origin_r;
|
||||||
@ -107,81 +129,70 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
|
|||||||
return Rect{};
|
return Rect{};
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bottom-right border of the element box
|
// 2. Compute the parent's view
|
||||||
Point pl_corner;
|
parent_bounds = parent.bounds;
|
||||||
|
parent_view = parent.get_view();
|
||||||
|
|
||||||
// 2. Calculate the placement
|
// 3. Compute the placement and occupied area
|
||||||
placement.x = (short)max(origin.x + rect.x, 0);
|
|
||||||
placement.y = (short)max(origin.y + rect.y, 0);
|
|
||||||
placement.w = rect.w > 0 ? rect.w : (short)max(parent.bounds.w - (placement.x - parent.bounds.x), 0);
|
|
||||||
placement.h = rect.h > 0 ? rect.h : (short)max(parent.bounds.h - (placement.y - parent.bounds.y), 0);
|
|
||||||
|
|
||||||
pl_corner.x = placement.x + placement.w;
|
// grow rect (wanted size) when widht or height are less than zero
|
||||||
pl_corner.y = placement.y + placement.h;
|
bool adapt_x = rect.w <= 0;
|
||||||
|
bool adapt_y = rect.h <= 0;
|
||||||
|
if (adapt_x) rect.w = parent_bounds.w - parent_bounds.x - origin.x;
|
||||||
|
if (adapt_y) rect.h = parent_bounds.h - parent_bounds.y - origin.y;
|
||||||
|
|
||||||
// 2.1 apply style, css box model
|
// offset placement and area
|
||||||
|
child_placement = child_placement.off(origin.add(rect.position()));
|
||||||
|
child_occupied = child_occupied.off(origin.add(rect.position()));
|
||||||
if (style) {
|
if (style) {
|
||||||
Rect margin = ctx.style.margin;
|
Rect margin = ctx.style.margin;
|
||||||
Rect border = ctx.style.border;
|
Rect border = ctx.style.border;
|
||||||
Rect padding = ctx.style.padding;
|
Rect padding = ctx.style.padding;
|
||||||
|
|
||||||
placement.x += margin.x;
|
// padding, grows both the placement and occupied area
|
||||||
placement.y += margin.y;
|
child_placement = child_placement.grow(padding.position().add(padding.size()));
|
||||||
if (rect.w != 0) { placement.w += border.x+border.w + padding.x+padding.w; }
|
child_occupied = child_occupied.grow(padding.position().add(padding.size()));
|
||||||
if (rect.h != 0) { placement.h += border.y+border.h + padding.y+padding.h; }
|
// border, grows both the placement and occupied area
|
||||||
|
child_placement = child_placement.grow(border.position().add(border.size()));
|
||||||
|
child_occupied = child_occupied.grow(border.position().add(border.size()));
|
||||||
|
// margin, offsets the placement and grows the occupied area
|
||||||
|
child_placement = child_placement.off(margin.position());
|
||||||
|
child_occupied = child_occupied.grow(margin.position().add(margin.size()));
|
||||||
|
|
||||||
pl_corner.x = placement.x + placement.w + margin.w;
|
// oh yeah also adjust the rect if i was to grow
|
||||||
pl_corner.y = placement.y + placement.h + margin.h;
|
if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w;
|
||||||
|
if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h;
|
||||||
}
|
}
|
||||||
|
// set the size
|
||||||
|
child_placement = child_placement.grow(rect.size());
|
||||||
|
child_occupied = child_occupied.grow(rect.size());
|
||||||
|
|
||||||
// 3. Update the origins of the parent
|
// 4. Update the parent's origin
|
||||||
div.origin_r = Point{
|
div.origin_r = Point{
|
||||||
.x = pl_corner.x,
|
.x = child_occupied.bottom_right().x,
|
||||||
.y = origin.y,
|
.y = origin.y,
|
||||||
};
|
};
|
||||||
div.origin_c = Point{
|
div.origin_c = Point{
|
||||||
.x = origin.x,
|
.x = origin.x,
|
||||||
.y = pl_corner.y,
|
.y = child_occupied.bottom_right().y,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. Calculate the "scrolled" view
|
// 5. Update the parent's children bounds
|
||||||
Rect off;
|
if (!child_occupied.bottom_right().in_rect(div.children_bounds)) {
|
||||||
Rect* cb = &div.children_bounds;
|
// right overflow
|
||||||
if (div.scroll_x.enabled && div.scroll_x.on) {
|
if (child_occupied.bottom_right().x > div.children_bounds.bottom_right().x) {
|
||||||
off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll_x.value);
|
div.children_bounds.w += child_occupied.bottom_right().x - div.children_bounds.bottom_right().x;
|
||||||
off.w = SCROLLBAR_DIM;
|
|
||||||
}
|
}
|
||||||
if (div.scroll_y.enabled && div.scroll_y.on) {
|
// left overflow
|
||||||
off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll_y.value);
|
if (child_occupied.bottom_right().y > div.children_bounds.bottom_right().y) {
|
||||||
off.h = SCROLLBAR_DIM;
|
div.children_bounds.h += child_occupied.bottom_right().y - div.children_bounds.bottom_right().y;
|
||||||
}
|
|
||||||
Rect view = {
|
|
||||||
.x = parent.bounds.x + off.x,
|
|
||||||
.y = parent.bounds.y + off.y,
|
|
||||||
.w = parent.bounds.w - off.w,
|
|
||||||
.h = parent.bounds.h - off.h,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 5. check if the placement overflows the children ounds, if so update them
|
|
||||||
if (!point_in_rect(pl_corner, *cb)) {
|
|
||||||
if (pl_corner.y > cb.y+cb.h) {
|
|
||||||
cb.h = pl_corner.y - cb.y;
|
|
||||||
}
|
|
||||||
if (pl_corner.x > cb.x+cb.w) {
|
|
||||||
cb.w += pl_corner.x - (cb.x + cb.w);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. check if the placement is inside the view
|
// 99. return the placement
|
||||||
if (placement.collides(view)) {
|
if (child_placement.collides(parent_view)) {
|
||||||
return Rect{
|
return child_placement.off(parent.get_view_off().neg());
|
||||||
.x = placement.x - off.x,
|
|
||||||
.y = placement.y - off.y,
|
|
||||||
.w = placement.w,
|
|
||||||
.h = placement.h,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return Rect{};
|
return Rect{};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
194
src/ugui_shapes.c3
Normal file
194
src/ugui_shapes.c3
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
module ugui;
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
// RECTANGLE //
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
// Rect and it's methods
|
||||||
|
struct Rect {
|
||||||
|
short x, y, w, h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if rect a contains b
|
||||||
|
macro bool Rect.contains(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the intersection of a and b
|
||||||
|
macro Rect Rect.intersection(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = (short)max(a.x, b.x),
|
||||||
|
.y = (short)max(a.y, b.y),
|
||||||
|
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
||||||
|
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the intersection not null
|
||||||
|
macro bool Rect.collides(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for empty rect
|
||||||
|
macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
|
||||||
|
|
||||||
|
// returns the element-wise addition of r1 and r2
|
||||||
|
macro Rect Rect.add(Rect r1, Rect r2)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = r1.x + r2.x,
|
||||||
|
.y = r1.y + r2.y,
|
||||||
|
.w = r1.w + r2.w,
|
||||||
|
.h = r1.h + r2.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the element-wise subtraction of r1 and r2
|
||||||
|
macro Rect Rect.sub(Rect r1, Rect r2)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = r1.x - r2.x,
|
||||||
|
.y = r1.y - r2.y,
|
||||||
|
.w = r1.w - r2.w,
|
||||||
|
.h = r1.h - r2.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the element-wise multiplication of r1 and r2
|
||||||
|
macro Rect Rect.mul(Rect r1, Rect r2)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = r1.x * r2.x,
|
||||||
|
.y = r1.y * r2.y,
|
||||||
|
.w = r1.w * r2.w,
|
||||||
|
.h = r1.h * r2.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Rect.position(Rect r)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = r.x,
|
||||||
|
.y = r.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Rect.size(Rect r)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = r.w,
|
||||||
|
.y = r.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Rect Rect.max(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = max(a.x, b.x),
|
||||||
|
.y = max(a.y, b.y),
|
||||||
|
.w = max(a.w, b.w),
|
||||||
|
.h = max(a.h, b.h),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Rect Rect.min(Rect a, Rect b)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = min(a.x, b.x),
|
||||||
|
.y = min(a.y, b.y),
|
||||||
|
.w = min(a.w, b.w),
|
||||||
|
.h = min(a.h, b.h),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset a rect by a point
|
||||||
|
macro Rect Rect.off(Rect r, Point p)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = r.x + p.x,
|
||||||
|
.y = r.y + p.y,
|
||||||
|
.w = r.w,
|
||||||
|
.h = r.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize a rect width and height
|
||||||
|
macro Rect Rect.grow(Rect r, Point p)
|
||||||
|
{
|
||||||
|
return Rect{
|
||||||
|
.x = r.x,
|
||||||
|
.y = r.y,
|
||||||
|
.w = r.w + p.x,
|
||||||
|
.h = r.h + p.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the bottom-right corner of a rectangle
|
||||||
|
macro Point Rect.bottom_right(Rect r)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = r.x + r.w,
|
||||||
|
.y = r.y + r.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
// POINT //
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
short x, y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if a point is inside the rectangle
|
||||||
|
macro bool Point.in_rect(Point p, Rect r)
|
||||||
|
{
|
||||||
|
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Point.add(Point a, Point b)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = a.x + b.x,
|
||||||
|
.y = a.y + b.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Point.sub(Point a, Point b)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = a.x - b.x,
|
||||||
|
.y = a.y - b.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Point.neg(Point p) => Point{-p.x, -p.y};
|
||||||
|
|
||||||
|
macro Point Point.max(Point a, Point b)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = max(a.x, b.x),
|
||||||
|
.y = max(a.y, b.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro Point Point.min(Point a, Point b)
|
||||||
|
{
|
||||||
|
return Point{
|
||||||
|
.x = min(a.x, b.x),
|
||||||
|
.y = min(a.y, b.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
// COLOR //
|
||||||
|
// ---------------------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
struct Color{
|
||||||
|
char r, g, b, a;
|
||||||
|
}
|
@ -130,5 +130,6 @@ fn ElemEvents! Ctx.slider_ver(&ctx,
|
|||||||
return elem.events;
|
return elem.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value);
|
macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value);
|
||||||
macro float calc_value(ushort off, ushort mouse, ushort dim, ushort slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
|
macro float calc_value(short off, short mouse, short dim, short slider)
|
||||||
|
=> math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f);
|
||||||
|
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);
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user