Skip to content

Add dependency-free NumericUpDown control for MAUI#60

Open
tig wants to merge 86 commits into
mainfrom
claude/hopeful-rubin-M9bCR
Open

Add dependency-free NumericUpDown control for MAUI#60
tig wants to merge 86 commits into
mainfrom
claude/hopeful-rubin-M9bCR

Conversation

@tig

@tig tig commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a reusable NumericUpDown control to WinPrint.Maui, built entirely on Microsoft.Maui.Controls primitives — no third-party UI toolkit dependency. This is the option-1 approach (combine an Entry with Stepper-style buttons) requested for the WinForms→MAUI port.

The control is a code-defined ContentView placing the up/down buttons below the text box, with these bindable properties:

Property Type Purpose
Value double Two-way bindable current value
Increment double Step applied by the up/down buttons
IsInteger bool Whole-numbers-only mode (rounds + formats without decimals)
Minimum / Maximum double Bounds, enforced via value coercion

Behavior:

  • Coercion clamps Value into [Minimum, Maximum] and rounds to whole numbers when IsInteger is set — applied for button clicks, bindings, and typed input alike.
  • Typed text is parsed on Completed/Unfocused, then snapped back to the canonical value; invalid input reverts.
  • Changing Minimum, Maximum, or IsInteger re-applies coercion to the current value.
  • StepUp() / StepDown() are public so callers can drive it programmatically.

MainPage.xaml gains two live examples (an integer "Copies" 1–999 and a decimal "Margin" 0–10 stepping by 0.25).

Tests

Adds tests/WinPrint.Maui.UnitTests, a platform-neutral net10.0 xUnit project that compiles the control's source directly. Because the MAUI app project targets only windows/maccatalyst (which can't build on Linux), compiling the source into a neutral test assembly lets the cross-platform logic build and run on any host, including CI on Linux. 15 tests cover clamping, integer rounding (half-away-from-zero), decimal preservation, step math, bound-respecting steps, and re-coercion when settings change.

Bugs caught while verifying

Building and running the code surfaced real defects that are fixed in this PR:

  1. CS0102 name collision — the increment/decrement methods shared the name Increment with the step property. Renamed to StepUp/StepDown.
  2. Coercion not re-applied — relying on BindableObject.CoerceValue to re-clamp when Minimum/Maximum/IsInteger changed did not actually re-apply coercion (its semantics differ from WPF). Replaced with an explicit ReapplyCoercion that re-runs the coerce delegate and refreshes the text.

Notes / not covered

  • The windows/maccatalyst app build and the MainPage.xaml wiring still need a real Windows/Mac build to validate end-to-end — the MAUI workload doesn't build those targets on Linux. The control's C# is verified-compiling and unit-tested.
  • Press-and-hold auto-repeat is not implemented (one step per click); the ▲/▼ glyphs can be swapped for icons if preferred.

https://claude.ai/code/session_01NrcwMcieTftQCjVg9D28Cz


Generated by Claude Code

tig and others added 28 commits May 26, 2026 14:48
- Remove git submodules: src/libvt100, src/PowershellAsync, src/LiteHtmlSharp
- Delete src/WinPrint.PowerShell (PS cmdlet project)
- Delete src/WinPrint.LiteHtml (replaced by TextMateSharp)
- Delete proto/ (old UWP/WPF/node/TypeScript prototypes)
- Delete tools/ (pygmentize.exe and pygments scripts replaced by TextMateSharp)
- Update src/WinPrint.slnx: remove External folder, WinPrint.LiteHtml, WinPrint.PowerShell
- Update WinPrint.Core.csproj: remove ProjectReferences to deleted projects
- Stub AnsiCte and HtmlCte to compile without deleted native deps (libvt100/LiteHtmlSharp)
- Remove LiteHtmlSharp using from WinPrint.WinForms/MainWindow.cs
- Remove pygmentize.exe PostBuild copy from WinPrint.WinForms and UnitTests csproj
- Fix WinPrint.cli icon reference (was pointing into deleted WinPrint.PowerShell dir)
- Skip AnsiCte rendering tests since libvt100 submodule is removed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove dead/obsolete projects and dependencies
Introduce IGraphicsContext and IPrintService in WinPrint.Core, add a System.Drawing graphics adapter, and route SheetViewModel/CTE/HeaderFooter painting through the abstraction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add root .editorconfig modeled on gui-cs/clet (file-scoped namespaces, var-when-apparent, space before parens) merged with universal indent rules from gui-cs/cli.
- Add Directory.Build.props enabling Nullable, TreatWarningsAsErrors, EnforceCodeStyleInBuild, and latest LangVersion across all projects.
- Add empty Directory.Build.targets.
- Add .config/dotnet-tools.json pinning JetBrains ReSharper CLI tools.
- Replace dotnetcore.yml with ci.yml mirroring gui-cs/cli: build, jb cleanupcode + dotnet format style verification, test.
- Replace local Terminal.Gui.Cli submodule with published NuGet package Terminal.Gui.Cli 0.1.0-develop.5.
- Remove redundant per-project Nullable and LangVersion settings now provided by Directory.Build.props.
- Add Solution Items references for Directory.Build.* and .editorconfig.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Applied dotnet format with IDE0161 (file-scoped namespaces) and IDE0011 (add braces) across all projects. Also normalizes line endings to LF per new .gitattributes and .editorconfig settings.

No behavior changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run JetBrains ReSharper cleanupcode + dotnet format to bring the codebase to the canonical CI style. Pure formatting changes; no behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New Roslyn analyzer project under tools/WinPrint.Analyzers/:
  WPA0001 - One type per file
  WPA0002 - No nested type declarations

Generated files (*.Designer.cs, *.Generated.cs, *.g.cs, *.g.i.cs,
anything under bin/ or obj/, and files with <auto-generated> headers)
are exempt. All rules are warnings, escalated to errors via the existing
TreatWarningsAsErrors setting in Directory.Build.props.

Directory.Build.props wires the analyzer into every project except the
analyzer project itself via ProjectReference with OutputItemType=Analyzer.

Violations still need to be fixed in follow-up commits.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The nullable cleanup pass initialized Settings.location and Settings.size
to 'new ()' to satisfy non-nullable field requirements. This caused
MainWindow's 'if (Settings.Size != null) Size = new Size(Width, Height)'
guard to take the branch on first run, sizing the window to 0x0 / 0,0.

Make the backing fields and properties nullable instead, restoring the
pre-existing semantics where null means 'use OS/WinForms default'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Define graphics and print abstractions in WinPrint.Core
Add initial .NET MAUI scaffold with WinPrint.Core integration hooks and solution registration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After the rebase onto post-PR-#58 develop, the new WinPrint.Maui project
breaks CI in three ways. Address them on this branch:

* Install the maui workload before restore (NETSDK1147 maui-tizen).
* Switch CI restore/build from msbuild to dotnet because the
  MacCatalyst SDK targets can't load Xamarin.MacDev.Tasks under the
  .NET Framework MSBuild.exe shipped by setup-msbuild.
* Suppress NU1902/NU1903 in WinPrint.Maui.csproj only - they come from
  transitive MAUI template deps (System.Private.Uri 4.3.0) we don't
  reference directly and can't upgrade. Other projects still enforce
  the audit.
* Apply jb cleanupcode + dotnet format to the new MAUI files so the
  'Verify code style' step is clean.

Addresses Codex review on src/WinPrint.slnx and items 1-6 of @tig's
review comment on PR #57.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When dotnet build src/WinPrint.slnx builds the two WinPrint.Maui
target frameworks (net10.0-windows10.0.19041.0 and net10.0-maccatalyst)
in parallel, both reference WinPrint.Analyzers via Directory.Build.props.
This races on writing the analyzer DLL and fails with CS2012
('Cannot open ... for writing'). Build the analyzer once up-front so
the solution build only loads it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create WinPrint.Maui project shell
Reusable ContentView combining an Entry with up/down buttons placed below
the text box. Supports configurable Increment, integer-vs-decimal mode via
IsInteger, and Minimum/Maximum bounds with value coercion (clamping and
integer rounding). Built only on Microsoft.Maui.Controls primitives.

Adds usage examples to MainPage.
The increment/decrement methods shared the name 'Increment' with the step
property (CS0102). Renamed them to StepUp/StepDown. Verified the control
compiles cleanly against Microsoft.Maui.Controls 10.0.20 with warnings as
errors.
Adds WinPrint.Maui.UnitTests, a platform-neutral (net10.0) xUnit project that
compiles the control's source directly so the cross-platform logic can be
tested and run on any host (including Linux CI). 15 tests cover clamping,
integer rounding, and step math.

Writing the tests surfaced a real bug: relying on BindableObject.CoerceValue
to re-clamp the value when bounds or IsInteger changed did not actually
re-apply coercion. Replaced with an explicit ReapplyCoercion that re-runs the
coerce delegate and refreshes the text.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c0615e35f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +56
LogService.TraceMessage ("HtmlCte is a stub - LiteHtmlSharp dependency removed.");
return await Task.FromResult (0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore HTML rendering instead of returning zero pages

When a user opens or prints an .htm/.html file, the default settings still map those extensions to text/html, and this class still advertises text/html, but RenderAsync now just logs and returns 0 pages while PaintPage is empty. That turns HTML printing into a silent blank/no-page output path, whereas the parent implementation rendered the document through LiteHtml.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/ci.yml
git diff --exit-code

- name: Test
run: dotnet test tests/WinPrint.Core.UnitTests/WinPrint.Core.UnitTests.csproj --configuration Debug -p:Platform=x64 --no-build --verbosity normal

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the new MAUI tests in CI

This workflow's only test step still runs WinPrint.Core.UnitTests, and the new WinPrint.Maui.UnitTests project is not referenced from src/WinPrint.slnx either, so the NumericUpDown tests added by this commit are not restored, built, or executed on PRs. Regressions in the new control can therefore merge even though the commit relies on those tests to validate clamping, rounding, and step behavior.

Useful? React with 👍 / 👎.

tig commented May 29, 2026

Copy link
Copy Markdown
Owner Author

Context for reviewers / future sessions

Notes that aren't obvious from the diff:

  • This is the dependency-free option (combine Entry + buttons), chosen over Syncfusion/Telerik/Xceed to avoid a new UI dependency. Up/down buttons sit below the text box; bindable Value/Increment/IsInteger/Minimum/Maximum with value coercion.
  • The increment/decrement methods are named StepUp/StepDown because Increment collided with the step property (a CS0102 the compiler caught) — don't rename them back.
  • Build/verification caveat: the MAUI app can't be built on the Linux container this was developed in (the maui workload isn't supported there). The control's C# was type-checked against Microsoft.Maui.Controls and is covered by the coercion unit tests, but the XAML wiring and on-device layout still need a real Windows/Mac build.
  • CI gap: .github/workflows/ci.yml only runs WinPrint.Core.UnitTests, so this PR's MAUI NumericUpDown tests won't execute in CI. If you want them gated, the test step (or a separate job) needs to run the MAUI test project too.
  • Not implemented: press-and-hold auto-repeat (one step per click). Easy follow-up if wanted.

(Separately, PR #65 adds a web SessionStart hook + CLAUDE.md to make future sessions test-ready and capture architecture/decisions.)


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants