Compare commits

...

4 Commits

8 changed files with 113 additions and 59 deletions

9
TODO
View File

@ -20,6 +20,14 @@ to maintain focus until mouse release (fix scroll bars)
[x] Fix scroll wheel when div is scrolled [x] Fix scroll wheel when div is scrolled
[ ] Be consistent with the initialization methods some are foo.new() and some are foo.init() [ ] Be consistent with the initialization methods some are foo.new() and some are foo.init()
[ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg [ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg
[x] .qoi
[ ] .ff
[ ] .bmp
[ ] .png
[ ] .jpg
[ ] gif support?
[ ] layout_set_max_rows() and layout_set_max_columns()
[ ] Maybe SDF sprites??
## Layout ## Layout
@ -65,3 +73,4 @@ _ border radius
[ ] Text Input box [ ] Text Input box
[ ] Icon Buttons [ ] Icon Buttons
[ ] Switch [ ] Switch
[ ] Checkbox

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

View File

@ -4,9 +4,9 @@
// Warnings used for all targets. // Warnings used for all targets.
"warnings": ["no-unused"], "warnings": ["no-unused"],
// Directories where C3 library files may be found. // Directories where C3 library files may be found.
"dependency-search-paths": ["lib"], "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"],
// Libraries to use for all targets. // Libraries to use for all targets.
"dependencies": ["raylib", "schrift", "grapheme", "mqoi"], "dependencies": ["raylib55", "schrift", "grapheme", "mqoi"],
"features": [ "features": [
// See rcore.c3 // See rcore.c3
//"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT",

View File

@ -2,7 +2,7 @@ import std::io;
import vtree; import vtree;
import cache; import cache;
import ugui; import ugui;
import rl; import raylib5::rl;
import std::time; import std::time;
import std::collections::ringbuffer; import std::collections::ringbuffer;
import std::core::string; import std::core::string;
@ -80,17 +80,19 @@ fn int main(String[] args)
{ {
ugui::Ctx ui; ugui::Ctx ui;
ui.init()!!; ui.init()!!;
defer ui.free();
ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!;
ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!;
ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!;
short width = 800; short width = 800;
short height = 450; short height = 450;
rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); rl::setConfigFlags(rl::FLAG_WINDOW_RESIZABLE);
rl::init_window(width, height, "Ugui Test"); rl::initWindow(width, height, "Ugui Test");
ui.input_window_size(width, height)!!; ui.input_window_size(width, height)!!;
rl::set_target_fps(60); rl::setTargetFPS(60);
rl::enable_event_waiting(); rl::enableEventWaiting();
isz frame; isz frame;
bool toggle = true; bool toggle = true;
@ -99,7 +101,7 @@ fn int main(String[] args)
Times draw_times; Times draw_times;
// font stuff // font stuff
rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); rl::Shader font_shader = rl::loadShaderFromMemory(null, FONT_FS);
rl::Image font_atlas; rl::Image font_atlas;
rl::Image sprite_atlas; rl::Image sprite_atlas;
rl::Texture2D font_texture; rl::Texture2D font_texture;
@ -108,7 +110,7 @@ fn int main(String[] args)
ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); ugui::Id sprite_id = ui.get_sprite_atlas_id("icons");
// Main loop // Main loop
while (!rl::window_should_close()) { while (!rl::windowShouldClose()) {
clock.mark(); clock.mark();
/* /*
@ -122,30 +124,37 @@ fn int main(String[] args)
io::printfn("%s", k); io::printfn("%s", k);
} while (k != 0); } while (k != 0);
*/ */
for (int c; (c = rl::get_char_pressed()) != 0;) {
int[1] ts; ugui::ModKeys mod;
ts[0] = c; mod.rctrl = rl::isKeyDown(rl::KEY_RIGHT_CONTROL);
ui.input_text_unicode(ts[..]); mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL);
ui.input_mod_keys(mod);
for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) {
ZString kname = rl::getKeyName(key);
if (kname == null) continue;
ui.input_text_unicode(kname.str_view());
} }
/* Start Input Handling */ /* Start Input Handling */
if (rl::is_window_resized()) { if (rl::isWindowResized()) {
width = (short)rl::get_screen_width(); width = (short)rl::getScreenWidth();
height = (short)rl::get_screen_height(); height = (short)rl::getScreenHeight();
ui.input_window_size(width, height)!!; ui.input_window_size(width, height)!!;
} }
ui.input_changefocus(rl::is_window_focused()); ui.input_changefocus(rl::isWindowFocused());
rl::Vector2 mpos = rl::get_mouse_position(); rl::Vector2 mpos = rl::getMousePosition();
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(); rl::Vector2 mwheel = rl::getMouseWheelMoveV();
ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y); ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y);
ugui::MouseButtons buttons = { ugui::MouseButtons buttons = {
.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT), .btn_left = rl::isMouseButtonDown(rl::MouseButton.LEFT),
.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT), .btn_right = rl::isMouseButtonDown(rl::MouseButton.RIGHT),
.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE), .btn_middle = rl::isMouseButtonDown(rl::MouseButton.MIDDLE),
}; };
ui.input_mouse_button(buttons); ui.input_mouse_button(buttons);
/* End Input Handling */ /* End Input Handling */
@ -153,6 +162,8 @@ fn int main(String[] args)
/* Start UI Handling */ /* Start UI Handling */
ui.frame_begin()!!; ui.frame_begin()!!;
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break;
// main div, fill the whole window // main div, fill the whole window
ui.div_begin("main", ugui::Rect{.w=-100})!!; ui.div_begin("main", ugui::Rect{.w=-100})!!;
{| {|
@ -179,12 +190,12 @@ fn int main(String[] args)
ui.slider_ver("slider_a", ugui::Rect{0,0,30,100}, &af)!!; ui.slider_ver("slider_a", ugui::Rect{0,0,30,100}, &af)!!;
ui.layout_next_column()!!; ui.layout_next_column()!!;
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
ui.layout_next_column()!!; ui.layout_next_column()!!;
ui.button_label("Continua!")!!; ui.button_label("Continua!")!!;
|}; |};
ui.draw_sprite("tux")!!; ui.draw_sprite("sprite1", "tux")!!;
ui.div_end()!!; ui.div_end()!!;
ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
@ -231,7 +242,7 @@ fn int main(String[] args)
//ui_times.print_stats(); //ui_times.print_stats();
/* Start UI Drawing */ /* Start UI Drawing */
rl::begin_drawing(); rl::beginDrawing();
// ClearBackground(BLACK); // ClearBackground(BLACK);
for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) {
@ -241,7 +252,7 @@ fn int main(String[] args)
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.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w;
rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); rl::drawRectangleRounded(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) { if (cmd.update_atlas.id == font_id) {
//rl::unload_image(font_atlas); //rl::unload_image(font_atlas);
@ -249,23 +260,21 @@ fn int main(String[] args)
font_atlas.width = cmd.update_atlas.width; font_atlas.width = cmd.update_atlas.width;
font_atlas.height = cmd.update_atlas.height; font_atlas.height = cmd.update_atlas.height;
font_atlas.mipmaps = 1; font_atlas.mipmaps = 1;
//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; font_atlas.format = rl::PixelFormat.UNCOMPRESSED_GRAYSCALE;
font_atlas.format = 1; if (rl::isTextureValid(font_texture)) {
if (rl::is_texture_ready(font_texture)) { rl::unloadTexture(font_texture);
rl::unload_texture(font_texture);
} }
font_texture = rl::load_texture_from_image(font_atlas); font_texture = rl::loadTextureFromImage(font_atlas);
} else if (cmd.update_atlas.id == sprite_id) { } else if (cmd.update_atlas.id == sprite_id) {
sprite_atlas.data = cmd.update_atlas.raw_buffer; sprite_atlas.data = cmd.update_atlas.raw_buffer;
sprite_atlas.width = cmd.update_atlas.width; sprite_atlas.width = cmd.update_atlas.width;
sprite_atlas.height = cmd.update_atlas.height; sprite_atlas.height = cmd.update_atlas.height;
sprite_atlas.mipmaps = 1; sprite_atlas.mipmaps = 1;
//sprite_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; sprite_atlas.format = rl::PixelFormat.UNCOMPRESSED_R8G8B8A8;
sprite_atlas.format = 7; if (rl::isTextureValid(sprite_texture)) {
if (rl::is_texture_ready(sprite_texture)) { rl::unloadTexture(sprite_texture);
rl::unload_texture(sprite_texture);
} }
sprite_texture = rl::load_texture_from_image(sprite_atlas); sprite_texture = rl::loadTextureFromImage(sprite_atlas);
} }
case ugui::CmdType.CMD_SPRITE: case ugui::CmdType.CMD_SPRITE:
if (cmd.sprite.texture_id == font_id) { if (cmd.sprite.texture_id == font_id) {
@ -273,23 +282,23 @@ fn int main(String[] args)
.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::beginShaderMode(font_shader);
rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); rl::drawTextureRec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
rl::end_shader_mode(); rl::endShaderMode();
} else if (cmd.sprite.texture_id == sprite_id) { } else if (cmd.sprite.texture_id == sprite_id) {
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::draw_texture_rec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); rl::drawTextureRec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv());
} else { } else {
io::printfn("unknown texture id: %d", cmd.sprite.texture_id); io::printfn("unknown texture id: %d", cmd.sprite.texture_id);
} }
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) {
rl::end_scissor_mode(); rl::endScissorMode();
} else { } else {
rl::begin_scissor_mode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h); rl::beginScissorMode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h);
} }
default: default:
io::printfn("Unknown cmd type: %s", cmd.type); io::printfn("Unknown cmd type: %s", cmd.type);
@ -297,13 +306,12 @@ fn int main(String[] args)
} }
draw_times.push(clock.mark()); draw_times.push(clock.mark());
//draw_times.print_stats(); //draw_times.print_stats();
rl::end_drawing(); rl::endDrawing();
/* End Drawing */ /* End Drawing */
} }
rl::close_window(); rl::closeWindow();
ui.free();
return 0; return 0;
} }

View File

@ -17,6 +17,7 @@ enum ElemType {
ETYPE_BUTTON, ETYPE_BUTTON,
ETYPE_SLIDER, ETYPE_SLIDER,
ETYPE_TEXT, ETYPE_TEXT,
ETYPE_SPRITE,
} }
bitstruct ElemFlags : uint { bitstruct ElemFlags : uint {
@ -47,6 +48,7 @@ struct Elem {
ElemButton button; ElemButton button;
ElemSlider slider; ElemSlider slider;
ElemText text; ElemText text;
ElemSprite sprite;
} }
} }
@ -55,7 +57,7 @@ struct Elem {
def IdTree = vtree::VTree(<Id>) @private; def IdTree = vtree::VTree(<Id>) @private;
// elements themselves are kept in a cache // elements themselves are kept in a cache
const uint MAX_ELEMENTS = 1024; const uint MAX_ELEMENTS = 256;
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private; def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
def CmdQueue = fifo::Fifo(<Cmd>); def CmdQueue = fifo::Fifo(<Cmd>);
@ -324,7 +326,7 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
{ {
bool hover = ctx.is_hovered(elem); bool hover = ctx.is_hovered(elem);
bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT)); bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT));
if (ctx.is_mouse_pressed(BTN_ANY) && !hover){ if (ctx.is_mouse_pressed(BTN_ANY) && !hover){
focus = false; focus = false;
if (ctx.focus_id == elem.id) ctx.focus_id = 0; if (ctx.focus_id == elem.id) ctx.focus_id = 0;
@ -332,7 +334,7 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
if (hover) { ctx.hover_id = elem.id; } if (hover) { ctx.hover_id = elem.id; }
if (focus) { ctx.focus_id = elem.id; } if (focus) { ctx.focus_id = elem.id; }
ElemEvents ev = { ElemEvents ev = {
.mouse_hover = hover, .mouse_hover = hover,
.mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY), .mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY),
@ -341,4 +343,3 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
}; };
return ev; return ev;
} }

View File

@ -39,7 +39,7 @@ struct Glyph {
short adv, ox, oy; short adv, ox, oy;
} }
const uint FONT_CACHED = 512; const uint FONT_CACHED = 128;
def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private; def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private;
fault UgFontError { fault UgFontError {
@ -89,7 +89,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale)
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
// like the letter 'A' // like the letter 'A'
ushort size = (ushort)font.size*256; ushort size = (ushort)font.size*(ushort)($$sqrt((float)FONT_CACHED));
font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!; font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!;
// preallocate the ASCII range // preallocate the ASCII range

View File

@ -3,7 +3,7 @@ module ugui;
import grapheme; import grapheme;
import std::io; import std::io;
import std::math; import std::math;
import std::core::string;
bitstruct InputEvents : uint { bitstruct InputEvents : uint {
bool resize : 0; // window size was changed bool resize : 0; // window size was changed
@ -58,6 +58,19 @@ const MouseButtons BTN_5 = {.btn_5 = true};
const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max); const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max);
fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String keys)
{
bool is_mod = (bool)(ctx.input.keyboard.down & mod);
bool is_keys = true;
String haystack = (String)ctx.input.keyboard.text[0..ctx.input.keyboard.text_len];
char[2] needle;
foreach (c: keys) {
needle[0] = c;
is_keys = is_keys && haystack.contains((String)needle[..]);
}
return is_mod && is_keys;
}
// Window size was changed // Window size was changed
fn void! Ctx.input_window_size(&ctx, short width, short height) fn void! Ctx.input_window_size(&ctx, short width, short height)
{ {
@ -148,9 +161,9 @@ fn void Ctx.input_text_utf8(&ctx, char[] text)
ctx.input.events.text_input = true; ctx.input.events.text_input = true;
} }
fn void Ctx.input_text_unicode(&ctx, int[] text) fn void Ctx.input_text_unicode(&ctx, char[] text)
{ {
if (text.len == 0) { return; } if (text.ptr == null || text.len == 0) { return; }
char[32] tmp; char[32] tmp;
usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len; usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len;

View File

@ -21,6 +21,9 @@ struct SpriteAtlas {
bool should_update; bool should_update;
} }
struct ElemSprite {
Id id;
}
// name: some examples are "icons" or "images" // name: some examples are "icons" or "images"
fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height)
@ -118,13 +121,34 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path)
ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!;
} }
// FIXME: test function, very different from every other function here fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
fn void! Ctx.draw_sprite(&ctx, String name)
{ {
Id id = ctx.gen_id(label)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id)!;
// add it to the tree
ctx.tree.add(id, ctx.active_div)!;
if (elem.flags.is_new) {
elem.type = ETYPE_SPRITE;
} else if (elem.type != ETYPE_SPRITE) {
return UgError.WRONG_ELEMENT_TYPE?;
}
Sprite* sprite = ctx.sprite_atlas.get(name)!; Sprite* sprite = ctx.sprite_atlas.get(name)!;
Rect bounds = { 100, 100, sprite.w, sprite.h };
Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h };
Rect bounds = { 0, 0, sprite.w, sprite.h };
elem.bounds = ctx.position_element(parent, bounds.off(off), true);
elem.sprite.id = ctx.get_sprite_atlas_id(name);
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) return;
Id tex_id = ctx.sprite_atlas.id; Id tex_id = ctx.sprite_atlas.id;
return ctx.push_sprite(bounds, uv, tex_id)!; return ctx.push_sprite(elem.bounds, uv, tex_id)!;
} }