Refactor: internal.RuleDiamond and internal.Frame ornaments

RuleDiamond draws the rule-diamond-rule of Heading, Dialog, Result, Card
titles and the ornament Divider; Frame draws the inset line with corner
diamonds of Card and decorated Dialog. Both snap the diamonds, and the
rule's diamond sits on the pixel-aligned line.

Snapshots: card (all densities, light and dark) moves the title rule's
diamond onto the rule's pixel row, an 8x8 region. Decorated dialog
(both densities, light and dark) loses the surface-colored halo under
each corner diamond, so the frame line now runs into the diamond as on
Card and the panel border is no longer notched at the corners.
Heading, Divider and Result render unchanged.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 13:44:59 -04:00
1 parent c22a09d327
commit 0651debf9d
16 files changed
+36 -48

No files matched your search

+2 -14
View File
@@ -34,7 +34,7 @@ func Card(c *ui.Context, opts CardOptions, body func()) *ui.Element {
}
if opts.Decorated {
card.Border(theme.BorderWidth, k.Ornament).DrawOver(func(p *ui.Painter, r ui.Rect) {
cardOrnament(p, r, k.Ornament)
internal.Frame(p, r, cardInset, 9, k.Ornament)
})
} else {
card.Border(theme.BorderWidth, k.Border)
@@ -44,10 +44,7 @@ func Card(c *ui.Context, opts CardOptions, body func()) *ui.Element {
ui.Text(c, opts.Title).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).FontWeight(600).LineHeight(1.3)
if opts.Decorated {
ui.Box(c).Height(7).FillWidth().Shrink(0).Draw(func(p *ui.Painter, r ui.Rect) {
cy := r.Y + r.H/2
p.Line(r.X, cy, r.X+r.W/2-6, cy, 1, k.Ornament)
p.Line(r.X+r.W/2+6, cy, r.X+r.W, cy, 1, k.Ornament)
p.FillPath(internal.Diamond(internal.Snap(p, r.X+r.W/2), internal.Snap(p, cy), 7), k.Ornament)
internal.RuleDiamond(p, r, 6, 7, k.Ornament)
})
}
}
@@ -59,12 +56,3 @@ func Card(c *ui.Context, opts CardOptions, body func()) *ui.Element {
})
return card
}
// cardOrnament draws the inner line and the four corner diamonds.
func cardOrnament(p *ui.Painter, r ui.Rect, col ui.Color) {
in := ui.Rect{X: r.X + cardInset, Y: r.Y + cardInset, W: r.W - 2*cardInset, H: r.H - 2*cardInset}
p.Stroke(in, col, 1, 1)
for _, pt := range [4][2]float32{{in.X, in.Y}, {in.X + in.W, in.Y}, {in.X, in.Y + in.H}, {in.X + in.W, in.Y + in.H}} {
p.FillPath(internal.Diamond(internal.Snap(p, pt[0]), internal.Snap(p, pt[1]), 9), col)
}
}
+2 -17
View File
@@ -91,23 +91,11 @@ func dialogPanel(c *ui.Context, panel *ui.Element, width float32, decorated bool
panel.Width(width).MaxWidth(w-32).MaxHeight(h-32).Padding(u*6).Gap(u*3).Margin(8*(1-p), 0, 0, 0)
if decorated {
panel.Border(theme.BorderWidth, k.Ornament).Padding(u*6 + 6).DrawOver(func(pt *ui.Painter, r ui.Rect) {
dialogOrnaments(pt, r, k.Ornament, k.Surface)
internal.Frame(pt, r, 5, 7, k.Ornament)
})
}
}
// dialogOrnaments draws the inner frame and the four corner diamonds.
func dialogOrnaments(p *ui.Painter, r ui.Rect, ink, ground ui.Color) {
const in = 5
f := ui.Rect{X: r.X + in, Y: r.Y + in, W: r.W - 2*in, H: r.H - 2*in}
p.Stroke(f, ink, 1, 1)
for _, pt := range [4][2]float32{{f.X, f.Y}, {f.X + f.W, f.Y}, {f.X, f.Y + f.H}, {f.X + f.W, f.Y + f.H}} {
x, y := internal.Snap(p, pt[0]), internal.Snap(p, pt[1])
p.FillPath(internal.Diamond(x, y, 11), ground)
p.FillPath(internal.Diamond(x, y, 7), ink)
}
}
// dialogHeader builds the title and close button; it reports a close click.
func dialogHeader(c *ui.Context, title string, decorated, closable bool, closeKey string) bool {
k := Tokens(c)
@@ -130,10 +118,7 @@ func dialogHeader(c *ui.Context, title string, decorated, closable bool, closeKe
// dialogRule is the ornament line under a decorated title.
func dialogRule(c *ui.Context, ink ui.Color) {
ui.Box(c).FillWidth().Height(9).Margin(2, 0, 2, 0).Draw(func(p *ui.Painter, r ui.Rect) {
y, cx := r.Y+r.H/2, r.X+r.W/2
p.Line(r.X, y, cx-10, y, 1, ink)
p.Line(cx+10, y, r.X+r.W, y, 1, ink)
p.FillPath(internal.Diamond(internal.Snap(p, cx), internal.Snap(p, y), 7), ink)
internal.RuleDiamond(p, r, 10, 7, ink)
})
}
+6
View File
@@ -37,6 +37,12 @@ func Divider(c *ui.Context, opts DividerOptions) *ui.Element {
}
return e.Height(theme.BorderWidth)
}
if opts.Label == "" && !opts.Vertical {
gap := Density(c).Unit()*2 + dividerDiamond/2
return ui.Box(c).Height(dividerDiamond).Shrink(0).AlignSelf(ui.Stretch).Draw(func(p *ui.Painter, r ui.Rect) {
internal.RuleDiamond(p, r, gap, dividerDiamond, line)
})
}
e := ui.Row(c).Gap(Density(c).Unit() * 2).Shrink(0).AlignSelf(ui.Stretch)
if opts.Vertical {
e.Column().AlignItems(ui.Center)
+1 -10
View File
@@ -31,7 +31,7 @@ func Heading(c *ui.Context, title string, opts HeadingOptions) *ui.Element {
}
if opts.Divider {
ui.Box(c).FillWidth().Height(9).Margin(u, 0, 0, 0).Draw(func(p *ui.Painter, r ui.Rect) {
headingOrnament(p, r, k.Ornament)
internal.RuleDiamond(p, r, 6, 7, k.Ornament)
})
}
})
@@ -49,12 +49,3 @@ func headingSize(level int) float32 {
}
panic("mujica: Heading Level must be 1, 2 or 3")
}
// headingOrnament draws symmetric rules around a center diamond.
func headingOrnament(p *ui.Painter, r ui.Rect, c ui.Color) {
cx, cy := r.X+r.W/2, r.Y+r.H/2
const d, gap = 7, 6
internal.Rule(p, r.X, cx-gap, cy, c)
internal.Rule(p, cx+gap, r.X+r.W, cy, c)
p.FillPath(internal.Diamond(cx, cy, d), c)
}
+24 -2
View File
@@ -17,11 +17,33 @@ func Fade(e *ui.Element, d time.Duration) *ui.Element {
// Rule draws a pixel-aligned 1 DIP horizontal line at y.
func Rule(p *ui.Painter, x0, x1, y float32, c ui.Color) {
yy := ruleY(p, y)
p.Line(x0, yy, x1, yy, 1, c)
}
// ruleY moves y to where a 1 DIP line fills whole device pixels.
func ruleY(p *ui.Painter, y float32) float32 {
s := float64(p.Scale())
px := math.Floor(float64(y) * s)
if int(math.Round(s))%2 == 1 {
px += 0.5
}
yy := float32(px / s)
p.Line(x0, yy, x1, yy, 1, c)
return float32(px / s)
}
// RuleDiamond draws a rule across the middle of r, broken by gap on each side of a size diamond.
func RuleDiamond(p *ui.Painter, r ui.Rect, gap, size float32, col ui.Color) {
cx, cy := r.X+r.W/2, ruleY(p, r.Y+r.H/2)
p.Line(r.X, cy, cx-gap, cy, 1, col)
p.Line(cx+gap, cy, r.X+r.W, cy, 1, col)
p.FillPath(Diamond(Snap(p, cx), Snap(p, cy), size), col)
}
// Frame strokes a 1 DIP line inset inside r with a size diamond on each corner.
func Frame(p *ui.Painter, r ui.Rect, inset, size float32, col ui.Color) {
f := ui.Rect{X: r.X + inset, Y: r.Y + inset, W: r.W - 2*inset, H: r.H - 2*inset}
p.Stroke(f, col, 1, 1)
for _, pt := range [4][2]float32{{f.X, f.Y}, {f.X + f.W, f.Y}, {f.X, f.Y + f.H}, {f.X + f.W, f.Y + f.H}} {
p.FillPath(Diamond(Snap(p, pt[0]), Snap(p, pt[1]), size), col)
}
}
+1 -5
View File
@@ -87,10 +87,6 @@ func Result(c *ui.Context, opts ResultOptions) ResultResult {
// resultOrnament draws two hairlines meeting a diamond, in ornament gold.
func resultOrnament(c *ui.Context, k theme.Tokens) {
ui.Box(c).Size(160, 12).Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) {
cy := float32(int(r.Y+r.H/2)) + 0.5
cx := r.X + r.W/2
p.Line(r.X, cy, cx-9, cy, 1, k.Ornament)
p.Line(cx+9, cy, r.X+r.W, cy, 1, k.Ornament)
p.FillPath(internal.Diamond(cx, cy, 8), k.Ornament)
internal.RuleDiamond(p, r, 9, 8, k.Ornament)
})
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB