Skip to content

Commit 9670ae5

Browse files
authored
Merge pull request #11 from russlank/feature/ui-upgrade
feat: responsive UI, reproducible export, and project sessions
2 parents 6323cc2 + 6e2234e commit 9670ae5

47 files changed

Lines changed: 6689 additions & 385 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ requirements.txt
9494
/build/**/*
9595
/artifacts/**/*
9696
/src/XpressFormula/imgui.ini
97+
/buildtmp_*/**/*
98+
/.github/upgrades/**/*
9799

98100
.ai/**/*
99101
**/*/.ai/**/*

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ The app supports:
1818
- `f(x,y,z)` scalar-field cross-sections at configurable `z` slices
1919
- `F(x,y,z)=0` implicit 3D surfaces (e.g. spheres and torus equations)
2020
- Auto/Force 2D/3D rendering preference with projected 3D grid-plane interleaving (`z=0`) for clearer depth ordering
21-
- Plot export dialog with size presets/scales, background modes including transparency, scene toggles, export-only quality/supersampling, `.png`/`.bmp` save, clipboard copy, and post-save actions
21+
- Resizable ImGui sidebar with aligned 3D camera/display property rows, per-control resets, and a compact plot toolbar for common view/export actions
22+
- Versioned `.xfplot` project files with New/Open/Save/Save As, recent projects, dirty-state tracking, and unsaved-change protection
23+
- Plot export profiles, aspect-safe framing, preview quality controls, transparent output, quality overrides, and optional JSON metadata sidecars
2224
- Startup/manual update checks against GitHub releases with quick link to the releases page
2325
- Versioned binary metadata and installer packaging (`.msi` + setup `.exe`)
2426

doc/algorithms-guide.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,12 @@ Then triangles are:
454454
2. sorted by depth
455455
3. drawn with fill color and optional wireframe
456456

457+
Wire density is separate from mesh resolution:
458+
459+
- explicit `z=f(x,y)` surfaces draw wire rows/columns using `Wire Stride`
460+
- implicit meshes draw a stride-filtered subset of mesh edges
461+
- changing stride affects readability and draw cost, but does not resample the surface
462+
457463
Important alignment detail (recent fix):
458464

459465
- 3D projected geometry is anchored to the same world origin (`0,0`) used by the 2D grid/axes.

doc/architecture.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ XpressFormula is organized into three primary layers:
2222
- [`src/XpressFormula/Core/UpdateVersionUtils.h`](../src/XpressFormula/Core/UpdateVersionUtils.h)
2323
- Small header-only utilities for semantic-version parsing/comparison and extracting GitHub release fields from API JSON.
2424
- [`src/XpressFormula/UI/Application.h`](../src/XpressFormula/UI/Application.h) and [`src/XpressFormula/UI/Application.cpp`](../src/XpressFormula/UI/Application.cpp)
25-
- Owns Win32 window, D3D11 resources, ImGui lifecycle, frame loop.
25+
- Owns Win32 window, D3D11 resources, ImGui lifecycle, frame loop, main sidebar/plot layout state, plot toolbar actions, and export workflow.
26+
- [`src/XpressFormula/UI/UiKit`](../src/XpressFormula/UI/UiKit)
27+
- Thin immediate-mode UI helpers: shared metrics, pure responsive layout planners, RAII ImGui scopes, deterministic toolbar rows, property grids, splitter sizing, and modal sizing.
28+
- [`src/XpressFormula/UI/Components`](../src/XpressFormula/UI/Components)
29+
- 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`.
2630
- [`src/XpressFormula/UI/FormulaPanel.h`](../src/XpressFormula/UI/FormulaPanel.h) and [`src/XpressFormula/UI/FormulaPanel.cpp`](../src/XpressFormula/UI/FormulaPanel.cpp)
27-
- Formula list management and per-formula controls.
31+
- Formula list management, editor modal workflow, collection mutations, and action handling returned by formula-card components.
2832
- [`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.
3034
- [`src/XpressFormula/UI/PlotPanel.h`](../src/XpressFormula/UI/PlotPanel.h) and [`src/XpressFormula/UI/PlotPanel.cpp`](../src/XpressFormula/UI/PlotPanel.cpp)
3135
- Interactive plotting area, mouse interactions, and export-time plot render overrides (background/grid/coordinates/wires).
36+
- [`src/XpressFormula/UI/ProjectSession.h`](../src/XpressFormula/UI/ProjectSession.h)
37+
- Versioned `.xfplot` persistence boundary: plain session records, JSON serialization/parsing, schema validation, and safe application of loaded values.
3238
- [`src/XpressFormula/Version.h`](../src/XpressFormula/Version.h)
3339
- Centralized semantic version metadata used by window title, resources, and packaging.
3440
- [`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:
4349
5. `PlotPanel` updates `ViewTransform` from current viewport and delegates drawing to `PlotRenderer`.
4450
6. `PlotRenderer` evaluates formulas through `Core::Evaluator` and draws based on variable dimensionality and equation form.
4551
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.
4782

4883
## Formula Rendering Modes
4984

doc/imgui-implementation-guide.md

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ The UI is split into focused panels:
4040
- plot canvas region, mouse interaction, renderer dispatch
4141
- [`src/XpressFormula/UI/PlotSettings.h`](../src/XpressFormula/UI/PlotSettings.h)
4242
- shared settings for rendering and camera behavior
43+
- [`src/XpressFormula/UI/ProjectSession.h`](../src/XpressFormula/UI/ProjectSession.h)
44+
- versioned `.xfplot` records, JSON serialization/parsing, and safe session application helpers
4345

4446
Why this split works well:
4547

@@ -114,9 +116,10 @@ This is the core frame template you can reuse in similar apps.
114116

115117
## 6. Layout Strategy in This App
116118

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:
118120

119121
- left sidebar (`##Sidebar`)
122+
- vertical splitter (`##SidebarSplitter`)
120123
- right plot area (`##Plot`)
121124

122125
This is done each frame by setting:
@@ -131,12 +134,44 @@ and then creating borderless windows with flags like:
131134
- `NoMove`
132135
- `NoCollapse`
133136

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+
134139
Why this pattern is useful:
135140

136141
- simple, deterministic layout
137-
- no docking complexity
142+
- resizable controls without docking complexity
138143
- easy to extend for desktop tooling apps
139144

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+
140175
## 7. State Ownership (Most Important ImGui Rule Here)
141176

142177
ImGui draws widgets, but your app owns the state.
@@ -147,6 +182,7 @@ In XpressFormula:
147182
- camera/view state in `m_viewTransform`
148183
- render settings in `m_plotSettings`
149184
- transient export requests in booleans/action flags
185+
- project path, dirty state, recent projects, and unsaved-change prompts in `Application`
150186

151187
Panels receive references to these objects and render widgets directly from them.
152188

@@ -161,6 +197,22 @@ This is the recommended mental model:
161197
162198
- UI is just a view/editor for your application state
163199
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+
164216
## 8. Panel Communication Pattern
165217
166218
XpressFormula uses two patterns for panel communication:
@@ -179,6 +231,12 @@ Used by `ControlPanel` for one-shot actions:
179231
180232
- open export settings dialog
181233
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+
182240
Used by `Application` (outside panel code) for other side-effecting actions:
183241
184242
- background update checks (GitHub releases)
@@ -208,6 +266,19 @@ Why "next frame" popup opening is common:
208266
- ImGui popups are frame-driven
209267
- opening and rendering often happens in a controlled sequence
210268
269+
### Formula Cards and List Actions
270+
271+
The formula sidebar renders each formula as a compact card:
272+
273+
- header row: visibility, color, formula index, validation status, edit/actions/delete controls
274+
- 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+
211282
### Live Validation in the Formula Editor
212283
213284
The editor does realtime validation while typing:
@@ -260,6 +331,7 @@ Then it delegates rendering to `PlotRenderer`, which draws:
260331
- background
261332
- grid
262333
- axes
334+
- corner HUD
263335
- labels
264336
- curves/heatmaps/triangles
265337
@@ -268,6 +340,19 @@ This separation is important:
268340
- `PlotPanel` handles interaction and viewport bounds
269341
- `PlotRenderer` handles math and draw primitives
270342
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+
271356
## 11. Why the Plot Uses `ImDrawList` Instead of ImGui Widgets
272357
273358
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.
306391
Instead:
307392
308393
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.
314400
315401
This avoids mixing:
316402

doc/release-packaging.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,35 @@ $expectedTag = "v$version"
130130
Write-Host "Expected release tag: $expectedTag"
131131
```
132132

133+
## v1.6.0 Release Verification Record
134+
135+
Last updated: 2026-07-18
136+
137+
Automated verification completed locally with Visual Studio MSBuild 18.8.2:
138+
139+
- [x] Debug x64 app build succeeds:
140+
`MSBuild src\XpressFormula\XpressFormula.vcxproj /p:Configuration=Debug /p:Platform=x64 /p:SolutionDir="C:\MyData\Projects\Digixoil\XpressFormula\src\" /m`
141+
- [x] Release x64 app build succeeds:
142+
`MSBuild src\XpressFormula\XpressFormula.vcxproj /p:Configuration=Release /p:Platform=x64 /p:SolutionDir="C:\MyData\Projects\Digixoil\XpressFormula\src\" /m`
143+
- [x] Debug x64 test project builds:
144+
`MSBuild src\XpressFormula.Tests\XpressFormula.Tests.vcxproj /p:Configuration=Debug /p:Platform=x64 /m`
145+
- [x] Release x64 test project builds:
146+
`MSBuild src\XpressFormula.Tests\XpressFormula.Tests.vcxproj /p:Configuration=Release /p:Platform=x64 /m`
147+
- [x] No new compiler warnings in the above builds (`0 Warning(s)`).
148+
- [x] Automated tests pass: `src\XpressFormula.Tests\x64\Debug\XpressFormula.Tests.exe` reported `359/359 tests passed`.
149+
- [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.
161+
133162
## How to Change Versions
134163

135164
1. Application version:

0 commit comments

Comments
 (0)