#include "ui.h" struct { float font_size; float color_text[4]; float color_text_light[4]; float color_background[4]; float color_main[4]; float color_light[4]; float color_info[4]; float color_select[4]; float color_background_console[4]; } theme = { .font_size = 20, .color_text = {0, 0, 0, 1}, .color_text_light = {0.5, 0.5, 0.5, 1}, .color_background = {0.1, 0.1, 0.1, 1}, .color_main = {0.75, 0.75, 0.75, 1}, .color_light = {0.85, 0.85, 0.85, 1}, .color_info = {1, 1, 1, 1}, .color_select = {1, 0, 0, 1}, .color_background_console = {1.0, 0.7, 0.0, 0.6} }; #define MAX_INFO 1024 #define MAX_CONSOLE 1023 typedef struct { phy_sim *sim; bool dragging, dragging_frac, selecting; int xsection_flags; float xsection_frac; bool select_valid; vec3_t select; float margin_bottom, margin_top; mst2_t origin, scale; // in virtual space mst2_t tf_v2s, tf_x2s; r_xsection xsection; } ui_simview; typedef enum { UI_WINDOW_SIMVIEW } ui_window_type; typedef struct ui_window_s ui_window; struct ui_window_s { int w, h; r_window *rw; ui_window *prev, *next; bool initialized; int64_t last_frame; size_t frame_count; vec2_t mouse; ui_window_type type; ui_simview simview; itc_chan ctl; char console[MAX_CONSOLE + 1]; int console_len; bool show_console; int64_t info_time; char info[MAX_INFO]; }; static struct { ui_window *windows; bool use_window_open_timer; int64_t window_open_timer; } uis; int ui_init(void) { return 0; } static void ui_window_destroy(ui_window *uiw) { con_printf("ui_window_destroy: destroying uiw=%p\n", uiw); if (uiw->prev) uiw->prev->next = uiw->next; if (uiw->next) uiw->next->prev = uiw->prev; if (uis.windows == uiw) uis.windows = uiw->next; r_xsection_destroy(&uiw->simview.xsection); itc_chan_destroy(&uiw->ctl); free(uiw); } void ui_quit(void) { while (uis.windows) ui_window_destroy(uis.windows); } void ui_renderer_window_register(r_window *rw) { ui_window *uiw; uiw = calloc(1, sizeof(ui_window)); if (!uiw) abort(); uiw->prev = NULL; if (uis.windows) uis.windows->prev = uiw; uiw->next = uis.windows; uis.windows = uiw; uiw->rw = rw; con_printf("ui_renderer_window_register: registered window #%i" ", uiw = %p\n", SDL_GetWindowID(rw->window), uiw); r_xsection_create(&uiw->simview.xsection); itc_chan_create(&uiw->ctl); } static ui_window *ui_find_window(int id) { ui_window *uiw; for (uiw = uis.windows; uiw; uiw = uiw->next) if (SDL_GetWindowID(uiw->rw->window) == id) return uiw; return NULL; } void ui_infof(ui_window *uiw, const char *fmt, ...) { va_list vl; va_start(vl, fmt); vsnprintf(uiw->info, MAX_INFO, fmt, vl); va_end(vl); uiw->info_time = get_time(); } void ui_console_add(ui_window *uiw, char *ch) { int add_len; add_len = strlen(ch); if (!add_len || *ch == 1) // wtf is \x01 ? return; if (uiw->console_len + add_len > MAX_CONSOLE) return; memcpy(uiw->console + uiw->console_len, ch, add_len); uiw->console_len += add_len; uiw->console[uiw->console_len] = 0; } void ui_console_erase(ui_window *uiw) { char *p; if (!uiw->console_len) return; p = uiw->console + uiw->console_len - 1; while (p > uiw->console && ((*p) & 0b11000000) == 0b10000000) p--; uiw->console_len = p - uiw->console; *p = 0; } void ui_event_console(SDL_Event *event, ui_window *uiw) { switch (event->type) { case SDL_KEYDOWN: switch (event->key.keysym.sym) { case SDLK_BACKSPACE: ui_console_erase(uiw); break; case SDLK_RETURN: con_exec(uiw->console, &uiw->ctl); uiw->console[0] = 0; uiw->console_len = 0; uiw->show_console = false; SDL_StopTextInput(); break; } case SDL_TEXTINPUT: ui_console_add(uiw, event->text.text); break; } } void ui_event_window(SDL_Event *event, ui_window *uiw) { ui_simview *sv = &uiw->simview; if (!uiw->initialized) return; if (uiw->show_console) { ui_event_console(event, uiw); return; } if (event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_RETURN) { uiw->show_console = true; SDL_StartTextInput(); } if (!uiw->simview.sim->valid) return; switch (event->type) { case SDL_MOUSEWHEEL: { vec2_t mouse_v; float delta; v2_div_mst2(mouse_v, uiw->mouse, sv->tf_v2s); delta = pow(1.1, event->wheel.y); v2_mul(sv->scale, sv->scale, delta); v2_sub(sv->origin, sv->origin, mouse_v); v2_mul(sv->origin, sv->origin, delta); v2_add(sv->origin, sv->origin, mouse_v); } break; case SDL_MOUSEBUTTONDOWN: if (event->button.y > sv->margin_top && event->button.y < uiw->h - sv->margin_bottom) { switch (event->button.button) { case SDL_BUTTON_LEFT: sv->dragging = true; break; case SDL_BUTTON_RIGHT: sv->dragging_frac = true; break; case SDL_BUTTON_MIDDLE: sv->selecting = true; break; } } break; case SDL_MOUSEBUTTONUP: switch (event->button.button) { case SDL_BUTTON_LEFT: sv->dragging = false; break; case SDL_BUTTON_RIGHT: sv->dragging_frac = false; break; case SDL_BUTTON_MIDDLE: sv->selecting = false; break; } case SDL_MOUSEMOTION: uiw->mouse[0] = event->motion.x; uiw->mouse[1] = event->motion.y; if (sv->dragging || sv->dragging_frac) { vec2_t delta_v; v2_set(delta_v, event->motion.xrel, event->motion.yrel); v2_div_mst2_nt(delta_v, delta_v, sv->tf_v2s); if (sv->dragging) v2_add(sv->origin, sv->origin, delta_v); if (sv->dragging_frac) { sv->xsection_frac += delta_v[1] * -2.0f; if (sv->xsection_frac > 1.0f) sv->xsection_frac = 1.0f; else if (sv->xsection_frac < 0.0f) sv->xsection_frac = 0.0f; } } break; case SDL_KEYDOWN: switch (event->key.keysym.sym) { case SDLK_1: ui_infof(uiw, "Plaszczyzna przekroju: XY"); sv->xsection_flags &= ~XSECTION_PLANES; sv->xsection_flags |= XSECTION_XY; break; case SDLK_2: ui_infof(uiw, "Plaszczyzna przekroju: XZ"); sv->xsection_flags &= ~XSECTION_PLANES; sv->xsection_flags |= XSECTION_XZ; break; case SDLK_3: ui_infof(uiw, "Plaszczyzna przekroju: YZ"); sv->xsection_flags &= ~XSECTION_PLANES; sv->xsection_flags |= XSECTION_YZ; break; case SDLK_o: ui_infof(uiw, "Symulacja wstrzymana"); itc_chan_push(&sv->sim->ctl, PHY_CMD_PAUSE, NULL); break; case SDLK_p: ui_infof(uiw, "Symulacja wznowiona"); itc_chan_push(&sv->sim->ctl, PHY_CMD_RESUME, NULL); break; case SDLK_l: ui_infof(uiw, "Pojedyńczy krok"); itc_chan_push(&sv->sim->ctl, PHY_CMD_STEP, NULL); break; case SDLK_4: ui_infof(uiw, "Przekrój: pola elektrycznego"); sv->xsection_flags &= ~XSECTION_FIELDS; sv->xsection_flags |= XSECTION_E; break; case SDLK_5: ui_infof(uiw, "Przekrój: pola magnetycznego"); sv->xsection_flags &= ~XSECTION_FIELDS; sv->xsection_flags |= XSECTION_H; break; case SDLK_6: ui_infof(uiw, "Przekrój: pola przenikalności"); sv->xsection_flags &= ~XSECTION_FIELDS; sv->xsection_flags |= XSECTION_EPS; break; case SDLK_z: if (sv->select_valid) { phy_field_info *fi = &sv->sim->field_info; size_t *coords; coords = calloc(3, sizeof(size_t)); assert(coords); coords[0] = sv->select[0] * fi->dims[0]; coords[1] = sv->select[1] * fi->dims[1]; coords[2] = sv->select[2] * fi->dims[2]; itc_chan_push(&sv->sim->ctl, PHY_CMD_DEBUG, coords); } break; } } } void ui_event(SDL_Event *event) { if (event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_F3) { if (!uis.use_window_open_timer || uis.window_open_timer + 500000000 <= get_time()) { r_window *rw; uis.use_window_open_timer = true; uis.window_open_timer = get_time(); rw = r_window_create(); if (rw) ui_renderer_window_register(rw); } } else if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_CLOSE) { ui_window *uiw; uiw = ui_find_window(event->window.windowID); if (!uiw) con_printf("WARNING: ui_event: SDL_WINDOWEVENT_" "CLOSE for a window not registered" " by ui\n"); else { r_window_destroy(uiw->rw); ui_window_destroy(uiw); } } else { ui_window *uiw; // SDL sends some SDL_WINDOWEVENTs after a window's been closed uiw = ui_find_window(event->window.windowID); if (uiw) ui_event_window(event, uiw); } } void ui_draw_simview_xsection(ui_window *uiw) { ui_simview *sv = &uiw->simview; phy_sim *sim = sv->sim; float aspect_ratio; vec2_t origin_s, scale_s; SDL_mutexP(sim->rotate_lock); r_xsection_update(uiw->rw, &sv->xsection, sim, sv->xsection_flags, sv->xsection_frac); SDL_mutexV(sim->rotate_lock); aspect_ratio = sv->xsection.aspect_ratio; if (uiw->w > uiw->h * aspect_ratio) mst2_set(sv->tf_v2s, (uiw->w - uiw->h * aspect_ratio) / 2.0f, 0, uiw->h * aspect_ratio, uiw->h); else mst2_set(sv->tf_v2s, 0, (uiw->h - uiw->w / aspect_ratio) / 2.0f, uiw->w, uiw->w / aspect_ratio); v2_mul_mst2(origin_s, sv->origin, sv->tf_v2s); v2_mul_mst2_nt(scale_s, sv->scale, sv->tf_v2s); mst2_set(sv->tf_x2s, origin_s[0], origin_s[1], scale_s[0], scale_s[1]); r_xsection_draw(uiw->rw, &sv->xsection, origin_s[0], origin_s[1], scale_s[0], scale_s[1]); } void ui_draw_simview_selection(ui_window *uiw) { ui_simview *sv = &uiw->simview; vec2_t select_s; if (sv->selecting) { vec2_t select_2d; v2_div_mst2(select_2d, uiw->mouse, sv->tf_x2s); if (select_2d[0] < 0.0f || select_2d[0] > 1.0f || select_2d[1] < 0.0f || select_2d[1] > 1.0f) { sv->select_valid = false; return; } if (sv->xsection_flags & XSECTION_XY) v3_set(sv->select, select_2d[0], select_2d[1], sv->xsection_frac); else if (sv->xsection_flags & XSECTION_XZ) v3_set(sv->select, select_2d[0], sv->xsection_frac, select_2d[1]); else v3_set(sv->select, sv->xsection_frac, select_2d[0], select_2d[1]); sv->select_valid = true; } if (!sv->select_valid) return; if (sv->xsection_flags & XSECTION_XY) v2_set(select_s, sv->select[0], sv->select[1]); else if (sv->xsection_flags & XSECTION_XZ) v2_set(select_s, sv->select[0], sv->select[2]); else v2_set(select_s, sv->select[1], sv->select[2]); v2_mul_mst2(select_s, select_s, sv->tf_x2s); r_draw_line(uiw->rw, select_s[0], 0, select_s[0], uiw->h, theme.color_select); r_draw_line(uiw->rw, 0, select_s[1], uiw->w, select_s[1], theme.color_select); } char *ui_draw_simview_top_bar_text(ui_simview *sv) { int flags = sv->xsection_flags; phy_field_info *fi = &sv->sim->field_info; char *rv; size_t total; float frac_real; if (flags & XSECTION_XY) total = fi->dims[2]; else if (flags & XSECTION_XZ) total = fi->dims[1]; else total = fi->dims[0]; frac_real = floor(sv->xsection_frac * total) / total; rv = va("Przekrój %s płaszczyzną %s=%f", (flags & XSECTION_E ? "E" : "H"), (flags & XSECTION_XY ? "Z" : flags & XSECTION_XZ ? "Y" : "X"), frac_real); return rv; } void ui_draw_simview_bars(ui_window *uiw, float dt) { ui_simview *sv = &uiw->simview; phy_sim *sim = sv->sim; phy_field_em *field_em = sim->fields + 1; phy_field_info *fi = &sim->field_info; SDL_mutexP(sim->rotate_lock); // upper sv->margin_top = theme.font_size; r_draw_rect(uiw->rw, 0, 0, uiw->w, theme.font_size, theme.color_main); r_draw_text(uiw->rw, uiw->w, 0, theme.font_size, va("%.0fkl./s", 1.0f / dt), theme.color_text_light, TEXT_RIGHTX); r_draw_text(uiw->rw, 0, 0, theme.font_size, ui_draw_simview_top_bar_text(sv), theme.color_text, 0); // lower sv->margin_bottom = theme.font_size; r_draw_rect(uiw->rw, 0, uiw->h - theme.font_size, uiw->w, theme.font_size, theme.color_main); r_draw_text(uiw->rw, 0, uiw->h - theme.font_size, theme.font_size, va("t = %f, Δt = %f", sim->time, sim->time_delta), theme.color_text, 0); r_draw_text(uiw->rw, uiw->w, uiw->h - theme.font_size, theme.font_size, va("%.3fs/krok", (sim->step_real_time * 1.0e-9f)), theme.color_text_light, TEXT_RIGHTX); if (sv->select_valid) { size_t x, y, z, offs; sv->margin_bottom += 3 * theme.font_size; r_draw_rect(uiw->rw, 0, uiw->h - 4 * theme.font_size, uiw->w, 3 * theme.font_size, theme.color_light); x = sv->select[0] * fi->dims[0]; y = sv->select[1] * fi->dims[1]; z = sv->select[2] * fi->dims[2]; offs = z * fi->zstr + y * fi->ystr + x * fi->xstr; r_draw_text(uiw->rw, 0, uiw->h - 4 * theme.font_size, theme.font_size, va("x = [%zu %zu %zu]", x, y, z), theme.color_text, 0); r_draw_text(uiw->rw, 0, uiw->h - 3 * theme.font_size, theme.font_size, va("E(x) = [%+E %+E %+E]", field_em->E[offs], field_em->E[offs + 1], field_em->E[offs + 2]), theme.color_text, 0); r_draw_text(uiw->rw, 0, uiw->h - 2 * theme.font_size, theme.font_size, va("H(x) = [%+E %+E %+E]", field_em->H[offs], field_em->H[offs + 1], field_em->H[offs + 2]), theme.color_text, 0); } SDL_mutexV(sim->rotate_lock); } void ui_draw_info(ui_window *uiw, int64_t time) { if (uiw->info_time && uiw->info_time + 3000000000ll > time) { vec4_t color; if (uiw->info_time + 2000000000ll >= time) color[3] = 1.0f; else color[3] = 1.0f - (time - uiw->info_time - 2000000000ll) / 1.0e9f; v3_copy(color, theme.color_info); r_draw_rect(uiw->rw, 0, theme.font_size, uiw->w, theme.font_size, color); v3_copy(color, theme.color_text); r_draw_text(uiw->rw, uiw->w / 2.0f, theme.font_size, theme.font_size, uiw->info, color, TEXT_CENTERX); } } void ui_draw_simview(ui_window *uiw, int64_t time, float dt) { if (!uiw->simview.sim->valid) return; ui_draw_simview_xsection(uiw); ui_draw_simview_selection(uiw); ui_draw_simview_bars(uiw, dt); } void ui_draw_console(ui_window *uiw) { float y; y = uiw->h / 2 - theme.font_size / 2; r_draw_rect(uiw->rw, 0, y, uiw->w, theme.font_size, theme.color_background_console); r_draw_text(uiw->rw, 0, y, theme.font_size, uiw->console, theme.color_text, 0); } void ui_draw_window(ui_window *uiw) { ui_simview *sv = &uiw->simview; int64_t time; float dt; int cmd_num; char *cmd_data; time = get_time(); while (!itc_chan_pop(&uiw->ctl, &cmd_num, (void**)&cmd_data)) { if (cmd_num == CMD_CON_FEEDBACK) { ui_infof(uiw, "%s", cmd_data); free(cmd_data); } } if (!uiw->initialized) { uiw->initialized = true; uiw->last_frame = time; v2_set(sv->origin, 0, 0); v2_set(sv->scale, 1, 1); sv->xsection_flags = XSECTION_XZ | XSECTION_E; sv->xsection_frac = 0.5f; sv->select_valid = false; sv->margin_top = theme.font_size; sv->margin_bottom = 0; r_clear(uiw->rw, r_color_black); return; } dt = (get_time() - uiw->last_frame) * 1.0e-9; r_clear(uiw->rw, r_color_black); ui_draw_simview(uiw, time, dt); if (uiw->show_console) ui_draw_console(uiw); ui_draw_info(uiw, time); r_flip(uiw->rw); uiw->last_frame = time; } void ui_draw(phy_sim *sim) { ui_window *uiw; for (uiw = uis.windows; uiw; uiw = uiw->next) { SDL_GetWindowSize(uiw->rw->window, &uiw->w, &uiw->h); uiw->simview.sim = sim; ui_draw_window(uiw); uiw->frame_count++; } }