Repository navigation
Conversation
Adds src/CalculatorCompact: a native Win32 front end that links the existing CalcManager engine as-is and builds to a single self-contained Calculator.exe of 297 KB. Arithmetic, precision, operator precedence, radix conversion, history and memory all come from the shipping engine, so behaviour matches the packaged app; only the presentation layer (XAML/WinUI/MSIX) is replaced, with GDI/GDI+ drawing. Covers Standard, Scientific and Programmer modes, the memory strip and multi-slot memory list, the history panel with command replay, light and dark themes following the system setting, the system accent colour, per-monitor DPI v2 and the documented keyboard shortcuts. The unit and currency converters, graphing and date calculation are not included: they live in the app layer rather than the engine. CalcManager changes, all behaviour-preserving, made so the engine links without dragging the C++ runtime's iostream, locale and regex machinery into every consumer (that was ~620 KB of a 1292 KB starting binary): - scidisp.cpp: replace std::wregex with a hand-written parser for the same grammar. The expression is deterministic -- the separator and 'e' are never digits, so the greedy \d* runs never need backtracking. A new differential test checks the two against each other over ~75,000 generated inputs. - Ratpack/support.cpp, ratpak.h: guard the constant-dump helpers and their <iostream> include behind GEN_CONST, which is what already gates their only callers. - scicomm.cpp: format the rand command with swprintf rather than wstringstream (identical output in the C locale); seed mt19937 from the OS CSPRNG via rand_s on Windows, since libstdc++'s std::random_device reports failure with std::runtime_error and that one reference pulls in std::logic_error, narrow std::string and more; turn the operator-name table into a flat constexpr array of wstring_view, removing 78 heap allocations and 12.6 KB of start-up code. - Ratpack/support.cpp: add the <cmath> include that MSVC's precompiled header was supplying for ceil/log2. - Ratpack/conv.cpp: add <cwctype> for towupper, previously arriving transitively. - pch.h: guard <ppltasks.h> with _MSC_VER so non-MSVC toolchains can use the header, matching the note already at the top of the file. Engine tests build and run with the host compiler on any platform: src/CalculatorCompact/tests/run_tests.sh (61 checks). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
Extends the compact build to cover every mode this repository can actually build, in 349 KB (budget raised to 350 KB, enforced by build.sh). New in the front end: - Converter: all 12 non-currency categories and their 158 units, driven by CalcManager's own UnitConverter. Unit tables are generated by tools/gen_converter_data.py from the shipping app's UnitConverterDataLoader, NavCategory manifest and en-US resources, so categories, ordering, default unit pairs, conversion factors, the whimsical units and the "About equal to" suggestions all match. Includes Temperature's explicit ratio/offset table. - Date Calculation: difference between two dates and add/subtract years/months/days, with month/day/year pickers. Proleptic Gregorian arithmetic with the calendar's end-of-month clamping. - Navigation pane listing both category groups, Always on top, and a Settings/About page. - Scrollable dropdown flyouts, needed for 38-unit and century-long lists. Graphing and Currency are deliberately absent: neither is functional in this repository. src/GraphingImpl ships only MockGraphingImpl, whose MathSolver returns an empty graph, and CurrencyHttpClient.cs states that the upstream rate endpoints are dead and substitutes placeholder data. Both are noted in the README rather than shipped as stubs. CalcManager changes, all behaviour-preserving: - UnitConverter.h/.cpp: guard the PPL-based live-currency fetch behind CALC_SUPPORTS_CURRENCY_ASYNC (defined automatically for MSVC), so toolchains without ppltasks.h can build the converter. GetCurrencyConverterDataLoader avoids dynamic_pointer_cast when that path is compiled out, which also lets the engine build with -fno-rtti. - New Header Files/NumericString.h: integer, double and parse conversions that do not route through std::to_wstring, std::stod or the iostream manipulators. Those pull a private copy of the C++ runtime's floating-point conversion into every binary linking CalcManager -- about 31 KB even for callers that only format integers. Includes an exact fixed-point path for values in [0, 1), used by the rand command so its digits match the previous stream output rather than stopping at a CRT's 17 significant digits. - NumberFormattingUtils.cpp: use those helpers and drop pch.h, which was pulling <regex>, <sstream> and <iostream>. - scicomm.cpp, ExpressionCommand.cpp, CalcEngine.h, UnitConverter.cpp: use the helpers in place of to_wstring/stod. - scicomm.cpp, EngineStrings.h: store the operator-name table and g_sids as plain pointers rather than string views, halving their size and relocations. Front-end fix: the navigation pane is a full-surface layout, not a flyout, so only the dropdown menu captures the mouse. Previously every click while the pane was open fell through to CloseMenus, which cleared the nav flag without relaying out and swallowed the click. Tests grow to 130 checks, now covering unit conversions in every category (gallons, miles, all three temperature scales, data, angle, reverse-field editing) and date arithmetic (leap years, end-of-month clamping, weekday anchors, 14,286 day-number round trips, difference decomposition). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
Brings the front end's motion in line with the shipping app. Timings and curves follow the WinUI guidance it is built on: 167ms for a small state change, 250ms for a surface entering, and a decelerating curve for anything arriving on screen. - Navigation pane and settings page slide in from the left over a scrim. Both became overlay surfaces above the content (their own button list, drawn after it) rather than replacing the layout, which is what lets them move over the keypad instead of cutting to it. - Mode changes fade the new content in while it rises into place. - Dropdown flyouts fade in and lift. - The history and memory panel slides in: from the right when it docks beside the keypad, upward when it covers it. - Keys cross-fade their hover highlight from one to the next, and pointer-down shrinks the key slightly while the pressed fill fades in, matching the Fluent pressed state. Animated values are computed from the clock on demand rather than stepped, so a dropped frame cannot strand an animation part-way, and the frame timer only runs while something is moving. Transitions that need a real fade render to an offscreen layer and AlphaBlend it, because GDI text has no alpha of its own. Two bugs found while verifying this: - BuildLayout returned early for Converter and Date modes, so the navigation overlay was never built in them: the pane rendered empty and swallowed clicks. All exit paths now end at BuildOverlayLayout. - The content entrance animation started at zero and was only ever advanced by a mode change, so the first frame after launch drew the content at zero alpha. It now starts already arrived. Size is 357 KB against a budget raised to 425 KB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
Compared each mode side by side with the shipping app and corrected the places where the compact build diverged. Keypad: leave 6px between keys and the same margin around the grid rather than running the keys to the window edge, and drop the corner radius to 4px. Standard and Scientific: set mathematical variables in italic the way the shipping app does - x squared, one over x, the roots, x to the y, ten to the x, n factorial, absolute value - while operators and named functions stay upright. Scientific: move DEG and F-E above the memory strip and the Trigonometry and Function dropdowns below it. Remove the separate hyp toggle and fold the hyperbolic functions into the Trigonometry flyout, which now carries all twelve entries. Programmer: left-align the HEX/DEC/OCT/BIN rows and mark the active radix with an accent bar instead of filling the whole row, use the guillemets for the shift keys, and set "Bit shift" in sentence case. Date calculation: replace the three month/day/year spinners with one field per date reading "September 18, 2026", backed by a calendar flyout that reuses the existing flyout surface. Settings: rebuild as cards under "Appearance" and "About" section headers, with the theme choice and the always-on-top state as card subtitles. Combo boxes: the unit pickers and the date-mode picker now anchor their text to the leading edge and park the chevron at the trailing one, rather than centring the pair. Glyphs: M- uses a real minus sign. 363 KB, tests 130/130. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
Screenshots against the shipping app showed four things wrong. The title-bar icons came out in colour. They were font glyphs, and when the icon font is missing a codepoint the fallback characters can be substituted by a colour emoji font, which puts blue and orange into a title bar that should be monochrome. The hamburger, back arrow, history, keep-on-top, backspace, chevrons, calendar, gear, radio buttons and the settings header icons are now GDI+ paths on the same 16x16 grid the icon fonts use, so they render identically everywhere and cannot be recoloured. The navigation pane's category icons still come from the icon font; their codepoints already match NavCategory.cs exactly. The title-bar layout did not match MainPage.xaml: the keep-on-top button belongs immediately after the mode name, not at the window edge, the hamburger is a 48px square, and the mode name is SubtitleTextBlockStyle, 20px semibold rather than 15. The navigation pane covered the whole window. A NavigationView overlay pane is SplitViewOpenPaneLength wide, 256px, so the keypad stays visible beside it under the scrim. It also gains the WinUI selection indicator, a 3x16 accent bar on the leading edge of the selected item, and a scrollbar. "Date calculation" now matches DateCalculationModeText. Settings cycled the theme on every click of the App theme card. Per Settings.xaml it is a SettingsExpander: the card expands to Light / Dark / Use system setting radio buttons. About expands to the licence links, and the page closes with the "Send feedback" link and the contribute paragraph. The always-on-top card is gone, matching the shipping page, and the title bar button still toggles it. Animation frames are now paced against the compositor with DwmFlush from the message loop. WM_TIMER's 15.6ms granularity, low priority and coalescing are what made the motion stutter. The loop blocks on GetMessage again once everything settles, so an idle window still costs nothing. 373 KB, tests 130/130. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
The window's off-screen bitmaps dominate this app's footprint and were being rebuilt constantly. Every WM_PAINT created and destroyed a full-window bitmap, and a frame compositing a transition or a flyout fade created a second and sometimes a third. At 360x620x32bpp each one is 892KB. They are now held in a small Surface wrapper: recreated only when the client area changes size, shared between the two transitions that composite through AlphaBlend (they never overlap inside a frame -- the content layer is blended and finished with before the overlays are drawn), and released two seconds after everything settles, when the window is minimised, and on shutdown. An idle process holds no full-window bitmaps at all. Counted inside the app over an identical scripted session of three Standard/Scientific/Programmer cycles: 779 allocations totalling 663 MB before, 19 totalling 16 MB after. Also starts GDI+ with SuppressBackgroundThread and pumps its notification hook by hand, dropping a thread and its stack, and uses the settle timer to trim the working set and decommit the heap's free blocks, since laying out a mode churns a lot of small allocations the heap otherwise holds pages for. Nothing about what is drawn changes. Five screens captured before and after are pixel-identical, zero differing pixels across all of them. Note the requested target of 100KB is not reachable for a Win32 window: one back buffer for this window is 892KB by itself, and GDI+ maps several MB when it initialises. 374 KB, tests 130/130. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
There is nothing to port for the maths: src/GraphingImpl contains only MockGraphingImpl, whose MathSolver returns an empty graph, and the real solver and renderer are a closed-source component shipped separately. So the parsing, evaluation and plotting are written here. What is taken from the repository is the shape of the feature -- the function set in GraphingNumPad.xaml, the fourteen equation colours in App.xaml in both theme variants, and the NavCategory.cs placement, glyph and shortcut. Expressions compile to a flat postfix program rather than a node tree, which avoids per-node allocation and is a good deal less code, and evaluate over doubles on a fixed stack. The grammar covers implicit multiplication, a right-associative power operator, absolute-value bars, unparenthesised function arguments, the superscript forms the keypads produce, and pi and e. A leading "y=" is stripped, and anything that does not parse leaves the curve undrawn rather than guessing. Undefined points come back as NaN and break the curve, so sqrt(x) stops at the origin; a jump larger than four times the view height breaks it too, which keeps the two branches of 1/x from being joined through the asymptote. Curves are sampled once per pixel column. Dragging pans, the wheel and three corner buttons zoom, and the Trigonometry and Function flyouts shared with the scientific keypad write their function into the equation instead of sending a command to the engine. Not covered, because it needs the solver: the key graph features panel, implicit relations and inequalities. This plots explicit functions of x. Separately, Execute()'s ranged-action chain was not in descending order -- ACT_DATE_VALUE_BASE (10000) was tested before every higher base, so it swallowed the calendar's actions. Picking a day out of the date picker did nothing before this. The chain now runs highest first, and the comment on the bases says why the order matters. Alt+1..5 now number the modes as NavCategory.cs does, so Alt+3 is Graphing. 399 KB, 25 KB of it the graphing mode, against a 75 KB budget. Tests 175/175. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
Adding the graphing mode pushed it to 399 KB. A linker map found the way back, and none of it touches behaviour. Link-time optimisation is worth about 52 KB. It ICEd on the first attempt during the original size work (binds_to_current_def_p, GCC 13 mingw), which is why the build has been without it; -flto-partition=none avoids the ICE. Another 22 KB comes from a force-included prelude that sets _GLIBCXX_EXTERN_TEMPLATE to -1. libstdc++ ships std::wstring as an explicit instantiation and declares it extern template, so any use of it links the whole of wstring-inst.o, most of it members this app never calls. c++config.h defines the macro unconditionally, so it cannot be set from the command line; including that header first and redefining it works because of its own include guard. The members actually used are then emitted locally, where -ffunction-sections and --gc-sections can drop the rest. The rest is CRT shims that had cheaper equivalents already linked: GetLocalTime in place of time plus localtime_s, and RtlGenRandom -- which is what rand_s calls -- in place of rand_s, which also drops the secure parameter handler behind them. About 10 KB of linked code, though PE section alignment absorbs most of it in the file. Also --disable-runtime-pseudo-reloc, since nothing here relies on auto-import. 409,088 bytes to 327,680, and the build now fails over 350 KB rather than 425. Every mode was captured before and after, including the arithmetic and divide-by-zero paths, and compared channel by channel: zero differing pixels across all eight screens. Tests 175/175. Two things were measured and then rejected rather than kept. Replacing libm's pow, cbrt and inverse hyperbolics with identities built from log, exp and sqrt saves about 13 KB, but it moves results by a few ulp, and LTO alone made the target, so precision stayed exactly as it was. And -fno-exceptions on the UI translation units is not available at all, since main.cpp catches what the engine throws. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
The display's auto-fit had two faults that only showed on the longest values the app can produce. It never measured its own floor: the loop condition was `while (dip > 16)`, so on reaching 16 it exited with the font from the previous iteration still selected and drew at 18, one step larger than intended and never checked. And when the text still did not fit, it was drawn right-aligned and clipped, which takes the characters off the *front* of a number. A scientific result read ".3333333333333333333333333333333e-1" with its leading digit gone, and a 64-bit binary word showed eight nibbles of sixteen with the first one cut through the middle. Both are worse than wrong: they are plausible. The floor now goes to 8 and is itself measured, which is low enough for the two genuine worst cases -- 64 binary digits with 15 nibble separators, and 32 significant digits plus an exponent. Small type is better typography than a number missing its leading digit. Values that already fitted are untouched, since the loop still stops at the first size that fits: the 12 + 34 = 46 screen is pixel-identical before and after. Found by driving the app: Programmer, HEX, sixteen Fs, switch to BIN; and Scientific, 1 / 3 =, F-E. Tests 175/175. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YLNYYTAuCvdsCs4jg7MCR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #.
Description of the changes:
How changes were validated: