Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6a15151
add direction enum
elcritch May 9, 2025
ccc9945
add directions
elcritch May 9, 2025
7dac070
add directions
elcritch May 9, 2025
f2f329c
add direction enum
elcritch May 9, 2025
1b6356c
add direction enum
elcritch May 9, 2025
03d6b80
add direction enum
elcritch May 9, 2025
29651e5
add direction enum
elcritch May 9, 2025
92cfa28
non sysmettric circle thing
elcritch May 9, 2025
40aa1e0
non sysmettric circle thing
elcritch May 9, 2025
ba10df4
non sysmettric circle thing
elcritch May 9, 2025
b29d049
non sysmettric circle thing
elcritch May 9, 2025
6788f2f
non sysmettric circle thing
elcritch May 9, 2025
887e6c4
non sysmettric circle thing
elcritch May 9, 2025
b8e3aba
non sysmettric circle thing
elcritch May 9, 2025
2c006af
non sysmettric circle thing
elcritch May 9, 2025
e8bcf65
non sysmettric circle thing
elcritch May 9, 2025
a7b757f
non sysmettric circle thing
elcritch May 9, 2025
537733a
non sysmettric circle thing
elcritch May 9, 2025
a467c44
non sysmettric circle thing
elcritch May 9, 2025
f3d2a26
non sysmettric circle thing
elcritch May 9, 2025
08de0ec
non sysmettric circle thing
elcritch May 9, 2025
c03c1b9
gl contexts - generateCircleBox
elcritch May 9, 2025
63301e4
gl contexts - generateCircleBox
elcritch May 9, 2025
2601c64
gl contexts - generateCircleBox
elcritch May 9, 2025
f616b88
gl contexts - generateCircleBox
elcritch May 9, 2025
bb8ac77
fix buttons
elcritch May 9, 2025
e61faf5
fix buttons
elcritch May 9, 2025
677ec65
fix buttons
elcritch May 9, 2025
f9d894c
fix buttons
elcritch May 9, 2025
0ad4165
fix buttons
elcritch May 9, 2025
0ad3485
fix buttons
elcritch May 9, 2025
ee62fe1
fix buttons
elcritch May 9, 2025
ae093e2
fix buttons
elcritch May 9, 2025
f0bb3e5
fix buttons
elcritch May 9, 2025
c03f209
fix buttons
elcritch May 9, 2025
0014f3f
fix buttons
elcritch May 9, 2025
5aba424
updates
elcritch May 9, 2025
66570e9
updates
elcritch May 9, 2025
47fe848
updates
elcritch May 9, 2025
18b3fda
updates
elcritch May 9, 2025
5db8cb9
updates
elcritch May 9, 2025
9d73ae9
updates
elcritch May 9, 2025
7b00cec
updates
elcritch May 9, 2025
9264b93
updates
elcritch May 9, 2025
5c5dbf8
updates
elcritch May 9, 2025
d3d6136
updates
elcritch May 9, 2025
f4094bb
updates
elcritch May 9, 2025
cba32c6
updates
elcritch May 9, 2025
25d3bc9
updates
elcritch May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions examples/circlebox.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
import pixie, pixie/simd


type
Directions* = enum
dTop
dRight
dBottom
dLeft

DirectionCorners* = enum
dcTopLeft
dcTopRight
dcBottomRight
dcBottomLeft

proc generateCircleBox*(
radii: array[DirectionCorners, int],
offset = vec2(0, 0),
spread: float32 = 0.0'f32,
blur: float32 = 0.0'f32,
stroked: bool = true,
lineWidth: float32 = 0.0'f32,
fillStyle: ColorRGBA = rgba(255, 255, 255, 255),
shadowColor: ColorRGBA = rgba(255, 255, 255, 255),
outerShadow = true,
innerShadow = true,
innerShadowBorder = true,
outerShadowFill = false,
): Image =
let origRadii = radii
var radii: array[DirectionCorners, int]
var maxRadius = 0
for i, r in origRadii:
radii[i] = max(r, 0)
maxRadius = max(maxRadius, r)

# Additional size for spread and blur
let padding = (spread.int + blur.int)
let totalSize = max(maxRadius * 2 + padding * 2, 10+padding*2)

# Create a canvas large enough to contain the box with all effects
let img = newImage(totalSize, totalSize)
let ctx = newContext(img)

# Calculate the inner box dimensions
let innerWidth = (totalSize - padding * 2).float32
let innerHeight = (totalSize - padding * 2).float32

# Create a path for the rounded rectangle with the given dimensions and corner radii
proc createRoundedRectPath(
width, height: float32,
radii: array[DirectionCorners, int],
padding: int
): Path =
# Start at top right after the corner radius
result = newPath()
let topRight = vec2(width - radii[dcTopRight].float32, 0)
result.moveTo(topRight + vec2(padding.float32, padding.float32))

# Top right corner
let trControl = vec2(width, 0)
result.quadraticCurveTo(
trControl + vec2(padding.float32, padding.float32),
vec2(width, radii[dcTopRight].float32) + vec2(padding.float32, padding.float32)
)

# Right side
result.lineTo(vec2(width, height - radii[dcBottomRight].float32) + vec2(padding.float32, padding.float32))

# Bottom right corner
let brControl = vec2(width, height)
result.quadraticCurveTo(
brControl + vec2(padding.float32, padding.float32),
vec2(width - radii[dcBottomRight].float32, height) + vec2(padding.float32, padding.float32)
)

# Bottom side
result.lineTo(vec2(radii[dcBottomLeft].float32, height) + vec2(padding.float32, padding.float32))

# Bottom left corner
let blControl = vec2(0, height)
result.quadraticCurveTo(
blControl + vec2(padding.float32, padding.float32),
vec2(0, height - radii[dcBottomLeft].float32) + vec2(padding.float32, padding.float32)
)

# Left side
result.lineTo(vec2(0, radii[dcTopLeft].float32) + vec2(padding.float32, padding.float32))

# Top left corner
let tlControl = vec2(0, 0)
result.quadraticCurveTo(
tlControl + vec2(padding.float32, padding.float32),
vec2(radii[dcTopLeft].float32, 0) + vec2(padding.float32, padding.float32)
)

# Close the path
result.lineTo(topRight + vec2(padding.float32, padding.float32))

# Create the path for our rounded rectangle
let path = createRoundedRectPath(innerWidth, innerHeight, radii, padding)

# Draw the box
if stroked:
ctx.strokeStyle = fillStyle
ctx.lineWidth = lineWidth
ctx.stroke(path)
else:
ctx.fillStyle = fillStyle
ctx.fill(path)

# Apply inner shadow if requested
if innerShadow or outerShadow or outerShadowFill:
let shadow = img.shadow(
offset = offset,
spread = spread,
blur = blur,
color = shadowColor
)

let spath = createRoundedRectPath(innerWidth, innerHeight, radii, padding)

let combined = newImage(totalSize, totalSize)
let ctx = newContext(combined)
if innerShadow:
ctx.saveLayer()
ctx.clip(spath, EvenOdd)
ctx.drawImage(shadow, pos = vec2(0, 0))
ctx.restore()
if outerShadowFill:
let spath = spath.copy()
spath.rect(0, 0, totalSize.float32, totalSize.float32)
ctx.saveLayer()
ctx.clip(spath, EvenOdd)
ctx.fillStyle = fillStyle
ctx.rect(0, 0, totalSize.float32, totalSize.float32)
ctx.fill()
ctx.restore()
if outerShadow:
let spath = spath.copy()
spath.rect(0, 0, totalSize.float32, totalSize.float32)
ctx.saveLayer()
ctx.clip(spath, EvenOdd)
ctx.drawImage(shadow, pos = vec2(0, 0))
ctx.restore()
if innerShadowBorder:
ctx.drawImage(img, pos = vec2(0, 0))
return combined
else:
return img



let imgA = generateCircleBox(
radii = [0, 20, 40, 10], # Different radius for each corner
offset = vec2(0, 0),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = false,
innerShadow = false,
)

imgA.writeFile("examples/circlebox-asymmetric.png")

let imgAnostroke = generateCircleBox(
radii = [30, 20, 40, 10], # Different radius for each corner
offset = vec2(0, 0),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = false,
lineWidth = 0.0,
outerShadow = false,
innerShadow = false,
)

imgAnostroke.writeFile("examples/circlebox-asymmetric-nostroke.png")

let imgAfillshadow = generateCircleBox(
radii = [30, 20, 40, 10], # Different radius for each corner
offset = vec2(0, 0),
spread = 20.0'f32,
blur = 20.0'f32,
stroked = false,
lineWidth = 0.0,
outerShadow = true,
innerShadow = false,
innerShadowBorder = true,
outerShadowFill = false,
)

imgAfillshadow.writeFile("examples/circlebox-asymmetric-fill-shadow.png")


let imgB = generateCircleBox(
radii = [1, 1, 1, 1], # Different radius for each corner
offset = vec2(0, 0),
spread = 0.0'f32,
blur = 10.0'f32,
stroked = false,
lineWidth = 2.0,
outerShadow = true,
innerShadow = false,
innerShadowBorder = true,
)

imgB.writeFile("examples/circlebox-symmetric.png")

# Only inner shadow example
let imgC = generateCircleBox(
radii = [30, 30, 30, 30],
offset = vec2(0, 0),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = false,
innerShadow = true,
innerShadowBorder = false,
)

imgC.writeFile("examples/circlebox-inner-only.png")

# Only outer shadow example
let imgD = generateCircleBox(
radii = [30, 30, 30, 30],
offset = vec2(2, 2),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = true,
innerShadow = false,
innerShadowBorder = false,
)

imgD.writeFile("examples/circlebox-outer-only.png")

# Only outer shadow example
let imgE = generateCircleBox(
radii = [30, 30, 30, 30],
offset = vec2(0, 0),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = true,
innerShadow = true,
innerShadowBorder = false,
)

imgE.writeFile("examples/circlebox-outer-inner.png")

let imgF = generateCircleBox(
radii = [30, 30, 30, 30],
offset = vec2(0, 0),
spread = 1.0'f32,
blur = 10.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = true,
innerShadow = true,
innerShadowBorder = false,
outerShadowFill = true,
)

imgF.writeFile("examples/circlebox-inner-and-fill-outer.png")

let imgG = generateCircleBox(
radii = [10, 10, 10, 10],
offset = vec2(0, 0),
spread = 0.0'f32,
blur = 5.0'f32,
stroked = true,
lineWidth = 2.0,
outerShadow = false,
innerShadow = true,
innerShadowBorder = true,
outerShadowFill = true,
)

imgG.writeFile("examples/circlebox-inner-and-fill-outer-0radius.png")
23 changes: 15 additions & 8 deletions examples/corners.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import pixie, pixie/simd

type
DirectionCorners* = enum
dcTopLeft
dcTopRight
dcBottomRight
dcBottomLeft

proc generateCorner(
radius: int,
quadrant: range[1 .. 4],
quadrant: DirectionCorners,
stroked: bool,
lineWidth: float32 = 0'f32,
fillStyle = rgba(255, 255, 255, 255),
Expand Down Expand Up @@ -39,16 +46,16 @@ proc generateCorner(
path.bezierCurveTo(blc, trc, tr)

case quadrant
of 1:
ctx.rotate(270 * PI / 180)
ctx.translate(-tr)
of 2:
of dcTopLeft: # TL
ctx.rotate(180 * PI / 180)
ctx.translate(-br)
of 3:
of dcTopRight: # TR
ctx.rotate(270 * PI / 180)
ctx.translate(-tr)
of dcBottomLeft: # BL
ctx.rotate(90 * PI / 180)
ctx.translate(-bl)
of 4:
of dcBottomRight: # BR
discard

if doStroke:
Expand Down Expand Up @@ -88,7 +95,7 @@ proc generateCorner(
result = image


for i in 1..4:
for i in DirectionCorners:
let img = generateCorner(30, i, stroked = true,
lineWidth = 3, innerShadow = true,
spread = 1, blur = 10,
Expand Down
12 changes: 12 additions & 0 deletions src/figuro/common/nodes/basics.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ else:
type NodeID* = int64

type
Directions* = enum
dTop
dRight
dBottom
dLeft

DirectionCorners* = enum
dcTopLeft
dcTopRight
dcBottomRight
dcBottomLeft

NodeKind* = enum
## Different types of nodes.
nkFrame
Expand Down
1 change: 1 addition & 0 deletions src/figuro/common/nodes/uinodes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ type

kind*: NodeKind
shadow*: array[ShadowStyle, Shadow]
# cornerRadius*: array[DirectionCorners, UiScalar]
cornerRadius*: UiScalar
image*: ImageStyle
textLayout*: GlyphArrangement
Expand Down
Loading