app_debug_panel_ui :: () { if !app.show_debug_panel then return; using ImGui; SetNextWindowSize(imgui_scale(Vector2.{500, 400}), .FirstUseEver); push_imgui_window("Debug Panel", p_open=*app.show_debug_panel); style := GetStyle(); io := GetIO(); Spacing(); SeparatorText("Machine"); Text("Machine name: %", app.machine_name); SameLine(); if SmallButton("Copy##machine_name") { clipboard_set_text(app.machine_name); toast_show(.Success, "Copied %", app.machine_name); } #if USING_WINDOWS { Text("Running as administrator: %", app.is_admin); Text("Large pages: %", ifx app.large_pages_allowed then "available" else "unavailable"); } Spacing(); frame_time := 1000.0 / io.Framerate; SeparatorText("Performance Metrics"); Text("% fps | % ms", FF(io.Framerate, 1), FF(frame_time, 3)); Text("Time to first frame = % ms", FF(perf.time_to_first_frame_ms, 1)); Checkbox("Frame Times window", *app.show_frame_time_graph); ButtonTooltip(to_string(toggle_frame_times_hotkey)); Checkbox("Command Line window", *app.show_help_panel); ButtonTooltip("The command-line options; mcap --help opens it"); Spacing(); SeparatorText("Memory Usage"); Text("Working set: % (peak: %)", format_bytes(perf.memory_usage.working_set), format_bytes(perf.memory_usage.peak_working_set)); Text("Virtual memory: % (peak: %)", format_bytes(perf.memory_usage.virtual_memory), format_bytes(perf.memory_usage.peak_virtual_memory)); #if DEBUG { if Button("Clear Unused Pages") { os_clear_unused_pages(); } } Spacing(); SeparatorText("Fonts"); ShowFontSelector("Font Selector"); if DragFloat("FontSizeBase", *style.FontSizeBase, 0.2, 8, 64, "%.0f", .AlwaysClamp) { style._NextFrameFontSizeBase = style.FontSizeBase; // #hack as of ImGui v1.92.7 } if IsItemDeactivatedAfterEdit() app_save_user_settings_file_and_toast("font size"); // This is how we adjust the font size in user-mode! DragFloat("FontScaleMain", *style.FontScaleMain, DEFAULT_FONT_SCALE_INCREMENT, FONT_SCALE_MIN, FONT_SCALE_MAX, flags = .AlwaysClamp); if IsItemDeactivatedAfterEdit() app_save_user_settings_file_and_toast("font size"); Text("FontScaleDpi: %", style.FontScaleDpi); // should adjust automatically. Text("Graphics Backend: %", perf.backend); if perf.adapter_name.count > 0 then Text("Adapter: % (%)", perf.adapter_name, ifx perf.renderer_is_software then "software" else "hardware"); #if USING_WINDOWS { Text("Startup device (%): % ms", ifx perf.started_on_warp then "WARP" else "hardware", FF(perf.startup_device_ms, 1)); Text("Startup swap chain: % ms", FF(perf.startup_swap_chain_ms, 1)); if perf.started_on_warp { if perf.hw_device_ready_ms > 0 { Text("Hardware device: created in % ms", FF(perf.hw_device_create_ms, 1)); Text("Hardware device: ready % ms after launch", FF(perf.hw_device_ready_ms, 1)); Text("Renderer swap: % ms", FF(perf.hw_swap_ms, 1)); } else { Text("Hardware device: pending"); } } } Spacing(); SeparatorText("Theme"); current := app_theme_current(); Text("Current theme: %1%2", ifx current then current.name else "(none)", ifx app_theme.dirty then " *" else ""); if Button("Open Theme Editor") app.show_style_editor = true; ButtonTooltip(to_string(toggle_style_editor_hotkey)); Spacing(); SeparatorText("Settings"); settings_path := app_user_settings_path(); if Button("Open settings.config") { if !file_exists(settings_path) app_save_user_settings_file(); app_open_config_file(settings_path); } ButtonTooltip(settings_path); TextColored(app_theme.palette.dim, "Applied when saved; the window size at the next start."); if Button("Reset Layout") imgui_reset_layout_requested = true; ButtonTooltip(tprint("Overwrites % with the default layout built into the app. (%)", to_string(io.IniFilename), to_string(reset_layout_hotkey))); #if USING_SDL { sdlv := SDL_GetVersion(); sdlr := SDL_GetRevision(); Text("SDL version: %, revision: %\n", sdlv, to_string(sdlr)); } Spacing(); } app_frame_times_ui :: () { if !app.show_frame_time_graph return; ImGui.SetNextWindowSize(imgui_scale(Vector2.{420, 160}), .FirstUseEver); push_imgui_window("Frame Times", .NoScrollbar | .NoScrollWithMouse, p_open=*app.show_frame_time_graph); frame_time_graph(); } // The command-line options, opened by mcap -h or --help, or from the Debug Panel. Keep in step with // the Command line table in README.md. app_help_panel_ui :: () { if !app.show_help_panel return; using ImGui; SetNextWindowSize(imgui_scale(Vector2.{760, 560}), .FirstUseEver); if app.help_panel_focus { SetNextWindowFocus(); app.help_panel_focus = false; } push_imgui_window("Command Line", p_open=*app.show_help_panel); Text("% % [FILE] [options]", APP_NAME, VERSION); SameLine(); if SmallButton("Copy") { builder: String_Builder; builder.allocator = temp; for APP_COMMAND_LINE_OPTIONS print_to_builder(*builder, "%\t%\n", it.option, it.text); clipboard_set_text(builder_to_string(*builder,, temp)); toast_show(.Success, "Copied the command-line options"); } TextColored(app_theme.palette.dim, "-import, -scan, -segment, -stats and -erase still open the window, to show their progress. README.md has the details."); Spacing(); if BeginTable("options", 2, .Borders | .RowBg | .SizingStretchProp | .ScrollY) { TableSetupColumn("Option", .WidthFixed); TableSetupColumn("What it does", .WidthStretch); TableSetupScrollFreeze(0, 1); TableHeadersRow(); for APP_COMMAND_LINE_OPTIONS { TableNextRow(); TableNextColumn(); TextUnformatted(it.option); TableNextColumn(); TextWrapped("%", it.text); } EndTable(); } } Command_Line_Option :: struct { option: string; text: string; } APP_COMMAND_LINE_OPTIONS :: Command_Line_Option.[ .{"FILE (first)", "Opens FILE: an .mcap, image or video. With mcap already running, it opens in view mode beside it."}, .{"-open-import DEST", "With FILE: imports it into the library folder DEST as its Import button does, then exits: 0 once the library has it (imported, or there already), else 1."}, .{"-library FOLDER", "Shows the .mcap files in FOLDER (and its folders) instead of D:\\McapIndex\\mcap."}, .{"-import SOURCE DEST", "Imports SOURCE, a folder or a single file, into the library folder DEST, as the Import window does, then exits: 0, or 1 if any file failed."}, .{"-search QUERY", "Starts with QUERY in the search box."}, .{"-scan", "Scans the library (the chosen OCR model, and labels when the label model is up) once it has loaded, then exits: 0, or 1 if any capture failed."}, .{"-segment", "Segments the images not yet segmented in the library folders set to segment, then exits: 0, or 1 if any image failed."}, .{"-stats", "Logs the Stats window's numbers once the catalog has loaded, then exits; with -search, for the matches."}, .{"-erase WHAT", "Erases ocr, labels, segments or all from every file in the library, then exits (for testing)."}, .{"-record SECONDS", "Records the screen under the cursor that long (no overlay), puts it in the library, then exits: 0, or 1 if it failed."}, .{"-record-region X Y W H", "With -record: records that region instead."}, .{"-record-quit", "With -record: quits while still recording, to check the recording is kept."}, .{"-record-ui-visible", "With -record: leaves the border and toolbar in screenshots, to look at them."}, .{"-cold", "Loads the library as on a cold start (its files dropped from the system's file cache first). With -stats, a startup benchmark."}, .{"-no-catalog-cache", "Reads every capture from its file, as without the catalog cache (for measuring); the cache is still written."}, .{"-catalog-threads N", "Loads the catalog on N threads instead of all the background group's (for measuring)."}, .{"-allow-multiple-instances", "Runs beside another copy instead of bringing that one forward."}, .{"-headless", "No message boxes: a failure is logged and the app exits with 1."}, .{"run_tests", "Runs the test suites (debug builds); with -headless, exits with 0 if all passed."}, .{"-version", "Prints the version, then exits."}, .{"-h, --help", "Opens this window."}, ]; frame_time_graph :: () { using ImGui; frames := perf.frame_times; newest := count(frames); first := newest; covered: float = 0; while first > 0 && covered < 1.0 { first -= 1; covered += frames[first].delta; } max_ms := FRAME_BUDGET_MS; for first..newest-1 max_ms = max(max_ms, frames[it].work * 1000); top_label := tprint("%", FF(max_ms, 1)); gutter := imgui_text_size(top_label).x + GetStyle().ItemInnerSpacing.x; origin := GetCursorScreenPos(); width := GetContentRegionAvail().x; height := max(GetContentRegionAvail().y, imgui_scale(FRAME_TIME_GRAPH_MIN_HEIGHT)); Dummy(.{width, height}); plot_min := Vector2.{origin.x + gutter, origin.y}; plot_max := Vector2.{origin.x + width, origin.y + height}; plot_w := plot_max.x - plot_min.x; if plot_w <= 0 return; draw := GetWindowDrawList(); draw.AddRectFilled(draw, plot_min, plot_max, GetColorU32(.FrameBg)); bars := newest - first; if bars > 0 { bar_w := plot_w / cast(float) bars; gap: float = xx ifx bar_w >= 3 then 1 else 0; for first..newest-1 { ms := frames[it].work * 1000; x0 := plot_min.x + cast(float) (it - first) * bar_w; y0 := plot_max.y - min(ms / max_ms, 1.0) * height; draw.AddRectFilled(draw, .{x0, y0}, .{x0 + bar_w - gap, plot_max.y}, GetColorU32(frame_time_color(ms))); } } font_size := GetFontSize(); frame_time_graph_y_label(draw, plot_min.x, plot_min.y, top_label); frame_time_graph_y_label(draw, plot_min.x, plot_max.y - font_size, "0"); if max_ms > FRAME_BUDGET_MS { budget_y := plot_max.y - (FRAME_BUDGET_MS / max_ms) * height; draw.AddLine(draw, .{plot_min.x, budget_y}, .{plot_max.x, budget_y}, GetColorU32(.TextDisabled)); label_y := budget_y - font_size * 0.5; if label_y > plot_min.y + font_size && label_y < plot_max.y - 2 * font_size { frame_time_graph_y_label(draw, plot_min.x, label_y, tprint("%", FF(FRAME_BUDGET_MS, 1))); } } if newest > 0 frame_time_graph_live_value(draw, plot_min, plot_max, frames[newest - 1].work * 1000); } frame_time_graph_live_value :: (draw: *ImGui.ImDrawList, plot_min: Vector2, plot_max: Vector2, ms: float) { using ImGui; push_imgui_font(header_font, scale=FRAME_TIME_LIVE_VALUE_SCALE); text := tprint("% ms", FF(ms, 2)); size := imgui_text_size(text); pad := GetStyle().FramePadding; pos := Vector2.{plot_max.x - pad.x - size.x, plot_min.y + pad.y}; draw.AddText(draw, pos, GetColorU32(.Text, alpha_mul=FRAME_TIME_LIVE_VALUE_ALPHA), text); } // Right-aligned against the plot's left edge. frame_time_graph_y_label :: (draw: *ImGui.ImDrawList, plot_left: float, y: float, text: string) { x := plot_left - ImGui.GetStyle().ItemInnerSpacing.x - imgui_text_size(text).x; draw.AddText(draw, .{x, y}, ImGui.GetColorU32(.Text), text); } // At or under FRAME_TIME_GREEN_MS is fully green, blending to orange at the budget; over it is red. frame_time_color :: (ms: float) -> Vector4 { if ms > FRAME_BUDGET_MS return app_theme.palette.error; t := clamp((ms - FRAME_TIME_GREEN_MS) / (FRAME_BUDGET_MS - FRAME_TIME_GREEN_MS), 0.0, 1.0); return lerp(app_theme.palette.success, app_theme.palette.warning, t); } FRAME_BUDGET_MS : float : 1000.0 / 60.0; FRAME_TIME_GREEN_MS : float : 1.0; FRAME_TIME_GRAPH_MIN_HEIGHT : float : 40.0; FRAME_TIME_LIVE_VALUE_SCALE : float : 2.0; // of the base font size, in the bold header font FRAME_TIME_LIVE_VALUE_ALPHA : float : 0.6;