// The app's own UI: the capture viewer (app/viewer.jai). Everything else in src/ is the shell // around it: the window, the frame loop, the tray, hotkeys, settings, themes, logging and the // debug panels. APP_MAIN_WINDOW_LABEL :: "Main"; // Once, after ImGui, the fonts, the theme and the settings are up and before the first frame. app_ui_startup :: () { viewer_startup(); import_startup(); ocr_ui_startup(); labels_ui_startup(); } // Every frame, before input is handed to ImGui. Poll background work here. app_ui_frame_start :: () { } // Once, on the shutdown thread after the window is asked to close (exit_shutdown.jai): save and // stop background work here. Must not touch ImGui, since the main thread is still drawing frames // (the overlay; app_main_window_ui stops running the app's frames once shutdown starts). app_ui_shutdown :: () { app_shutdown_step(.IMPORT); import_shutdown(); // before the viewer: it hands the viewer what it writes app_shutdown_step(.VIEWER); viewer_shutdown(); app_shutdown_step(.OCR); ocr_ui_shutdown(); app_shutdown_step(.LABELS); labels_ui_shutdown(); // after the OCR thread and the background workers have stopped app_shutdown_step(.STATS); stats_ui_shutdown(); } // Which step of app_ui_shutdown is running, and since when, for the overlay a slow shutdown shows. App_Shutdown_Step :: enum s32 { NONE; IMPORT; VIEWER; OCR; LABELS; STATS; } app_shutdown: struct { step: App_Shutdown_Step; step_started_ns: s64; // monotonic } app_shutdown_step :: (step: App_Shutdown_Step) { atomic_write(*app_shutdown.step_started_ns, to_nanoseconds(current_time_monotonic())); atomic_write(*app_shutdown.step, step); } // Main thread, while app_ui_shutdown runs: what it is waiting on, as a line or two for the overlay // (temp); "" before it has started a step. app_ui_shutdown_status :: () -> string { step := atomic_read(*app_shutdown.step); text: string; if step == { case .NONE; return ""; case .IMPORT; if importer.phase == .INDEXING text = "Stopping the import: it is indexing the library, and stops after the files it is on."; else if importer.running text = "Stopping the import: finishing the files it is on (a large video takes a while to copy)."; else text = "Stopping the import."; case .VIEWER; text = "Stopping the thumbnail, catalog and search workers"; if viewer.open_started text = tprint("%, and writing out % to play", text, viewer.open_name); text = tprint("%.", text); case .OCR; text = "Closing the OCR model."; if ocr_ui.thread_busy if ocr_ui.task.kind == { case .SCAN; text = "Stopping the OCR scan: finishing the text lines it is reading."; case .ERASE; text = "Stopping the erase: finishing the capture it is on."; case .READ; text = "Waiting for OCR to finish reading a capture."; case .CREATE; text = "Waiting for the OCR model to finish loading."; case .LABELS_CREATE; text = "Waiting for the label model to finish loading."; case .LABEL_SET; text = "Waiting for the label list to finish."; case; text = "Stopping OCR."; } case .LABELS; text = "Closing the label model."; case .STATS; text = "Closing Stats."; } waited := cast(float64) (to_nanoseconds(current_time_monotonic()) - atomic_read(*app_shutdown.step_started_ns)) / 1_000_000_000.0; if waited >= 2 text = tprint("%\n(this step: % s)", text, FormatFloat.{value = waited, trailing_width = 0, zero_removal = .NO}); return text; } app_main_window_ui :: () { push_imgui_window(APP_MAIN_WINDOW_LABEL); // Once shutdown has started, its thread is stopping the threads these frames hand work to and take // it from (the viewer's workers, the search group, the import, OCR), and freeing what they share: // running them now would race it. The exit overlay says what it is waiting on. if app_exit_shutdown_started() return; viewer_ui(); ocr_ui_frame(); labels_ui_frame(); stats_ui_frame(); import_frame(); }