import std::io; import vtree; import cache; import ugui; import std::time; import std::collections::ringbuffer; import std::core::string; import std::ascii; import std::core::mem::allocator; import sdlrenderer::ren; import sdl3::sdl; alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]}; fn void Times.print_stats(×) { time::NanoDuration min, max, avg, x; min = times.get(0); for (usz i = 0; i < times.written; i++) { x = times.get(i); if (x < min) { min = x; } if (x > max) { max = x; } avg += x; } avg = (NanoDuration)((ulong)avg/128.0); io::printfn("min=%s, max=%s, avg=%s", min, max, avg); } struct TimeStats { time::NanoDuration min, max, avg; } fn TimeStats Times.get_stats(×) { time::NanoDuration min, max, avg, x; min = times.get(0); for (usz i = 0; i < times.written; i++) { x = times.get(i); if (x < min) { min = x; } if (x > max) { max = x; } avg += x; } avg = (NanoDuration)((ulong)avg/128.0); return {.min = min, .max = max, .avg = avg}; } const char[*] VS_PATH = "resources/shaders/compiled/ugui.vert.spv"; const char[*] FS_PATH = "resources/shaders/compiled/ugui.frag.spv"; const char[*] STYLESHEET_PATH = "resources/style.css"; const bool LIMIT_FPS = true; const bool VSYNC = true; fn int main(String[] args) { ArenaAllocator arena; char[] mem = mem::new_array(char, 1024*1024); defer (void)mem::free(mem); arena.init(mem); ugui::Ctx ui; ui.init(&arena)!!; defer ui.free(); ren::Renderer ren; ren.init("Ugui Test", 800, 600, VSYNC); defer ren.free(); ui.input_window_size(800, 600)!!; // ========================================================================================== // // FONT LOADING // // ========================================================================================== // // import font in the ui context ui.load_font("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 // // ========================================================================================== // sdl::start_text_input(ren.win); sdl::Event e; bool quit = false; ugui::ModKeys mod; ugui::MouseButtons btn; while (!quit) { clock.mark(); fps_clock.mark(); sleep_clock.mark(); do { switch (e.type) { case EVENT_QUIT: quit = true; case EVENT_KEY_UP: ui.input_key_release(); nextcase; case EVENT_KEY_DOWN: ui.input_key_press(); if (e.key.repeat) ui.input_key_repeat(); mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl; mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl; mod.bkspc = e.key.key == K_BACKSPACE ? !!(e.type == EVENT_KEY_DOWN) : mod.bkspc; mod.del = e.key.key == K_DELETE ? !!(e.type == EVENT_KEY_DOWN) : mod.del; mod.up = e.key.key == K_UP ? !!(e.type == EVENT_KEY_DOWN) : mod.up; mod.down = e.key.key == K_DOWN ? !!(e.type == EVENT_KEY_DOWN) : mod.down; mod.left = e.key.key == K_LEFT ? !!(e.type == EVENT_KEY_DOWN) : mod.left; mod.right = e.key.key == K_RIGHT ? !!(e.type == EVENT_KEY_DOWN) : mod.right; // pressing ctrl+key or alt+key does not generate a character as such no // TEXT_INPUT event is generated. When those keys are pressed we have to // do manual text input, bummer if (e.type == EVENT_KEY_DOWN && (mod.lctrl || mod.rctrl)) { if (ascii::is_alnum_m((uint)e.key.key)) { ui.input_char((char)e.key.key); } } if (e.type == EVENT_KEY_DOWN && e.key.key == K_RETURN) ui.input_char('\n'); case EVENT_TEXT_INPUT: ui.input_text_utf8(e.text.text.str_view()); case EVENT_WINDOW_RESIZED: ui.input_window_size((short)e.window.data1, (short)e.window.data2)!!; case EVENT_WINDOW_FOCUS_GAINED: ui.input_changefocus(true); case EVENT_WINDOW_FOCUS_LOST: ui.input_changefocus(false); case EVENT_MOUSE_MOTION: ui.input_mouse_abs((short)e.motion.x, (short)e.motion.y); case EVENT_MOUSE_WHEEL: ui.input_mouse_wheel((short)e.wheel.integer_x, (short)e.wheel.integer_y); case EVENT_MOUSE_BUTTON_DOWN: nextcase; case EVENT_MOUSE_BUTTON_UP: sdl::MouseButtonFlags mb = sdl::get_mouse_state(null, null); btn = { .btn_left = !!(mb & BUTTON_LMASK), .btn_right = !!(mb & BUTTON_RMASK), .btn_middle = !!(mb & BUTTON_MMASK), .btn_4 = !!(mb & BUTTON_X1MASK), .btn_5 = !!(mb & BUTTON_X2MASK), }; case EVENT_POLL_SENTINEL: break; default: io::eprintfn("unhandled event: %s", e.type); } } while(sdl::poll_event(&e)); ui.input_mod_keys(mod); ui.input_mouse_button(btn); /* 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); $endswitch // Timings counter TimeStats dts = draw_times.get_stats(); TimeStats uts = ui_times.get_stats(); ui.@div(ugui::@fit(), ugui::@fit(), COLUMN, TOP_LEFT, true) { 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; sdl::wait_event_timeout(&e, timeout); fps = 1.0 / fps_clock.mark().to_sec(); frame++; } return 0; } /* 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--; } // 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.@div(ugui::@fit(), ugui::@fit(), ROW, TOP_LEFT) { ui.@div(ugui::@fit(), ugui::@fit(), 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.@div(ugui::@fit(), ugui::@fit(), 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.@div(ugui::@fit(), ugui::@fit(), 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.@div(ugui::@fit(), ugui::@fit(), 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.@div(ugui::@fit(), ugui::@fit(), 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")!!; 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, scroll_y: true) { ui.text_box(ugui::@grow(), ugui::@exact(100), te)!!; }!!; }!!; }!!; }