Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
inspector_overlay.hpp
Go to the documentation of this file.
1// inspector_overlay.hpp — Visual inspector overlay for Pulp view trees
2// Renders highlight, property panel, and frame stats on top of the plugin UI.
3// Toggled via Cmd+I (macOS) / Ctrl+I (Windows/Linux).
4#pragma once
5
6#include <pulp/view/view.hpp>
14
15#include <choc/containers/choc_Value.h>
16
17#include <array>
18#include <cstddef>
19#include <cstdint>
20#include <functional>
21#include <optional>
22#include <string>
23#include <string_view>
24#include <unordered_map>
25#include <unordered_set>
26#include <vector>
27
28namespace pulp::render { class RenderPassManager; }
29namespace pulp::render { class AtlasInventory; }
30
31namespace pulp::inspect {
32
33using namespace pulp::view;
34using namespace pulp::canvas;
35
37public:
38 explicit InspectorOverlay(View& root);
40
41 // The root View this overlay inspects and paints into. Used by the
42 // installed paint hook to gate: the overlay's selection box /
43 // handles / drop indicators paint only when this exact root is the one being
44 // painted, so they never leak into the floating inspector window's surface.
45 View& inspected_root() const { return root_; }
46
47 // ── Toggle ──────────────────────────────────────────────────────
48 bool is_active() const { return active_; }
49 void set_active(bool active);
50 void toggle() { set_active(!active_); }
51
52 // ── Tool palette ─────────────────────────────────────────────────
53 //
54 // The active tool gates what a canvas click DOES, making the
55 // select-vs-manipulate-vs-edit modality explicit (Figma convention):
56 // - Select (V): the existing overlay behavior — click selects,
57 // body-drag moves (reflow), handle-drag resizes, Shift scales.
58 // UNCHANGED.
59 // - Text (T): click a text-bearing element (Label / TextEditor) to
60 // edit its copy in place. Enter commits (live View text + a `text`
61 // content tweak, one undoable EditHistory unit), Esc cancels.
62 // Default Select so every existing host + test is unaffected until the
63 // user opts into the Text tool. V/T are bound in handle_key_event(),
64 // gated to active_ && editing_field_.empty() && text edit not in
65 // progress so they never fire mid-edit.
66 enum class Tool : std::uint8_t { select, text };
67 Tool tool() const { return tool_; }
69 void toggle_tool() {
71 }
72
73 // ── Input interception ──────────────────────────────────────────
74 // Call from host BEFORE dispatching to view tree.
75 // Returns true if the inspector consumed the event.
76 bool handle_key_event(const KeyEvent& event);
77 bool handle_mouse_event(const MouseEvent& event);
78 // Text-tool inline editing consumes UTF-8 character input (separate
79 // from KeyEvent, which carries no character payload). The host routes
80 // TextInputEvents here BEFORE the view tree while the overlay is
81 // active; returns true when an inline text edit is in progress and
82 // the text was appended to the edit buffer. A no-op (returns false)
83 // otherwise, so non-text-tool input falls through to widgets.
85
86 // ── Painting ────────────────────────────────────────────────────
87 // Call AFTER View::paint_overlays() to paint on top of everything.
89
90 // ── Data sources ────────────────────────────────────────────────
92
101 atlas_inventory_ = inv;
102 }
104 return atlas_inventory_;
105 }
106
114 void set_tweak_store(TweakStore* store) { tweak_store_ = store; }
115 TweakStore* tweak_store() const { return tweak_store_; }
116
130 void set_edit_history(pulp::state::EditHistory* h) { edit_history_ = h; }
131 pulp::state::EditHistory* edit_history() const { return edit_history_; }
132
158 std::string child_anchor;
159 std::string new_parent_anchor;
160 std::string insert_after_anchor; // preceding sibling, or "" = first child
161 };
162 void set_reparent_source_sink(std::function<void(const ReparentSourceEdit&)> sink) {
163 reparent_source_sink_ = std::move(sink);
164 }
166 return static_cast<bool>(reparent_source_sink_);
167 }
168
180 void set_knob_schema(pulp::design::KnobSchema schema) { knob_schema_ = std::move(schema); }
181 const pulp::design::KnobSchema& knob_schema() const { return knob_schema_; }
182
184 std::string knob_id;
185 std::string value;
186 };
187 void set_knob_apply_sink(std::function<void(const KnobApplyEdit&)> sink) {
188 knob_apply_sink_ = std::move(sink);
189 }
190 bool has_knob_apply_sink() const { return static_cast<bool>(knob_apply_sink_); }
191
194 std::string knob_selection(std::string_view knob_id) const;
195
204 std::string text;
205 };
206 void set_agent_request_sink(std::function<void(const AgentRequestEdit&)> sink) {
207 agent_request_sink_ = std::move(sink);
208 }
209 bool has_agent_request_sink() const { return static_cast<bool>(agent_request_sink_); }
210
215 struct KnobHit {
216 std::string knob_id;
217 std::string value;
219 };
220 const std::vector<KnobHit>& knob_hits() const { return knob_hits_; }
221 Rect agent_field_bounds() const { return agent_field_bounds_; }
222 Rect agent_send_bounds() const { return agent_send_bounds_; }
223 const std::string& agent_text_buffer() const { return agent_text_buffer_; }
224 bool agent_field_active() const { return agent_field_active_; }
225
244 if (edit_history_) edit_history_->clear();
245 }
246
251 bool emit_tweak_for_selection(std::string_view property_path,
252 choc::value::Value value,
253 std::string_view source = "inspector-gesture");
254
255 // ── Drag handles ─────────────────────────────────────────────────
263 void set_dragging_enabled(bool enabled) { dragging_enabled_ = enabled; }
264 bool dragging_enabled() const { return dragging_enabled_; }
265 void toggle_dragging() { dragging_enabled_ = !dragging_enabled_; }
266
267 // ── Minimal in-canvas manipulate layer ───────────────────────────
268 //
269 // The full inspector HUD (tree / props / stats side panel) belongs
270 // in the FLOATING InspectorWindow. When the in-canvas overlay is used
271 // purely for direct manipulation (move / resize handles), it should
272 // paint ONLY the selection box + handles, NOT its dev side-panel —
273 // otherwise the panel duplicates the floating window and clutters the
274 // canvas. `manipulate_only_` flips paint() into that bare mode and
275 // makes point_in_panel() always return false so the whole canvas is
276 // live for selection + drag. Default OFF so the standalone dev HUD is
277 // unchanged. When this mode is on, dragging is implicitly enabled so
278 // handles paint without a separate D-key toggle.
280 manipulate_only_ = on;
281 if (on) dragging_enabled_ = true;
282 }
283 bool manipulate_only() const { return manipulate_only_; }
284
288 void set_selected_view(View* v) { selected_ = v; }
289
290 // ── Drill-down / nested selection ────────────────────────────────
291 //
292 // Selection resolves to the DEEPEST hittable element at the click
293 // point (View::hit_test already returns deepest). Esc-to-ascend
294 // walks the selection up to its parent so a user can reach a
295 // container after landing on a nested child. select_parent() is a
296 // no-op (returns false) at the root or with no selection.
298
299 // ── Cursor affordances over the selected element ────────────────
300 //
301 // While dragging mode is on and a view is selected, hovering the
302 // selection should reveal whether a press will MOVE vs RESIZE before the
303 // user commits: a resize cursor (NW/NE/SW/SE diagonal) over a corner
304 // handle, a move cursor (4-way) over the element body, and the default
305 // cursor anywhere else. `cursor_affordance_at` is the pure hit-test that
306 // resolves a root-coord point to the right CursorStyle; the host wires it
307 // through View::set_inspector_cursor_hook so the platform layer applies
308 // the NSCursor continuously on hover. Returns `default_` when there is no
309 // selection, dragging is off, or the point is outside the selection (so
310 // the host falls back to the normal hit-view cursor). Visible for tests.
319
328 return move_active_ && !move_float_ && drop_target_ != nullptr &&
329 (drop_indicator_.width > 0 || drop_indicator_.height > 0);
330 }
331
338 int drop_index() const { return drop_index_; }
339 bool drop_indicator_is_line() const { return drop_indicator_is_line_; }
340 Rect drop_indicator_rect() const { return drop_indicator_; }
341 const View* drop_target() const { return drop_target_; }
344 int cursor_style_for(Point pos) const;
345
346 // ── Per-pass GPU/render attribution viewer ───────────────────────
347 //
348 // The RenderPassManager already populates per-pass
349 // `PassStats { type, draw_calls, time_ms }` every frame, but only
350 // keeps the *last* frame. The attribution viewer accumulates a
351 // rolling history (kPassHistoryFrames) per pass type so the panel
352 // can show frame-over-frame trend + smoothed averages + a
353 // budget-overrun event count.
354 //
355 // IMPORTANT (honesty about what's measured): `PassStats::time_ms`
356 // is CPU wall-time around the pass's draw calls — NOT true GPU
357 // time. The viewer labels its timings "cpu" so nobody mistakes them
358 // for GPU-side numbers.
359
363 int type = 0;
364 const char* name = "";
365 bool present = false;
366 float last_cpu_ms = 0.0f;
367 float avg_cpu_ms = 0.0f;
368 float peak_cpu_ms = 0.0f;
371 std::size_t samples = 0;
372 };
373
376 static constexpr std::size_t kPassHistoryFrames = 60;
377
385
390 std::vector<PassAttribution> pass_attribution() const;
391
395 int budget_overrun_count() const { return budget_overrun_count_; }
396
400 std::uint64_t pass_frames_captured() const { return pass_frames_captured_; }
401
406 void set_pass_viewer_enabled(bool enabled) { pass_viewer_enabled_ = enabled; }
407 bool pass_viewer_enabled() const { return pass_viewer_enabled_; }
408 void toggle_pass_viewer() { pass_viewer_enabled_ = !pass_viewer_enabled_; }
409
410 // ── Color eyedropper ─────────────────────────────────────────────
427 void set_eyedropper_active(bool active);
428 bool eyedropper_active() const { return eyedropper_active_; }
429 void toggle_eyedropper() { set_eyedropper_active(!eyedropper_active_); }
430
435 Color eyedropper_sample() const { return eyedropper_sample_; }
436
439 bool eyedropper_has_sample() const { return eyedropper_has_sample_; }
440
445 const std::string& eyedropper_target() const { return eyedropper_target_; }
446 void set_eyedropper_target(std::string path) {
447 if (!path.empty()) eyedropper_target_ = std::move(path);
448 }
449
454 bool sample_color_at(Point pos, Canvas* canvas, Color& out) const;
455
464
465 // ── Selection ───────────────────────────────────────────────────
466 View* selected_view() const { return selected_; }
467 View* hovered_view() const { return hovered_; }
468
469 // ── Selection-mode toggle ────────────────────────────────────────
478 enum class SelectionMode : std::uint8_t { follows_focus, follows_mouse };
479
483 SelectionMode selection_mode() const { return selection_mode_; }
484 void set_selection_mode(SelectionMode mode) { selection_mode_ = mode; }
489 selection_mode_ = (selection_mode_ == SelectionMode::follows_focus)
492 }
493
494 // ── Source-jump ──────────────────────────────────────────────────
499 void set_config(InspectorConfig config) { config_ = std::move(config); }
500 const InspectorConfig& config() const { return config_; }
501
510
511 // ── Live-editable box-model fields ──────────────────────────────
512 //
513 // Public read-only accessors for the editing state — used by tests
514 // and by host-side cursor hints. The setter side is internal: edit
515 // mode is entered by clicking on a numeric value in the property
516 // panel and exited via Enter / Esc / Tab / blur.
517 //
518 // `editing_field()` returns the dotted property path of the field
519 // currently being edited (e.g. "layout.padding"), or an empty
520 // string when not editing. `edit_buffer()` is the live text the
521 // user has typed so far (digits + optional sign / decimal); empty
522 // until at least one character is entered. `edit_caret_pos()` is
523 // the 0-based caret index into `edit_buffer()`.
524 const std::string& editing_field() const { return editing_field_; }
525 const std::string& edit_buffer() const { return edit_buffer_; }
526 std::size_t edit_caret_pos() const { return edit_caret_pos_; }
527 bool is_editing() const { return !editing_field_.empty(); }
528
535 bool begin_field_edit(std::string field_path, float initial_value);
536
543
548
549 // ── Inline text editing (Text tool) ──────────────────────────────
550 //
551 // Distinct from the numeric field edit above: this edits a
552 // text-bearing View's COPY (Label / TextEditor) in place. The Text
553 // tool (set_tool(Tool::text)) routes a canvas click on a text element
554 // here; typing live-updates the View's text for an immediate preview,
555 // Enter commits, Esc cancels. On commit the live text stays AND a
556 // `text` content tweak is emitted via TweakStore as ONE undoable
557 // EditHistory unit (Cmd+Z restores the prior text).
558 //
559 // `text_editing()` is true while an inline text edit is in progress;
560 // `text_edit_buffer()` is the live edited copy; `text_edit_target()`
561 // is the View being edited (or null). These are visible for tests +
562 // host cursor hints.
563 bool text_editing() const { return text_edit_target_ != nullptr; }
564 const std::string& text_edit_buffer() const { return text_edit_buffer_; }
565 View* text_edit_target() const { return text_edit_target_; }
566
567 // Selection chrome predicate. While an inline text edit is active the
568 // orange resize box + corner/edge handles would obstruct the
569 // text being edited (and resize is meaningless mid-edit), so the selection
570 // is drawn as a SUBTLE thin blue outline with NO handles. The Select tool
571 // (move/resize) keeps the orange box + handles. This predicate reports
572 // which chrome paint_highlight() draws for the selected element, so a
573 // headless test can assert "Text-edit mode → no handles / subtle" vs
574 // "Select mode → orange box + handles" without inspecting pixels.
576 // Handles only ever paint in drag-handles mode AND never while a text
577 // edit is active (the subtle-outline path owns the visuals then).
578 return dragging_enabled_ && !text_editing();
579 }
580
584 return text_editing() && selected_ != nullptr &&
585 selected_ == text_edit_target_;
586 }
587
590 static bool view_has_editable_text(const View* v);
593 static std::string editable_text_of(const View* v);
596 static void set_editable_text_of(View* v, const std::string& text);
597
604
612
615
616 // ── In-place text-edit caret + selection ─────────────────────────
617 //
618 // The inline Text-tool edit keeps the LIVE View text (real-UI look —
619 // no TextEditor widget chrome is swapped in). This layers TextEditor's
620 // text-manipulation LOGIC on top: a caret index into the buffer, a
621 // selection range, arrow / word / line caret movement, shift-select,
622 // and Cmd+A/C/V/X clipboard. The caret + selection are painted as a
623 // light overlay on the in-place text (see paint()), NOT as a styled
624 // input box. Esc=cancel and Enter/click-away=commit are unchanged.
625
627 std::size_t text_caret() const { return text_caret_; }
629 std::pair<std::size_t, std::size_t> text_selection() const;
630 bool text_has_selection() const { return text_caret_ != text_sel_anchor_; }
631
635 void text_move_caret(int delta, bool extend);
639 void text_move_word(int direction, bool extend);
642 void text_move_home(bool extend);
643 void text_move_end(bool extend);
650 bool text_copy();
651 bool text_cut();
655 void text_insert(const std::string& utf8);
660
664 const std::string& text_tweak_path() const { return text_tweak_path_; }
665 void set_text_tweak_path(std::string path) {
666 if (!path.empty()) text_tweak_path_ = std::move(path);
667 }
668
669 // ── Tweak management panel ───────────────────────────────────────
670 //
671 // A scrollable panel listing every tweak in the attached
672 // TweakStore, grouped by anchor. Each tweak row carries three
673 // per-tweak controls: bypass (eye), lock, and delete. Toggled with
674 // `Shift+T` while the inspector is active (the bare `T` key now
675 // selects the Figma-style Text tool). Default OFF so the inspector
676 // layout is unchanged until the user opts in.
677 void set_tweaks_panel_visible(bool visible) { tweaks_panel_visible_ = visible; }
678 bool tweaks_panel_visible() const { return tweaks_panel_visible_; }
679 void toggle_tweaks_panel() { tweaks_panel_visible_ = !tweaks_panel_visible_; }
680
685 std::size_t tweak_row_count() const { return tweak_rows_.size(); }
686
687 // ── Drift drawer ─────────────────────────────────────────────────
688 //
689 // A collapsible panel that lists tweaks whose anchor_id no longer
690 // resolves to any live view (orphaned) or whose property path no
691 // longer maps (drifted). It surfaces the silent-failure case where
692 // a design re-import changed an element so a stored tweak quietly
693 // stops applying. The drawer auto-expands the first time drift is
694 // detected after a re-import; the user can collapse it with the
695 // header chevron.
696
703
705 std::size_t drift_count() const { return drifted_.size(); }
706
708 bool drift_drawer_open() const { return drift_drawer_open_; }
709 void set_drift_drawer_open(bool open) { drift_drawer_open_ = open; }
710 void toggle_drift_drawer() { drift_drawer_open_ = !drift_drawer_open_; }
711
714 const std::vector<TweakStore::DriftedTweak>& drifted() const {
715 return drifted_;
716 }
717
718 // ── Reconciliation tab ───────────────────────────────────────────
719 //
720 // The reconciliation tab answers one question per tweak: "will this
721 // edit survive a re-import?" It builds on lock-to-source and
722 // source-jump data — it is a *read-only* report over the
723 // existing TweakStore + live view tree, inventing no parallel data
724 // model. Every stored tweak is classified into exactly one of three
725 // reconciliation states:
726 //
727 // * locked-to-source — the tweak's anchor is in the TweakStore
728 // lock set AND still resolves to a live view. It has been (or
729 // will be) promoted into the authored source, so it survives a
730 // fresh re-import. This is the "reconciled" state.
731 // * drifted — the tweak's anchor resolves to a live view but the
732 // anchor is NOT locked. The edit lives only in the runtime
733 // tweak layer (`pulp-tweaks.json`); a re-import that regenerates
734 // the element would not carry it. "Live-only, not written back."
735 // * unresolvable — no live view carries the tweak's anchor_id
736 // (orphaned, per TweakStore::DriftReason::anchor_not_found). The
737 // inspector cannot tell where this edit belongs; locking it is
738 // impossible until the anchor is re-established. Conservative
739 // fallback — never crash, never guess.
740 //
741 // The tab toggles with the `R` key (reconcile) and, when on, takes
742 // over the property-panel region exactly like the pass attribution
743 // viewer does — the tree section above stays put.
744
751
753 static const char* reconcile_status_str(ReconcileStatus status);
754
761
766 std::vector<ReconcileRow> rows;
767 std::size_t locked_count = 0;
768 std::size_t drifted_count = 0;
769 std::size_t unresolvable_count = 0;
770
771 std::size_t total() const { return rows.size(); }
772 };
773
781
787 reconcile_tab_visible_ = visible;
788 }
789 bool reconcile_tab_visible() const { return reconcile_tab_visible_; }
791 reconcile_tab_visible_ = !reconcile_tab_visible_;
792 }
793
798 std::size_t reconcile_row_count() const {
799 return reconcile_rows_.size();
800 }
801
802 // ── Texture atlas viewer ─────────────────────────────────────────
803 //
804 // The render layer packs small bitmaps into a handful
805 // of shelf-packed GPU texture atlases — the glyph atlas (SDF text),
806 // the image atlas, the gradient ramp atlas, the path atlas. When an
807 // atlas fills, rendering thrashes (evict + re-pack churn). The
808 // atlas viewer answers "is my SDF atlas thrashing?" without leaving
809 // the inspector.
810 //
811 // It is a *read-only* report over a render::AtlasInventory — a
812 // value-snapshot collection the host wires in via
813 // set_atlas_inventory(). The tab invents no parallel data model:
814 // every per-atlas figure (dimensions, page count, occupancy, entry
815 // count) comes straight from the Pulp-owned atlas classes'
816 // introspection accessors. When no inventory is attached the tab
817 // renders a graceful "GPU atlas unavailable" empty state.
818 //
819 // The tab toggles with the `A` key (atlas) and, when on, takes over
820 // the property-panel region exactly like the pass attribution viewer
821 // and reconciliation tab — the tree section above stays
822 // put so the user keeps navigation context.
823
829 atlas_viewer_visible_ = visible;
830 }
831 bool atlas_viewer_visible() const { return atlas_viewer_visible_; }
833 atlas_viewer_visible_ = !atlas_viewer_visible_;
834 }
835
840 std::size_t atlas_row_count() const { return atlas_row_count_; }
841
842 // ── 20× zoom loupe ───────────────────────────────────────────────
843 //
844 // A magnified-pixel preview panel ("loupe") that shows the region
845 // under the cursor blown up by `zoom_factor_`. It complements the
846 // eyedropper: where the eyedropper grabs a single pixel, the loupe
847 // shows a grid of surrounding pixels with a center
848 // crosshair so the user can align edges + verify color boundaries.
849 //
850 // Toggled with the `Z` key (drag=D, eyedropper=E, text=T, and
851 // tweak-panel=Shift+T are already taken). When active, mouse-move re-centers the sampled
852 // region on the cursor; paint() draws paint_zoom_panel() last so
853 // the loupe sits on top of everything, including the props panel.
854 //
855 // Pixel source mirrors the eyedropper's dual path:
856 // - Skia raster surface → Canvas::read_pixels() gives true RGBA
857 // - non-Skia / headless → graceful degradation: the loupe still
858 // paints its frame + readout, filling the grid with the
859 // resolved background color of the View under the cursor (or a
860 // neutral checkerboard when that View has no background).
861 bool zoom_active() const { return zoom_active_; }
862 void set_zoom_active(bool active);
863 void toggle_zoom() { set_zoom_active(!zoom_active_); }
864
868 int zoom_factor() const { return zoom_factor_; }
869 void set_zoom_factor(int factor);
870
873 Point zoom_sample_center() const { return zoom_sample_center_; }
874
878 Color zoom_center_color() const { return zoom_center_color_; }
879
880private:
881 View& root_;
882 bool active_ = false;
883
884 View* selected_ = nullptr;
885 View* hovered_ = nullptr;
886
887 // Distance measurement mode
888 View* distance_anchor_ = nullptr;
889
890 // Selection-mode toggle. follows_focus (default) keeps the
891 // selection pinned until an explicit click; follows_mouse re-selects
892 // the hovered View on every pointer-move. Toggled via the `M` hotkey.
894
895 // Alt-hover sibling distance (Figma-style spacing reveal).
896 // Tracks the View under the cursor while Alt is held; cleared as soon
897 // as Alt is released or the cursor leaves the view area. Distinct from
898 // distance_anchor_ (which is Alt+click sticky-anchor mode).
899 View* alt_hover_target_ = nullptr;
900
901 // Panel layout
902 float panel_width_ = 300.0f;
903 float tree_scroll_y_ = 0.0f;
904
905 // ── Editable-field state ─────────────────────────────────────────
906 //
907 // editing_field_ doubles as the "currently editing" flag and the
908 // dotted property path (e.g. "layout.padding"). Empty = not
909 // editing. edit_buffer_ holds the live numeric text the user has
910 // typed; edit_caret_pos_ is a 0-based index into it. The original
911 // value is captured at edit-begin time so Esc can revert without
912 // re-querying the View (which may have been mutated by Yoga
913 // reflow already).
914 std::string editing_field_;
915 std::string edit_buffer_;
916 std::size_t edit_caret_pos_ = 0;
917 float edit_original_value_ = 0.0f;
918 View* edit_target_view_ = nullptr;
919
920 // ── Tool palette + inline text edit state ────────────────────────
921 //
922 // tool_ is the active canvas tool (Select default, Text). The Text
923 // tool drives the inline-text-edit state below: text_edit_target_ is
924 // the View whose copy is being edited (null = not editing),
925 // text_edit_buffer_ is the live edited string, text_edit_original_ is
926 // the copy at edit start (for Esc-cancel + undo). text_tweak_path_ is
927 // the dotted path the committed copy is persisted under ("text").
928 Tool tool_ = Tool::select;
929 View* text_edit_target_ = nullptr;
930 std::string text_edit_buffer_;
931 std::string text_edit_original_;
932 std::string text_tweak_path_ = "text";
933
934 // Caret + selection over the live in-place edit buffer.
935 // text_caret_ is the active caret byte-offset; text_sel_anchor_ is the
936 // selection anchor (== caret when there is no selection). Both are kept
937 // on UTF-8 codepoint boundaries. text_blink_ticks_ is a free-running
938 // paint counter that drives the blink (no wall clock needed — works
939 // headless); the caret shows while (ticks / kBlinkPeriod) is even.
940 std::size_t text_caret_ = 0;
941 std::size_t text_sel_anchor_ = 0;
942 mutable std::uint32_t text_blink_ticks_ = 0;
943
944 // Paint helper for the caret + selection highlight as a light
945 // overlay on the live in-place edit (called from paint()).
946 void paint_text_edit_overlay(Canvas& canvas);
947
948 // Ordered list of editable fields populated during paint_props_section()
949 // — used (a) for Tab to walk forward, and (b) for click hit-testing.
950 struct EditableField {
951 std::string path;
952 Rect bounds;
953 float value;
954 };
955 std::vector<EditableField> editable_fields_;
956
957 // ── Tweak management panel state ─────────────────────────────────
958 //
959 // The panel is laid out during paint_tweaks_section(); the row
960 // hit-rects are stashed in tweak_rows_ so the SAME-frame mouse
961 // handler can resolve a click to a (anchor, path, action). Each
962 // row carries three 16×16 icon hit-rects: bypass / lock / delete.
963 bool tweaks_panel_visible_ = false;
964 float tweaks_scroll_y_ = 0.0f;
965 enum class TweakAction : std::uint8_t { none, bypass, lock, remove };
966 struct TweakRow {
967 std::string anchor_id;
968 std::string property_path;
969 Rect bypass_icon;
970 Rect lock_icon;
971 Rect delete_icon;
972 };
973 std::vector<TweakRow> tweak_rows_;
974
975 // ── Semantic knobs + send-to-agent (tweaks-panel top region) ──────
976 // The knob schema the panel renders; the current selection per knob id
977 // (display only — the host owns authoritative state); and this frame's
978 // knob hit-rects, stashed like tweak_rows_ so the SAME-frame mouse handler
979 // can resolve a click to a (knob_id, value). The agent text field is a
980 // single-line free-text buffer whose bounds + Send-button rect are captured
981 // each paint for hit-testing.
982 pulp::design::KnobSchema knob_schema_;
983 std::function<void(const KnobApplyEdit&)> knob_apply_sink_ = nullptr;
984 std::function<void(const AgentRequestEdit&)> agent_request_sink_ = nullptr;
985 std::unordered_map<std::string, std::string> knob_selection_;
986 std::vector<KnobHit> knob_hits_;
987 bool agent_field_active_ = false;
988 std::string agent_text_buffer_;
989 std::size_t agent_caret_ = 0;
990 Rect agent_field_bounds_{};
991 Rect agent_send_bounds_{};
992
993 // Tree expansion: collapsed nodes
994 std::unordered_set<const View*> collapsed_;
995
996 // Optional stats source
997 render::RenderPassManager* rpm_ = nullptr;
998
999 // ── Per-pass attribution history ─────────────────────────────────
1000 //
1001 // The RenderPassManager forgets everything but the current frame,
1002 // so the viewer keeps its own rolling buffers. There are exactly 5
1003 // RenderPassType values; each gets a fixed-size ring of CPU-time +
1004 // draw-call samples. Indexing the outer array by RenderPassType
1005 // (cast to size_t) keeps capture O(passes) with no allocation.
1006 static constexpr std::size_t kPassTypeCount = 5;
1007 struct PassRing {
1008 std::array<float, kPassHistoryFrames> cpu_ms{};
1009 std::array<int, kPassHistoryFrames> draw_calls{};
1010 std::size_t count = 0;
1011 std::size_t head = 0;
1012 // Whether this pass type appeared in the most recently captured
1013 // frame — distinguishes "no history" from "history but absent
1014 // this frame" so the panel can dim a pass that just went quiet.
1015 bool present_last_frame = false;
1016 };
1017 std::array<PassRing, kPassTypeCount> pass_rings_{};
1018 int budget_overrun_count_ = 0;
1019 std::uint64_t pass_frames_captured_ = 0;
1020 // Last frame_count() observed from the RPM — capture is a no-op if
1021 // the manager hasn't advanced a frame, so multiple paints of the
1022 // same frame don't inflate the history with duplicates.
1023 std::uint64_t last_captured_frame_ = 0;
1024 bool pass_viewer_enabled_ = false;
1025
1026 // Optional in-process gesture-tweak persistence.
1027 // When null, emit_tweak_for_selection() is a no-op.
1028 TweakStore* tweak_store_ = nullptr;
1029
1030 // Optional EditHistory. When null, gestures
1031 // apply exactly as before with no undo entry pushed. See
1032 // set_edit_history().
1033 pulp::state::EditHistory* edit_history_ = nullptr;
1034
1035 // Optional sink for promoting a live structural reparent into
1036 // the generated source. When null (the default), a reparent affects only
1037 // the live tree + EditHistory; the structural edit is not locked to source.
1038 // See set_reparent_source_sink().
1039 std::function<void(const ReparentSourceEdit&)> reparent_source_sink_ = nullptr;
1040
1041 // ── Gesture undo helpers ─────────────────────────────────────────
1042 //
1043 // A snapshot of every live View layout input a gesture mutates, plus
1044 // the prior TweakStore value for each path the gesture writes (so undo
1045 // can apply-or-remove). Captured at gesture START, consumed at COMMIT
1046 // when building the EditHistory entry's undo lambda.
1047 struct LayoutSnapshot {
1048 // Resize inputs.
1049 float preferred_width = 0.0f;
1050 float preferred_height = 0.0f;
1051 Dimension dim_width{};
1052 Dimension dim_height{};
1053 // Move inputs.
1054 View::Position position = View::Position::static_;
1055 float left = 0.0f;
1056 float top = 0.0f;
1057 DimensionUnit left_unit = DimensionUnit::px;
1058 DimensionUnit top_unit = DimensionUnit::px;
1059 bool has_left = false;
1060 bool has_top = false;
1061 // Content scale (proportional resize via View::set_scale()).
1062 float scale = 1.0f;
1063 // Transform-origin + overflow captured so undo of a proportional
1064 // resize also reverts the top-left scale anchor and
1065 // the box-clip we apply to keep scaled content inside the box.
1066 float origin_x = 0.5f;
1067 float origin_y = 0.5f;
1068 View::Overflow overflow = View::Overflow::visible;
1069 // Resolved bounds at capture (so undo restores the local paint box
1070 // before Yoga's next layout pass overwrites it).
1071 Rect bounds{};
1072 };
1073
1074 // Per-path prior-value capture: nullopt means "no tweak existed, so
1075 // undo must remove_tweak"; a value means "restore via apply_tweak".
1076 struct PriorTweak {
1077 std::string path;
1078 std::optional<choc::value::Value> value;
1079 };
1080
1081 // Capture the resize-relevant View inputs of the selected view.
1082 LayoutSnapshot snapshot_layout(const View* v) const;
1083 // Restore a previously-captured snapshot onto `v` (resize fields only
1084 // for resize undo, plus position/insets for move undo). Re-applies the
1085 // captured bounds and invalidates layout.
1086 void restore_layout(View* v, const LayoutSnapshot& s) const;
1087 // Capture the current TweakStore values for `paths` under `anchor`.
1088 std::vector<PriorTweak> snapshot_tweaks(
1089 std::string_view anchor,
1090 const std::vector<std::string>& paths) const;
1091 // Apply a previously-captured prior-tweak list: restore-or-remove each.
1092 void restore_tweaks(std::string_view anchor,
1093 const std::vector<PriorTweak>& prior,
1094 std::string_view source) const;
1095
1096 // Resize gesture-start captures (consumed at resize commit to build the
1097 // EditHistory entry). Populated alongside drag_start_pref_w_/h_.
1098 LayoutSnapshot resize_before_layout_{};
1099 std::vector<PriorTweak> resize_before_tweaks_;
1100 std::string resize_anchor_;
1101
1102 // Move gesture-start captures (consumed at move commit).
1103 LayoutSnapshot move_before_layout_{};
1104 std::vector<PriorTweak> move_before_tweaks_;
1105 std::string move_anchor_;
1106
1107 // Editor-URL config for the `J` source-jump hotkey.
1108 // Defaults to the built-in VS Code template; the env override
1109 // (PULP_INSPECTOR_EDITOR_URL) still applies at jump time.
1110 InspectorConfig config_{};
1111
1112 // ── Drift-drawer state ───────────────────────────────────────────
1113 //
1114 // drifted_ is the cached drift list from the last refresh_drift().
1115 // drift_drawer_open_ tracks the expand/collapse state; it flips to
1116 // true automatically the first time drift is detected (so a stale
1117 // tweak is never silent), and the user can collapse it after.
1118 // drift_refreshed_once_ guards the first-paint auto-refresh so the
1119 // drawer is populated even if the host never calls refresh_drift()
1120 // explicitly.
1121 std::vector<TweakStore::DriftedTweak> drifted_;
1122 bool drift_drawer_open_ = false;
1123 bool drift_refreshed_once_ = false;
1124 // Hit-rect of the drift drawer header chevron from the last paint,
1125 // in root coords — used by handle_mouse_event() to toggle the
1126 // drawer. width==0 means "not painted this frame".
1127 Rect drift_header_hit_{};
1128
1129 // ── Reconciliation-tab state ─────────────────────────────────────
1130 //
1131 // reconcile_tab_visible_ tracks the R-key toggle; off by default so
1132 // the panel layout is unchanged until the user opts in.
1133 // reconcile_rows_ caches the rows the tab laid out on the last
1134 // paint so reconcile_row_count() can report them to tests and the
1135 // tab scroll stays stable across frames.
1136 bool reconcile_tab_visible_ = false;
1137 float reconcile_scroll_y_ = 0.0f;
1138 std::vector<ReconcileRow> reconcile_rows_;
1139
1140 // ── Texture-atlas-viewer state ───────────────────────────────────
1141 //
1142 // atlas_viewer_visible_ tracks the `A`-key toggle; off by default
1143 // so the panel layout is unchanged until the user opts in.
1144 // atlas_inventory_ is a non-owning pointer to the render layer's
1145 // snapshot collection (nullptr → graceful empty state).
1146 // atlas_scroll_y_ keeps the row list stable across frames;
1147 // atlas_row_count_ caches how many rows the last paint laid out so
1148 // atlas_row_count() can report it to tests without scraping pixels.
1149 bool atlas_viewer_visible_ = false;
1150 const render::AtlasInventory* atlas_inventory_ = nullptr;
1151 float atlas_scroll_y_ = 0.0f;
1152 std::size_t atlas_row_count_ = 0;
1153
1154 // Drag-handles state. Off by default so the inspector behaves
1155 // identically until the user opts in (D-key toggle in handle_key_event).
1156 bool dragging_enabled_ = false;
1157 // Resize handles: four corners + four edge midpoints. Edge handles
1158 // resize a single axis (n/s → height, e/w → width); corner handles
1159 // resize both. Edge handles let the selection box resize from any
1160 // side, not only the corners, and let each zone drive a
1161 // context-aware resize cursor.
1162 enum class DragCorner : std::uint8_t {
1163 none, nw, ne, sw, se, n, s, e, w
1164 };
1165 DragCorner active_drag_ = DragCorner::none;
1166 Point drag_start_pos_{}; // mouse pos when drag began (root coords)
1167 Rect drag_start_bounds_{}; // selected_->bounds() snapshot
1168 float drag_start_pref_w_ = 0.0f; // flex().preferred_width snapshot
1169 float drag_start_pref_h_ = 0.0f; // flex().preferred_height snapshot
1170
1171 // ── Proportional (Shift) resize ──────────────────────────────────
1172 //
1173 // Plain corner-drag resizes the box (children reflow). Shift +
1174 // corner-drag scales the container's
1175 // CONTENT proportionally — a uniform View::set_scale() on the
1176 // selected view tied to the box delta, so the panel keeps its
1177 // internal proportions instead of just stretching the box. We track
1178 // whether the active resize is proportional and the content scale at
1179 // gesture start so undo restores it exactly.
1180 bool resize_proportional_ = false; // Shift held when this resize began
1181 float drag_start_scale_ = 1.0f; // selected_->scale() at gesture start
1182
1183 // Returns the corner whose 8px handle hit-rectangle contains `pos`,
1184 // for the currently-selected view (root coords). Returns
1185 // DragCorner::none if no handle is hit or no view selected.
1186 DragCorner hit_test_drag_handle(Point pos) const;
1187
1188 // Map a resize-handle corner/edge to its cursor affordance. Pure
1189 // helper shared by cursor_affordance_at's active-drag pin and
1190 // idle-hover paths. Declared after DragCorner so the
1191 // return/parameter types are complete at the declaration point.
1192 static CursorAffordance affordance_for_corner(DragCorner c);
1193
1194 // ── Minimal manipulate layer ─────────────────────────────────────
1195 // When true, paint() draws only the selection box + handles (no dev
1196 // side-panel) and point_in_panel() reports false everywhere. See
1197 // set_manipulate_only().
1198 bool manipulate_only_ = false;
1199
1200 // ── Drag-to-move gesture state ───────────────────────────────────
1201 //
1202 // A body-drag (mouse-down on the selected view's body, NOT a resize
1203 // handle) repositions the view via position:absolute + left/top.
1204 // Mirrors the resize state machine: press starts, every
1205 // is_down=false event live-updates and overwrites the tweaks, the
1206 // next is_down=true ends the gesture. The three move tweaks
1207 // (layout.position / layout.left / layout.top) land in a single
1208 // atomic TweakStore batch each tick.
1209 bool move_active_ = false; // a move gesture is in progress
1210 bool move_refused_grid_ = false; // last body-press was refused (grid parent)
1211 Point move_start_pos_{}; // mouse pos when move began (root coords)
1212 float move_seed_left_ = 0.0f; // seeded left at conversion (no delta)
1213 float move_seed_top_ = 0.0f; // seeded top at conversion (no delta)
1214
1215 // ── Drag ghost for smooth reflow-move tracking ───────────────────
1216 //
1217 // A reflow (non-float) move does NOT mutate the live layout while
1218 // dragging — the dragged element stays in flow and only the drop
1219 // indicator moves. Without a follower, the element appears to do
1220 // nothing and then teleport to the drop slot on release. To make the
1221 // drag track the cursor
1222 // fluidly we paint a translucent GHOST of the dragged element that
1223 // follows the pointer with a STABLE grab offset (the delta between the
1224 // press point and the element's top-left), so there is no teleport on
1225 // grab and no per-tick jitter — the ghost is pure paint, no relayout.
1226 // move_grab_offset_ is (press - element-origin) captured once at press;
1227 // move_ghost_ is the element's size, also captured at press so the
1228 // ghost keeps a constant size even though no relayout runs.
1229 Point move_grab_offset_{}; // press point minus element origin
1230 Rect move_ghost_{}; // element bounds snapshot at press
1231 Point move_cursor_{}; // latest cursor pos during the drag
1232
1233 // ── Reflow-aware move ────────────────────────────────────────────
1234 //
1235 // The default body-drag is reflow-aware, not free-absolute: during
1236 // the drag we resolve a drop
1237 // target (an insertion point between flex siblings, or a container to
1238 // drop INTO) and on release we reorder via flex().order and/or
1239 // reparent via remove_child/add_child, then the tree reflows. The
1240 // free-absolute float (position:absolute + left/top) is the explicit
1241 // ⌘-drag escape hatch.
1242 //
1243 // move_float_ is true for a ⌘-drag (absolute float). When false, the
1244 // gesture is reflow-aware and the drop-target fields below describe
1245 // where a release would land.
1246 bool move_float_ = false;
1247
1248 // The container the cursor is currently over for a reflow drop, and
1249 // the insertion index among that container's children (0..count).
1250 // When drop_inside_ is true the element reparents INTO drop_target_
1251 // (appended / inserted at drop_index_); when false it reorders among
1252 // drop_target_'s existing children at drop_index_. null target = no
1253 // valid drop resolved this tick (release is a no-op reflow).
1254 View* drop_target_ = nullptr;
1255 int drop_index_ = 0;
1256 bool drop_inside_ = false;
1257 // Geometry of the drop affordance in root coords, computed each move
1258 // tick for paint(): an insertion LINE (zero-area rect along an edge)
1259 // when reordering, or a container HIGHLIGHT rect when dropping inside.
1260 Rect drop_indicator_{};
1261 bool drop_indicator_is_line_ = false;
1262
1263 // Resolve the drop target for the current cursor position during a
1264 // reflow move. Walks the tree for the deepest flex container under
1265 // the cursor (excluding the dragged subtree), picks an insertion gap
1266 // among its children by the cursor's main-axis position, and fills
1267 // drop_target_ / drop_index_ / drop_inside_ / drop_indicator_*.
1268 void resolve_drop_target(Point pos);
1269 // Commit the resolved reflow drop: reorder (rewrite flex().order) or
1270 // reparent (remove_child + add_child at drop_index_). Returns true if
1271 // the tree actually changed. Normalizes sibling order on success.
1272 bool commit_reflow_drop(View* dragged);
1273 // True if `ancestor` is `v` or an ancestor of `v` — guards against
1274 // dropping a node into its own subtree.
1275 static bool is_self_or_ancestor(const View* ancestor, const View* v);
1276
1277 // Structural primitive: detach `v` from its current parent and insert
1278 // it into `new_parent` at child index `index` (clamped). When the view
1279 // is already a child of new_parent the index is honored (reorder by
1280 // insertion position). No-op when v / new_parent is null. Used by the
1281 // reflow commit and by undo/redo to round-trip a reparent. Updates
1282 // selected_ to keep pointing at the moved view (its address is stable —
1283 // ownership transfers via unique_ptr, the View itself is not copied).
1284 void reparent_view(View* v, View* new_parent, int index);
1285
1286 // True if `pos` (root coords) is inside the selected view's body but
1287 // NOT on a resize handle — the body-drag trigger for a move.
1288 bool hit_test_body(Point pos) const;
1289
1290 // Resolve the containing block Yoga uses for the selected view's
1291 // absolute insets: the nearest ancestor View whose position is NOT
1292 // static_/relative-mapped-as-static (i.e. relative/absolute/fixed),
1293 // else the root. Returns the ancestor and its origin in root coords.
1294 // Because Pulp maps static_ -> Yoga Relative, in practice the
1295 // immediate parent is almost always the containing block.
1296 const View* containing_block_of(const View* v, Rect& block_root_out) const;
1297
1298 // Seed left/top so converting `v` to absolute does not visually jump,
1299 // using the border-edge formula:
1300 // left = childRootX - blockRootX - blockBorderLeft - childMarginLeft
1301 // top = childRootY - blockRootY - blockBorderTop - childMarginTop
1302 // Writes the seed into move_seed_left_ / move_seed_top_.
1303 void seed_move_origin(const View* v);
1304
1305 // Whether the selected view's PARENT is a grid container — a move is
1306 // refused for grid children (grid ignores position/top/left today).
1307 bool selected_parent_is_grid() const;
1308
1309 // ── Color eyedropper state ───────────────────────────────────────
1310 // Off by default so the inspector behaves identically until the
1311 // user opts in (E-key toggle).
1312 bool eyedropper_active_ = false;
1313 bool eyedropper_has_sample_ = false;
1314 Color eyedropper_sample_{0.0f, 0.0f, 0.0f, 0.0f};
1315 Point eyedropper_cursor_{};
1316 std::string eyedropper_target_ = "style.background_color";
1317
1318 // Walk from the View under `pos` up to the nearest ancestor that
1319 // has an explicit background color; returns that color. Returns
1320 // false when no view under the cursor carries a background — the
1321 // resolved-style fallback used when Canvas pixel readback is
1322 // unavailable.
1323 bool resolved_color_under(Point pos, Color& out) const;
1324
1325 // Paint the cursor-following swatch + hex readout. No-op unless
1326 // eyedropper mode is active and at least one sample has landed.
1327 void paint_eyedropper_cursor(Canvas& canvas);
1328
1329 // ── Zoom loupe state ─────────────────────────────────────────────
1330 // Off by default; the inspector behaves identically to the
1331 // normal inspector until the user opts in via the Z-key toggle.
1332 bool zoom_active_ = false;
1333 int zoom_factor_ = 20;
1334 Point zoom_sample_center_{};
1335 Color zoom_center_color_{};
1336 bool zoom_center_from_readback_ = false;
1337
1338 // Recompute zoom_center_color_ for the current zoom_sample_center_,
1339 // sampling `canvas` via read_pixels() when the surface supports it
1340 // and falling back to the resolved View color otherwise. Sets
1341 // zoom_center_from_readback_ to record which path was taken.
1342 void update_zoom_sample(Canvas& canvas);
1343 // Resolve the on-screen color at root-coord point `p` WITHOUT a
1344 // pixel readback — walks the view tree top-most-first and returns
1345 // the deepest hit View's background color. Returns false (and
1346 // leaves `out` untouched) when no background-bearing view is hit.
1347 bool resolve_view_color_at(Point p, Color& out) const;
1348
1349 // ── Flat tree for rendering ─────────────────────────────────────
1350 struct TreeItem {
1351 const View* view;
1352 int depth;
1353 };
1354 std::vector<TreeItem> flat_tree_;
1355 void rebuild_flat_tree();
1356
1357 // text_edit_target_ is a raw View*. If the edited
1358 // Label/TextEditor is destroyed mid-edit (e.g. a live React tree
1359 // rebuild), every subsequent text op (handle_text_input, Backspace,
1360 // commit/cancel) would deref freed memory. These guards keep the
1361 // text-edit state coherent with the live tree:
1362 // - text_edit_target_reachable() walks from root_ to confirm the
1363 // target is still attached (does NOT deref the target itself).
1364 // - clear_text_edit_state() drops the target + buffers WITHOUT
1365 // touching the (possibly freed) view.
1366 bool text_edit_target_reachable() const;
1367 void clear_text_edit_state();
1368
1369 // Resolve a live View by its stable anchor id at
1370 // undo/redo time. The text-edit and reparent EditHistory closures used to
1371 // capture raw `View*` (e.g. `View* tgt = selected_;`). When a live React
1372 // SUBTREE rebuilds before the user hits Cmd+Z — which clear_edit_history()
1373 // does NOT cover, since it only fires at ROOT replacement — those pointers
1374 // dangle and undo derefs freed memory. Anchored captures now store the
1375 // anchor id and re-find the live view here (nullptr → graceful no-op).
1376 // Returns nullptr for an empty anchor or a view no longer in the tree.
1377 View* resolve_anchor(const std::string& anchor) const;
1378
1379 // The anchor of the visible sibling immediately preceding
1380 // `child` under `parent` in flex-order (or "" when `child` is the first
1381 // child / parent is null / the preceding sibling is un-anchored). Used to
1382 // carry the requested insertion slot in ReparentSourceEdit so the source
1383 // rewrite drops the moved block at the dragged position, not first-child.
1384 static std::string preceding_sibling_anchor(const View* parent,
1385 const View* child);
1386
1387 // Un-anchored fallback. Some edited/reparented views
1388 // carry NO anchor (e.g. a --script Chainer label), so they cannot be
1389 // re-found by anchor. For those we keep the raw-pointer capture but RECORD
1390 // the pointer here; rebuild_flat_tree() clears the WHOLE EditHistory if any
1391 // tracked raw view left the tree, before its closures can deref it. Anchored
1392 // captures never go in this set (they self-heal via resolve_anchor).
1393 std::vector<View*> raw_history_views_;
1394 void track_raw_history_view(View* v);
1395 bool any_raw_history_view_dangling() const;
1396
1397 // ── Gesture undo target ──────────────────────────────────────────
1398 //
1399 // Every per-gesture EditHistory closure that mutates a captured View
1400 // must survive a live React SUBTREE rebuild between capture and Cmd+Z:
1401 // clear_edit_history() only fires at ROOT replacement, so a bare `View*`
1402 // held in a closure can dangle. GestureUndoTarget centralizes the one
1403 // dangling-mitigation rule that the text-edit and reparent sites
1404 // hand-rolled — and that the resize + move-float sites historically
1405 // LACKED, a latent use-after-free. It holds the stable anchor when the
1406 // view has one (re-found via resolve_anchor() at replay) and falls back
1407 // to the raw pointer, TRACKED via track_raw_history_view() so
1408 // rebuild_flat_tree() clears the history if it leaves the tree. resolve()
1409 // returns nullptr for a freed/unresolvable target so a closure can no-op
1410 // gracefully. Copyable (an EditHistory closure captures it by value).
1411 struct GestureUndoTarget {
1412 View* raw = nullptr;
1413 std::string anchor;
1414 bool anchored = false;
1415 // Re-find the live view at replay time. A nested type may touch the
1416 // enclosing class's private resolve_anchor().
1417 View* resolve(const InspectorOverlay& ov) const {
1418 return anchored ? ov.resolve_anchor(anchor) : raw;
1419 }
1420 };
1421 // Capture `v` as a gesture undo target. `anchored_gate` lets a site keep
1422 // its own notion of when the anchored replay path applies (the text-edit
1423 // site ties it to a wired TweakStore); the target is anchored only when
1424 // the gate holds AND the view carries a non-empty anchor id. Tracks the
1425 // raw pointer when the target is NOT anchored so the rebuild seam can
1426 // clear the history before a dangling closure runs.
1427 GestureUndoTarget capture_undo_target(View* v, bool anchored_gate = true);
1428
1429 // ── Coordinate helpers ──────────────────────────────────────────
1430 Rect view_bounds_in_root(const View* v) const;
1431
1432 // Effective on-screen scale of `v`, the product of
1433 // its own View::scale() and every ancestor's scale up to (but excluding)
1434 // root_. View::paint_all renders a subtree under each ancestor's
1435 // `scale(s,s)`, so a glyph at element-local distance d renders d*eff_scale
1436 // wide on screen. The inline-text-edit caret/selection overlay multiplies
1437 // the unscaled Label::text_edit_metrics by this factor so the caret tracks
1438 // the rendered glyphs after a proportional (Shift) resize, which sets
1439 // View::set_scale() rather than growing bounds. Returns 1.0 for an
1440 // untransformed chain.
1441 float effective_scale_in_root(const View* v) const;
1442
1443 // ── Paint helpers ───────────────────────────────────────────────
1444 void paint_highlight(Canvas& canvas);
1445 // Paint the reflow-move drop affordance: a blue insertion line
1446 // between siblings (reorder) or a translucent container highlight
1447 // (drop-inside). No-op when no reflow move is in progress / no drop
1448 // target resolved.
1449 void paint_drop_indicator(Canvas& canvas);
1450 // Paint the translucent drag ghost that follows the cursor
1451 // during a reflow (non-float) move, giving the move smooth cursor
1452 // tracking without any per-tick relayout. No-op when no reflow move is
1453 // active.
1454 void paint_drag_ghost(Canvas& canvas);
1455 void paint_distance_lines(Canvas& canvas);
1456 void paint_box_model(Canvas& canvas, const View* v);
1457 void paint_panel(Canvas& canvas);
1458 void paint_tree_section(Canvas& canvas, float x, float y, float w, float& cursor_y);
1459 void paint_props_section(Canvas& canvas, float x, float y, float w, float h);
1460 void paint_stats_bar(Canvas& canvas, float x, float y, float w);
1465 void paint_pass_attribution(Canvas& canvas, float x, float y, float w, float h);
1470 float paint_drift_drawer(Canvas& canvas, float x, float y, float w);
1471 // Magnified-pixel loupe. Draws a fixed-corner panel with
1472 // a grid of `zoom_factor_`-scaled pixels sampled around the cursor,
1473 // a center crosshair marking the sample pixel, and a coordinate +
1474 // hex color readout strip beneath the grid.
1475 void paint_zoom_panel(Canvas& canvas);
1476
1477 // Render the tweak management panel into the given
1478 // rect. Repopulates tweak_rows_ with this frame's hit-rects.
1479 void paint_tweaks_section(Canvas& canvas, float x, float y, float w, float h);
1480
1481 // Render the semantic-knob controls + send-to-agent text field at the top of
1482 // the tweaks panel. Repopulates knob_hits_ and the agent field/send rects
1483 // with this frame's hit-rects. Returns the vertical space used so the tweak
1484 // list below can start beneath it. Paints nothing (returns 0) when there are
1485 // no knobs and no agent sink.
1486 float paint_knob_controls(Canvas& canvas, float x, float y, float w, float h);
1487
1488 // Resolve a click in the tweaks panel against this frame's knob controls /
1489 // agent field. Returns true when the click was consumed (a knob was set, the
1490 // field focused, or Send pressed). Fires the relevant sink.
1491 bool handle_knob_panel_click(Point p);
1492
1493 // Submit the current agent text buffer through the agent-request sink (when
1494 // non-empty and a sink is attached), then clear + defocus the field.
1495 void submit_agent_request();
1496
1502 void paint_reconcile_tab(Canvas& canvas, float x, float y, float w, float h);
1503
1509 void paint_atlas_tab(Canvas& canvas, float x, float y, float w, float h);
1510
1511 // ── Mouse-event precedence chain ─────────────────────────────────
1512 //
1513 // handle_mouse_event() resolves the event's gesture phase ONCE, then
1514 // offers it to the handlers below in a fixed order. Modality
1515 // precedence — text tool over eyedropper, an in-flight gesture over a
1516 // fresh press, the panel over the canvas — IS that order.
1517 //
1518 // A handler returns a value to CONSUME the event (the bool is what
1519 // handle_mouse_event returns) or std::nullopt to decline and let the
1520 // next one look. Declining is not the same as not acting: a handler
1521 // may update state and still fall through, which is how a resize
1522 // release commits and then lets the same click re-select.
1523
1527 struct MouseGesture {
1528 const MouseEvent& event;
1529 Point pos;
1530 bool is_press;
1531 bool is_drag_tick;
1532 bool is_release;
1533 };
1534
1537 MouseGesture resolve_mouse_gesture(const MouseEvent& event) const;
1538
1539 std::optional<bool> mouse_text_tool(const MouseGesture& g);
1541 void mouse_zoom_loupe(const MouseGesture& g);
1542 std::optional<bool> mouse_eyedropper(const MouseGesture& g);
1543 std::optional<bool> mouse_active_resize(const MouseGesture& g);
1544 std::optional<bool> mouse_begin_resize(const MouseGesture& g);
1545 std::optional<bool> mouse_active_move(const MouseGesture& g);
1546 std::optional<bool> mouse_begin_move(const MouseGesture& g);
1547 std::optional<bool> mouse_panel(const MouseGesture& g);
1549 std::optional<bool> mouse_select(const MouseGesture& g);
1550
1551 // ── Panel hit testing ───────────────────────────────────────────
1552 bool point_in_panel(Point p) const;
1553 const TreeItem* tree_item_at_y(float panel_y) const;
1554
1555 // Resolve a root-coord point to a tweak-row icon hit.
1556 // Sets `out_row` to the matching row index and returns the action;
1557 // returns TweakAction::none (out_row untouched) on a miss. Called
1558 // by handle_mouse_event() before the tree-selection fallthrough.
1559 TweakAction tweak_action_at(Point p, std::size_t& out_row) const;
1560
1561 // ── Editable-field helpers ───────────────────────────────────────
1564 int editable_field_at(Point p) const;
1567 bool handle_edit_key(const KeyEvent& event);
1571 void apply_edit_buffer_to_view();
1574 float read_field_value(std::string_view field_path) const;
1577 void write_field_value(std::string_view field_path, float value);
1578
1579 // ── Constants ───────────────────────────────────────────────────
1580 static constexpr float kRowHeight = 20.0f;
1581 static constexpr float kIndent = 16.0f;
1582 static constexpr float kFontSize = 11.0f;
1583 static constexpr float kStatsBarHeight = 24.0f;
1584
1585 // Zoom loupe layout. The loupe samples a square grid of
1586 // kZoomGridCells × kZoomGridCells source pixels (odd so there's an
1587 // exact center pixel) and renders each at zoom_factor_ scale. The
1588 // panel sits in a fixed corner with a readout strip beneath.
1589 static constexpr int kZoomGridCells = 11;
1590 static constexpr int kZoomFactorMin = 4;
1591 static constexpr int kZoomFactorMax = 40;
1592 static constexpr float kZoomReadoutH = 36.0f;
1593 static constexpr float kZoomPanelMargin = 12.0f;
1594};
1595
1618 std::string_view property_path,
1619 const choc::value::Value& value);
1620
1624 float x = 0.0f;
1625 float y = 0.0f;
1626 bool below = false;
1627};
1628
1639BadgePlacement compute_badge_placement(float sel_x, float sel_y, float sel_h,
1640 float badge_w, float badge_h,
1641 float root_w,
1642 float gap = 5.0f,
1643 float top_margin = 0.0f);
1644
1650
1656
1662
1663} // namespace pulp::inspect
Definition canvas.hpp:240
Definition inspector_overlay.hpp:36
void text_move_word(int direction, bool extend)
Move the caret one word in direction (-1 left / +1 right), skipping the run of non-word then word byt...
bool handle_text_input(const TextInputEvent &event)
void set_selected_view(View *v)
Set the selected view directly (used by the floating Inspector window so the two surfaces share one s...
Definition inspector_overlay.hpp:288
std::string knob_selection(std::string_view knob_id) const
The knob's currently-selected value (empty when never set).
void text_move_home(bool extend)
Move the caret to the buffer start / end (Cmd+Left / Cmd+Right / Home / End — single-line).
void set_edit_history(pulp::state::EditHistory *h)
Attach an EditHistory so every committed manipulation gesture (drag-to-move, corner-resize,...
Definition inspector_overlay.hpp:130
const std::string & text_edit_buffer() const
Definition inspector_overlay.hpp:564
const std::string & agent_text_buffer() const
Definition inspector_overlay.hpp:223
void toggle_dragging()
Definition inspector_overlay.hpp:265
bool is_active() const
Definition inspector_overlay.hpp:48
void text_select_all()
Select the entire buffer (Cmd+A).
void set_eyedropper_target(std::string path)
Definition inspector_overlay.hpp:446
bool text_editing() const
Definition inspector_overlay.hpp:563
std::size_t reconcile_row_count() const
Number of tweak rows the reconciliation tab laid out on the most recent paint() while it was visible.
Definition inspector_overlay.hpp:798
bool drop_indicator_is_line() const
Definition inspector_overlay.hpp:339
bool has_knob_apply_sink() const
Definition inspector_overlay.hpp:190
void set_eyedropper_active(bool active)
Toggle eyedropper mode.
std::size_t atlas_row_count() const
Number of atlas rows the viewer laid out on the most recent paint() while it was visible.
Definition inspector_overlay.hpp:840
const pulp::design::KnobSchema & knob_schema() const
Definition inspector_overlay.hpp:181
bool tweaks_panel_visible() const
Definition inspector_overlay.hpp:678
void toggle_tool()
Definition inspector_overlay.hpp:69
bool eyedropper_active() const
Definition inspector_overlay.hpp:428
std::uint64_t pass_frames_captured() const
Total frames sampled into the rolling history since construction (saturates conceptually — only the l...
Definition inspector_overlay.hpp:400
CursorAffordance cursor_affordance_at(Point pos) const
Point zoom_sample_center() const
Last cursor position the loupe sampled around, in root (window) coordinates.
Definition inspector_overlay.hpp:873
View * text_edit_target() const
Definition inspector_overlay.hpp:565
int cursor_style_for(Point pos) const
Map a CursorAffordance to a View::CursorStyle; returns the int cast the cursor hook reports to the ho...
void set_manipulate_only(bool on)
Definition inspector_overlay.hpp:279
void toggle_tweaks_panel()
Definition inspector_overlay.hpp:679
void set_text_tweak_path(std::string path)
Definition inspector_overlay.hpp:665
void set_tweaks_panel_visible(bool visible)
Definition inspector_overlay.hpp:677
void set_atlas_viewer_visible(bool visible)
Toggle the texture-atlas viewer tab.
Definition inspector_overlay.hpp:828
bool commit_field_edit()
Commit the current edit buffer as a tweak, then exit edit mode.
Rect agent_field_bounds() const
Definition inspector_overlay.hpp:221
void clear_edit_history()
Clear the attached EditHistory.
Definition inspector_overlay.hpp:243
SelectionMode selection_mode() const
Current selection mode.
Definition inspector_overlay.hpp:483
void text_insert(const std::string &utf8)
Insert UTF-8 text at the caret, replacing any selection.
void text_delete_backward()
Delete the selection if any; otherwise the codepoint before the caret (Backspace) or after it (Delete...
void paint(Canvas &canvas)
bool text_has_selection() const
Definition inspector_overlay.hpp:630
std::size_t drift_count() const
Number of drifted/orphaned tweaks from the last refresh_drift().
Definition inspector_overlay.hpp:705
std::vector< PassAttribution > pass_attribution() const
Per-pass attribution computed over the current rolling history, ordered by RenderPassType (background...
void set_dragging_enabled(bool enabled)
Toggle drag-handles mode.
Definition inspector_overlay.hpp:263
bool dragging_enabled() const
Definition inspector_overlay.hpp:264
void text_move_caret(int delta, bool extend)
Move the caret by delta UTF-8 codepoints (negative = left).
Tool
Definition inspector_overlay.hpp:66
@ text
Definition inspector_overlay.hpp:66
@ select
Definition inspector_overlay.hpp:66
void set_agent_request_sink(std::function< void(const AgentRequestEdit &)> sink)
Definition inspector_overlay.hpp:206
void toggle_pass_viewer()
Definition inspector_overlay.hpp:408
bool emit_tweak_for_selection(std::string_view property_path, choc::value::Value value, std::string_view source="inspector-gesture")
Emit a tweak for the currently-selected view's anchor.
bool handle_key_event(const KeyEvent &event)
View * selected_view() const
Definition inspector_overlay.hpp:466
Color eyedropper_sample() const
Last color sampled by the eyedropper (updated on every mouse-move while eyedropper mode is active).
Definition inspector_overlay.hpp:435
const View * drop_target() const
Definition inspector_overlay.hpp:341
const std::string & eyedropper_target() const
The dotted color property the eyedropper writes to on click.
Definition inspector_overlay.hpp:445
bool selection_shows_resize_handles() const
Definition inspector_overlay.hpp:575
void set_render_pass_manager(render::RenderPassManager *rpm)
Definition inspector_overlay.hpp:91
static const char * reconcile_status_str(ReconcileStatus status)
Stringify a ReconcileStatus for the panel + tests.
void toggle_reconcile_tab()
Definition inspector_overlay.hpp:790
bool selection_uses_subtle_edit_outline() const
True when the selected element should render the subtle thin-blue text-edit outline instead of the or...
Definition inspector_overlay.hpp:583
int budget_overrun_count() const
Count of frames in history where the manager reported the frame exceeded its time budget (RenderPassM...
Definition inspector_overlay.hpp:395
bool atlas_viewer_visible() const
Definition inspector_overlay.hpp:831
Tool tool() const
Definition inspector_overlay.hpp:67
CursorAffordance
Definition inspector_overlay.hpp:311
@ resize_ns
T/B edge — vertical ↕
Definition inspector_overlay.hpp:315
@ none
Definition inspector_overlay.hpp:312
@ resize_ne_sw
TR/BL corner — diagonal ↗↙
Definition inspector_overlay.hpp:314
@ move
Definition inspector_overlay.hpp:312
@ resize_nw_se
TL/BR corner — diagonal ↖↘
Definition inspector_overlay.hpp:313
@ resize_ew
L/R edge — horizontal ↔
Definition inspector_overlay.hpp:316
const std::vector< TweakStore::DriftedTweak > & drifted() const
Read-only access to the last computed drift list — for tests and host-side badges.
Definition inspector_overlay.hpp:714
const std::string & editing_field() const
Definition inspector_overlay.hpp:524
bool manipulate_only() const
Definition inspector_overlay.hpp:283
bool drop_indicator_active() const
Whether a drop indicator (the blue insertion line / container highlight) would paint right now.
Definition inspector_overlay.hpp:327
Rect drop_indicator_rect() const
Definition inspector_overlay.hpp:340
std::size_t tweak_row_count() const
Number of tweak rows currently laid out in the management panel (populated by the most recent paint()...
Definition inspector_overlay.hpp:685
void set_reparent_source_sink(std::function< void(const ReparentSourceEdit &)> sink)
Definition inspector_overlay.hpp:162
bool apply_eyedropper_pick()
Apply the last sampled color as a tweak to the current selection's eyedropper_target_ property,...
ReconcileStatus
Reconciliation state of a single stored tweak.
Definition inspector_overlay.hpp:746
@ locked_to_source
Anchor locked + resolves — survives re-import.
Definition inspector_overlay.hpp:747
@ unresolvable
Anchor not found in the live tree (orphaned).
Definition inspector_overlay.hpp:749
bool is_editing() const
Definition inspector_overlay.hpp:527
int zoom_factor() const
Magnification factor — how many panel pixels per source pixel.
Definition inspector_overlay.hpp:868
void toggle_selection_mode()
Flip between follows_focus and follows_mouse.
Definition inspector_overlay.hpp:488
bool begin_field_edit(std::string field_path, float initial_value)
Begin editing the named property on the current selection.
void toggle()
Definition inspector_overlay.hpp:50
bool text_copy()
Clipboard: copy / cut / paste the selection (Cmd+C / X / V).
bool agent_field_active() const
Definition inspector_overlay.hpp:224
void set_reconcile_tab_visible(bool visible)
Toggle the reconciliation tab.
Definition inspector_overlay.hpp:786
Color zoom_center_color() const
Resolved color of the center (sample) pixel — what the loupe readout reports as a hex string.
Definition inspector_overlay.hpp:878
bool has_agent_request_sink() const
Definition inspector_overlay.hpp:209
void cancel_field_edit()
Cancel the current edit without writing a tweak.
bool capture_pass_frame()
Sample the attached RenderPassManager's current-frame pass stats into the rolling history.
bool sample_color_at(Point pos, Canvas *canvas, Color &out) const
Sample the color under pos (root coords) WITHOUT applying it.
SelectionMode
How the selected node is chosen as the pointer moves.
Definition inspector_overlay.hpp:478
@ follows_focus
Definition inspector_overlay.hpp:478
@ follows_mouse
Definition inspector_overlay.hpp:478
ReconcileReport reconcile_report() const
Recompute the reconciliation report by classifying every stored tweak against the live view tree's an...
const InspectorConfig & config() const
Definition inspector_overlay.hpp:500
static void set_editable_text_of(View *v, const std::string &text)
Write text to a text-bearing View's copy, marking layout dirty so the new text reflows + repaints.
static std::string editable_text_of(const View *v)
Read the current text of a text-bearing View (empty if v is null or carries no editable text).
bool eyedropper_has_sample() const
Whether eyedropper_sample_ reflects a real sample yet (false until the first mouse-move inside the ca...
Definition inspector_overlay.hpp:439
pulp::state::EditHistory * edit_history() const
Definition inspector_overlay.hpp:131
View & inspected_root() const
Definition inspector_overlay.hpp:45
std::pair< std::size_t, std::size_t > text_selection() const
Selection range as [start, end) byte offsets (start == end → none).
bool begin_text_edit(View *v)
Begin an inline text edit of v's copy, seeding the buffer with the current text.
void toggle_zoom()
Definition inspector_overlay.hpp:863
void set_pass_viewer_enabled(bool enabled)
Toggle the per-pass attribution panel section.
Definition inspector_overlay.hpp:406
void toggle_eyedropper()
Definition inspector_overlay.hpp:429
void toggle_drift_drawer()
Definition inspector_overlay.hpp:710
static constexpr std::size_t kPassHistoryFrames
Number of frames the rolling per-pass history retains.
Definition inspector_overlay.hpp:376
View * hovered_view() const
Definition inspector_overlay.hpp:467
Rect agent_send_bounds() const
Definition inspector_overlay.hpp:222
void set_atlas_inventory(const render::AtlasInventory *inv)
Attach the render layer's texture-atlas inventory so the atlas viewer tab (A hotkey) can report per-a...
Definition inspector_overlay.hpp:100
void set_drift_drawer_open(bool open)
Definition inspector_overlay.hpp:709
void set_tweak_store(TweakStore *store)
Connect the inspector overlay to the in-process TweakStore so gesture detectors can persist direct-ma...
Definition inspector_overlay.hpp:114
SourceJumpResult jump_to_selection_source(bool dry_run=true)
Resolve the selected view's authored source location and (unless dry_run) open the user's editor at t...
std::size_t text_caret() const
Caret byte-offset into the edit buffer (UTF-8 boundary-aligned).
Definition inspector_overlay.hpp:627
void set_knob_apply_sink(std::function< void(const KnobApplyEdit &)> sink)
Definition inspector_overlay.hpp:187
static bool view_has_editable_text(const View *v)
True if v exposes editable text the Text tool can edit (today a Label or a TextEditor).
int drop_index() const
Test-visible accessors for the resolved reflow drop.
Definition inspector_overlay.hpp:338
bool has_reparent_source_sink() const
Definition inspector_overlay.hpp:165
const std::string & text_tweak_path() const
Dotted property path the Text tool writes the edited copy to.
Definition inspector_overlay.hpp:664
const std::string & edit_buffer() const
Definition inspector_overlay.hpp:525
void set_knob_schema(pulp::design::KnobSchema schema)
Semantic knobs → apply-to-artifact.
Definition inspector_overlay.hpp:180
void cancel_text_edit()
Cancel the inline text edit, restoring the View's original text.
void toggle_atlas_viewer()
Definition inspector_overlay.hpp:832
bool drift_drawer_open() const
Whether the drift drawer is currently expanded.
Definition inspector_overlay.hpp:708
void refresh_drift()
Recompute the drift list by diffing the attached TweakStore against the current live view tree's anch...
void set_zoom_factor(int factor)
void set_config(InspectorConfig config)
The editor-URL config used to format source-jump URLs.
Definition inspector_overlay.hpp:499
void set_selection_mode(SelectionMode mode)
Definition inspector_overlay.hpp:484
TweakStore * tweak_store() const
Definition inspector_overlay.hpp:115
bool zoom_active() const
Definition inspector_overlay.hpp:861
bool pass_viewer_enabled() const
Definition inspector_overlay.hpp:407
const std::vector< KnobHit > & knob_hits() const
Definition inspector_overlay.hpp:220
void text_move_end(bool extend)
const render::AtlasInventory * atlas_inventory() const
Definition inspector_overlay.hpp:103
bool handle_mouse_event(const MouseEvent &event)
bool reconcile_tab_visible() const
Definition inspector_overlay.hpp:789
void set_zoom_active(bool active)
bool commit_text_edit()
Commit the inline text edit: keep the live View text, emit a text content tweak (path == text_tweak_p...
std::size_t edit_caret_pos() const
Definition inspector_overlay.hpp:526
In-memory record of inspector direct-manipulation edits ("tweaks"), keyed by stable_anchor_id + dotte...
Definition tweak_store.hpp:53
A read-only collection of atlas snapshots for the inspector.
Definition atlas_inventory.hpp:96
Frame-level render pass manager.
Definition render_pass.hpp:41
Global undo/redo history for the application.
Definition edit_history.hpp:39
Definition view.hpp:73
Overflow
Overflow mode.
Definition view.hpp:1826
Position
CSS position property.
Definition view.hpp:1753
Input event types with modifier keys and pointer IDs.
Definition affine_transform.hpp:24
@ path
Definition scene_node.hpp:93
Definition agent_context.hpp:7
bool apply_move_tweak_to_view(View &view, std::string_view property_path, const choc::value::Value &value)
Move-tweak namespace bridge.
InspectorOverlay * g_active_inspector
Global inspector instance for the current window.
Definition inspector_overlay.hpp:1649
BadgePlacement compute_badge_placement(float sel_x, float sel_y, float sel_h, float badge_w, float badge_h, float root_w, float gap=5.0f, float top_margin=0.0f)
Place a badge of size (badge_w × badge_h) for a selection whose top-left is at (sel_x,...
void uninstall_inspector_hooks()
Tear down what install_inspector_hooks() set up: nulls g_active_inspector and releases all five View ...
void install_inspector_hooks(InspectorOverlay &inspector)
Install the inspector into the View system's paint and input hooks.
Definition gpu_fwd.hpp:31
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ inspector
Resizable inspector panel.
Definition window_manager.hpp:20
DimensionUnit
Definition geometry.hpp:392
canvas::Color Color
Definition theme.hpp:12
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ visible
Content renders beyond bounds (default for views).
Definition geometry.hpp:384
@ h
Definition input_events.hpp:39
@ y
Definition input_events.hpp:42
@ c
Definition input_events.hpp:39
@ p
Definition input_events.hpp:40
@ x
Definition input_events.hpp:41
@ v
Definition input_events.hpp:41
@ g
Definition input_events.hpp:39
@ t
Definition input_events.hpp:41
@ on
Definition accessibility.hpp:132
Definition canvas.hpp:52
A design's declared set of knobs.
Definition design_knobs.hpp:70
Computed top-left placement for a selection badge (the type W×H hover tooltip and the grid-refuse "ca...
Definition inspector_overlay.hpp:1623
float y
Definition inspector_overlay.hpp:1625
float x
Definition inspector_overlay.hpp:1624
bool below
true when flipped below the selection
Definition inspector_overlay.hpp:1626
Inspector runtime configuration.
Definition editor_url.hpp:38
Send-to-agent → free-text request.
Definition inspector_overlay.hpp:203
std::string text
the human's free-text request
Definition inspector_overlay.hpp:204
Definition inspector_overlay.hpp:183
std::string knob_id
the chosen knob's stable id
Definition inspector_overlay.hpp:184
std::string value
the selected position: label (Enum/Toggle) or slider value
Definition inspector_overlay.hpp:185
One knob control's on-screen hit-rect from the last paint.
Definition inspector_overlay.hpp:215
Rect bounds
root coords
Definition inspector_overlay.hpp:218
std::string value
Definition inspector_overlay.hpp:217
std::string knob_id
Definition inspector_overlay.hpp:216
Aggregated attribution data for a single render pass type, computed over the rolling frame history.
Definition inspector_overlay.hpp:362
const char * name
Human-readable pass name.
Definition inspector_overlay.hpp:364
float avg_cpu_ms
Mean CPU time_ms over recorded history.
Definition inspector_overlay.hpp:367
int type
RenderPassType cast to int.
Definition inspector_overlay.hpp:363
int last_draw_calls
Draw calls in the most recent frame.
Definition inspector_overlay.hpp:369
bool present
Pass appeared in the most recent frame.
Definition inspector_overlay.hpp:365
float peak_cpu_ms
Worst CPU time_ms over recorded history.
Definition inspector_overlay.hpp:368
float last_cpu_ms
CPU time_ms in the most recent frame.
Definition inspector_overlay.hpp:366
int peak_draw_calls
Worst draw-call count over history.
Definition inspector_overlay.hpp:370
std::size_t samples
Number of recorded frames for this pass.
Definition inspector_overlay.hpp:371
Aggregate reconciliation report over every tweak in the attached TweakStore, classified against the c...
Definition inspector_overlay.hpp:765
std::size_t total() const
Definition inspector_overlay.hpp:771
std::size_t unresolvable_count
Definition inspector_overlay.hpp:769
std::size_t locked_count
Definition inspector_overlay.hpp:767
std::vector< ReconcileRow > rows
Definition inspector_overlay.hpp:766
std::size_t drifted_count
Definition inspector_overlay.hpp:768
One classified tweak row in the reconciliation report.
Definition inspector_overlay.hpp:756
std::string property_path
Definition inspector_overlay.hpp:758
ReconcileStatus status
Definition inspector_overlay.hpp:759
std::string anchor_id
Definition inspector_overlay.hpp:757
Structural reparent → lock-to-source.
Definition inspector_overlay.hpp:157
std::string new_parent_anchor
Definition inspector_overlay.hpp:159
std::string insert_after_anchor
Definition inspector_overlay.hpp:160
std::string child_anchor
Definition inspector_overlay.hpp:158
Outcome of a source-jump resolution.
Definition source_jump.hpp:34
Rich key event with key code, modifiers, and repeat state.
Definition input_events.hpp:193
Rich mouse/pointer event with position, modifiers, button state, pointer ID, and stylus properties.
Definition input_events.hpp:104
Definition geometry.hpp:20
Axis-aligned rectangle in continuous (float) coordinates.
Definition geometry.hpp:52
Text input from IME or keyboard composition (separate from KeyEvent).
Definition input_events.hpp:210