From ed7622ba4009cea173754efba0cb60cd4f2e664e Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 20:46:54 -0400 Subject: [PATCH 01/11] Replace stale-sibling-size layout with measure/arrange split StackView::layoutSubviews sized and positioned siblings from each subview's leftover frame.w/h from the previous layout pass, so a Contain-sized child's real intrinsic size was only known after it had already been used (stale) to size and position its siblings within the same pass -- no fixed point was guaranteed, and a Fill child inside a Contain ancestor was flatly unresolvable, since the ancestor can't hand a real bound to a child it's still sizing itself from. Split layout into a bottom-up View::sizeThatSatisfies pass (what size do you want, given this ViewConstraint) that runs to completion for a dirty subtree before any frame is committed, followed by the existing top-down View::layoutSubviews arrange pass. A Fill child measured while its Contain ancestor is itself unresolved receives ViewConstraintUnspecified and degrades to its own intrinsic size, rather than reading a stale number; it only actually fills once an ancestor hands it ViewConstraintEqual, during arrange. View::sizeToSatisfy joins sizeToContain/sizeToFit/sizeToFill as the resize-and-mark-dirty sibling of sizeThatSatisfies. layoutIfNeeded, base layoutSubviews's arrange loop, StackView's arrange loop, and Panel's contentView bypass each used to hand-duplicate a "resize (or sizeToSatisfy), clearWarnings, layoutSubviews, needsLayout = false" sequence, with a comment at each explaining why layoutIfNeeded itself couldn't be called instead (it would re-derive its own, weaker guess at the applicable constraint instead of using the one the caller already resolved). That shared tail is now View::layoutWithConstraint ("what size do you want, given this ViewConstraint" -- resolved via sizeToSatisfy, honoring ViewAutoresizingWidth/Height per axis) and View::layoutWithSize ("this is your size" -- applied verbatim, no negotiation, for StackView's and CollectionView's own distribution math, which overrides a subview's size regardless of its own autoresizing bits). needsLayout = false is now written in exactly one place in the codebase instead of four, and layoutWithConstraint always resolves regardless of isContainer -- gating it on isContainer (an earlier version of this same change) skipped resizing entirely for a Fill-only, non-container View, such as Slider's `bar`. The `constrainedSize` field this went through along the way -- cached by sizeThatSatisfies, consumed by StackView -- is gone; sizeThatSatisfies is a pure function of a View's own state, and StackView holds its subviews' measured sizes in a plain local array scoped to its own layoutSubviews call, not on the View instances themselves. Fixes found only by running Examples/Hello against real widgets, not caught by the synthetic fixture tests: sizeThatSatisfies must skip a Contain view whose own sizeThatFits override (Text, TableView, Select) is meaningful only when that view opted into Contain/Fit itself (TableView.c), a StackView subview without a matching autoresizing bit must not have its distribution-computed size re-derived through a constraint it never agreed to honor, and a View re-laid-out standalone (e.g. a selected TableRowView, whose style rebind marks only itself dirty) must trust its own established frame as exact rather than merely an upper bound, or it shrinks back to its unconstrained content size. Slider and TextView needed an explicit `min-width` in CSS for the same reason a Contain view's authored size can't otherwise survive being summed from children that have none of their own. Adds Tests/ObjectivelyMVC/View.c (StackView and plain-View fixture graphs asserting exact post-layout frame values) as a fourth check_PROGRAMS entry alongside Selector/Style/Stylesheet -- there was previously no ObjectivelyMVC-View test target at all. --- Assets/stylesheet.css | 2 + Assets/stylesheet.css.h | 271 ++++++++++++------------ Sources/ObjectivelyMVC/CollectionView.c | 7 +- Sources/ObjectivelyMVC/Panel.c | 8 +- Sources/ObjectivelyMVC/StackView.c | 60 ++++-- Sources/ObjectivelyMVC/TableView.c | 8 + Sources/ObjectivelyMVC/View.c | 141 ++++++++---- Sources/ObjectivelyMVC/View.h | 134 +++++++++++- Tests/ObjectivelyMVC/.gitignore | 1 + Tests/ObjectivelyMVC/Makefile.am | 3 +- Tests/ObjectivelyMVC/View.c | 259 ++++++++++++++++++++++ 11 files changed, 696 insertions(+), 198 deletions(-) create mode 100644 Tests/ObjectivelyMVC/View.c diff --git a/Assets/stylesheet.css b/Assets/stylesheet.css index 96e1045c..f21276f3 100644 --- a/Assets/stylesheet.css +++ b/Assets/stylesheet.css @@ -211,6 +211,7 @@ Select > StackView { Slider { autoresizing-mask: contain; + min-width: 140; width: 140; } @@ -315,6 +316,7 @@ TextView { autoresizing-mask: contain; background-color: #22222266; bevel: inset; + min-width: 160; padding: 4 4 4 4; width: 160; } diff --git a/Assets/stylesheet.css.h b/Assets/stylesheet.css.h index 1202b8d9..5096e5d7 100644 --- a/Assets/stylesheet.css.h +++ b/Assets/stylesheet.css.h @@ -257,142 +257,145 @@ unsigned char stylesheet_css[] = { 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, - 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, - 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x62, 0x61, 0x72, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, - 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, - 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, - 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, - 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, - 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x62, 0x61, 0x72, 0x20, 0x3e, - 0x20, 0x2e, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, - 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, + 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, + 0x2e, 0x62, 0x61, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, + 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x61, + 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, + 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, + 0x2e, 0x62, 0x61, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x68, 0x61, 0x6e, 0x64, + 0x6c, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x62, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, + 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x38, 0x3b, 0x0a, 0x20, + 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x20, 0x31, 0x36, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, + 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, + 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x38, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, + 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, + 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, + 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, + 0x20, 0x20, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x2d, 0x73, 0x75, 0x62, 0x76, + 0x69, 0x65, 0x77, 0x73, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, + 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x56, 0x69, 0x65, 0x77, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x31, 0x32, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, + 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, + 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, + 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, + 0x3a, 0x20, 0x30, 0x20, 0x32, 0x20, 0x30, 0x20, 0x32, 0x3b, 0x0a, 0x20, + 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, + 0x38, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x32, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, + 0x65, 0x77, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, + 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x32, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, + 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, + 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76, + 0x65, 0x6e, 0x29, 0x20, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, + 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, - 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x66, - 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, - 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, - 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, - 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x66, 0x66, 0x3b, - 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x3a, 0x20, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x36, 0x3b, 0x0a, 0x7d, - 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, - 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, - 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x31, 0x38, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, - 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, - 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, - 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6c, 0x69, 0x70, - 0x73, 0x2d, 0x73, 0x75, 0x62, 0x76, 0x69, 0x65, 0x77, 0x73, 0x3a, 0x20, - 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, - 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, - 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, - 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, - 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x63, 0x6b, - 0x56, 0x69, 0x65, 0x77, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, - 0x6e, 0x67, 0x3a, 0x20, 0x31, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, - 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x70, - 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x32, 0x20, - 0x30, 0x20, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x38, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, - 0x32, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, - 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x54, - 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, - 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, - 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x31, + 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, + 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, + 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, + 0x20, 0x3e, 0x20, 0x53, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, + 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, + 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, + 0x20, 0x2e, 0x74, 0x61, 0x62, 0x50, 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x73, 0x69, 0x6c, 0x76, + 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, + 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, + 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, + 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, + 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, + 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, + 0x6c, 0x6c, 0x2d, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x3b, 0x0a, + 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, + 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, + 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, - 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x32, 0x32, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, - 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, - 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, - 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, - 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, - 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, - 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, - 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, - 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, - 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x29, 0x20, 0x54, 0x61, - 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, - 0x66, 0x66, 0x66, 0x66, 0x66, 0x31, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, - 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, - 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x53, 0x63, 0x72, - 0x6f, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, - 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, - 0x68, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, - 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, - 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, - 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x50, - 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, - 0x3a, 0x20, 0x73, 0x69, 0x6c, 0x76, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, - 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, - 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, - 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x65, 0x71, 0x75, - 0x61, 0x6c, 0x6c, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, - 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, - 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, - 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, - 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, - 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, - 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, + 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x3e, + 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x72, 0x65, 0x79, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, + 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, + 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x65, + 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, + 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, + 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, + 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, - 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, - 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, - 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, - 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x64, 0x69, 0x73, 0x61, - 0x62, 0x6c, 0x65, 0x64, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, - 0x72, 0x65, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, - 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, - 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, - 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x3e, 0x20, 0x54, 0x65, - 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, - 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, - 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, - 0x20, 0x23, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x36, 0x36, 0x3b, 0x0a, - 0x20, 0x20, 0x62, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x20, 0x69, 0x6e, 0x73, - 0x65, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x36, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x65, 0x76, 0x65, + 0x6c, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x74, 0x3b, 0x0a, 0x20, 0x20, + 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, + 0x36, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x36, 0x30, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, @@ -431,4 +434,4 @@ unsigned char stylesheet_css[] = { 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x69, 0x6d, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x00 }; -unsigned int stylesheet_css_len = 5170; +unsigned int stylesheet_css_len = 5206; diff --git a/Sources/ObjectivelyMVC/CollectionView.c b/Sources/ObjectivelyMVC/CollectionView.c index 200fb61c..e3d6708f 100644 --- a/Sources/ObjectivelyMVC/CollectionView.c +++ b/Sources/ObjectivelyMVC/CollectionView.c @@ -96,8 +96,11 @@ static void layoutSubviews(View *self) { CollectionItemView *item = (CollectionItemView *) $(items, objectAtIndex, i); - $((View *) item, resize, &this->itemSize); - $((View *) item, layoutIfNeeded); + // itemSize is dictated here, not negotiated: layoutWithSize applies it verbatim, since + // layoutIfNeeded would instead offer it as an Equal ViewConstraint, which a Contain item + // with no ViewAutoresizingWidth/Height of its own would ignore in favor of its own + // sizeThatFits value (see View::layoutWithSize). + $((View *) item, layoutWithSize, &this->itemSize); item->view.frame.x = x; item->view.frame.y = y; diff --git a/Sources/ObjectivelyMVC/Panel.c b/Sources/ObjectivelyMVC/Panel.c index e1553815..7523d818 100644 --- a/Sources/ObjectivelyMVC/Panel.c +++ b/Sources/ObjectivelyMVC/Panel.c @@ -108,8 +108,12 @@ static void layoutSubviews(View *self) { const SDL_Size size = $(this, contentSize); - $((View *) this->contentView, resize, &size); - $((View *) this->contentView, layoutIfNeeded); + View *contentView = (View *) this->contentView; + + const ViewConstraint w = MakeConstraint(ViewConstraintEqual, size.w); + const ViewConstraint h = MakeConstraint(ViewConstraintEqual, size.h); + + $(contentView, layoutWithConstraint, w, h); super(View, self, layoutSubviews); diff --git a/Sources/ObjectivelyMVC/StackView.c b/Sources/ObjectivelyMVC/StackView.c index 2a2ba963..69384d49 100644 --- a/Sources/ObjectivelyMVC/StackView.c +++ b/Sources/ObjectivelyMVC/StackView.c @@ -70,8 +70,6 @@ static View *init(View *self) { */ static void layoutSubviews(View *self) { - super(View, self, layoutSubviews); - Array *subviews = $(self, visibleSubviews); if (subviews->count) { @@ -91,18 +89,25 @@ static void layoutSubviews(View *self) { availableSize -= this->spacing * (subviews->count - 1); + const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0); + + // Each subview's unconstrained size, measured once here and consumed by the second loop + // below -- scoped to this call, not cached on the View itself, since nothing outside this + // function has any business reading a mid-layout intermediate value. + SDL_Size sizes[subviews->count]; + for (size_t i = 0; i < subviews->count; i++) { View *subview = $(subviews, objectAtIndex, i); - const SDL_Size subviewSize = $(subview, size); + sizes[i] = $(subview, sizeThatSatisfies, unspecified, unspecified); switch (this->axis) { case StackViewAxisVertical: - requestedSize += subviewSize.h; + requestedSize += sizes[i].h; break; case StackViewAxisHorizontal: - requestedSize += subviewSize.w; + requestedSize += sizes[i].w; break; } } @@ -124,7 +129,7 @@ static void layoutSubviews(View *self) { break; } - SDL_Size subviewSize = $(subview, size); + SDL_Size subviewSize = sizes[i]; switch (this->axis) { case StackViewAxisVertical: @@ -166,14 +171,39 @@ static void layoutSubviews(View *self) { break; } - $(subview, resize, &subviewSize); - $(subview, layoutIfNeeded); + // subviewSize is the final, already-computed distribution/bounds-override size; see + // View::layoutWithSize for why it's applied directly rather than via a ViewConstraint. + $(subview, layoutWithSize, &subviewSize); + // The switch above positioned subview along the stack's primary axis; align it along the + // cross axis here, now that its final (post-resize) frame is known. switch (this->axis) { case StackViewAxisVertical: + switch (subview->alignment & ViewAlignmentMaskHorizontal) { + case ViewAlignmentLeft: + subview->frame.x = 0; + break; + case ViewAlignmentCenter: + subview->frame.x = (bounds.w - subview->frame.w) * 0.5f; + break; + case ViewAlignmentRight: + subview->frame.x = bounds.w - subview->frame.w; + break; + } pos += subviewSize.h; break; case StackViewAxisHorizontal: + switch (subview->alignment & ViewAlignmentMaskVertical) { + case ViewAlignmentMaskTop: + subview->frame.y = 0; + break; + case ViewAlignmentMaskMiddle: + subview->frame.y = (bounds.h - subview->frame.h) * 0.5f; + break; + case ViewAlignmentMaskBottom: + subview->frame.y = bounds.h - subview->frame.h; + break; + } pos += subviewSize.w; break; } @@ -204,18 +234,14 @@ static SDL_Size sizeThatFits(const View *self) { } Array *subviews = $(self, visibleSubviews); + + const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0); + for (size_t i = 0; i < subviews->count; i++) { - const View *subview = $(subviews, objectAtIndex, i); + View *subview = $(subviews, objectAtIndex, i); - SDL_Size subviewSize; - if (subview->autoresizingMask & ViewAutoresizingContain) { - subviewSize = $(subview, sizeThatContains); - } else if (subview->autoresizingMask & ViewAutoresizingFit) { - subviewSize = $(subview, sizeThatFits); - } else { - subviewSize = $(subview, size); - } + const SDL_Size subviewSize = $(subview, sizeThatSatisfies, unspecified, unspecified); switch (this->axis) { case StackViewAxisVertical: diff --git a/Sources/ObjectivelyMVC/TableView.c b/Sources/ObjectivelyMVC/TableView.c index ca89b440..28c43472 100644 --- a/Sources/ObjectivelyMVC/TableView.c +++ b/Sources/ObjectivelyMVC/TableView.c @@ -121,6 +121,14 @@ static void layoutSubviews(View *self) { */ static SDL_Size sizeThatFits(const View *self) { + // naturalSize sums every row's height unconditionally; only report it as this View's own size + // when self is actually a container, so an explicit JSON/CSS width or height isn't silently + // discarded for a TableView that manages its own size (e.g. a fixed viewport with a + // ScrollView). + if ($(self, isContainer) == false) { + return $(self, size); + } + const TableView *this = (TableView *) self; return $(this, naturalSize); diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index 13584428..dc2a0005 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -1107,11 +1107,18 @@ static void layoutIfNeeded(View *self) { if (self->needsLayout) { - $(self, clearWarnings, WarningTypeLayout); - - $(self, layoutSubviews); + // No ancestor is actively arranging self right now, so there's no fresh constraint to + // consult; treat an existing frame as authoritative (Equal, not Max -- see ViewConstraintMax). + ViewConstraint w, h; + if (self->frame.w || self->frame.h) { + w = MakeConstraint(ViewConstraintEqual, self->frame.w); + h = MakeConstraint(ViewConstraintEqual, self->frame.h); + } else { + w = MakeConstraint(ViewConstraintUnspecified, 0); + h = MakeConstraint(ViewConstraintUnspecified, 0); + } - self->needsLayout = false; + $(self, layoutWithConstraint, w, h); } } @@ -1121,12 +1128,6 @@ static void layoutIfNeeded(View *self) { */ static void layoutSubviews(View *self) { - if (self->autoresizingMask & ViewAutoresizingContain) { - $(self, sizeToContain); - } else if (self->autoresizingMask & ViewAutoresizingFit) { - $(self, sizeToFit); - } - const SDL_Rect bounds = $(self, bounds); const Array *subviews = (Array *) self->subviews; @@ -1134,18 +1135,13 @@ static void layoutSubviews(View *self) { View *subview = subviews->elements[i]; - SDL_Size subviewSize = $(subview, size); - - if (subview->autoresizingMask & ViewAutoresizingWidth) { - subviewSize.w = bounds.w; - } + const ViewConstraint w = (subview->autoresizingMask & ViewAutoresizingWidth) ? + MakeConstraint(ViewConstraintEqual, bounds.w) : MakeConstraint(ViewConstraintUnspecified, 0); - if (subview->autoresizingMask & ViewAutoresizingHeight) { - subviewSize.h = bounds.h; - } + const ViewConstraint h = (subview->autoresizingMask & ViewAutoresizingHeight) ? + MakeConstraint(ViewConstraintEqual, bounds.h) : MakeConstraint(ViewConstraintUnspecified, 0); - $(subview, resize, &subviewSize); - $(subview, layoutIfNeeded); + $(subview, layoutWithConstraint, w, h); switch (subview->alignment & ViewAlignmentMaskHorizontal) { case ViewAlignmentLeft: @@ -1173,6 +1169,39 @@ static void layoutSubviews(View *self) { } } +/** + * @fn void View::layoutWithConstraint(View *self, ViewConstraint width, ViewConstraint height) + * @memberof View + */ +static void layoutWithConstraint(View *self, ViewConstraint width, ViewConstraint height) { + + // Always resolve, regardless of isContainer: resolveViewConstraint's own per-axis + // ViewAutoresizingWidth/Height check is what decides whether width/height actually changes + // anything -- for a non-container with no matching bit, sizeThatFits already returns self's + // current size unchanged, so this is a no-op. Gating this on isContainer would (and did) skip + // resizing entirely for a Fill-only, non-container View -- e.g. Slider's `bar`, which is + // `fill` but not `Contain`/`Fit` -- leaving it at its stale or zero frame. + $(self, sizeToSatisfy, width, height); + + $(self, clearWarnings, WarningTypeLayout); + $(self, layoutSubviews); + + self->needsLayout = false; +} + +/** + * @fn void View::layoutWithSize(View *self, const SDL_Size *size) + * @memberof View + */ +static void layoutWithSize(View *self, const SDL_Size *size) { + + $(self, resize, size); + $(self, clearWarnings, WarningTypeLayout); + $(self, layoutSubviews); + + self->needsLayout = false; +} + /** * @fn bool View::matchesSelector(const View *self, const SimpleSelector *simpleSelector) * @memberof View @@ -1677,30 +1706,16 @@ static SDL_Size sizeThatFits(const View *self) { SDL_Size size = $(self, size); - if (self->autoresizingMask & ViewAutoresizingWidth) { - size.w = 0; - } - - if (self->autoresizingMask & ViewAutoresizingHeight) { - size.h = 0; - } - if ($(self, isContainer)) { size = MakeSize(0, 0); Array *subviews = $(self, visibleSubviews); for (size_t i = 0; i < subviews->count; i++) { - const View *subview = subviews->elements[i]; + View *subview = subviews->elements[i]; - SDL_Size subviewSize; - if (subview->autoresizingMask & ViewAutoresizingContain) { - subviewSize = $(subview, sizeThatContains); - } else if (subview->autoresizingMask & ViewAutoresizingFit) { - subviewSize = $(subview, sizeThatFits); - } else { - subviewSize = $(subview, size); - } + const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0); + const SDL_Size subviewSize = $(subview, sizeThatSatisfies, unspecified, unspecified); SDL_Point subviewOrigin = MakePoint(0, 0); switch (subview->alignment) { @@ -1727,6 +1742,43 @@ static SDL_Size sizeThatFits(const View *self) { return size; } +/** + * @brief Resolves an axis's `sizeThatFits` value against an offered ViewConstraint, if the + * View has opted into that axis's autoresizing bit; otherwise returns the value unmodified. + */ +static int resolveViewConstraint(int hasAutoresizingBit, ViewConstraint constraint, int intrinsic) { + + if (!hasAutoresizingBit) { + return intrinsic; + } + + switch (constraint.mode) { + case ViewConstraintEqual: + return constraint.value; + case ViewConstraintMax: + return min(intrinsic, constraint.value); + default: + return intrinsic; + } +} + +/** + * @fn SDL_Size View::sizeThatSatisfies(View *self, ViewConstraint width, ViewConstraint height) + * @memberof View + */ +static SDL_Size sizeThatSatisfies(View *self, ViewConstraint width, ViewConstraint height) { + + SDL_Size size = $(self, sizeThatFits); + + size.w = resolveViewConstraint(self->autoresizingMask & ViewAutoresizingWidth, width, size.w); + size.h = resolveViewConstraint(self->autoresizingMask & ViewAutoresizingHeight, height, size.h); + + size.w = clamp(size.w, self->minSize.w, self->maxSize.w); + size.h = clamp(size.h, self->minSize.h, self->maxSize.h); + + return size; +} + /** * @fn void View::sizeToContain(View *self) * @memberof View @@ -1760,6 +1812,17 @@ static void sizeToFit(View *self) { $(self, resize, &size); } +/** + * @fn void View::sizeToSatisfy(View *self, ViewConstraint width, ViewConstraint height) + * @memberof View + */ +static void sizeToSatisfy(View *self, ViewConstraint width, ViewConstraint height) { + + const SDL_Size size = $(self, sizeThatSatisfies, width, height); + + $(self, resize, &size); +} + /** * @fn View::subviewWithIdentifier(const View *self, const char *identifier) * @memberof View @@ -2003,6 +2066,8 @@ static void initialize(Class *clazz) { ((ViewInterface *) clazz->interface)->isVisible = isVisible; ((ViewInterface *) clazz->interface)->layoutIfNeeded = layoutIfNeeded; ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews; + ((ViewInterface *) clazz->interface)->layoutWithConstraint = layoutWithConstraint; + ((ViewInterface *) clazz->interface)->layoutWithSize = layoutWithSize; ((ViewInterface *) clazz->interface)->matchesSelector = matchesSelector; ((ViewInterface *) clazz->interface)->moveToWindow = moveToWindow; ((ViewInterface *) clazz->interface)->path = path; @@ -2027,9 +2092,11 @@ static void initialize(Class *clazz) { ((ViewInterface *) clazz->interface)->sizeThatContains = sizeThatContains; ((ViewInterface *) clazz->interface)->sizeThatFills = sizeThatFills; ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits; + ((ViewInterface *) clazz->interface)->sizeThatSatisfies = sizeThatSatisfies; ((ViewInterface *) clazz->interface)->sizeToContain = sizeToContain; ((ViewInterface *) clazz->interface)->sizeToFill = sizeToFill; ((ViewInterface *) clazz->interface)->sizeToFit = sizeToFit; + ((ViewInterface *) clazz->interface)->sizeToSatisfy = sizeToSatisfy; ((ViewInterface *) clazz->interface)->subviewWithIdentifier = subviewWithIdentifier; ((ViewInterface *) clazz->interface)->updateBindings = updateBindings; ((ViewInterface *) clazz->interface)->viewport = viewport; diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index 666c167a..ae6a1dc1 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -94,6 +94,44 @@ typedef enum { OBJECTIVELYMVC_EXPORT const EnumName ViewAutoresizingNames[]; +/** + * @brief A constraint offered by an ancestor View to a descendant during View::sizeThatSatisfies. + */ +typedef struct { + enum { + /** + * @brief The offered value is final; the View must resolve to exactly this size. + */ + ViewConstraintEqual, + + /** + * @brief The offered value is an upper bound; the View may resolve to any size up to it. + * @remarks Do not use this for a View's own already-resolved frame (e.g. re-deriving a + * constraint to re-layout a View standalone): a `ViewAutoresizingWidth`/`Height` axis whose + * sizeThatFits value is smaller than that frame would shrink to it, discarding a size a + * parent previously forced with `ViewConstraintEqual`. Use `ViewConstraintEqual` instead to + * treat an existing frame as authoritative. + */ + ViewConstraintMax, + + /** + * @brief No bound is offered; the ancestor is itself unresolved for this axis. + */ + ViewConstraintUnspecified + } mode; + + /** + * @brief The constraint value; a size in pixels. + */ + int value; +} ViewConstraint; + +/** + * @return A ViewConstraint with the given mode and value. + */ +#define MakeConstraint(mode, value) \ + (ViewConstraint) { (mode), (value) } + /** * @brief Spacing applied to the inside of a View's frame. */ @@ -765,20 +803,69 @@ struct ViewInterface { * @fn void View::layoutIfNeeded(View *self) * @brief Recursively updates the layout of this View and its subviews. * @param self The View. + * @remarks Drives the two-phase layout pass for a dirty View: View::sizeThatSatisfies resolves + * this View's own size (bottom-up, from its subviews), if it is a container, before + * View::layoutSubviews arranges its subviews (top-down) using that now-final frame. This + * ordering guarantees every View's size is settled before any View's position is committed. + * @remarks This View may be laying out standalone here -- e.g. marked dirty directly by a + * style rebind (View::_bind unconditionally sets needsLayout on the View it re-binds), not as + * part of an active parent-driven arrange pass -- so there is no fresh ViewConstraint from an + * ancestor to consult. If self already has a frame, this is resolved as `Equal` rather than + * `Max`, trusting it as authoritative rather than merely an upper bound; see + * View::layoutWithConstraint, which performs the actual resolve-and-arrange once that + * ViewConstraint is determined. * @memberof View */ void (*layoutIfNeeded)(View *self); /** * @fn View::layoutSubviews(View *self) - * @brief Performs layout for this View's immediate subviews. + * @brief Arranges this View's immediate subviews within its (already final) frame. * @param self The View. - * @remarks Subclasses may override this method to perform their own layout operations. This - * method is called recursively by View::layoutIfNeeded. + * @remarks Subclasses may override this method to perform their own arrangement of subviews. + * This method is called by View::layoutIfNeeded only after a View::sizeThatSatisfies pass has + * already resolved `self->frame`; it must not perform any sizing of its own, only positioning + * and committing of subview frames. + * @remarks The default implementation resolves each subview's size via View::layoutWithConstraint, + * offering `Exact` for a `ViewAutoresizingWidth`/`Height` subview (since this View's bounds are + * already final) or `Unspecified` otherwise (so the subview sizes itself from its own content). * @memberof View */ void (*layoutSubviews)(View *self); + /** + * @fn void View::layoutWithConstraint(View *self, ViewConstraint width, ViewConstraint height) + * @brief Resolves this View's size against the given ViewConstraints, then arranges it. + * @param self The View. + * @param width The ViewConstraint offered for this View's width. + * @param height The ViewConstraint offered for this View's height. + * @remarks This is the shared tail of View::layoutIfNeeded: resolve self's size via + * View::sizeToSatisfy if self is a container, then View::layoutSubviews, then clear + * `needsLayout`. It exists so a caller that already knows the correct ViewConstraint for a + * View it is arranging -- e.g. a parent's own View::layoutSubviews, positioning one of its + * subviews -- can drive that View's layout directly, without going through + * View::layoutIfNeeded's own guess at what constraint applies (which is only appropriate when + * a View is laying out standalone, with no parent currently arranging it). + * @see View::layoutWithSize(View *, const SDL_Size *) + * @memberof View + */ + void (*layoutWithConstraint)(View *self, ViewConstraint width, ViewConstraint height); + + /** + * @fn void View::layoutWithSize(View *self, const SDL_Size *size) + * @brief Resizes this View to the given size, then arranges it. + * @param self The View. + * @param size The size to resize this View to. + * @remarks Like View::layoutWithConstraint, but for a caller that has already computed this + * View's exact final size and must apply it verbatim -- e.g. a StackView applying its + * distribution math to a subview -- rather than resolving it through the ViewConstraint + * pipeline, which would ignore that computed size on any axis the subview has not opted into + * `ViewAutoresizingWidth`/`ViewAutoresizingHeight` for. + * @see View::layoutWithConstraint(View *, ViewConstraint, ViewConstraint) + * @memberof View + */ + void (*layoutWithSize)(View *self, const SDL_Size *size); + /** * @fn bool View::matchesSelector(const View *self, const SimpleSelector *simpleSelector) * @param self The View. @@ -978,8 +1065,12 @@ struct ViewInterface { * @fn SDL_Size View::sizeThatFits(const View *self) * @param self The View. * @return An SDL_Size that fits this View's subviews. - * @remarks The default implementation of this method returns the View's current size, or an - * appropriate value based on this View's `autoresizingMask`. + * @remarks The default implementation returns this View's current size if it is not a + * container. If it is a container, it instead returns the size that contains all of its + * subviews, each measured via View::sizeThatSatisfies with `ViewConstraintUnspecified` for + * both axes -- so a subview with `ViewAutoresizingWidth`/`ViewAutoresizingHeight` degrades to + * its own sizeThatFits value here, rather than inflating to fill a not-yet-determined bound. + * Subclasses with their own sizing logic (e.g. Text, TableView) should override this method. * @memberof View */ SDL_Size (*sizeThatFits)(const View *self); @@ -992,6 +1083,28 @@ struct ViewInterface { */ SDL_Size (*sizeThatFills)(const View *self); + /** + * @fn SDL_Size View::sizeThatSatisfies(View *self, ViewConstraint width, ViewConstraint height) + * @brief Resolves the size this View wants to be, given the offered ViewConstraints. + * @param self The View. + * @param width The ViewConstraint offered for this View's width. + * @param height The ViewConstraint offered for this View's height. + * @return The resolved size. + * @remarks This is the bottom-up half of layout: it computes this View's sizeThatFits value, + * then resolves that value against the offered ViewConstraints for any axis on which this View + * has `ViewAutoresizingWidth` or `ViewAutoresizingHeight` set. It never mutates `self->frame` + * or any subview's frame, and is always run to completion for an entire dirty subtree before + * View::layoutSubviews arranges any View in that subtree. + * @remarks Always consults View::sizeThatFits, so that a subclass's sizeThatFits override + * (Text, TableView, Select) is honored by any container summing over it as a subview, + * regardless of whether self itself is a container. A sizeThatFits override that only makes + * sense to consult for self's own sizing when self is a container (e.g. TableView, whose + * override is otherwise an expensive, unclamped sum of every row) is responsible for making + * that check itself -- see View::sizeThatFits(const View *) on TableView. + * @memberof View + */ + SDL_Size (*sizeThatSatisfies)(View *self, ViewConstraint width, ViewConstraint height); + /** * @fn void View::sizeToContain(View *self) * @brief Resizes this View to contain its subviews. @@ -1019,6 +1132,17 @@ struct ViewInterface { */ void (*sizeToFit)(View *self); + /** + * @fn void View::sizeToSatisfy(View *self, ViewConstraint width, ViewConstraint height) + * @brief Resizes this View to satisfy the given ViewConstraints. + * @param self The View. + * @param width The ViewConstraint offered for this View's width. + * @param height The ViewConstraint offered for this View's height. + * @see View::sizeThatSatisfies(View *, ViewConstraint, ViewConstraint) + * @memberof View + */ + void (*sizeToSatisfy)(View *self, ViewConstraint width, ViewConstraint height); + /** * @fn View *View::subviewWithIdentifier(const View *self, const char *identifier) * @param self The View. diff --git a/Tests/ObjectivelyMVC/.gitignore b/Tests/ObjectivelyMVC/.gitignore index 9111c0a9..f584bd1e 100644 --- a/Tests/ObjectivelyMVC/.gitignore +++ b/Tests/ObjectivelyMVC/.gitignore @@ -4,3 +4,4 @@ Constraint Selector Style Stylesheet +View diff --git a/Tests/ObjectivelyMVC/Makefile.am b/Tests/ObjectivelyMVC/Makefile.am index e5fa33f9..32897b09 100644 --- a/Tests/ObjectivelyMVC/Makefile.am +++ b/Tests/ObjectivelyMVC/Makefile.am @@ -8,7 +8,8 @@ DEFAULT_INCLUDES = \ TESTS = \ Selector \ Style \ - Stylesheet + Stylesheet \ + View CFLAGS += \ -I$(top_srcdir)/Sources \ diff --git a/Tests/ObjectivelyMVC/View.c b/Tests/ObjectivelyMVC/View.c new file mode 100644 index 00000000..f0b558c0 --- /dev/null +++ b/Tests/ObjectivelyMVC/View.c @@ -0,0 +1,259 @@ +/* + * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C. + * Copyright (C) 2014 Jay Dolan + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include + +#include "ObjectivelyMVC.h" + +/** + * @brief Allocates a leaf View with a fixed, locked size (minSize == maxSize == size). + */ +static View *fixedView(int w, int h) { + + View *view = $(alloc(View), initWithFrame, NULL); + + const SDL_Size size = MakeSize(w, h); + + view->minSize = size; + view->maxSize = size; + + view->frame.w = w; + view->frame.h = h; + + return view; +} + +START_TEST(fixedSizeLeavesInStackView) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *a = fixedView(40, 20); + View *b = fixedView(60, 30); + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(60, stackView->view.frame.w); + ck_assert_int_eq(50, stackView->view.frame.h); + + ck_assert_int_eq(0, a->frame.y); + ck_assert_int_eq(20, b->frame.y); + + release(stackView); + +} END_TEST + +START_TEST(nestedContainStackViews) { + + StackView *outer = $(alloc(StackView), initWithFrame, NULL); + outer->axis = StackViewAxisVertical; + + StackView *inner = $(alloc(StackView), initWithFrame, NULL); + inner->axis = StackViewAxisHorizontal; + + View *innerA = fixedView(10, 15); + View *innerB = fixedView(20, 25); + + $((View *) inner, addSubview, innerA); + $((View *) inner, addSubview, innerB); + + View *sibling = fixedView(50, 5); + + $((View *) outer, addSubview, (View *) inner); + $((View *) outer, addSubview, sibling); + + $((View *) outer, layoutIfNeeded); + + ck_assert_int_eq(30, inner->view.frame.w); + ck_assert_int_eq(25, inner->view.frame.h); + + ck_assert_int_eq(50, outer->view.frame.w); + ck_assert_int_eq(30, outer->view.frame.h); + + ck_assert_int_eq(0, inner->view.frame.y); + ck_assert_int_eq(25, sibling->frame.y); + + release(outer); + +} END_TEST + +START_TEST(containStackViewWithFillChild) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + StackView *fillChild = $(alloc(StackView), initWithFrame, NULL); + fillChild->view.autoresizingMask |= ViewAutoresizingWidth; + + View *grandchild = fixedView(35, 10); + $((View *) fillChild, addSubview, grandchild); + + $((View *) stackView, addSubview, (View *) fillChild); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(35, stackView->view.frame.w); + ck_assert_int_eq(35, fillChild->view.frame.w); + + const SDL_Rect frame = fillChild->view.frame; + + stackView->view.needsLayout = true; + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(frame.w, fillChild->view.frame.w); + ck_assert_int_eq(frame.h, fillChild->view.frame.h); + ck_assert_int_eq(35, stackView->view.frame.w); + + release(stackView); + +} END_TEST + +START_TEST(containStackViewWithFillAndFixedSiblings) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *fixedSibling = fixedView(80, 20); + + View *fillSibling = fixedView(30, 10); + fillSibling->autoresizingMask |= ViewAutoresizingWidth; + fillSibling->maxSize.w = INT32_MAX; + + $((View *) stackView, addSubview, fixedSibling); + $((View *) stackView, addSubview, fillSibling); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(80, stackView->view.frame.w); + ck_assert_int_eq(80, fillSibling->frame.w); + + release(stackView); + +} END_TEST + +START_TEST(stackViewMeasuresFreshNotStale) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *child = fixedView(10, 10); + $((View *) stackView, addSubview, child); + + const SDL_Size grown = MakeSize(90, 40); + child->minSize = grown; + child->maxSize = grown; + child->frame.w = grown.w; + child->frame.h = grown.h; + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(90, stackView->view.frame.w); + ck_assert_int_eq(40, stackView->view.frame.h); + + release(stackView); + +} END_TEST + +START_TEST(standaloneRelayoutDoesNotShrinkFillChild) { + + // Mirrors a selected TableRowView: a Contain|Width StackView (row) whose own intrinsic + // content is much narrower than the width its Contain parent (table) already forced it to. + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *wideSibling = fixedView(200, 10); + $((View *) stackView, addSubview, wideSibling); + + StackView *row = $(alloc(StackView), initWithFrame, NULL); + row->view.autoresizingMask |= ViewAutoresizingWidth; + + View *cell = fixedView(50, 10); + $((View *) row, addSubview, cell); + + $((View *) stackView, addSubview, (View *) row); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(200, stackView->view.frame.w); + ck_assert_int_eq(200, row->view.frame.w); + + // Simulate a style rebind on the row alone (e.g. a `:selected` pseudo-class match), which + // marks only the row -- not its superview -- needsLayout, per View::_bind's contract. + row->view.needsLayout = true; + $((View *) row, layoutIfNeeded); + + ck_assert_int_eq(200, row->view.frame.w); + + release(stackView); + +} END_TEST + +START_TEST(fillOnlyChildFillsContainBounds) { + + // Mirrors Slider's `bar`: a plain `fill` (Width|Height) leaf, not itself Contain/Fit, whose + // parent is a Contain container with no other content to size from (a bare `fill` leaf has no + // independent intrinsic size of its own, so the parent's floor comes entirely from minSize -- + // exactly how Slider's own width is floored via CSS `min-width`). + + View *outer = $(alloc(View), initWithFrame, NULL); + outer->autoresizingMask = ViewAutoresizingContain; + outer->minSize = MakeSize(100, 50); + + View *child = $(alloc(View), initWithFrame, NULL); + child->autoresizingMask = ViewAutoresizingFill; + + $(outer, addSubview, child); + + $(outer, layoutIfNeeded); + + ck_assert_int_eq(100, outer->frame.w); + ck_assert_int_eq(50, outer->frame.h); + ck_assert_int_eq(100, child->frame.w); + ck_assert_int_eq(50, child->frame.h); + + release(outer); + +} END_TEST + +int main(int argc, char **argv) { + + TCase *tcase = tcase_create("View"); + tcase_add_test(tcase, fixedSizeLeavesInStackView); + tcase_add_test(tcase, nestedContainStackViews); + tcase_add_test(tcase, containStackViewWithFillChild); + tcase_add_test(tcase, containStackViewWithFillAndFixedSiblings); + tcase_add_test(tcase, stackViewMeasuresFreshNotStale); + tcase_add_test(tcase, standaloneRelayoutDoesNotShrinkFillChild); + tcase_add_test(tcase, fillOnlyChildFillsContainBounds); + + Suite *suite = suite_create("View"); + suite_add_tcase(suite, tcase); + + SRunner *runner = srunner_create(suite); + + srunner_run_all(runner, CK_VERBOSE); + int failed = srunner_ntests_failed(runner); + + srunner_free(runner); + + return failed; +} From 357f4572aa80568c74e1f52efb7543065b4776e2 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 20:47:11 -0400 Subject: [PATCH 02/11] Add ObjectivelyMVC-View Xcode target and scheme Mirrors ObjectivelyMVC-Style: a command-line-tool target building Tests/ObjectivelyMVC/View.c against the same frameworks, plus a shared scheme, so the new View check_PROGRAMS test can run from Xcode like Selector/Style/Stylesheet already could. --- ObjectivelyMVC.xcodeproj/project.pbxproj | 109 ++++++++++++++++++ .../xcschemes/ObjectivelyMVC-View.xcscheme | 80 +++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-View.xcscheme diff --git a/ObjectivelyMVC.xcodeproj/project.pbxproj b/ObjectivelyMVC.xcodeproj/project.pbxproj index f2f016c4..17604838 100644 --- a/ObjectivelyMVC.xcodeproj/project.pbxproj +++ b/ObjectivelyMVC.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ CE423A4D1F5346A7002767E7 /* ShellScript */, ); dependencies = ( + CED40A633046145B0090C57A /* PBXTargetDependency */, CE34C8A51FB2A0210025F231 /* PBXTargetDependency */, CE88199A1F91B0DA000D5AB7 /* PBXTargetDependency */, CE34C8A71FB2A0210025F231 /* PBXTargetDependency */, @@ -35,9 +36,14 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 10B43D5BFBA4557A7326854D /* Objectively.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73E9FA2FEEA5F60048BC24 /* Objectively.framework */; }; + 124341A5EE68480EEA539233 /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; 12CB053DBF7CE0752A5AE6E9 /* ScrollBar.c in Sources */ = {isa = PBXBuildFile; fileRef = 7889454C3F84200465B57578 /* ScrollBar.c */; }; + 22FD8C57C4593EFD73A0BA80 /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; 3081E9324C929F44F2424D7C /* ScrollHandle.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F25B4D3F9924D41ADDBF368 /* ScrollHandle.h */; settings = {ATTRIBUTES = (Public, ); }; }; 373CCC1EDB79A83F1F913610 /* ScrollHandle.c in Sources */ = {isa = PBXBuildFile; fileRef = 6B2F1D0FBAAAAB9E9859A08E /* ScrollHandle.c */; }; + 4FC583A880396777FF1EF843 /* ObjectivelyMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */; }; + 872D178C50AF548C35D14652 /* View.c in Sources */ = {isa = PBXBuildFile; fileRef = 58441034DC162E9BE53173F4 /* View.c */; }; B1BB0001000000000000000A /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; B1BB000100000000000000A0 /* SDL3.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B1BB000100000000000000AB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA000100000000000000AB /* Foundation.framework */; }; @@ -205,6 +211,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 142E9647D6EC3FAD6E24CA61 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = CED157CC1C4BF3AC00FBA2DE; + remoteInfo = ObjectivelyMVC; + }; CE12D4631C4C8F5400CD0B13 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; @@ -261,9 +274,25 @@ remoteGlobalIDString = CE88198A1F91B0BE000D5AB7; remoteInfo = "ObjectivelyMVC-Style"; }; + CED40A623046145B0090C57A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8A563153D68C9DE444FBF31C; + remoteInfo = "ObjectivelyMVC-View"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + A3611941E3F959776989AD53 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; B1CC000100000000000000D0 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -320,11 +349,13 @@ /* Begin PBXFileReference section */ 14C7C5885104229637E1E9D5 /* Renderer.frag.glsl */ = {isa = PBXFileReference; lastKnownFileType = text; path = Renderer.frag.glsl; sourceTree = ""; }; + 58441034DC162E9BE53173F4 /* View.c */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.c; path = View.c; sourceTree = ""; }; 64D1E41ED038FCC725227A5B /* ScrollBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ScrollBar.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 6B2F1D0FBAAAAB9E9859A08E /* ScrollHandle.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ScrollHandle.c; sourceTree = ""; }; 7819952C297345768B746A46 /* SlideShowView.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SlideShowView.c; sourceTree = ""; }; 7889454C3F84200465B57578 /* ScrollBar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ScrollBar.c; sourceTree = ""; }; 7F6735C65F7CAB13D8D96588 /* Renderer.frag.spv.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Renderer.frag.spv.h; sourceTree = ""; }; + 84BBDF9467CD6F80ACF8AADF /* ObjectivelyMVC-View */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ObjectivelyMVC-View"; sourceTree = BUILT_PRODUCTS_DIR; }; 8D42EC0E254AF0B8789148FA /* Renderer.frag.metal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Renderer.frag.metal.h; sourceTree = ""; }; 8F25B4D3F9924D41ADDBF368 /* ScrollHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ScrollHandle.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 965273D7A59E496B86FE11EF /* SlideShowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = SlideShowView.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; @@ -521,6 +552,17 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 552A2017A38847FC4DB96EB2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 22FD8C57C4593EFD73A0BA80 /* SDL3.xcframework in Frameworks */, + 10B43D5BFBA4557A7326854D /* Objectively.framework in Frameworks */, + 124341A5EE68480EEA539233 /* ObjectivelyGPU.framework in Frameworks */, + 4FC583A880396777FF1EF843 /* ObjectivelyMVC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B1CC000100000000000000B0 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -739,6 +781,7 @@ CE8819891F91B094000D5AB7 /* Style.c */, CE34C8941FB29F3A0025F231 /* Stylesheet.c */, CE423A2C1F534127002767E7 /* Makefile.am */, + 58441034DC162E9BE53173F4 /* View.c */, ); path = ObjectivelyMVC; sourceTree = ""; @@ -880,6 +923,7 @@ CE55C1C91F944C6D00D5A326 /* ObjectivelyMVC-Selector */, CE34C8A21FB29F700025F231 /* ObjectivelyMVC-Stylesheet */, B1AA000100000000000000ED /* ObjectivelyMVC-Hello-iOS.app */, + 84BBDF9467CD6F80ACF8AADF /* ObjectivelyMVC-View */, ); name = Products; sourceTree = ""; @@ -953,6 +997,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 8A563153D68C9DE444FBF31C /* ObjectivelyMVC-View */ = { + isa = PBXNativeTarget; + buildConfigurationList = 326EB91F357CD75FD3012D86 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-View" */; + buildPhases = ( + 09AB70E63430F8B404005FCA /* Sources */, + 552A2017A38847FC4DB96EB2 /* Frameworks */, + A3611941E3F959776989AD53 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + A1D73378A0C8B95650970925 /* PBXTargetDependency */, + ); + name = "ObjectivelyMVC-View"; + productName = "ObjectivelyMVC-View"; + productReference = 84BBDF9467CD6F80ACF8AADF /* ObjectivelyMVC-View */; + productType = "com.apple.product-type.tool"; + }; B1DD00010000000000000001 /* ObjectivelyMVC-Hello-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = B1EE000100000000000000C0 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Hello-iOS" */; @@ -1107,6 +1169,7 @@ CE55C1BC1F944C6D00D5A326 /* ObjectivelyMVC-Selector */, CE88198A1F91B0BE000D5AB7 /* ObjectivelyMVC-Style */, CE34C8951FB29F700025F231 /* ObjectivelyMVC-Stylesheet */, + 8A563153D68C9DE444FBF31C /* ObjectivelyMVC-View */, CE423A451F534657002767E7 /* ObjectivelyMVC-Tests */, CE12D4541C4C8E8E00CD0B13 /* ObjectivelyMVC-Hello */, B1DD00010000000000000001 /* ObjectivelyMVC-Hello-iOS */, @@ -1250,6 +1313,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 09AB70E63430F8B404005FCA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 872D178C50AF548C35D14652 /* View.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B1CC000100000000000000A0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1354,6 +1425,12 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + A1D73378A0C8B95650970925 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ObjectivelyMVC; + target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; + targetProxy = 142E9647D6EC3FAD6E24CA61 /* PBXContainerItemProxy */; + }; CE12D4641C4C8F5400CD0B13 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; @@ -1394,9 +1471,23 @@ target = CE88198A1F91B0BE000D5AB7 /* ObjectivelyMVC-Style */; targetProxy = CE8819991F91B0DA000D5AB7 /* PBXContainerItemProxy */; }; + CED40A633046145B0090C57A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8A563153D68C9DE444FBF31C /* ObjectivelyMVC-View */; + targetProxy = CED40A623046145B0090C57A /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 7A49C7A9A3020379E61EC10B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; + OTHER_LDFLAGS = "-lcheck"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; B1EE000100000000000000A0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1483,6 +1574,15 @@ }; name = Release; }; + B74C1EEAAC88021A8E16E1BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; + OTHER_LDFLAGS = "-lcheck"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; CE12D45A1C4C8E8E00CD0B13 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1705,6 +1805,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 326EB91F357CD75FD3012D86 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-View" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7A49C7A9A3020379E61EC10B /* Debug */, + B74C1EEAAC88021A8E16E1BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; B1EE000100000000000000C0 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Hello-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-View.xcscheme b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-View.xcscheme new file mode 100644 index 00000000..f4abc0db --- /dev/null +++ b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-View.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f75f4e6ef7f0b519d2c0c72fb1142d6a40961b7d Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 21:17:58 -0400 Subject: [PATCH 03/11] Remove stupid fucking comments. --- Sources/ObjectivelyMVC/CollectionView.c | 4 ---- Sources/ObjectivelyMVC/StackView.c | 11 ++++------- Sources/ObjectivelyMVC/TableView.c | 4 ---- Sources/ObjectivelyMVC/View.c | 7 ------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/Sources/ObjectivelyMVC/CollectionView.c b/Sources/ObjectivelyMVC/CollectionView.c index e3d6708f..cc678aed 100644 --- a/Sources/ObjectivelyMVC/CollectionView.c +++ b/Sources/ObjectivelyMVC/CollectionView.c @@ -96,10 +96,6 @@ static void layoutSubviews(View *self) { CollectionItemView *item = (CollectionItemView *) $(items, objectAtIndex, i); - // itemSize is dictated here, not negotiated: layoutWithSize applies it verbatim, since - // layoutIfNeeded would instead offer it as an Equal ViewConstraint, which a Contain item - // with no ViewAutoresizingWidth/Height of its own would ignore in favor of its own - // sizeThatFits value (see View::layoutWithSize). $((View *) item, layoutWithSize, &this->itemSize); item->view.frame.x = x; diff --git a/Sources/ObjectivelyMVC/StackView.c b/Sources/ObjectivelyMVC/StackView.c index 69384d49..68e7cf03 100644 --- a/Sources/ObjectivelyMVC/StackView.c +++ b/Sources/ObjectivelyMVC/StackView.c @@ -91,9 +91,6 @@ static void layoutSubviews(View *self) { const ViewConstraint unspecified = MakeConstraint(ViewConstraintUnspecified, 0); - // Each subview's unconstrained size, measured once here and consumed by the second loop - // below -- scoped to this call, not cached on the View itself, since nothing outside this - // function has any business reading a mid-layout intermediate value. SDL_Size sizes[subviews->count]; for (size_t i = 0; i < subviews->count; i++) { @@ -144,6 +141,8 @@ static void layoutSubviews(View *self) { break; } + // Distribute along primary axis + switch (this->distribution) { case StackViewDistributionDefault: break; @@ -171,12 +170,10 @@ static void layoutSubviews(View *self) { break; } - // subviewSize is the final, already-computed distribution/bounds-override size; see - // View::layoutWithSize for why it's applied directly rather than via a ViewConstraint. $(subview, layoutWithSize, &subviewSize); - // The switch above positioned subview along the stack's primary axis; align it along the - // cross axis here, now that its final (post-resize) frame is known. + // Align along secondary axis + switch (this->axis) { case StackViewAxisVertical: switch (subview->alignment & ViewAlignmentMaskHorizontal) { diff --git a/Sources/ObjectivelyMVC/TableView.c b/Sources/ObjectivelyMVC/TableView.c index 28c43472..a7a997a9 100644 --- a/Sources/ObjectivelyMVC/TableView.c +++ b/Sources/ObjectivelyMVC/TableView.c @@ -121,10 +121,6 @@ static void layoutSubviews(View *self) { */ static SDL_Size sizeThatFits(const View *self) { - // naturalSize sums every row's height unconditionally; only report it as this View's own size - // when self is actually a container, so an explicit JSON/CSS width or height isn't silently - // discarded for a TableView that manages its own size (e.g. a fixed viewport with a - // ScrollView). if ($(self, isContainer) == false) { return $(self, size); } diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index dc2a0005..b55887f7 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -1175,14 +1175,7 @@ static void layoutSubviews(View *self) { */ static void layoutWithConstraint(View *self, ViewConstraint width, ViewConstraint height) { - // Always resolve, regardless of isContainer: resolveViewConstraint's own per-axis - // ViewAutoresizingWidth/Height check is what decides whether width/height actually changes - // anything -- for a non-container with no matching bit, sizeThatFits already returns self's - // current size unchanged, so this is a no-op. Gating this on isContainer would (and did) skip - // resizing entirely for a Fill-only, non-container View -- e.g. Slider's `bar`, which is - // `fill` but not `Contain`/`Fit` -- leaving it at its stale or zero frame. $(self, sizeToSatisfy, width, height); - $(self, clearWarnings, WarningTypeLayout); $(self, layoutSubviews); From 8e0adf3e5f7d3e213a5ed42bb0aa9fb62b81c0ed Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 21:32:31 -0400 Subject: [PATCH 04/11] Introduce View::styledSize as a floor for resizing operations. --- Sources/ObjectivelyMVC/View.c | 15 +++++++++++++++ Sources/ObjectivelyMVC/View.h | 11 +++++++++++ Tests/ObjectivelyMVC/View.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index b55887f7..b7a28047 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -276,6 +276,15 @@ static void applyStyle(View *self, const Style *style) { ); $(self, bind, inlets, style->attributes); + + // Capture the authored width/height, if either was actually present in this Style -- distinct + // from self->frame.w/h, which layout goes on to freely resize. + if ($(style->attributes, objectForKeyPath, "width")) { + self->styledSize.w = self->frame.w; + } + if ($(style->attributes, objectForKeyPath, "height")) { + self->styledSize.h = self->frame.h; + } } /** @@ -1727,6 +1736,12 @@ static SDL_Size sizeThatFits(const View *self) { size.h += self->padding.top + self->padding.bottom; release(subviews); + + // A styled width/height is authored intent for this View's own frame; floor the + // children-derived sum against it so it survives summing subviews with no size of their + // own to contribute (e.g. Slider, whose bar and handle have none independently). + size.w = max(size.w, self->styledSize.w); + size.h = max(size.h, self->styledSize.h); } size.w = clamp(size.w, self->minSize.w, self->maxSize.w); diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index ae6a1dc1..79036870 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -278,6 +278,17 @@ struct View { */ Style *style; + /** + * @brief The `width`/`height` this View was most recently given via styling, per axis. + * @remarks Written only when a `width` or `height` Style attribute is actually present -- not + * on every View::applyStyle call -- so it reflects the last authored value for an axis, or + * `0` if that axis has never been styled. View::sizeThatFits's container branch floors its + * children-derived sum against this, so an authored size survives being a Contain view with no + * content of its own to size from (e.g. Slider, whose `bar` and `handle` have none + * independently). + */ + SDL_Size styledSize; + /** * @brief An optional Stylesheet. * @remarks If set, this Stylesheet is added to or removed from the current Theme when this diff --git a/Tests/ObjectivelyMVC/View.c b/Tests/ObjectivelyMVC/View.c index f0b558c0..da015fde 100644 --- a/Tests/ObjectivelyMVC/View.c +++ b/Tests/ObjectivelyMVC/View.c @@ -234,6 +234,38 @@ START_TEST(fillOnlyChildFillsContainBounds) { } END_TEST +START_TEST(styledSizeFloorsContainBoundsWithNoMinSize) { + + // Mirrors Slider itself: a styled `width` is the only thing giving a Contain view with no + // sized content of its own (a bare `fill` leaf, contributing nothing) a real width -- with no + // minSize set at all, unlike fillOnlyChildFillsContainBounds above. + + View *outer = $(alloc(View), initWithFrame, NULL); + outer->autoresizingMask = ViewAutoresizingContain; + + Style *style = $(alloc(Style), initWithAttributes, NULL); + $(style, addIntegerAttribute, "width", 140); + $(style, addIntegerAttribute, "height", 24); + + $(outer, applyStyle, style); + release(style); + + View *child = $(alloc(View), initWithFrame, NULL); + child->autoresizingMask = ViewAutoresizingFill; + + $(outer, addSubview, child); + + $(outer, layoutIfNeeded); + + ck_assert_int_eq(140, outer->frame.w); + ck_assert_int_eq(24, outer->frame.h); + ck_assert_int_eq(140, child->frame.w); + ck_assert_int_eq(24, child->frame.h); + + release(outer); + +} END_TEST + int main(int argc, char **argv) { TCase *tcase = tcase_create("View"); @@ -244,6 +276,7 @@ int main(int argc, char **argv) { tcase_add_test(tcase, stackViewMeasuresFreshNotStale); tcase_add_test(tcase, standaloneRelayoutDoesNotShrinkFillChild); tcase_add_test(tcase, fillOnlyChildFillsContainBounds); + tcase_add_test(tcase, styledSizeFloorsContainBoundsWithNoMinSize); Suite *suite = suite_create("View"); suite_add_tcase(suite, tcase); From 1e55bd193751b0f42d526c7ea064453a7c7bcee8 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 21:32:42 -0400 Subject: [PATCH 05/11] Update stylesheet. --- Assets/stylesheet.css | 2 - Assets/stylesheet.css.h | 271 ++++++++++++++++++++-------------------- 2 files changed, 134 insertions(+), 139 deletions(-) diff --git a/Assets/stylesheet.css b/Assets/stylesheet.css index f21276f3..96e1045c 100644 --- a/Assets/stylesheet.css +++ b/Assets/stylesheet.css @@ -211,7 +211,6 @@ Select > StackView { Slider { autoresizing-mask: contain; - min-width: 140; width: 140; } @@ -316,7 +315,6 @@ TextView { autoresizing-mask: contain; background-color: #22222266; bevel: inset; - min-width: 160; padding: 4 4 4 4; width: 160; } diff --git a/Assets/stylesheet.css.h b/Assets/stylesheet.css.h index 5096e5d7..1202b8d9 100644 --- a/Assets/stylesheet.css.h +++ b/Assets/stylesheet.css.h @@ -257,145 +257,142 @@ unsigned char stylesheet_css[] = { 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, - 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, - 0x2e, 0x62, 0x61, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, - 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, - 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x61, - 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, - 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, - 0x2e, 0x62, 0x61, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x68, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, - 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x34, - 0x34, 0x34, 0x34, 0x34, 0x34, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x62, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, - 0x61, 0x61, 0x61, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, - 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x38, 0x3b, 0x0a, 0x20, - 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, - 0x20, 0x31, 0x36, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, - 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, - 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x72, 0x69, - 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, - 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x38, - 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, - 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, - 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, - 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, - 0x20, 0x20, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x2d, 0x73, 0x75, 0x62, 0x76, - 0x69, 0x65, 0x77, 0x73, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, - 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, - 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, - 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, - 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, - 0x0a, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x56, 0x69, 0x65, 0x77, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x31, 0x32, - 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, - 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, - 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, - 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, - 0x3a, 0x20, 0x30, 0x20, 0x32, 0x20, 0x30, 0x20, 0x32, 0x3b, 0x0a, 0x20, - 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, - 0x38, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x32, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, - 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, - 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, - 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, - 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, - 0x66, 0x66, 0x32, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, - 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, - 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76, - 0x65, 0x6e, 0x29, 0x20, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, - 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, + 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x20, 0x31, 0x34, 0x30, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, + 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x62, 0x61, 0x72, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, + 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, + 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, + 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, 0x62, 0x61, 0x72, 0x20, 0x3e, + 0x20, 0x2e, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, + 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, - 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x31, - 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, - 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, - 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, - 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, - 0x20, 0x3e, 0x20, 0x53, 0x63, 0x72, 0x6f, 0x6c, 0x6c, 0x56, 0x69, 0x65, - 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, - 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, - 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, - 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, - 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, - 0x20, 0x2e, 0x74, 0x61, 0x62, 0x50, 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, - 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x73, 0x69, 0x6c, 0x76, - 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x7d, - 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, - 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, - 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, - 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, - 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, - 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, - 0x6c, 0x6c, 0x2d, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x3b, 0x0a, - 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, - 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, - 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, - 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, - 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, - 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, - 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, - 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x66, + 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x66, 0x66, 0x3b, + 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3a, 0x20, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x36, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x53, 0x6c, 0x69, 0x64, 0x65, 0x72, 0x20, 0x3e, 0x20, 0x2e, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, + 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, + 0x64, 0x6c, 0x65, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, + 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, + 0x30, 0x20, 0x30, 0x20, 0x31, 0x38, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, + 0x6c, 0x69, 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, + 0x66, 0x69, 0x6c, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6c, 0x69, 0x70, + 0x73, 0x2d, 0x73, 0x75, 0x62, 0x76, 0x69, 0x65, 0x77, 0x73, 0x3a, 0x20, + 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x6c, 0x69, + 0x64, 0x65, 0x53, 0x68, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, + 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, + 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x66, 0x69, + 0x6c, 0x6c, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x63, 0x6b, + 0x56, 0x69, 0x65, 0x77, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, + 0x6e, 0x67, 0x3a, 0x20, 0x31, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x70, + 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x32, 0x20, + 0x30, 0x20, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x38, 0x30, 0x3b, 0x0a, 0x20, 0x20, + 0x6d, 0x69, 0x6e, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, + 0x32, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x54, + 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, + 0x61, 0x62, 0x6c, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, + 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, - 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, - 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, - 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x3e, - 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, - 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x72, 0x65, 0x79, 0x3b, 0x0a, 0x7d, - 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, - 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, - 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x65, - 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, - 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, - 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, + 0x20, 0x23, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x32, 0x32, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, + 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, + 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, + 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, + 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, + 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, + 0x77, 0x56, 0x69, 0x65, 0x77, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, + 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x29, 0x20, 0x54, 0x61, + 0x62, 0x6c, 0x65, 0x43, 0x65, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x31, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x56, 0x69, 0x65, 0x77, + 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, + 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, + 0x6c, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x53, 0x63, 0x72, + 0x6f, 0x6c, 0x6c, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, + 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, + 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x50, + 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x3a, 0x20, 0x73, 0x69, 0x6c, 0x76, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, + 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, + 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, + 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x65, 0x71, 0x75, + 0x61, 0x6c, 0x6c, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, + 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, + 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, + 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, + 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, + 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, + 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, + 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, - 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x32, 0x32, 0x32, 0x32, - 0x32, 0x32, 0x36, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x65, 0x76, 0x65, - 0x6c, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x74, 0x3b, 0x0a, 0x20, 0x20, - 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, - 0x36, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, + 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, + 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, + 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, + 0x72, 0x65, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, + 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, + 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x3e, 0x20, 0x54, 0x65, + 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, + 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, + 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, + 0x20, 0x23, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x36, 0x36, 0x3b, 0x0a, + 0x20, 0x20, 0x62, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x20, 0x69, 0x6e, 0x73, + 0x65, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x36, 0x30, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, @@ -434,4 +431,4 @@ unsigned char stylesheet_css[] = { 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x69, 0x6d, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x00 }; -unsigned int stylesheet_css_len = 5206; +unsigned int stylesheet_css_len = 5170; From 72e1637fb0c7935a89648e855aa3810e03a24b98 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Mon, 31 Aug 2026 21:35:11 -0400 Subject: [PATCH 06/11] Address CoPilot review feedback on layout constraints - Panel::layoutSubviews now dictates contentView's size via layoutWithSize instead of layoutWithConstraint, so a height change (e.g. from an accessory view appearing) isn't silently dropped when contentView only opts into ViewAutoresizingWidth. - View::layoutIfNeeded resolves its per-axis fallback constraint independently, so a frame with only one dimension set no longer forces the other axis to Equal(0). - Fix two doc comments in View.h left stale by the layoutWithConstraint/ layoutWithSize split: layoutSubviews's remark now names the actual ViewConstraint enumerators, and layoutWithConstraint's remark no longer claims sizeToSatisfy only runs for containers. --- Sources/ObjectivelyMVC/Panel.c | 5 +---- Sources/ObjectivelyMVC/View.c | 15 ++++++--------- Sources/ObjectivelyMVC/View.h | 17 +++++++++-------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Sources/ObjectivelyMVC/Panel.c b/Sources/ObjectivelyMVC/Panel.c index 7523d818..c929dcd1 100644 --- a/Sources/ObjectivelyMVC/Panel.c +++ b/Sources/ObjectivelyMVC/Panel.c @@ -110,10 +110,7 @@ static void layoutSubviews(View *self) { View *contentView = (View *) this->contentView; - const ViewConstraint w = MakeConstraint(ViewConstraintEqual, size.w); - const ViewConstraint h = MakeConstraint(ViewConstraintEqual, size.h); - - $(contentView, layoutWithConstraint, w, h); + $(contentView, layoutWithSize, &size); super(View, self, layoutSubviews); diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index b7a28047..fe2a6285 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -1117,15 +1117,12 @@ static void layoutIfNeeded(View *self) { if (self->needsLayout) { // No ancestor is actively arranging self right now, so there's no fresh constraint to - // consult; treat an existing frame as authoritative (Equal, not Max -- see ViewConstraintMax). - ViewConstraint w, h; - if (self->frame.w || self->frame.h) { - w = MakeConstraint(ViewConstraintEqual, self->frame.w); - h = MakeConstraint(ViewConstraintEqual, self->frame.h); - } else { - w = MakeConstraint(ViewConstraintUnspecified, 0); - h = MakeConstraint(ViewConstraintUnspecified, 0); - } + // consult; treat an existing frame as authoritative per-axis (Equal, not Max -- see + // ViewConstraintMax), leaving an axis with no frame yet Unspecified. + const ViewConstraint w = self->frame.w ? + MakeConstraint(ViewConstraintEqual, self->frame.w) : MakeConstraint(ViewConstraintUnspecified, 0); + const ViewConstraint h = self->frame.h ? + MakeConstraint(ViewConstraintEqual, self->frame.h) : MakeConstraint(ViewConstraintUnspecified, 0); $(self, layoutWithConstraint, w, h); } diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index 79036870..5c33ab75 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -838,8 +838,9 @@ struct ViewInterface { * already resolved `self->frame`; it must not perform any sizing of its own, only positioning * and committing of subview frames. * @remarks The default implementation resolves each subview's size via View::layoutWithConstraint, - * offering `Exact` for a `ViewAutoresizingWidth`/`Height` subview (since this View's bounds are - * already final) or `Unspecified` otherwise (so the subview sizes itself from its own content). + * offering `ViewConstraintEqual` for a `ViewAutoresizingWidth`/`Height` subview (since this + * View's bounds are already final) or `ViewConstraintUnspecified` otherwise (so the subview + * sizes itself from its own content). * @memberof View */ void (*layoutSubviews)(View *self); @@ -851,12 +852,12 @@ struct ViewInterface { * @param width The ViewConstraint offered for this View's width. * @param height The ViewConstraint offered for this View's height. * @remarks This is the shared tail of View::layoutIfNeeded: resolve self's size via - * View::sizeToSatisfy if self is a container, then View::layoutSubviews, then clear - * `needsLayout`. It exists so a caller that already knows the correct ViewConstraint for a - * View it is arranging -- e.g. a parent's own View::layoutSubviews, positioning one of its - * subviews -- can drive that View's layout directly, without going through - * View::layoutIfNeeded's own guess at what constraint applies (which is only appropriate when - * a View is laying out standalone, with no parent currently arranging it). + * View::sizeToSatisfy, then View::layoutSubviews, then clear `needsLayout`. It exists so a + * caller that already knows the correct ViewConstraint for a View it is arranging -- e.g. a + * parent's own View::layoutSubviews, positioning one of its subviews -- can drive that View's + * layout directly, without going through View::layoutIfNeeded's own guess at what constraint + * applies (which is only appropriate when a View is laying out standalone, with no parent + * currently arranging it). * @see View::layoutWithSize(View *, const SDL_Size *) * @memberof View */ From 69cb34b540d4c87b282ccb67b58c50e8bbf16c45 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Tue, 1 Sep 2026 08:51:00 -0400 Subject: [PATCH 07/11] Delete Claude diarrhea comments. --- Sources/ObjectivelyMVC/View.h | 39 +++++++---------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index 5c33ab75..92573340 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -818,13 +818,6 @@ struct ViewInterface { * this View's own size (bottom-up, from its subviews), if it is a container, before * View::layoutSubviews arranges its subviews (top-down) using that now-final frame. This * ordering guarantees every View's size is settled before any View's position is committed. - * @remarks This View may be laying out standalone here -- e.g. marked dirty directly by a - * style rebind (View::_bind unconditionally sets needsLayout on the View it re-binds), not as - * part of an active parent-driven arrange pass -- so there is no fresh ViewConstraint from an - * ancestor to consult. If self already has a frame, this is resolved as `Equal` rather than - * `Max`, trusting it as authoritative rather than merely an upper bound; see - * View::layoutWithConstraint, which performs the actual resolve-and-arrange once that - * ViewConstraint is determined. * @memberof View */ void (*layoutIfNeeded)(View *self); @@ -852,12 +845,7 @@ struct ViewInterface { * @param width The ViewConstraint offered for this View's width. * @param height The ViewConstraint offered for this View's height. * @remarks This is the shared tail of View::layoutIfNeeded: resolve self's size via - * View::sizeToSatisfy, then View::layoutSubviews, then clear `needsLayout`. It exists so a - * caller that already knows the correct ViewConstraint for a View it is arranging -- e.g. a - * parent's own View::layoutSubviews, positioning one of its subviews -- can drive that View's - * layout directly, without going through View::layoutIfNeeded's own guess at what constraint - * applies (which is only appropriate when a View is laying out standalone, with no parent - * currently arranging it). + * View::sizeToSatisfy, then View::layoutSubviews, then clear `needsLayout`. * @see View::layoutWithSize(View *, const SDL_Size *) * @memberof View */ @@ -870,9 +858,7 @@ struct ViewInterface { * @param size The size to resize this View to. * @remarks Like View::layoutWithConstraint, but for a caller that has already computed this * View's exact final size and must apply it verbatim -- e.g. a StackView applying its - * distribution math to a subview -- rather than resolving it through the ViewConstraint - * pipeline, which would ignore that computed size on any axis the subview has not opted into - * `ViewAutoresizingWidth`/`ViewAutoresizingHeight` for. + * distribution math to a subview. * @see View::layoutWithConstraint(View *, ViewConstraint, ViewConstraint) * @memberof View */ @@ -1078,11 +1064,9 @@ struct ViewInterface { * @param self The View. * @return An SDL_Size that fits this View's subviews. * @remarks The default implementation returns this View's current size if it is not a - * container. If it is a container, it instead returns the size that contains all of its + * container. If it is a container, it instead returns the smallest size that fits all of its * subviews, each measured via View::sizeThatSatisfies with `ViewConstraintUnspecified` for - * both axes -- so a subview with `ViewAutoresizingWidth`/`ViewAutoresizingHeight` degrades to - * its own sizeThatFits value here, rather than inflating to fill a not-yet-determined bound. - * Subclasses with their own sizing logic (e.g. Text, TableView) should override this method. + * both axes. Subclasses with their own sizing logic (e.g. Text, TableView) should override this method. * @memberof View */ SDL_Size (*sizeThatFits)(const View *self); @@ -1097,22 +1081,13 @@ struct ViewInterface { /** * @fn SDL_Size View::sizeThatSatisfies(View *self, ViewConstraint width, ViewConstraint height) - * @brief Resolves the size this View wants to be, given the offered ViewConstraints. + * @brief Resolves the size this View wants to be, within the offered ViewConstraints. * @param self The View. * @param width The ViewConstraint offered for this View's width. * @param height The ViewConstraint offered for this View's height. * @return The resolved size. - * @remarks This is the bottom-up half of layout: it computes this View's sizeThatFits value, - * then resolves that value against the offered ViewConstraints for any axis on which this View - * has `ViewAutoresizingWidth` or `ViewAutoresizingHeight` set. It never mutates `self->frame` - * or any subview's frame, and is always run to completion for an entire dirty subtree before - * View::layoutSubviews arranges any View in that subtree. - * @remarks Always consults View::sizeThatFits, so that a subclass's sizeThatFits override - * (Text, TableView, Select) is honored by any container summing over it as a subview, - * regardless of whether self itself is a container. A sizeThatFits override that only makes - * sense to consult for self's own sizing when self is a container (e.g. TableView, whose - * override is otherwise an expensive, unclamped sum of every row) is responsible for making - * that check itself -- see View::sizeThatFits(const View *) on TableView. + * @remarks Computes this View's sizeThatFits and resolves it against the offered ViewConstraints + * for any axis on which this View has `ViewAutoresizingWidth` or `ViewAutoresizingHeight` set. * @memberof View */ SDL_Size (*sizeThatSatisfies)(View *self, ViewConstraint width, ViewConstraint height); From 537a900ef2e0cb2de6f1474bdea4351631381715 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Tue, 1 Sep 2026 12:26:26 -0400 Subject: [PATCH 08/11] Fix Text::renderDeviceDidReset using stale size from default window scale. --- Sources/ObjectivelyMVC/Text.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/ObjectivelyMVC/Text.c b/Sources/ObjectivelyMVC/Text.c index 07f28f9d..59b80d2f 100644 --- a/Sources/ObjectivelyMVC/Text.c +++ b/Sources/ObjectivelyMVC/Text.c @@ -353,8 +353,7 @@ static void render(View *self, Renderer *renderer) { const float scale = SDL_GetWindowPixelDensity(self->window); if (this->font->scale != scale) { - this->font->scale = scale; - $(this->font, renderDeviceDidReset); + $(self, renderDeviceDidReset); this->texture = release(this->texture); this->textureSize = MakeSize(0, 0); } @@ -449,7 +448,10 @@ static void renderDeviceDidReset(View *self) { Text *this = (Text *) self; this->font->scale = SDL_GetWindowPixelDensity(self->window); + $(this->font, renderDeviceDidReset); + + $(self, sizeToFit); super(View, self, renderDeviceDidReset); } From 5485dcb0161bf81cd4cde38188d54af32a5da317 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Tue, 1 Sep 2026 12:26:51 -0400 Subject: [PATCH 09/11] Fix StackView shrinking pre-sized content. --- Sources/ObjectivelyMVC/StackView.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ObjectivelyMVC/StackView.c b/Sources/ObjectivelyMVC/StackView.c index 68e7cf03..8105a23b 100644 --- a/Sources/ObjectivelyMVC/StackView.c +++ b/Sources/ObjectivelyMVC/StackView.c @@ -161,10 +161,10 @@ static void layoutSubviews(View *self) { case StackViewDistributionFillEqually: switch (this->axis) { case StackViewAxisVertical: - subviewSize.h = availableSize / (float) subviews->count; + subviewSize.h = max(availableSize / (float) subviews->count, sizes[i].h); break; case StackViewAxisHorizontal: - subviewSize.w = availableSize / (float) subviews->count; + subviewSize.w = max(availableSize / (float) subviews->count, sizes[i].w); break; } break; From 6322c0fb072f6e5e3839c4a71e793fe96e0c0287 Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Tue, 1 Sep 2026 17:35:02 -0400 Subject: [PATCH 10/11] Add View::setHidden which triggers parent container layout --- Sources/ObjectivelyMVC/PageView.c | 8 ++++---- Sources/ObjectivelyMVC/Panel.c | 2 +- Sources/ObjectivelyMVC/ScrollView.c | 16 ++++++---------- Sources/ObjectivelyMVC/Select.c | 6 +++--- Sources/ObjectivelyMVC/View.c | 17 +++++++++++++++++ Sources/ObjectivelyMVC/View.h | 13 +++++++++++++ Tests/ObjectivelyMVC/View.c | 24 ++++++++++++++++++++++++ 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/Sources/ObjectivelyMVC/PageView.c b/Sources/ObjectivelyMVC/PageView.c index 14e1554d..5aa28aaa 100644 --- a/Sources/ObjectivelyMVC/PageView.c +++ b/Sources/ObjectivelyMVC/PageView.c @@ -52,7 +52,7 @@ static void addSubview(View *self, View *subview) { PageView *this = (PageView *) self; - subview->hidden = true; + $(subview, setHidden, true); if (this->currentPage == NULL) { $(this, setCurrentPage, subview); @@ -77,7 +77,7 @@ static void removeSubview(View *self, View *subview) { super(View, self, removeSubview, subview); - subview->hidden = false; + $(subview, setHidden, false); if (subview == this->currentPage) { $(this, setCurrentPage, NULL); @@ -117,9 +117,9 @@ static void setCurrentPage_enumerate(const Array *array, ident obj, ident data) View *subview = obj; if (subview == ((PageView *) data)->currentPage) { - subview->hidden = false; + $(subview, setHidden, false); } else { - subview->hidden = true; + $(subview, setHidden, true); } } diff --git a/Sources/ObjectivelyMVC/Panel.c b/Sources/ObjectivelyMVC/Panel.c index c929dcd1..10c6be14 100644 --- a/Sources/ObjectivelyMVC/Panel.c +++ b/Sources/ObjectivelyMVC/Panel.c @@ -119,7 +119,7 @@ static void layoutSubviews(View *self) { resizeHandle->frame.x = self->frame.w - resizeHandle->frame.w; resizeHandle->frame.y = self->frame.h - resizeHandle->frame.h; - resizeHandle->hidden = !this->isResizable; + $(resizeHandle, setHidden, !this->isResizable); } #pragma mark - Control diff --git a/Sources/ObjectivelyMVC/ScrollView.c b/Sources/ObjectivelyMVC/ScrollView.c index 506ac0fc..edfc10e6 100644 --- a/Sources/ObjectivelyMVC/ScrollView.c +++ b/Sources/ObjectivelyMVC/ScrollView.c @@ -86,27 +86,27 @@ static void layoutSubviews(View *self) { ScrollView *this = (ScrollView *) self; - bool showScrollBar; + bool scrollBarHidden; switch (this->scrollBarVisibility) { case ScrollBarShow: - showScrollBar = true; + scrollBarHidden = false; break; case ScrollBarHide: - showScrollBar = false; + scrollBarHidden = true; break; default: { if (this->contentView == NULL) { - showScrollBar = false; + scrollBarHidden = true; } else { const SDL_Size contentSize = $(this->contentView, size); const SDL_Rect bounds = $(self, bounds); - showScrollBar = contentSize.h > bounds.h; + scrollBarHidden = contentSize.h <= bounds.h; } break; } } - ((View *) this->scrollBar)->hidden = !showScrollBar; + $((View *) this->scrollBar, setHidden, scrollBarHidden); super(View, self, layoutSubviews); @@ -114,10 +114,6 @@ static void layoutSubviews(View *self) { this->contentView->frame.x = this->contentOffset.x; this->contentView->frame.y = this->contentOffset.y; } - - if (showScrollBar) { - ((View *) this->scrollBar)->needsLayout = true; - } } #pragma mark - Control diff --git a/Sources/ObjectivelyMVC/Select.c b/Sources/ObjectivelyMVC/Select.c index 099e5b31..a65a5527 100644 --- a/Sources/ObjectivelyMVC/Select.c +++ b/Sources/ObjectivelyMVC/Select.c @@ -71,11 +71,11 @@ static void layoutSubviews(View *self) { Option *option = $(options, objectAtIndex, i); if (option->isSelected) { - option->view.hidden = false; + $((View *) option, setHidden, false); } else if ($(control, isHighlighted)) { - option->view.hidden = false; + $((View *) option, setHidden, false); } else { - option->view.hidden = true; + $((View *) option, setHidden, true); } } diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index fe2a6285..5fe8795b 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -1661,6 +1661,22 @@ static View *selectFirst(View *self, const char *rule) { return first; } +/** + * @fn void View::setHidden(View *self, bool hidden) + * @memberof View + */ +static void setHidden(View *self, bool hidden) { + + if (self->hidden != hidden) { + + self->hidden = hidden; + + if (self->superview && $(self->superview, isContainer)) { + self->superview->needsLayout = true; + } + } +} + /** * @fn SDL_Size View::size(const View *self) * @memberof View @@ -2093,6 +2109,7 @@ static void initialize(Class *clazz) { ((ViewInterface *) clazz->interface)->respondToEvent = respondToEvent; ((ViewInterface *) clazz->interface)->select = _select; ((ViewInterface *) clazz->interface)->selectFirst = selectFirst; + ((ViewInterface *) clazz->interface)->setHidden = setHidden; ((ViewInterface *) clazz->interface)->size = size; ((ViewInterface *) clazz->interface)->sizeThatContains = sizeThatContains; ((ViewInterface *) clazz->interface)->sizeThatFills = sizeThatFills; diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index 92573340..2fd90596 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -230,6 +230,7 @@ struct View { /** * @brief If `true`, this View is not drawn. + * @see View::setHidden(View *, bool) */ bool hidden; @@ -1042,6 +1043,18 @@ struct ViewInterface { */ View *(*selectFirst)(View *self, const char *rule); + /** + * @fn void View::setHidden(View *self, bool hidden) + * @brief Sets this View's hidden state. + * @param self The View. + * @param hidden The hidden state. + * @remarks Hiding or unhiding a View changes its superview's View::visibleSubviews, so this + * marks the superview `needsLayout` if it is a container; writing `self->hidden` directly does + * not. + * @memberof View + */ + void (*setHidden)(View *self, bool hidden); + /** * @fn SDL_Size View::size(const View *self) * @param self The View. diff --git a/Tests/ObjectivelyMVC/View.c b/Tests/ObjectivelyMVC/View.c index da015fde..b39b0425 100644 --- a/Tests/ObjectivelyMVC/View.c +++ b/Tests/ObjectivelyMVC/View.c @@ -266,6 +266,29 @@ START_TEST(styledSizeFloorsContainBoundsWithNoMinSize) { } END_TEST +START_TEST(setHiddenMarksSuperviewNeedsLayout) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *a = fixedView(40, 20); + View *b = fixedView(60, 30); + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + + $((View *) stackView, layoutIfNeeded); + + $(a, setHidden, true); + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(60, stackView->view.frame.w); + ck_assert_int_eq(30, stackView->view.frame.h); + ck_assert_int_eq(0, b->frame.y); + + release(stackView); + +} END_TEST + int main(int argc, char **argv) { TCase *tcase = tcase_create("View"); @@ -277,6 +300,7 @@ int main(int argc, char **argv) { tcase_add_test(tcase, standaloneRelayoutDoesNotShrinkFillChild); tcase_add_test(tcase, fillOnlyChildFillsContainBounds); tcase_add_test(tcase, styledSizeFloorsContainBoundsWithNoMinSize); + tcase_add_test(tcase, setHiddenMarksSuperviewNeedsLayout); Suite *suite = suite_create("View"); suite_add_tcase(suite, tcase); From a9d926a854eff5f47040ee59f3a5d6de7025e75c Mon Sep 17 00:00:00 2001 From: Jay Dolan Date: Tue, 1 Sep 2026 21:11:10 -0400 Subject: [PATCH 11/11] Preserve inherited link settings in test target configurations Use the array form of LIBRARY_SEARCH_PATHS and OTHER_LDFLAGS with $(inherited) so project/xcconfig-level linker flags and search paths are not silently discarded. Applied to all eight test target configurations sharing the pattern, not only the two flagged. Addresses CoPilot review feedback on #45. Co-Authored-By: Claude Fable 5 --- ObjectivelyMVC.xcodeproj/project.pbxproj | 80 +++++++++++++++++++----- 1 file changed, 64 insertions(+), 16 deletions(-) diff --git a/ObjectivelyMVC.xcodeproj/project.pbxproj b/ObjectivelyMVC.xcodeproj/project.pbxproj index 17604838..580860c4 100644 --- a/ObjectivelyMVC.xcodeproj/project.pbxproj +++ b/ObjectivelyMVC.xcodeproj/project.pbxproj @@ -1482,8 +1482,14 @@ 7A49C7A9A3020379E61EC10B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1577,8 +1583,14 @@ B74C1EEAAC88021A8E16E1BA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -1620,8 +1632,14 @@ CE34C8A01FB29F700025F231 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1629,8 +1647,14 @@ CE34C8A11FB29F700025F231 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -1666,8 +1690,14 @@ CE55C1C71F944C6D00D5A326 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1675,8 +1705,14 @@ CE55C1C81F944C6D00D5A326 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -1684,8 +1720,14 @@ CE8819951F91B0BE000D5AB7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -1693,8 +1735,14 @@ CE8819961F91B0BE000D5AB7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - LIBRARY_SEARCH_PATHS = "$(HOMEBREW_PREFIX)/lib"; - OTHER_LDFLAGS = "-lcheck"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(HOMEBREW_PREFIX)/lib", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release;