extends Control class_name BoltRule ## A lightning bolt struck through a horizontal rule. ## ## The motif the whole UI is built around, in one reusable widget: menus use it ## as a section divider, the HUD uses it to underline a heading. It is drawn ## rather than shipped as an image so it takes the theme's colours and scales to ## any width without a second asset. ## ## The zigzag is deliberately not centred. A bolt centred in its rule reads as an ## ornament; struck a third of the way along, it reads as something that HIT the ## line — which is the difference between decorative and energetic. @export var line_color: Color = Color(1.0, 0.47, 0.10) @export var bolt_color: Color = Color(1.0, 0.93, 0.22) @export var ink: Color = Color(0.045, 0.040, 0.075) ## Where along the rule the bolt strikes, 0..1. @export var strike_at: float = 0.34 @export var thickness: float = 3.0 func _init() -> void: custom_minimum_size = Vector2(0, 22) mouse_filter = Control.MOUSE_FILTER_IGNORE func _draw() -> void: var w := size.x var h := size.y if w <= 1.0: return var mid := h * 0.5 var strike := w * clampf(strike_at, 0.05, 0.95) var half := h * 0.42 # The rule, broken where the bolt lands so the bolt reads as passing # THROUGH it rather than sitting on top. var gap := h * 0.55 draw_line(Vector2(0, mid), Vector2(maxf(0.0, strike - gap), mid), line_color, thickness) draw_line(Vector2(minf(w, strike + gap), mid), Vector2(w, mid), line_color, thickness) # Bolt: down-right, kick back left, down-right again. var pts := PackedVector2Array([ Vector2(strike + half * 0.55, mid - half), Vector2(strike - half * 0.10, mid + half * 0.12), Vector2(strike + half * 0.30, mid + half * 0.12), Vector2(strike - half * 0.55, mid + half), Vector2(strike + half * 0.12, mid - half * 0.10), Vector2(strike - half * 0.28, mid - half * 0.10), ]) # Ink first, one step out, so the bolt keeps a drawn edge against any # background it is placed on. draw_colored_polygon(_grown(pts, 2.0), ink) draw_colored_polygon(pts, bolt_color) ## The same polygon pushed out from its own centre — a cheap outline that needs ## no second point list to maintain. func _grown(pts: PackedVector2Array, by: float) -> PackedVector2Array: var centre := Vector2.ZERO for p in pts: centre += p centre /= float(pts.size()) var out := PackedVector2Array() for p in pts: var dir := (p - centre) out.append(p + (dir.normalized() * by if dir.length() > 0.001 else Vector2.ZERO)) return out