// 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 :: () { // View mode (app/open_file.jai): the file alone. The library is not listed, and no model, scan, // watcher or recording starts. if app.view_mode { import_startup(); open_file_startup(); return; } viewer_startup(); import_startup(); ocr_ui_startup(); labels_ui_startup(); segment_ui_startup(); // the library folders' options: after the viewer, which knows the library screen_record_startup(); // recordings a crash left behind, to offer open_file_startup(); // mcap.exe FILE: after the viewer, which knows whether it is in the library } // Every frame, before input is handed to ImGui. Poll background work here. app_ui_frame_start :: () { if app.view_mode return; screen_capture_frame(); // also while hidden in the tray, where the frame ends before the UI ocr_ui_unload_idle_models(); // models unused for a while, also while in the tray } // 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 :: () { if app.view_mode { app_shutdown_step(.IMPORT); import_shutdown(); open_file_shutdown(); return; } gpu_bench_ui_shutdown(); // ends a benchmark run still going app_shutdown_step(.CAPTURE); screen_capture_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(.SEGMENT); segment_ui_shutdown(); // after the OCR thread, which runs its scans app_shutdown_step(.LABELS); labels_ui_shutdown(); // after the OCR thread and the background workers have stopped app_shutdown_step(.STATS); stats_ui_shutdown(); open_file_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; CAPTURE; IMPORT; VIEWER; OCR; SEGMENT; 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 .CAPTURE; text = "Saving a screen capture."; 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 .SEGMENT; text = "Stopping the segmentation scan: finishing the images it is segmenting."; case .UNLOAD; text = "Waiting for the models unused for a while to unload."; case; text = "Stopping OCR."; } case .SEGMENT; text = "Closing the segmentation model."; 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 :: () { if app.view_mode { if !app_exit_shutdown_started() open_file_view_mode_ui(); // the whole window (app/open_file.jai) return; } 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(); segment_ui_frame(); stats_ui_frame(); gpu_bench_ui_frame(); // app/gpu_bench_ui.jai import_frame(); open_file_frame(); // after import_frame, which hands the grid what an import of it wrote screen_record_leftovers_popup(); }