Skip to content

Commit 02e24bf

Browse files
authored
Improvements v15.1 (#143)
* add direction enum * add directions * add directions * add direction enum * add direction enum * add direction enum * add direction enum * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * non sysmettric circle thing * gl contexts - generateCircleBox * gl contexts - generateCircleBox * gl contexts - generateCircleBox * gl contexts - generateCircleBox * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * fix buttons * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates * updates
1 parent 40959ff commit 02e24bf

12 files changed

Lines changed: 544 additions & 88 deletions

File tree

examples/circlebox.nim

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
import pixie, pixie/simd
2+
3+
4+
type
5+
Directions* = enum
6+
dTop
7+
dRight
8+
dBottom
9+
dLeft
10+
11+
DirectionCorners* = enum
12+
dcTopLeft
13+
dcTopRight
14+
dcBottomRight
15+
dcBottomLeft
16+
17+
proc generateCircleBox*(
18+
radii: array[DirectionCorners, int],
19+
offset = vec2(0, 0),
20+
spread: float32 = 0.0'f32,
21+
blur: float32 = 0.0'f32,
22+
stroked: bool = true,
23+
lineWidth: float32 = 0.0'f32,
24+
fillStyle: ColorRGBA = rgba(255, 255, 255, 255),
25+
shadowColor: ColorRGBA = rgba(255, 255, 255, 255),
26+
outerShadow = true,
27+
innerShadow = true,
28+
innerShadowBorder = true,
29+
outerShadowFill = false,
30+
): Image =
31+
let origRadii = radii
32+
var radii: array[DirectionCorners, int]
33+
var maxRadius = 0
34+
for i, r in origRadii:
35+
radii[i] = max(r, 0)
36+
maxRadius = max(maxRadius, r)
37+
38+
# Additional size for spread and blur
39+
let padding = (spread.int + blur.int)
40+
let totalSize = max(maxRadius * 2 + padding * 2, 10+padding*2)
41+
42+
# Create a canvas large enough to contain the box with all effects
43+
let img = newImage(totalSize, totalSize)
44+
let ctx = newContext(img)
45+
46+
# Calculate the inner box dimensions
47+
let innerWidth = (totalSize - padding * 2).float32
48+
let innerHeight = (totalSize - padding * 2).float32
49+
50+
# Create a path for the rounded rectangle with the given dimensions and corner radii
51+
proc createRoundedRectPath(
52+
width, height: float32,
53+
radii: array[DirectionCorners, int],
54+
padding: int
55+
): Path =
56+
# Start at top right after the corner radius
57+
result = newPath()
58+
let topRight = vec2(width - radii[dcTopRight].float32, 0)
59+
result.moveTo(topRight + vec2(padding.float32, padding.float32))
60+
61+
# Top right corner
62+
let trControl = vec2(width, 0)
63+
result.quadraticCurveTo(
64+
trControl + vec2(padding.float32, padding.float32),
65+
vec2(width, radii[dcTopRight].float32) + vec2(padding.float32, padding.float32)
66+
)
67+
68+
# Right side
69+
result.lineTo(vec2(width, height - radii[dcBottomRight].float32) + vec2(padding.float32, padding.float32))
70+
71+
# Bottom right corner
72+
let brControl = vec2(width, height)
73+
result.quadraticCurveTo(
74+
brControl + vec2(padding.float32, padding.float32),
75+
vec2(width - radii[dcBottomRight].float32, height) + vec2(padding.float32, padding.float32)
76+
)
77+
78+
# Bottom side
79+
result.lineTo(vec2(radii[dcBottomLeft].float32, height) + vec2(padding.float32, padding.float32))
80+
81+
# Bottom left corner
82+
let blControl = vec2(0, height)
83+
result.quadraticCurveTo(
84+
blControl + vec2(padding.float32, padding.float32),
85+
vec2(0, height - radii[dcBottomLeft].float32) + vec2(padding.float32, padding.float32)
86+
)
87+
88+
# Left side
89+
result.lineTo(vec2(0, radii[dcTopLeft].float32) + vec2(padding.float32, padding.float32))
90+
91+
# Top left corner
92+
let tlControl = vec2(0, 0)
93+
result.quadraticCurveTo(
94+
tlControl + vec2(padding.float32, padding.float32),
95+
vec2(radii[dcTopLeft].float32, 0) + vec2(padding.float32, padding.float32)
96+
)
97+
98+
# Close the path
99+
result.lineTo(topRight + vec2(padding.float32, padding.float32))
100+
101+
# Create the path for our rounded rectangle
102+
let path = createRoundedRectPath(innerWidth, innerHeight, radii, padding)
103+
104+
# Draw the box
105+
if stroked:
106+
ctx.strokeStyle = fillStyle
107+
ctx.lineWidth = lineWidth
108+
ctx.stroke(path)
109+
else:
110+
ctx.fillStyle = fillStyle
111+
ctx.fill(path)
112+
113+
# Apply inner shadow if requested
114+
if innerShadow or outerShadow or outerShadowFill:
115+
let shadow = img.shadow(
116+
offset = offset,
117+
spread = spread,
118+
blur = blur,
119+
color = shadowColor
120+
)
121+
122+
let spath = createRoundedRectPath(innerWidth, innerHeight, radii, padding)
123+
124+
let combined = newImage(totalSize, totalSize)
125+
let ctx = newContext(combined)
126+
if innerShadow:
127+
ctx.saveLayer()
128+
ctx.clip(spath, EvenOdd)
129+
ctx.drawImage(shadow, pos = vec2(0, 0))
130+
ctx.restore()
131+
if outerShadowFill:
132+
let spath = spath.copy()
133+
spath.rect(0, 0, totalSize.float32, totalSize.float32)
134+
ctx.saveLayer()
135+
ctx.clip(spath, EvenOdd)
136+
ctx.fillStyle = fillStyle
137+
ctx.rect(0, 0, totalSize.float32, totalSize.float32)
138+
ctx.fill()
139+
ctx.restore()
140+
if outerShadow:
141+
let spath = spath.copy()
142+
spath.rect(0, 0, totalSize.float32, totalSize.float32)
143+
ctx.saveLayer()
144+
ctx.clip(spath, EvenOdd)
145+
ctx.drawImage(shadow, pos = vec2(0, 0))
146+
ctx.restore()
147+
if innerShadowBorder:
148+
ctx.drawImage(img, pos = vec2(0, 0))
149+
return combined
150+
else:
151+
return img
152+
153+
154+
155+
let imgA = generateCircleBox(
156+
radii = [0, 20, 40, 10], # Different radius for each corner
157+
offset = vec2(0, 0),
158+
spread = 1.0'f32,
159+
blur = 10.0'f32,
160+
stroked = true,
161+
lineWidth = 2.0,
162+
outerShadow = false,
163+
innerShadow = false,
164+
)
165+
166+
imgA.writeFile("examples/circlebox-asymmetric.png")
167+
168+
let imgAnostroke = generateCircleBox(
169+
radii = [30, 20, 40, 10], # Different radius for each corner
170+
offset = vec2(0, 0),
171+
spread = 1.0'f32,
172+
blur = 10.0'f32,
173+
stroked = false,
174+
lineWidth = 0.0,
175+
outerShadow = false,
176+
innerShadow = false,
177+
)
178+
179+
imgAnostroke.writeFile("examples/circlebox-asymmetric-nostroke.png")
180+
181+
let imgAfillshadow = generateCircleBox(
182+
radii = [30, 20, 40, 10], # Different radius for each corner
183+
offset = vec2(0, 0),
184+
spread = 20.0'f32,
185+
blur = 20.0'f32,
186+
stroked = false,
187+
lineWidth = 0.0,
188+
outerShadow = true,
189+
innerShadow = false,
190+
innerShadowBorder = true,
191+
outerShadowFill = false,
192+
)
193+
194+
imgAfillshadow.writeFile("examples/circlebox-asymmetric-fill-shadow.png")
195+
196+
197+
let imgB = generateCircleBox(
198+
radii = [1, 1, 1, 1], # Different radius for each corner
199+
offset = vec2(0, 0),
200+
spread = 0.0'f32,
201+
blur = 10.0'f32,
202+
stroked = false,
203+
lineWidth = 2.0,
204+
outerShadow = true,
205+
innerShadow = false,
206+
innerShadowBorder = true,
207+
)
208+
209+
imgB.writeFile("examples/circlebox-symmetric.png")
210+
211+
# Only inner shadow example
212+
let imgC = generateCircleBox(
213+
radii = [30, 30, 30, 30],
214+
offset = vec2(0, 0),
215+
spread = 1.0'f32,
216+
blur = 10.0'f32,
217+
stroked = true,
218+
lineWidth = 2.0,
219+
outerShadow = false,
220+
innerShadow = true,
221+
innerShadowBorder = false,
222+
)
223+
224+
imgC.writeFile("examples/circlebox-inner-only.png")
225+
226+
# Only outer shadow example
227+
let imgD = generateCircleBox(
228+
radii = [30, 30, 30, 30],
229+
offset = vec2(2, 2),
230+
spread = 1.0'f32,
231+
blur = 10.0'f32,
232+
stroked = true,
233+
lineWidth = 2.0,
234+
outerShadow = true,
235+
innerShadow = false,
236+
innerShadowBorder = false,
237+
)
238+
239+
imgD.writeFile("examples/circlebox-outer-only.png")
240+
241+
# Only outer shadow example
242+
let imgE = generateCircleBox(
243+
radii = [30, 30, 30, 30],
244+
offset = vec2(0, 0),
245+
spread = 1.0'f32,
246+
blur = 10.0'f32,
247+
stroked = true,
248+
lineWidth = 2.0,
249+
outerShadow = true,
250+
innerShadow = true,
251+
innerShadowBorder = false,
252+
)
253+
254+
imgE.writeFile("examples/circlebox-outer-inner.png")
255+
256+
let imgF = generateCircleBox(
257+
radii = [30, 30, 30, 30],
258+
offset = vec2(0, 0),
259+
spread = 1.0'f32,
260+
blur = 10.0'f32,
261+
stroked = true,
262+
lineWidth = 2.0,
263+
outerShadow = true,
264+
innerShadow = true,
265+
innerShadowBorder = false,
266+
outerShadowFill = true,
267+
)
268+
269+
imgF.writeFile("examples/circlebox-inner-and-fill-outer.png")
270+
271+
let imgG = generateCircleBox(
272+
radii = [10, 10, 10, 10],
273+
offset = vec2(0, 0),
274+
spread = 0.0'f32,
275+
blur = 5.0'f32,
276+
stroked = true,
277+
lineWidth = 2.0,
278+
outerShadow = false,
279+
innerShadow = true,
280+
innerShadowBorder = true,
281+
outerShadowFill = true,
282+
)
283+
284+
imgG.writeFile("examples/circlebox-inner-and-fill-outer-0radius.png")

examples/corners.nim

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import pixie, pixie/simd
22

3+
type
4+
DirectionCorners* = enum
5+
dcTopLeft
6+
dcTopRight
7+
dcBottomRight
8+
dcBottomLeft
9+
310
proc generateCorner(
411
radius: int,
5-
quadrant: range[1 .. 4],
12+
quadrant: DirectionCorners,
613
stroked: bool,
714
lineWidth: float32 = 0'f32,
815
fillStyle = rgba(255, 255, 255, 255),
@@ -39,16 +46,16 @@ proc generateCorner(
3946
path.bezierCurveTo(blc, trc, tr)
4047

4148
case quadrant
42-
of 1:
43-
ctx.rotate(270 * PI / 180)
44-
ctx.translate(-tr)
45-
of 2:
49+
of dcTopLeft: # TL
4650
ctx.rotate(180 * PI / 180)
4751
ctx.translate(-br)
48-
of 3:
52+
of dcTopRight: # TR
53+
ctx.rotate(270 * PI / 180)
54+
ctx.translate(-tr)
55+
of dcBottomLeft: # BL
4956
ctx.rotate(90 * PI / 180)
5057
ctx.translate(-bl)
51-
of 4:
58+
of dcBottomRight: # BR
5259
discard
5360

5461
if doStroke:
@@ -88,7 +95,7 @@ proc generateCorner(
8895
result = image
8996

9097

91-
for i in 1..4:
98+
for i in DirectionCorners:
9299
let img = generateCorner(30, i, stroked = true,
93100
lineWidth = 3, innerShadow = true,
94101
spread = 1, blur = 10,

src/figuro/common/nodes/basics.nim

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ else:
1515
type NodeID* = int64
1616

1717
type
18+
Directions* = enum
19+
dTop
20+
dRight
21+
dBottom
22+
dLeft
23+
24+
DirectionCorners* = enum
25+
dcTopLeft
26+
dcTopRight
27+
dcBottomRight
28+
dcBottomLeft
29+
1830
NodeKind* = enum
1931
## Different types of nodes.
2032
nkFrame

src/figuro/common/nodes/uinodes.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ type
102102

103103
kind*: NodeKind
104104
shadow*: array[ShadowStyle, Shadow]
105+
# cornerRadius*: array[DirectionCorners, UiScalar]
105106
cornerRadius*: UiScalar
106107
image*: ImageStyle
107108
textLayout*: GlyphArrangement

0 commit comments

Comments
 (0)