Skip to content

Add background gradients, corner cuts, and reversed StackView - #63

Merged
jdolan merged 5 commits into
mainfrom
corner-cut-and-gradient
Sep 9, 2026
Merged

Add background gradients, corner cuts, and reversed StackView#63
jdolan merged 5 commits into
mainfrom
corner-cut-and-gradient

Conversation

@jdolan

@jdolan jdolan commented Sep 9, 2026

Copy link
Copy Markdown
Owner

This pull request adds support for advanced shape rendering and gradient fills in the UI framework, with a focus on customizable polygons, rounded rectangles, and corner-cut rectangles. It introduces new rendering primitives for polygons (both outlines and filled, including gradients), enables background gradients and corner cuts in views, and adds the ability to reverse the layout order in stack views. The changes also include improvements to the rendering pipeline to support these new features.

Rendering enhancements:

  • Added drawPolygon, drawPolygonFilled, and drawRoundedRectGradientFilled methods to the Renderer and RendererInterface for drawing outlined polygons, filled convex polygons (with optional gradients), and rounded rectangles with linear gradients. [1] [2] [3] [4] [5] [6]
  • Introduced gradientColor utility for per-vertex gradient color calculation, and refactored the rendering pipeline to allow per-vertex coloring (including gradients) by making the color parameter in pushDrawArrays optional. [1] [2] [3] [4]

View background and border customization:

  • Added support for background gradients (background-gradient-angle, background-gradient-color) and corner cuts (corner-cut, corner-cut-*) to View, including style inlets, default initialization, and rendering logic for both fills and borders. [1] [2] [3] [4] [5]

StackView improvements:

  • Added a reversed property to StackView to allow subviews to be laid out in reverse order along the main axis, including style inlet and layout logic. [1] [2] [3]

These changes collectively provide much more flexibility and visual richness for UI components, making it easier to create modern, visually distinct interfaces.

A View could only ever be a flat rectangle, rounded or not. Two properties widen that: a
background gradient, and a horizontal inset per corner that slants the edge it shares with
the corner above or below it. Together they draw the angled, gradient filled cards a HUD or
a panel wants, from CSS alone, with no new View subclass.

MVC_Vertex::color was already a per vertex attribute that both shaders interpolate, but
pushDrawArrays overwrote it with its color argument, so no caller could reach it. That
argument is now optional: NULL keeps the colors the vertices carry, which is what makes a
gradient expressible. Renderer gains drawPolygon, drawPolygonFilled and
drawRoundedRectGradientFilled, and drawLines factors out the stroking it shares with
drawPolygon so a border can follow a slanted edge at any width.

The gradient interpolant is a vertex's projection onto the gradient axis, normalized by the
extent of the bounds along it. That makes the color an affine function of position, which
barycentric interpolation reproduces exactly, so a gradient at any angle is right from
colors assigned only at the four corners.

A cut corner leaves the rounded rectangle shader, which is what supplies the anti aliasing
band, so its diagonals are hard edged and View::borderRadius does not apply to it. Cuts are
clamped to the width, so an over large one degenerates to a triangle rather than crossing
over itself.
A StackView lays its subviews out in the order they were added, and that order is baked into
whatever built the view. A HUD element that wants its icon leading in one variant and
trailing in another had no way to say so from CSS. reversed mirrors the positions along the
axis, the way flexbox's row-reverse does, and composes with both axes.

Only the placement is mirrored. View::subviews keeps its order, so drawing and hit testing
are unaffected.
Copilot AI lite review requested due to automatic review settings September 9, 2026 00:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are confirmed rendering correctness/portability issues (corner-cut point clamping can self-intersect; odd-width border expansion is wrong; M_PI usage can break builds on some platforms).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This pull request expands the UI rendering capabilities by adding polygon drawing/filling (including linear gradients), enabling View background gradients and corner-cut rectangles, and introducing a reversed layout option for StackView.

Changes:

  • Added renderer primitives for polygon outlines, filled convex polygons with optional gradients, and rounded-rect gradient fills.
  • Extended View styling/rendering to support background gradients and corner cuts (including corresponding style inlets and default angle initialization).
  • Added StackView::reversed to mirror layout order along the main axis without changing draw/hit-test order.
File summaries
File Description
Sources/ObjectivelyMVC/View.h Adds ViewCornerCut plus new View fields for background gradients and corner cuts.
Sources/ObjectivelyMVC/View.c Adds style inlets and rendering logic for gradients and corner-cut polygon rendering.
Sources/ObjectivelyMVC/StackView.h Introduces reversed layout flag.
Sources/ObjectivelyMVC/StackView.c Applies reversed by indexing subviews in reverse during layout.
Sources/ObjectivelyMVC/Renderer.h Extends renderer interface with polygon + gradient fill APIs; allows per-vertex colors via pushDrawArrays(color=NULL).
Sources/ObjectivelyMVC/Renderer.c Implements polygon stroking/filling, gradient color computation, and per-vertex color support.
configure.ac Bumps package version to 2.4.7.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Sources/ObjectivelyMVC/Renderer.c Outdated
Comment thread Sources/ObjectivelyMVC/View.c Outdated
if (cut) {

// The stroke is centered on the outline, so grow by half of it to sit outside the fill
const int inset = self->borderWidth / 2;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Not taking this one, and I want to be explicit about why rather than silently skip it.

The border is drawn after the fill, over it. With floor (current), a 1px border insets by 0 and the centered stroke covers -0.5..+0.5 of the fill edge: it overlaps the fill by half a pixel, which is invisible. With ceil, it insets by 1 and covers +0.5..+1.5, leaving a half-pixel gap between fill and border that shows the background through. Even widths are identical either way, so ceil only changes the odd case, and changes it for the worse.

1px is the common case here -- it's what the HUD uses on a selected weapon slot and on the local player's scoreboard row -- so this would be a visible regression. Leaving the thread open for your call.

M_PI is not standard C and needs a feature macro on MSVC. Mathlib.h defines it when the
platform does not, and offers float_radians, which says what the conversion is for.

Clamping each of an edge's two insets to the width independently still let them sum past it:
two over-large cuts on one edge crossed the quad over itself rather than closing it, as the
comment claimed. Each is now clamped against what the other leaves.
The vertex buffer is created only when a frame has drawn something, but endFrame bound it
either way, so any frame that drew nothing dereferenced NULL. A View culled to an empty
frame before its first layout is enough to reach it.
@jdolan

jdolan commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Pushed 515401c, an unrelated crash fix I hit while testing this branch, flagging it here since it isn't part of the feature.

Renderer::endFrame creates vertexBuffer only inside if (vertexCount > 0) but binds it unconditionally, so any frame that draws nothing dereferences NULL. A View culled to an empty frame before its first layout is enough to reach it. Guarded the bind and the draw loop.

Reproduced with a minimal harness: segfault before, clean exit after, with a frame that does draw unaffected.

@jdolan
jdolan merged commit e04af5c into main Sep 9, 2026
4 checks passed
@jdolan
jdolan deleted the corner-cut-and-gradient branch September 9, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants