mcap/src/app/app_ui.jai
Musa Mahmood 3e6e733a9d [0.0.13] Segmentation phase 4: per-folder opt-in, the segmentation scan, -segment
- .library-folders in the library (src/app/library_folders.jai): each library folder's options,
  starting with segment: true. Every folder starts off; unknown keys are kept when it is rewritten.
- The Segmentation window (Segments, above the grid): each folder's Segment checkbox, how many of
  its images are segmented, and Erase per folder; a button to segment the folders set to segment.
- The segmentation scan (src/app/segment_ui.jai) runs on the OCR thread as its own task, with the
  OCR scan's progress, pause and cancel: images only (never videos), not yet segmented, in folders
  set to segment. The engine loads with the first scan (2.4 GB of GPU memory). Each image's segs and
  mask are stored in one rewrite and its catalog entry loaded again.
- Importing into a folder set to segment segments the new images; the Import window's checkbox is
  the folder's setting.
- -segment segments and exits; -erase segments, and -erase all now includes segments.
- The catalog reads each capture's segs (class list hash, segment count). The exit overlay names the
  segmentation scan and model. segment_mcap_chunks moved into modules/Segment, shared with
  segment-probe. The quoting helpers of Capture_File's text formats are public.
- Tests: .library-folders read and written back (quoted names, unknown keys, versions).
2026-10-10 04:13:03 +01:00

99 lines
4.4 KiB
Plaintext

// 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();
segment_ui_startup(); // the library folders' options: after the viewer, which knows the library
}
// 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(.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();
}
// 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; 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 .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; 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 :: () {
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();
import_frame();
}