From ff704930c8b8312260a62a319d55e7f1e6a79512 Mon Sep 17 00:00:00 2001 From: Lily Rose Date: Mon, 28 Jul 2025 18:45:10 +1000 Subject: [PATCH] Refactor style logic --- src/substrate.gleam | 174 ++++---------- .../attribute.gleam} | 13 +- src/svg/element.gleam | 202 ++++++++++++++++ src/{svg_path.gleam => svg/path.gleam} | 0 src/svg/styles.gleam | 53 +++++ .../transform.gleam} | 0 src/svg_element.gleam | 221 ------------------ 7 files changed, 308 insertions(+), 355 deletions(-) rename src/{svg_attribute.gleam => svg/attribute.gleam} (86%) create mode 100644 src/svg/element.gleam rename src/{svg_path.gleam => svg/path.gleam} (100%) create mode 100644 src/svg/styles.gleam rename src/{svg_transform.gleam => svg/transform.gleam} (100%) delete mode 100644 src/svg_element.gleam diff --git a/src/substrate.gleam b/src/substrate.gleam index fd577d2..c8f0670 100644 --- a/src/substrate.gleam +++ b/src/substrate.gleam @@ -9,10 +9,11 @@ import lustre/attribute.{style} as _attribute import lustre/element.{type Element, fragment, to_readable_string} as _element import lustre/element/html.{svg} import simplifile -import svg_attribute as attribute -import svg_element as element -import svg_path as path -import svg_transform as transform +import svg/attribute +import svg/element +import svg/path +import svg/styles +import svg/transform pub fn get_footprint( library_location: String, @@ -122,71 +123,31 @@ pub fn main() -> Nil { const bg_col = "#001021" fn create_svg(footprint: Footprint) -> String { - let fill = None - let stroke = None - let stroke_width = None + let styles = styles.new() let transform = None svg([attribute.view_box(-4.0, -4.0, 8.0, 8.0), style("background", bg_col)], [ element.group( - fill:, - stroke: Some("#BBBBBB"), - stroke_width: Some(0.01), + styles: styles + |> styles.with_stroke("#BBBBBB") + |> styles.with_stroke_width(0.01), transform:, children: [ - element.line( - fill:, - stroke:, - stroke_width:, - transform:, - x1: -4.0, - y1: 0.0, - x2: 4.0, - y2: 0.0, - ), - element.line( - fill:, - stroke:, - stroke_width:, - transform:, - x1: 0.0, - y1: -4.0, - x2: 0.0, - y2: 4.0, - ), + element.line(styles:, transform:, x1: -4.0, y1: 0.0, x2: 4.0, y2: 0.0), + element.line(styles:, transform:, x1: 0.0, y1: -4.0, x2: 0.0, y2: 4.0), ], ), element.group( - fill:, - stroke: Some("#FF00FF"), - stroke_width: Some(0.01), + styles: styles + |> styles.with_stroke("#FF00FF") + |> styles.with_stroke_width(0.01), transform:, children: [ - element.line( - fill:, - stroke:, - stroke_width:, - transform:, - x1: -0.05, - y1: 0.0, - x2: 0.05, - y2: 0.0, - ), - element.line( - fill:, - stroke:, - stroke_width:, - transform:, - x1: 0.0, - y1: -0.05, - x2: 0.0, - y2: 0.05, - ), + element.line(styles:, transform:, x1: -0.05, y1: 0.0, x2: 0.05, y2: 0.0), + element.line(styles:, transform:, x1: 0.0, y1: -0.05, x2: 0.0, y2: 0.05), ], ), element.group( - fill: Some("#CE3431"), - stroke:, - stroke_width:, + styles: styles |> styles.with_fill("#CE3431"), transform:, children: footprint.pads |> list.filter(fn(pad) { @@ -206,9 +167,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { let nh = -1.0 *. h let hw = w /. 2.0 let hh = h /. 2.0 - let fill = None - let stroke = None - let stroke_width = None + let styles = styles.new() let transform = angle |> option.map(fn(angle) { @@ -227,29 +186,12 @@ fn pad_to_element(pad: Pad) -> Element(a) { } let elem = case pad.shape { token.CirclePadShape -> - element.circle( - fill:, - stroke:, - stroke_width:, - transform:, - cx: ox, - cy: oy, - r: hw, - ) + element.circle(styles:, transform:, cx: ox, cy: oy, r: hw) token.OvalPadShape -> case float.compare(w, h) { - order.Eq -> - element.circle( - fill:, - stroke:, - stroke_width:, - transform:, - cx: ox, - cy: oy, - r: hw, - ) + order.Eq -> element.circle(styles:, transform:, cx: ox, cy: oy, r: hw) order.Lt -> - element.path(fill:, stroke:, stroke_width:, transform:, path: [ + element.path(styles:, transform:, path: [ path.MoveAbsolute(x: ox +. hw, y: oy +. hw -. hh), path.ArcCurveRelative( rx: hw, @@ -273,7 +215,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { path.ClosePath, ]) order.Gt -> - element.path(fill:, stroke:, stroke_width:, transform:, path: [ + element.path(styles:, transform:, path: [ path.MoveAbsolute(x: ox +. hh -. hw, y: oy -. hh), path.ArcCurveRelative( rx: hh, @@ -317,9 +259,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { case rounding, pad.chamfer, chamfering { None, Some([]), _ | None, None, _ | None, _, None -> element.centered_rect( - fill:, - stroke:, - stroke_width:, + styles:, transform:, x: ox, y: oy, @@ -332,9 +272,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { -> { let radius = rounding *. float.min(w, h) element.centered_rounded_rect( - fill:, - stroke:, - stroke_width:, + styles:, transform:, x: ox, y: oy, @@ -393,7 +331,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { ] False -> [path.MoveAbsolute(x: ox -. hw, y: oy -. hh), ..path] } - element.path(fill:, stroke:, stroke_width:, transform:, path:) + element.path(styles:, transform:, path:) } Some(rounding), Some(corners), Some(chamfering) -> { let #(tl, tr, bl, br) = chamfered_corners(corners) @@ -494,7 +432,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { ..path ] } - element.path(fill:, stroke:, stroke_width:, transform:, path:) + element.path(styles:, transform:, path:) } } } @@ -510,7 +448,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { path.LineRelative(dx: no, dy: nh), path.ClosePath, ] - element.path(fill:, stroke:, stroke_width:, transform:, path:) + element.path(styles:, transform:, path:) } Some(token.XY(x: o, y: 0.0)) -> { let no = -1.0 *. o @@ -522,13 +460,11 @@ fn pad_to_element(pad: Pad) -> Element(a) { path.LineRelative(dx: nw, dy: no), path.ClosePath, ] - element.path(fill:, stroke:, stroke_width:, transform:, path:) + element.path(styles:, transform:, path:) } _ -> element.centered_rect( - fill:, - stroke:, - stroke_width:, + styles:, transform:, x: ox, y: oy, @@ -539,21 +475,11 @@ fn pad_to_element(pad: Pad) -> Element(a) { token.CustomPadShape -> case pad.custom_options { Some(token.CustomPadOptions(anchor: token.CircleAnchorPadShape, ..)) -> - element.circle( - fill:, - stroke:, - stroke_width:, - transform:, - cx: ox, - cy: oy, - r: hw, - ) + element.circle(styles:, transform:, cx: ox, cy: oy, r: hw) Some(token.CustomPadOptions(anchor: token.RectangleAnchorPadShape, ..)) | None -> element.centered_rect( - fill:, - stroke:, - stroke_width:, + styles:, transform:, x: ox, y: oy, @@ -562,25 +488,22 @@ fn pad_to_element(pad: Pad) -> Element(a) { ) } } - let #(fill, stroke, stroke_width) = case pad.type_ { - token.ThroughHolePadType -> #(Some(bg_col), Some("#E7B629"), Some(0.03333)) - token.NpThroughHolePadType -> #(Some("#18C7D6"), None, None) - _ -> #(None, None, None) + let styles = case pad.type_ { + token.ThroughHolePadType -> + styles.new() + |> styles.with_fill(bg_col) + |> styles.with_stroke("#E7B629") + |> styles.with_stroke_width(0.06666) + |> styles.with_paint_order([styles.StrokeComponent]) + token.NpThroughHolePadType -> styles.new() |> styles.with_fill("#18C7D6") + _ -> styles } case pad.drill { Some(token.PadDrillDefinition(False, Some(d), ..)) | Some(token.PadDrillDefinition(True, Some(d), None, ..)) -> fragment([ elem, - element.circle( - fill:, - stroke:, - stroke_width:, - transform:, - cx: x, - cy: y, - r: d /. 2.0, - ), + element.circle(styles:, transform:, cx: x, cy: y, r: d /. 2.0), ]) Some(token.PadDrillDefinition(True, Some(w), Some(h), ..)) -> { let hw = w /. 2.0 @@ -590,18 +513,9 @@ fn pad_to_element(pad: Pad) -> Element(a) { fragment([ elem, case float.compare(w, h) { - order.Eq -> - element.circle( - fill:, - stroke:, - stroke_width:, - transform:, - cx: x, - cy: y, - r: hw, - ) + order.Eq -> element.circle(styles:, transform:, cx: x, cy: y, r: hw) order.Lt -> - element.path(fill:, stroke:, stroke_width:, transform:, path: [ + element.path(styles:, transform:, path: [ path.MoveAbsolute(x: x +. hw, y: y +. hw -. hh), path.ArcCurveRelative( rx: hw, @@ -625,7 +539,7 @@ fn pad_to_element(pad: Pad) -> Element(a) { path.ClosePath, ]) order.Gt -> - element.path(fill:, stroke:, stroke_width:, transform:, path: [ + element.path(styles:, transform:, path: [ path.MoveAbsolute(x: x +. hh -. hw, y: y -. hh), path.ArcCurveRelative( rx: hh, diff --git a/src/svg_attribute.gleam b/src/svg/attribute.gleam similarity index 86% rename from src/svg_attribute.gleam rename to src/svg/attribute.gleam index 1845b7c..c2dcb00 100644 --- a/src/svg_attribute.gleam +++ b/src/svg/attribute.gleam @@ -2,8 +2,9 @@ import gleam/float import gleam/list import gleam/string import lustre/attribute.{type Attribute, attribute} -import svg_path.{type Path} -import svg_transform.{type Transform} +import svg/path.{type Path} +import svg/styles.{type PaintOrder} +import svg/transform.{type Transform} pub fn view_box( min_x: Float, @@ -35,6 +36,10 @@ pub fn stroke_width(value: Float) -> Attribute(a) { float_attribute("stroke-width", value) } +pub fn paint_order(value: PaintOrder) -> Attribute(a) { + attribute("paint-order", styles.paint_order_to_string(value)) +} + pub fn width(value: Float) -> Attribute(a) { float_attribute("width", value) } @@ -96,9 +101,9 @@ pub fn r(value: Float) -> Attribute(a) { } pub fn d(value: Path) -> Attribute(a) { - attribute("d", svg_path.to_string(value)) + attribute("d", path.to_string(value)) } pub fn transform(value: Transform) -> Attribute(a) { - attribute("transform", svg_transform.to_string(value)) + attribute("transform", transform.to_string(value)) } diff --git a/src/svg/element.gleam b/src/svg/element.gleam new file mode 100644 index 0000000..69231b3 --- /dev/null +++ b/src/svg/element.gleam @@ -0,0 +1,202 @@ +import gleam/option.{type Option, None, Some} +import lustre/attribute.{type Attribute} as _attribute +import lustre/element.{type Element, element} +import svg/attribute +import svg/path.{type Path} +import svg/styles.{type Styles} +import svg/transform.{type Transform} + +fn styles_transform( + styles styles: Styles, + transform transform: Option(Transform), +) -> List(Attribute(a)) { + let attrs = [] + let attrs = case transform { + Some(value) -> [attribute.transform(value), ..attrs] + None -> attrs + } + let attrs = case styles.paint_order { + Some(value) -> [attribute.paint_order(value), ..attrs] + None -> attrs + } + let attrs = case styles.stroke_width { + Some(value) -> [attribute.stroke_width(value), ..attrs] + None -> attrs + } + let attrs = case styles.stroke { + Some(value) -> [attribute.stroke(value), ..attrs] + None -> attrs + } + let attrs = case styles.fill { + Some(value) -> [attribute.fill(value), ..attrs] + None -> attrs + } + attrs +} + +pub fn group( + styles styles: Styles, + transform transform: Option(Transform), + children children: List(Element(a)), +) -> Element(a) { + element("g", styles_transform(styles:, transform:), children) +} + +pub fn line( + styles styles: Styles, + transform transform: Option(Transform), + x1 x1: Float, + y1 y1: Float, + x2 x2: Float, + y2 y2: Float, +) -> Element(a) { + element( + "line", + [ + attribute.x1(x1), + attribute.y1(y1), + attribute.x2(x2), + attribute.y2(y2), + ..styles_transform(styles:, transform:) + ], + [], + ) +} + +pub fn circle( + styles styles: Styles, + transform transform: Option(Transform), + cx cx: Float, + cy cy: Float, + r r: Float, +) -> Element(a) { + element( + "circle", + [ + attribute.cx(cx), + attribute.cy(cy), + attribute.r(r), + ..styles_transform(styles:, transform:) + ], + [], + ) +} + +pub fn ellipse( + styles styles: Styles, + transform transform: Option(Transform), + cx cx: Float, + cy cy: Float, + rx rx: Float, + ry ry: Float, +) -> Element(a) { + element( + "ellipse", + [ + attribute.cx(cx), + attribute.cy(cy), + attribute.rx(rx), + attribute.ry(ry), + ..styles_transform(styles:, transform:) + ], + [], + ) +} + +pub fn rect( + styles styles: Styles, + transform transform: Option(Transform), + x x: Float, + y y: Float, + width width: Float, + height height: Float, +) -> Element(a) { + element( + "rect", + [ + attribute.x(x), + attribute.y(y), + attribute.width(width), + attribute.height(height), + ..styles_transform(styles:, transform:) + ], + [], + ) +} + +pub fn rounded_rect( + styles styles: Styles, + transform transform: Option(Transform), + x x: Float, + y y: Float, + rx rx: Float, + ry ry: Float, + width width: Float, + height height: Float, +) -> Element(a) { + element( + "rect", + [ + attribute.x(x), + attribute.y(y), + attribute.rx(rx), + attribute.ry(ry), + attribute.width(width), + attribute.height(height), + ..styles_transform(styles:, transform:) + ], + [], + ) +} + +pub fn centered_rect( + styles styles: Styles, + transform transform: Option(Transform), + x x: Float, + y y: Float, + width width: Float, + height height: Float, +) -> Element(a) { + rect( + styles:, + transform:, + x: x -. width /. 2.0, + y: y -. height /. 2.0, + width:, + height:, + ) +} + +pub fn centered_rounded_rect( + styles styles: Styles, + transform transform: Option(Transform), + x x: Float, + y y: Float, + rx rx: Float, + ry ry: Float, + width width: Float, + height height: Float, +) -> Element(a) { + rounded_rect( + styles:, + transform:, + x: x -. width /. 2.0, + y: y -. height /. 2.0, + rx:, + ry:, + width:, + height:, + ) +} + +pub fn path( + styles styles: Styles, + transform transform: Option(Transform), + path path: Path, +) -> Element(a) { + element( + "path", + [attribute.d(path), ..styles_transform(styles:, transform:)], + [], + ) +} diff --git a/src/svg_path.gleam b/src/svg/path.gleam similarity index 100% rename from src/svg_path.gleam rename to src/svg/path.gleam diff --git a/src/svg/styles.gleam b/src/svg/styles.gleam new file mode 100644 index 0000000..d2922e8 --- /dev/null +++ b/src/svg/styles.gleam @@ -0,0 +1,53 @@ +import gleam/list +import gleam/option.{type Option, None, Some} +import gleam/string + +pub type PaintComponent { + FillComponent + StrokeComponent + MarkerComponent +} + +pub type PaintOrder = + List(PaintComponent) + +pub fn paint_order_to_string(value: PaintOrder) -> String { + value + |> list.map(fn(comp) { + case comp { + FillComponent -> "fill" + StrokeComponent -> "stroke" + MarkerComponent -> "marker" + } + }) + |> string.join(" ") +} + +pub type Styles { + Styles( + fill: Option(String), + stroke: Option(String), + stroke_width: Option(Float), + paint_order: Option(List(PaintComponent)), + ) +} + +pub fn new() { + Styles(fill: None, stroke: None, stroke_width: None, paint_order: None) +} + +pub fn with_fill(styles: Styles, fill: String) { + Styles(..styles, fill: Some(fill)) +} + +pub fn with_stroke(styles: Styles, stroke: String) { + Styles(..styles, stroke: Some(stroke)) +} + +pub fn with_stroke_width(styles: Styles, stroke_width: Float) { + Styles(..styles, stroke_width: Some(stroke_width)) +} + +pub fn with_paint_order(styles: Styles, paint_order: PaintOrder) { + Styles(..styles, paint_order: Some(paint_order)) +} diff --git a/src/svg_transform.gleam b/src/svg/transform.gleam similarity index 100% rename from src/svg_transform.gleam rename to src/svg/transform.gleam diff --git a/src/svg_element.gleam b/src/svg_element.gleam deleted file mode 100644 index e72db94..0000000 --- a/src/svg_element.gleam +++ /dev/null @@ -1,221 +0,0 @@ -import gleam/option.{type Option, None, Some} -import lustre/attribute.{type Attribute} -import lustre/element.{type Element, element} -import svg_attribute -import svg_path.{type Path} -import svg_transform.{type Transform} - -fn styles( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), -) -> List(Attribute(a)) { - let attrs = [] - let attrs = case transform { - Some(value) -> [svg_attribute.transform(value), ..attrs] - None -> attrs - } - let attrs = case stroke_width { - Some(value) -> [svg_attribute.stroke_width(value), ..attrs] - None -> attrs - } - let attrs = case stroke { - Some(value) -> [svg_attribute.stroke(value), ..attrs] - None -> attrs - } - let attrs = case fill { - Some(value) -> [svg_attribute.fill(value), ..attrs] - None -> attrs - } - attrs -} - -pub fn group( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - children children: List(Element(a)), -) -> Element(a) { - element("g", styles(fill:, stroke:, stroke_width:, transform:), children) -} - -pub fn line( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - x1 x1: Float, - y1 y1: Float, - x2 x2: Float, - y2 y2: Float, -) -> Element(a) { - element( - "line", - [ - svg_attribute.x1(x1), - svg_attribute.y1(y1), - svg_attribute.x2(x2), - svg_attribute.y2(y2), - ..styles(fill:, stroke:, stroke_width:, transform:) - ], - [], - ) -} - -pub fn circle( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - cx cx: Float, - cy cy: Float, - r r: Float, -) -> Element(a) { - element( - "circle", - [ - svg_attribute.cx(cx), - svg_attribute.cy(cy), - svg_attribute.r(r), - ..styles(fill:, stroke:, stroke_width:, transform:) - ], - [], - ) -} - -pub fn ellipse( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - cx cx: Float, - cy cy: Float, - rx rx: Float, - ry ry: Float, -) -> Element(a) { - element( - "ellipse", - [ - svg_attribute.cx(cx), - svg_attribute.cy(cy), - svg_attribute.rx(rx), - svg_attribute.ry(ry), - ..styles(fill:, stroke:, stroke_width:, transform:) - ], - [], - ) -} - -pub fn rect( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - x x: Float, - y y: Float, - width width: Float, - height height: Float, -) -> Element(a) { - element( - "rect", - [ - svg_attribute.x(x), - svg_attribute.y(y), - svg_attribute.width(width), - svg_attribute.height(height), - ..styles(fill:, stroke:, stroke_width:, transform:) - ], - [], - ) -} - -pub fn rounded_rect( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - x x: Float, - y y: Float, - rx rx: Float, - ry ry: Float, - width width: Float, - height height: Float, -) -> Element(a) { - element( - "rect", - [ - svg_attribute.x(x), - svg_attribute.y(y), - svg_attribute.rx(rx), - svg_attribute.ry(ry), - svg_attribute.width(width), - svg_attribute.height(height), - ..styles(fill:, stroke:, stroke_width:, transform:) - ], - [], - ) -} - -pub fn centered_rect( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - x x: Float, - y y: Float, - width width: Float, - height height: Float, -) -> Element(a) { - rect( - fill:, - stroke:, - stroke_width:, - transform:, - x: x -. width /. 2.0, - y: y -. height /. 2.0, - width:, - height:, - ) -} - -pub fn centered_rounded_rect( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - x x: Float, - y y: Float, - rx rx: Float, - ry ry: Float, - width width: Float, - height height: Float, -) -> Element(a) { - rounded_rect( - fill:, - stroke:, - stroke_width:, - transform:, - x: x -. width /. 2.0, - y: y -. height /. 2.0, - rx:, - ry:, - width:, - height:, - ) -} - -pub fn path( - fill fill: Option(String), - stroke stroke: Option(String), - stroke_width stroke_width: Option(Float), - transform transform: Option(Transform), - path path: Path, -) -> Element(a) { - element( - "path", - [svg_attribute.d(path), ..styles(fill:, stroke:, stroke_width:, transform:)], - [], - ) -}