import std::io; import vtree; import cache; import ugui; import std::time; import std::collections::ringbuffer; import std::core::string; import std::ascii; 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[*] MSDF_FS_PATH = "resources/shaders/compiled/msdf.frag.spv"; const char[*] SPRITE_FS_PATH = "resources/shaders/compiled/sprite.frag.spv"; const char[*] FONT_FS_PATH = "resources/shaders/compiled/font.frag.spv"; const char[*] RECT_FS_PATH = "resources/shaders/compiled/rect.frag.spv"; const char[*] SPRITE_VS_PATH = "resources/shaders/compiled/sprite.vert.spv"; const char[*] RECT_VS_PATH = "resources/shaders/compiled/rect.vert.spv"; fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; defer ui.free(); ren::Renderer ren; ren.init("Ugui Test", 800, 600, true); 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)!!; // create the rendering pipeline ren.font_atlas_id = ui.get_font_id("font1"); ren.load_spirv_shader_from_file("UGUI_PIPELINE_FONT", SPRITE_VS_PATH, FONT_FS_PATH, 1, 0); ren.create_pipeline("UGUI_PIPELINE_FONT", SPRITE); // 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)!!; // create the rendering pipelines ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons"); // normal sprite pipeline ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE", SPRITE_VS_PATH, SPRITE_FS_PATH, 1, 0); ren.create_pipeline("UGUI_PIPELINE_SPRITE", SPRITE); // msdf sprite pipeline ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE_MSDF", SPRITE_VS_PATH, MSDF_FS_PATH, 1, 0); ren.create_pipeline("UGUI_PIPELINE_SPRITE_MSDF", SPRITE); // upload the atlas to the gpu Atlas atlas = ui.sprite_atlas.atlas; ren.new_texture("icons", FULL_COLOR, atlas.buffer, atlas.width, atlas.height); } // // RECT PIPELINE // ren.load_spirv_shader_from_file("UGUI_PIPELINE_RECT", RECT_VS_PATH, RECT_FS_PATH, 0, 0); ren.create_pipeline("UGUI_PIPELINE_RECT", RECT); isz frame; double fps; bool toggle = true; 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: nextcase; case EVENT_KEY_DOWN: 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; // 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; ui.div_begin({.w=-100})!!; { ui.layout_set_column()!!; if (ui.button({0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; } //ui.layout_next_column()!!; if (ui.button({0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; if (ui.button({0,0,30,30})!!.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_label("Continua!")!!; ui.layout_next_row()!!; static bool check; ui.checkbox("", {}, &check, "tick")!!; ui.toggle("", {}, &toggle)!!; }; ui.draw_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({0,0,50,50})!!; ui.button({0,0,50,50})!!; ui.button({0,0,50,50})!!; ui.button({0,0,50,50})!!; if (toggle) { ui.button({0,0,50,50})!!; ui.button({0,0,50,50})!!; ui.button({0,0,50,50})!!; ui.button({0,0,50,50})!!; } 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()!!; // Timings counter TimeStats dts = draw_times.get_stats(); TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; ui.div_begin({0, ui.height-150, -300, 150})!!; { ui.layout_set_column()!!; ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!; ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; }; ui.div_end()!!; 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 sdl::wait_event_timeout(&e, (int)(100.0-sleep_clock.mark().to_ms()-0.5)); fps = 1.0 / fps_clock.mark().to_sec(); frame++; } return 0; }