Skip to content

Commit b3a3176

Browse files
authored
Add sdfy symmetric corner (#158)
* add sdfy * refactor * refactor * adding in sdfy * adding in sdfy * adding in sdfy * adding in sdfy * adding in sdfy * adding in sdfy * adding in sdfy * update sdfy * revert back draw extras * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * update sdfy * fix simd rgbx * fix simd rgbx * fix simd rgbx * fix simd rgbx * update sdfy * fix inset shadow insets - sse * fix inset shadow insets - sse * fix inset shadow insets - sse * css update * renderer update * updates * updates * updates * refactor to enum loops * change to non-transform offset * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * reuse sides * change corners order * change corners order * change corners order * change corners order * change corners order * change corners order * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * cleanup * rename * rename * refactor * refactor * refactor * refactor * refactor * refactor * refactor * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * trying independently generated corners * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * perfect corners same radii * change to loop * change to loop * change to loop * change to loop * change to loop * change to loop * fixing 21 patch setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * shadow patch21 setup * fix ci * re-add clips * fixes * fixes * fixes * fixes * fixes * fixes * fix prs * fix prs * fix prs * fix prs * fix prs * fix tbutton * fix tbutton * fix tbutton * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner * fix inner
1 parent 6649835 commit b3a3176

File tree

6 files changed

+272
-389
lines changed

6 files changed

+272
-389
lines changed

figuro.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "0.16.0"
1+
version = "0.16.1"
22
author = "Jaremy Creechley"
33
description = "UI Engine for Nim"
44
license = "MIT"

src/figuro/renderer/opengl/glcontext.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export drawextras
1616
logScope:
1717
scope = "opengl"
1818

19+
proc round*(v: Vec2): Vec2 =
20+
vec2(round(v.x), round(v.y))
21+
1922
const quadLimit = 10_921
2023

2124
type Context* = ref object

src/figuro/renderer/utils/drawboxes.nim

Lines changed: 95 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ proc drawOuterBox*[R](ctx: R, rect: Rect, padding: float32, color: Color) =
2222
ctx.drawRect(rectBottom, color)
2323
ctx.drawRect(rectRight, color)
2424
25+
proc toHex*(h: Hash): string =
26+
const HexChars = "0123456789ABCDEF"
27+
result = newString(sizeof(Hash) * 2)
28+
var h = h
29+
for i in countdown(result.high, 0):
30+
result[i] = HexChars[h and 0xF]
31+
h = h shr 4
32+
33+
2534
proc drawRoundedRect*[R](
2635
ctx: R,
2736
rect: Rect,
@@ -45,106 +54,119 @@ proc drawRoundedRect*[R](
4554
bw = cbs.sideSize.float32
4655
bh = cbs.sideSize.float32
4756
48-
let hash = hash((6217, int(cbs.sideSize), int(cbs.maxRadius), int(weight), doStroke, outerShadowFill))
57+
# let hash = hash((6217, int(cbs.sideSize), int(cbs.maxRadius), int(weight), doStroke, outerShadowFill))
58+
let hash = hash((6217, doStroke, outerShadowFill, cbs.padding, cbs.weightSize))
59+
let cornerCbs = cbs.roundedBoxCornerSizes(radii, innerShadow = false)
4960
5061
block drawCorners:
5162
var cornerHashes: array[DirectionCorners, Hash]
5263
for corner in DirectionCorners:
5364
cornerHashes[corner] = hash((hash, 41, int(radii[corner])))
5465
55-
var missingAnyCorner = false
66+
let fill = rgba(255, 255, 255, 255)
67+
let clear = rgba(0, 0, 0, 0)
68+
5669
for corner in DirectionCorners:
57-
if cornerHashes[corner] notin ctx.entries:
58-
# echo "missing corner: ", corner, " hash: ", cornerHashes[corner], " radius: ", radii[corner], " sideSize: ", cbs.sideSize, " maxRadius: ", cbs.maxRadius, " weight: ", weight, " doStroke: ", doStroke, " outerShadowFill: ", outerShadowFill
59-
missingAnyCorner = true
60-
break
61-
62-
if missingAnyCorner:
63-
let fill = rgba(255, 255, 255, 255)
64-
let clear = rgba(0, 0, 0, 0)
65-
var center = vec2(bw, bh)
66-
let wh = vec2(2*bw+1, 2*bh+1)
67-
let corners = radii.cornersToSdfRadii()
68-
var circle = newImage(int(2*bw), int(2*bh))
70+
if cornerHashes[corner] in ctx.entries:
71+
continue
72+
73+
let cornerCbs = cornerCbs[corner]
74+
let corners = vec4(cornerCbs.radius.float32)
75+
var image = newImage(cornerCbs.sideSize, cornerCbs.sideSize)
76+
let wh = vec2(2*cornerCbs.sideSize.float32, 2*cornerCbs.sideSize.float32)
6977
7078
if doStroke:
71-
drawSdfShape(circle,
72-
center = center,
79+
drawSdfShape(image,
80+
center = vec2(cornerCbs.center.float32),
7381
wh = wh,
7482
params = RoundedBoxParams(r: corners),
7583
pos = fill.to(ColorRGBA),
7684
neg = clear.to(ColorRGBA),
77-
factor = weight + 0.5,
85+
factor = cbs.weightSize.float32,
7886
spread = 0.0,
7987
mode = sdfModeAnnular)
8088
else:
81-
drawSdfShape(circle,
82-
center = center,
89+
drawSdfShape(image,
90+
center = vec2(cornerCbs.center.float32, cornerCbs.center.float32),
8391
wh = wh,
8492
params = RoundedBoxParams(r: corners),
8593
pos = fill.to(ColorRGBA),
8694
neg = clear.to(ColorRGBA),
8795
mode = sdfModeClipAA)
8896
89-
let patches = sliceToNinePatch(circle)
90-
# Store each piece in the atlas
91-
let cornerImages: array[DirectionCorners, Image] = [
92-
dcTopLeft: patches.topLeft,
93-
dcTopRight: patches.topRight,
94-
dcBottomLeft: patches.bottomLeft,
95-
dcBottomRight: patches.bottomRight,
96-
]
97-
98-
for corner in DirectionCorners:
99-
let cornerHash = cornerHashes[corner]
100-
if cornerHash notin ctx.entries:
101-
let image = cornerImages[corner]
102-
case corner:
103-
of dcTopLeft:
104-
discard
105-
of dcTopRight:
106-
image.flipHorizontal()
107-
of dcBottomRight:
108-
image.flipHorizontal()
109-
image.flipVertical()
110-
of dcBottomLeft:
111-
image.flipVertical()
112-
ctx.putImage(toKey(cornerHash), image)
97+
if color.a != 1.0 and false:
98+
var msg = "corner"
99+
msg &= (if doStroke: "-stroke" else: "-noStroke")
100+
msg &= "-weight" & $weight
101+
msg &= "-radius" & $cornerCbs.radius
102+
msg &= "-sideSize" & $cornerCbs.sideSize
103+
msg &= "-wh" & $wh.x
104+
msg &= "-padding" & $cbs.padding
105+
msg &= "-center" & $cornerCbs.center
106+
msg &= "-doStroke" & (if doStroke: "true" else: "false")
107+
msg &= "-outerShadowFill" & (if outerShadowFill: "true" else: "false")
108+
msg &= "-corner-" & $corner
109+
msg &= "-hash" & toHex(cornerHashes[corner])
110+
echo "generating corner: ", msg
111+
image.writeFile("examples/" & msg & ".png")
112+
113+
ctx.putImage(toKey(cornerHashes[corner]), image)
113114
114115
let
115116
xy = rect.xy
116117
zero = vec2(0, 0)
117118
cornerSize = vec2(bw, bh)
118-
topLeft = xy + vec2(0, 0)
119-
topRight = xy + vec2(w - bw, 0)
120-
bottomLeft = xy + vec2(0, h - bh)
121-
bottomRight = xy + vec2(w - bw, h - bh)
122-
123-
ctx.saveTransform()
124-
ctx.translate(topLeft)
125-
ctx.drawImage(cornerHashes[dcTopLeft], zero, color)
126-
ctx.restoreTransform()
127-
128-
ctx.saveTransform()
129-
ctx.translate(topRight + cornerSize / 2)
130-
ctx.rotate(-Pi/2)
131-
ctx.translate(-cornerSize / 2)
132-
ctx.drawImage(cornerHashes[dcTopRight], zero, color)
133-
ctx.restoreTransform()
134-
135-
ctx.saveTransform()
136-
ctx.translate(bottomLeft + cornerSize / 2)
137-
ctx.rotate(Pi/2)
138-
ctx.translate(-cornerSize / 2)
139-
ctx.drawImage(cornerHashes[dcBottomLeft], zero, color)
140-
ctx.restoreTransform()
141-
142-
ctx.saveTransform()
143-
ctx.translate(bottomRight + cornerSize / 2)
144-
ctx.rotate(Pi)
145-
ctx.translate(-cornerSize / 2)
146-
ctx.drawImage(cornerHashes[dcBottomRight], zero, color)
147-
ctx.restoreTransform()
119+
120+
cpos = [
121+
dcTopLeft: xy + vec2(0, 0),
122+
dcTopRight: xy + vec2(w - bw, 0),
123+
dcBottomLeft: xy + vec2(0, h - bh),
124+
dcBottomRight: xy + vec2(w - bw, h - bh)
125+
]
126+
127+
coffset = [
128+
dcTopLeft: vec2(0, 0),
129+
dcTopRight: vec2(cornerCbs[dcTopRight].sideDelta.float32, 0),
130+
dcBottomLeft: vec2(0, cornerCbs[dcBottomLeft].sideDelta.float32),
131+
dcBottomRight: vec2(cornerCbs[dcBottomRight].sideDelta.float32, cornerCbs[dcBottomRight].sideDelta.float32)
132+
]
133+
134+
ccenter = [
135+
dcTopLeft: vec2(0.0, 0.0),
136+
dcTopRight: vec2(cornerCbs[dcTopRight].sideSize.float32, cornerCbs[dcTopRight].sideSize.float32),
137+
dcBottomLeft: vec2(cornerCbs[dcBottomLeft].sideSize.float32, cornerCbs[dcBottomLeft].sideSize.float32),
138+
dcBottomRight: vec2(cornerCbs[dcBottomRight].sideSize.float32, cornerCbs[dcBottomRight].sideSize.float32)
139+
]
140+
141+
darkGrey = rgba(50, 50, 50, 255).to(Color)
142+
143+
angles = [dcTopLeft: 0.0, dcTopRight: -Pi/2, dcBottomLeft: Pi/2, dcBottomRight: Pi]
144+
145+
# if color.a != 1.0:
146+
# echo "drawing corners: ", "BL: " & toHex(cornerHashes[dcBottomLeft]) & " color: " & $color & " hasImage: " & $ctx.hasImage(cornerHashes[dcBottomLeft]) & " cornerSize: " & $blCornerSize & " blPos: " & $(bottomLeft + blCornerSize / 2) & " delta: " & $cornerCbs[dcBottomLeft].sideDelta & " doStroke: " & $doStroke
147+
148+
for corner in DirectionCorners:
149+
ctx.saveTransform()
150+
ctx.translate(cpos[corner] + coffset[corner] + ccenter[corner] / 2)
151+
ctx.rotate(angles[corner])
152+
ctx.translate(-ccenter[corner] / 2)
153+
ctx.drawImage(cornerHashes[corner], zero, color)
154+
155+
if cornerCbs[corner].sideDelta > 0:
156+
let inner = cornerCbs[corner].inner.float32
157+
let sideDelta = cornerCbs[corner].sideDelta.float32
158+
let sideSize = cornerCbs[corner].sideSize.float32
159+
# inner patch left, right, and then center
160+
if doStroke:
161+
ctx.drawRect(rect(0, inner, cbs.weightSize.float32, sideDelta), color)
162+
ctx.drawRect(rect(inner, 0, sideDelta, cbs.weightSize.float32), color)
163+
else:
164+
ctx.drawRect(rect(0, inner, inner, sideDelta), color)
165+
ctx.drawRect(rect(inner, 0, sideDelta, sideSize), color)
166+
# we could do two boxes, but this matches our shadow needs
167+
ctx.drawRect(rect(inner, inner, sideDelta, sideDelta), color)
168+
169+
ctx.restoreTransform()
148170

149171
block drawEdgeBoxes:
150172
let

src/figuro/renderer/utils/drawextras.nim

Lines changed: 0 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -73,143 +73,3 @@ proc sliceToNinePatch*(img: Image): tuple[
7373
bottom: fbottom,
7474
left: fleft
7575
)
76-
77-
proc generateCircleBox*(
78-
radii: array[DirectionCorners, float32],
79-
offset = vec2(0, 0),
80-
spread: float32 = 0.0'f32,
81-
blur: float32 = 0.0'f32,
82-
stroked: bool = true,
83-
lineWidth: float32 = 0.0'f32,
84-
fillStyle: ColorRGBA = rgba(255, 255, 255, 255),
85-
shadowColor: ColorRGBA = rgba(255, 255, 255, 255),
86-
outerShadow = true,
87-
innerShadow = true,
88-
innerShadowBorder = true,
89-
outerShadowFill = false,
90-
): Image =
91-
92-
# Additional size for spread and blur
93-
let lw = lineWidth.ceil()
94-
let (maxRadius, sideSize, totalSize, padding, inner) = getCircleBoxSizes(radii, blur, spread, lineWidth)
95-
96-
# Create a canvas large enough to contain the box with all effects
97-
let img = newImage(totalSize, totalSize)
98-
let bxy = newContext(img)
99-
100-
# Calculate the inner box dimensions
101-
let innerWidth = inner.float32
102-
let innerHeight = inner.float32
103-
104-
# Create a path for the rounded rectangle with the given dimensions and corner radii
105-
proc createRoundedRectPath(
106-
width, height: float32,
107-
radii: array[DirectionCorners, float32],
108-
padding: float32,
109-
lw: float32
110-
): pixie.Path =
111-
# Start at top right after the corner radius
112-
let hlw = lw / 2.0
113-
let padding = padding + hlw
114-
let width = width - lw
115-
let height = height - lw
116-
117-
result = newPath()
118-
let topRight = vec2(width - radii[dcTopRight], 0)
119-
result.moveTo(topRight + vec2(padding, padding))
120-
121-
# Top right corner
122-
let trControl = vec2(width, 0)
123-
result.quadraticCurveTo(
124-
trControl + vec2(padding, padding),
125-
vec2(width, radii[dcTopRight]) + vec2(padding, padding)
126-
)
127-
128-
# Right side
129-
result.lineTo(vec2(width, height - radii[dcBottomRight]) + vec2(padding, padding))
130-
131-
# Bottom right corner
132-
let brControl = vec2(width, height)
133-
result.quadraticCurveTo(
134-
brControl + vec2(padding, padding),
135-
vec2(width - radii[dcBottomRight], height) + vec2(padding, padding)
136-
)
137-
138-
# Bottom side
139-
result.lineTo(vec2(radii[dcBottomLeft], height) + vec2(padding, padding))
140-
141-
# Bottom left corner
142-
let blControl = vec2(0, height)
143-
result.quadraticCurveTo(
144-
blControl + vec2(padding, padding),
145-
vec2(0, height - radii[dcBottomLeft]) + vec2(padding, padding)
146-
)
147-
148-
# Left side
149-
result.lineTo(vec2(0, radii[dcTopLeft]) + vec2(padding, padding))
150-
151-
# Top left corner
152-
let tlControl = vec2(0, 0)
153-
result.quadraticCurveTo(
154-
tlControl + vec2(padding, padding),
155-
vec2(radii[dcTopLeft], 0) + vec2(padding, padding)
156-
)
157-
158-
# Close the path
159-
result.lineTo(topRight + vec2(padding, padding))
160-
161-
# Create the path for our rounded rectangle
162-
let path = createRoundedRectPath(innerWidth, innerHeight, radii, padding.float32, lw)
163-
164-
# Draw the box
165-
if stroked:
166-
bxy.strokeStyle = fillStyle
167-
bxy.lineWidth = lineWidth
168-
bxy.stroke(path)
169-
else:
170-
bxy.fillStyle = fillStyle
171-
bxy.fill(path)
172-
173-
# Apply inner shadow if requested
174-
if innerShadow or outerShadow or outerShadowFill:
175-
let spath = createRoundedRectPath(innerWidth, innerHeight, radii, padding.float32, lw)
176-
177-
let ctxImg = newContext(img)
178-
if outerShadowFill:
179-
let spath = spath.copy()
180-
spath.rect(0, 0, totalSize.float32, totalSize.float32)
181-
ctxImg.saveLayer()
182-
ctxImg.clip(spath, EvenOdd)
183-
ctxImg.fillStyle = fillStyle
184-
ctxImg.rect(0, 0, totalSize.float32, totalSize.float32)
185-
ctxImg.fill()
186-
ctxImg.restore()
187-
188-
let shadow = img.shadow(
189-
offset = offset,
190-
spread = spread,
191-
blur = blur,
192-
color = shadowColor
193-
)
194-
195-
let combined = newImage(totalSize, totalSize)
196-
let bxy = newContext(combined)
197-
if innerShadow:
198-
bxy.saveLayer()
199-
bxy.clip(spath, EvenOdd)
200-
bxy.drawImage(shadow, pos = vec2(0, 0))
201-
bxy.drawImage(shadow, pos = vec2(0, 0))
202-
bxy.drawImage(shadow, pos = vec2(0, 0))
203-
bxy.restore()
204-
if outerShadow:
205-
let spath = spath.copy()
206-
spath.rect(0, 0, totalSize.float32, totalSize.float32)
207-
bxy.saveLayer()
208-
bxy.clip(spath, EvenOdd)
209-
bxy.drawImage(shadow, pos = vec2(0, 0))
210-
bxy.restore()
211-
if innerShadowBorder:
212-
bxy.drawImage(img, pos = vec2(0, 0))
213-
return combined
214-
else:
215-
return img

0 commit comments

Comments
 (0)