You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Small header-only utilities for semantic-version parsing/comparison and extracting GitHub release fields from API JSON.
24
24
-[`src/XpressFormula/UI/Application.h`](../src/XpressFormula/UI/Application.h) and [`src/XpressFormula/UI/Application.cpp`](../src/XpressFormula/UI/Application.cpp)
- Reusable XpressFormula-specific UI components such as `PlotToolbar` and `FormulaCard`. Components may edit ordinary widget state passed by reference, but collection mutations and application commands stay with panels or `Application`.
26
30
-[`src/XpressFormula/UI/FormulaPanel.h`](../src/XpressFormula/UI/FormulaPanel.h) and [`src/XpressFormula/UI/FormulaPanel.cpp`](../src/XpressFormula/UI/FormulaPanel.cpp)
27
-
- Formula list managementand per-formula controls.
31
+
- Formula list management, editor modal workflow, collection mutations, and action handling returned by formula-card components.
28
32
-[`src/XpressFormula/UI/ControlPanel.h`](../src/XpressFormula/UI/ControlPanel.h) and [`src/XpressFormula/UI/ControlPanel.cpp`](../src/XpressFormula/UI/ControlPanel.cpp)
29
-
- Global 2D view controls, display toggles (grid/coordinates/wires), 3D surface camera settings, and export dialog launch action.
33
+
- Global 2D view controls, display toggles (grid/coordinates/wires), reusable property-grid rows for 3D/heatmap controls, and export dialog launch action.
30
34
-[`src/XpressFormula/UI/PlotPanel.h`](../src/XpressFormula/UI/PlotPanel.h) and [`src/XpressFormula/UI/PlotPanel.cpp`](../src/XpressFormula/UI/PlotPanel.cpp)
- Centralized semantic version metadata used by window title, resources, and packaging.
34
40
-[`src/XpressFormula/Plotting/PlotRenderer.h`](../src/XpressFormula/Plotting/PlotRenderer.h) and [`src/XpressFormula/Plotting/PlotRenderer.cpp`](../src/XpressFormula/Plotting/PlotRenderer.cpp)
@@ -43,7 +49,36 @@ XpressFormula is organized into three primary layers:
43
49
5.`PlotPanel` updates `ViewTransform` from current viewport and delegates drawing to `PlotRenderer`.
44
50
6.`PlotRenderer` evaluates formulas through `Core::Evaluator` and draws based on variable dimensionality and equation form.
45
51
7.`Application` also polls a background GitHub release check future and updates sidebar notification state when a result arrives.
46
-
8. Export requests trigger a plot-only offscreen render pass (temporary D3D11 render target) with export-specific overrides, then post-processing (pixel-format normalization, optional resize/grayscale) before file/clipboard output.
52
+
8. Export requests resolve aspect/framing settings, trigger a plot-only offscreen render pass (temporary D3D11 render target) with export-specific overrides, then post-processing (pixel-format normalization, optional resize/grayscale) before file/clipboard output.
53
+
9. Project New/Open/Save/Save As workflows stay in `Application`; `.xfplot` parsing completes before active formulas, view, or plot settings are mutated.
54
+
55
+
## Project Persistence Boundary
56
+
57
+
`ProjectSession` is the versioned boundary for `.xfplot` files. `Application` still owns live state (`FormulaEntry`, `ViewTransform`, `PlotSettings`, project path, dirty flag, and recent list), while the serializer works on plain records that do not depend on ImGui widgets.
58
+
59
+
Important rules:
60
+
61
+
- Build a `ProjectSession` snapshot from application state before saving.
62
+
- Parse and validate a full project file before mutating active application state.
63
+
- Retain invalid loaded formulas where possible and report load warnings after reparsing.
64
+
- Clamp or ignore unsafe numeric values before applying them to the live view and plot settings.
65
+
- Keep unknown fields tolerated for schema version 1 so future writers can add data without breaking older builds.
66
+
- Write project files through a temporary file followed by replacement so failed writes do not leave a partial target file.
67
+
- Treat dirty state as a serialized-state comparison against the last clean snapshot.
68
+
69
+
`ProjectSession.h` currently contains both the schema records and the small JSON parser/serializer. That can be split later if the format grows, but file size alone is not a reason to refactor it.
70
+
71
+
## UI Toolkit Boundary
72
+
73
+
The UI toolkit is intentionally small and immediate-mode. `UiKit` does not own application state, retain widget objects, or replace ordinary ImGui controls. It centralizes policies that are easy to get wrong when repeated inline:
74
+
75
+
- responsive breakpoints and shared dimensions in `UiMetrics`
76
+
- pure layout decisions that can be unit tested
77
+
- row-height and cursor placement mechanics for responsive toolbars
78
+
- scope safety for ImGui push/pop and disabled blocks
79
+
- repeated table layout behavior for property controls
80
+
81
+
Domain components live one level above `UiKit`. They render reusable XpressFormula UI surfaces and return explicit action structs for one-shot commands. Panels and `Application` remain responsible for workflows, vector mutation, file/clipboard actions, export processing, and persistent state.
@@ -114,9 +116,10 @@ This is the core frame template you can reuse in similar apps.
114
116
115
117
## 6. Layout Strategy in This App
116
118
117
-
The app uses two fixed ImGui windows that fill the OS window:
119
+
The app uses borderless ImGui windows that fill the OS window:
118
120
119
121
- left sidebar (`##Sidebar`)
122
+
- vertical splitter (`##SidebarSplitter`)
120
123
- right plot area (`##Plot`)
121
124
122
125
This is done each frame by setting:
@@ -131,12 +134,44 @@ and then creating borderless windows with flags like:
131
134
-`NoMove`
132
135
-`NoCollapse`
133
136
137
+
The sidebar width is owned by `Application` state and adjusted through the splitter each frame, with min/max constraints so the plot retains usable space. The plot window reserves a small toolbar child before the canvas; the toolbar mutates the same `ViewTransform` and `PlotSettings` objects as the sidebar, so both control surfaces stay synchronized.
138
+
134
139
Why this pattern is useful:
135
140
136
141
- simple, deterministic layout
137
-
-no docking complexity
142
+
-resizable controls without docking complexity
138
143
- easy to extend for desktop tooling apps
139
144
145
+
## UI Toolkit Design Rules
146
+
147
+
XpressFormula has a thin UI toolkit in [`src/XpressFormula/UI/UiKit`](../src/XpressFormula/UI/UiKit) and XpressFormula-specific components in [`src/XpressFormula/UI/Components`](../src/XpressFormula/UI/Components).
148
+
149
+
Use `UiKit` for recurring ImGui mechanics:
150
+
151
+
- shared spacing, breakpoints, and dimensions in `UiMetrics`
152
+
- pure responsive plans such as toolbar, formula-card, splitter, and modal sizing decisions
153
+
- RAII guards for `Push/Pop`, `Begin/EndDisabled`, and text wrapping
154
+
- small structural helpers such as `ResponsiveRows` and `PropertyGrid`
155
+
156
+
Use `Components` for reusable app UI that still follows immediate mode:
157
+
158
+
-`PlotToolbar` renders the plot toolbar and returns one-shot app commands
159
+
-`FormulaCard` renders one formula row/card and returns one formula action
160
+
161
+
Panels and dialogs still own workflows and state. For example, `FormulaPanel` owns editor state and vector mutations, while `FormulaCard` only renders one card and reports the selected action.
162
+
163
+
Direct ImGui remains the default for ordinary controls such as buttons, text, sliders, checkboxes, and one-off layouts. Do not add wrappers that only rename ImGui calls.
164
+
165
+
> Add a toolkit abstraction only after the same layout or safety problem appears in at least two places, or when a pure tested plan can replace fragile cursor arithmetic.
166
+
167
+
When adding a reusable layout primitive:
168
+
169
+
1. Put pure decisions in a testable planner where practical.
170
+
2. Keep application state outside the toolkit.
171
+
3. Keep row counts, heights, and cursor placement derived from one calculation.
172
+
4. Return explicit action structs for one-shot commands instead of performing workflow side effects inside components.
173
+
5. Keep escape hatches simple by allowing direct ImGui alongside toolkit helpers.
174
+
140
175
## 7. State Ownership (Most Important ImGui Rule Here)
141
176
142
177
ImGui draws widgets, but your app owns the state.
@@ -147,6 +182,7 @@ In XpressFormula:
147
182
- camera/view state in `m_viewTransform`
148
183
- render settings in `m_plotSettings`
149
184
- transient export requests in booleans/action flags
185
+
- project path, dirty state, recent projects, and unsaved-change prompts in `Application`
150
186
151
187
Panels receive references to these objects and render widgets directly from them.
152
188
@@ -161,6 +197,22 @@ This is the recommended mental model:
161
197
162
198
- UI is just a view/editor for your application state
163
199
200
+
### Project Persistence State
201
+
202
+
Project files are deliberately outside the widget layer:
203
+
204
+
- `Application` owns live state and project workflow commands.
205
+
- `ProjectSession` converts live state to/from plain records.
206
+
- Serializers do not know about ImGui IDs, popups, panels, or layout.
207
+
- Open parses the full file before replacing active formulas, view, and plot settings.
208
+
- Invalid loaded formulas are copied into edit buffers, reparsed, and surfaced as warnings instead of being silently dropped.
209
+
- Dirty state is based on serialized state comparison with the last clean snapshot.
210
+
- Save writes to a temporary file, then replaces the target path.
211
+
212
+
The unsaved-change modal may request a close, but it must not destroy the Win32 window while ImGui is still rendering. It sets a deferred close flag; `Application::run()` processes that flag after the current frame is complete.
213
+
214
+
`ProjectSession.h` currently contains both the schema and JSON parser/serializer. Keep it as the persistence boundary unless the format grows enough to justify splitting schema records from parsing code.
215
+
164
216
## 8. Panel Communication Pattern
165
217
166
218
XpressFormula uses two patterns for panel communication:
@@ -179,6 +231,12 @@ Used by `ControlPanel` for one-shot actions:
179
231
180
232
- open export settings dialog
181
233
234
+
Used by the plot toolbar for direct application-level commands:
235
+
236
+
- fit/reset the view
237
+
- open export settings dialog
238
+
- apply deterministic 3D camera presets
239
+
182
240
Used by `Application` (outside panel code) for other side-effecting actions:
- expression row: clipped preview text with a full wrapped tooltip
275
+
- metadata row: parsed render type and validation state
276
+
- optional `z slice` control for scalar-field formulas
277
+
278
+
The card list defers structural mutations until after all cards are drawn for the frame. This matters because duplicating, moving, or deleting a `std::vector<FormulaEntry>` item during the loop would invalidate references used by later cards.
279
+
280
+
Reusable formula-list operations live in [`src/XpressFormula/UI/FormulaListActions.h`](../src/XpressFormula/UI/FormulaListActions.h). The helper covers duplicate, reorder, delete-index adjustment, hide-others, and exact expression-copy behavior, and is tested outside ImGui. Keep future formula-list state changes in that helper when practical.
281
+
211
282
### Live Validation in the Formula Editor
212
283
213
284
The editor does realtime validation while typing:
@@ -260,6 +331,7 @@ Then it delegates rendering to `PlotRenderer`, which draws:
260
331
- background
261
332
- grid
262
333
- axes
334
+
- corner HUD
263
335
- labels
264
336
- curves/heatmaps/triangles
265
337
@@ -268,6 +340,19 @@ This separation is important:
268
340
- `PlotPanel` handles interaction and viewport bounds
269
341
- `PlotRenderer` handles math and draw primitives
270
342
343
+
### Plot HUD and Wire Readability
344
+
345
+
`PlotSettings` owns the configurable plot HUD mode and the wire styling values. `PlotPanel` renders the HUD in a stable plot corner instead of using a cursor-following tooltip, so it does not cover central geometry while reading coordinates.
346
+
347
+
The wire controls are intentionally separate:
348
+
349
+
- `Surface Density` and `Implicit Resolution` change sampling/mesh quality
350
+
- `Wire Opacity` changes visual strength
351
+
- `Wire Thickness` changes line width
352
+
- `Wire Stride` changes displayed wire density without changing mesh sampling
353
+
354
+
Export preview/final rendering clones `PlotSettings`, so wire opacity, thickness, and stride match the interactive plot. Export overrides disable the interactive HUD so saved images do not include the corner readout.
355
+
271
356
## 11. Why the Plot Uses `ImDrawList` Instead of ImGui Widgets
272
357
273
358
The plotting output is custom geometry, not forms/controls.
@@ -306,11 +391,12 @@ The UI does not directly save images when a button is clicked.
306
391
Instead:
307
392
308
393
1. `ControlPanel` returns a one-shot action to open the export dialog.
309
-
2. `Application` owns and renders the export settings window (size, colors, background, include/exclude overlays).
310
-
3. When the user clicks **Save** or **Copy**, `Application` stores pending export flags + a snapshot of export settings.
311
-
4. Export dialog preview uses a cached offscreen render texture (refreshed outside the main UI frame to avoid nested ImGui frames).
312
-
5. `PlotPanel` receives temporary render overrides for export and is rendered into a temporary offscreen D3D11 render target (plot-only ImGui frame).
313
-
6. Export is processed after frame rendering (`processPendingExportActions()`), including pixel-format normalization (RGBA->BGRA, alpha handling) and post-processing (optional resize/grayscale), then file/clipboard output.
394
+
2. `Application` owns and renders the export settings window (size, aspect mode, colors, background, include/exclude overlays, preview controls).
395
+
3. Export aspect/framing is resolved through `ExportSettings` helpers before rendering so default exports preserve mathematical proportions.
396
+
4. When the user clicks **Save As...** or **Copy**, `Application` stores pending export flags + a snapshot of export settings.
397
+
5. Export dialog preview uses a cached offscreen render texture (refreshed outside the main UI frame to avoid nested ImGui frames), with Draft/Normal preview quality capped separately from final output.
398
+
6. `PlotPanel` receives temporary render overrides for export and is rendered into a temporary offscreen D3D11 render target (plot-only ImGui frame).
399
+
7. Export is processed after frame rendering (`processPendingExportActions()`), including pixel-format normalization (RGBA->BGRA, alpha handling) and post-processing (optional resize/grayscale), then file/clipboard output.
-[x] Release test executable also reported `359/359 tests passed`.
150
+
-[x] Existing core tests pass.
151
+
-[x] New project-session tests pass.
152
+
-[x] Export settings and metadata tests pass.
153
+
-[x] UI layout-plan tests pass.
154
+
-[x] Formula-list action tests pass.
155
+
156
+
Manual verification still required before tagging:
157
+
158
+
-[ ] Project workflows: New, Open valid `.xfplot`, Save, Save As, Recent reopen, dirty marker set/clear, Save/Discard/Cancel before New/Open/Close, unsupported schema error, malformed file error, invalid formula warning, Unicode formula round trip, multiple save/load cycles retaining values.
159
+
-[ ] Export workflows: every export profile, transparent PNG, grayscale export, metadata sidecar output, metadata JSON opens in a parser/editor, preview/final export parity, offscreen fallback behavior if reproducible.
160
+
-[ ] Responsive UI: wide/medium/compact/extra-compact toolbar, minimum/default/maximum sidebar width, narrow/short windows, formula cards at narrow widths, and 100%, 125%, 150%, and 200% Windows scaling where available.
0 commit comments