Files
aetherbound-guild/runtime/entry/battle_live_art.gd
T

450 lines
20 KiB
GDScript

class_name ABGBattleLiveArt
extends Control
## Engine-drawn battle chrome reconstructed from the approved BAT-002 master.
## Runtime labels, values and hit targets remain separate overlay nodes.
var live_state := "waiting_for_next_event"
var event_kind := ""
var source_side := ""
var target_side := ""
var source_anchor := Vector2(0.46, 0.60)
var target_anchor := Vector2(0.67, 0.58)
var ally_anchors: Array[Vector2] = []
var enemy_anchors: Array[Vector2] = []
var party_count := 0
var enemy_count := 0
var playback_paused := true
var playback_speed := 1
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
queue_redraw()
func _notification(what: int) -> void:
if what == NOTIFICATION_RESIZED:
queue_redraw()
func set_event_state(state: String, kind: String, source: String, target: String) -> void:
live_state = state
event_kind = kind
source_side = source
target_side = target
queue_redraw()
func set_presentation_state(
paused: bool,
speed: float,
allies: Array[Vector2],
enemies: Array[Vector2],
event_source: Vector2,
event_target: Vector2
) -> void:
playback_paused = paused
playback_speed = int(speed)
ally_anchors = allies.duplicate()
enemy_anchors = enemies.duplicate()
party_count = ally_anchors.size()
enemy_count = enemy_anchors.size()
source_anchor = event_source
target_anchor = event_target
queue_redraw()
func _draw() -> void:
if size.x <= 0.0 or size.y <= 0.0:
return
_draw_top_rails()
_draw_field_language()
_draw_bottom_rail()
func _draw_top_rails() -> void:
var w := size.x
var h := size.y
var gold := Color("#d6ad58", 0.98)
var gold_dim := Color("#78633f", 0.92)
var navy := Color("#101b2a", 0.96)
var navy_inner := Color("#18293b", 0.96)
var blue := Color("#315a87", 0.98)
var red := Color("#8b3d47", 0.98)
var ivory := Color("#f1e4c2", 0.98)
# Long asymmetric faction rails with pointed inner shoulders. The master is
# deliberately quiet here: the rail carries count/pips, while all readable
# values remain engine-owned overlay text.
var left_outer := PackedVector2Array([
Vector2(w * 0.050, h * 0.016), Vector2(w * 0.325, h * 0.016),
Vector2(w * 0.344, h * 0.090), Vector2(w * 0.337, h * 0.108),
Vector2(w * 0.318, h * 0.122), Vector2(w * 0.060, h * 0.108),
])
draw_colored_polygon(left_outer, gold_dim)
var left_inner := PackedVector2Array([
Vector2(w * 0.058, h * 0.024), Vector2(w * 0.319, h * 0.024),
Vector2(w * 0.335, h * 0.087), Vector2(w * 0.328, h * 0.100),
Vector2(w * 0.316, h * 0.111), Vector2(w * 0.058, h * 0.099),
])
draw_colored_polygon(left_inner, navy)
draw_polyline(left_outer + PackedVector2Array([left_outer[0]]), gold, 2.0)
draw_line(Vector2(w * 0.060, h * 0.091), Vector2(w * 0.321, h * 0.102), Color(gold, 0.42), 1.0)
draw_line(Vector2(w * 0.070, h * 0.032), Vector2(w * 0.314, h * 0.032), Color(gold, 0.22), 1.0)
draw_line(Vector2(w * 0.060, h * 0.100), Vector2(w * 0.305, h * 0.111), Color(gold, 0.18), 1.0)
draw_line(Vector2(w * 0.082, h * 0.043), Vector2(w * 0.314, h * 0.043), Color("#e8c878", 0.11), 1.0)
draw_circle(Vector2(w * 0.307, h * 0.101), maxf(1.5, w * 0.0022), Color("#efcf83", 0.72))
var right_outer := PackedVector2Array([
Vector2(w * 0.675, h * 0.016), Vector2(w * 0.950, h * 0.016),
Vector2(w * 0.950, h * 0.108), Vector2(w * 0.682, h * 0.122),
Vector2(w * 0.663, h * 0.108), Vector2(w * 0.656, h * 0.090),
])
draw_colored_polygon(right_outer, gold_dim)
var right_inner := PackedVector2Array([
Vector2(w * 0.681, h * 0.024), Vector2(w * 0.943, h * 0.024),
Vector2(w * 0.943, h * 0.099), Vector2(w * 0.678, h * 0.111),
Vector2(w * 0.666, h * 0.100), Vector2(w * 0.662, h * 0.087),
])
draw_colored_polygon(right_inner, navy)
draw_polyline(right_outer + PackedVector2Array([right_outer[0]]), gold, 2.0)
draw_line(Vector2(w * 0.679, h * 0.102), Vector2(w * 0.940, h * 0.091), Color(gold, 0.42), 1.0)
draw_line(Vector2(w * 0.686, h * 0.032), Vector2(w * 0.936, h * 0.032), Color(gold, 0.22), 1.0)
draw_line(Vector2(w * 0.695, h * 0.111), Vector2(w * 0.940, h * 0.100), Color(gold, 0.18), 1.0)
draw_line(Vector2(w * 0.686, h * 0.043), Vector2(w * 0.918, h * 0.043), Color("#e8c878", 0.11), 1.0)
draw_circle(Vector2(w * 0.693, h * 0.101), maxf(1.5, w * 0.0022), Color("#efcf83", 0.72))
# Hanging crest endcaps preserve the approved blue/red silhouette.
var left_cap := PackedVector2Array([
Vector2(w * 0.014, 0.0), Vector2(w * 0.073, 0.0),
Vector2(w * 0.073, h * 0.105), Vector2(w * 0.052, h * 0.144),
Vector2(w * 0.015, h * 0.112),
])
draw_colored_polygon(left_cap, blue)
draw_polyline(left_cap + PackedVector2Array([left_cap[0]]), gold, 2.0)
var right_cap := PackedVector2Array([
Vector2(w * 0.927, 0.0), Vector2(w * 0.986, 0.0),
Vector2(w * 0.985, h * 0.112), Vector2(w * 0.948, h * 0.144),
Vector2(w * 0.927, h * 0.105),
])
draw_colored_polygon(right_cap, red)
draw_polyline(right_cap + PackedVector2Array([right_cap[0]]), gold, 2.0)
draw_line(Vector2(w * 0.933, h * 0.014), Vector2(w * 0.933, h * 0.099), Color("#e8a26e", 0.24), 1.0)
_draw_crest(Vector2(w * 0.044, h * 0.057), minf(w, h) * 0.026, Color("#edf1ec"))
_draw_crest(Vector2(w * 0.956, h * 0.057), minf(w, h) * 0.026, Color("#f3e8dc"))
# Status pips remain inside the long rails instead of becoming cards. Keep
# the spacing stable for the small BAT-002 fixtures while allowing larger
# committed lines to use the same authored rail.
var ally_pip_count := mini(party_count, 10)
var enemy_pip_count := mini(enemy_count, 10)
for index in range(ally_pip_count):
_draw_diamond(Vector2(w * (0.145 + index * 0.034), h * 0.064), w * 0.0075, Color("#68b8e9"))
for index in range(enemy_pip_count):
_draw_diamond(Vector2(w * (0.855 - index * 0.034), h * 0.064), w * 0.0075, Color("#e05c5c"))
# The small ivory markers on the lower rail line are part of the approved
# rhythm and make the opposing tracks read as one family.
_draw_diamond(Vector2(w * 0.119, h * 0.096), w * 0.0065, Color("#f3e7c8"))
_draw_diamond(Vector2(w * 0.821, h * 0.096), w * 0.0065, Color("#f3e7c8"))
# One continuous ivory playback ribbon. Logical buttons sit transparently on top.
var ribbon_outer := PackedVector2Array([
Vector2(w * 0.390, 0.0), Vector2(w * 0.610, 0.0),
Vector2(w * 0.598, h * 0.103), Vector2(w * 0.402, h * 0.103),
])
draw_colored_polygon(ribbon_outer, gold_dim)
var ribbon_inner := PackedVector2Array([
Vector2(w * 0.394, 0.0), Vector2(w * 0.606, 0.0),
Vector2(w * 0.594, h * 0.093), Vector2(w * 0.406, h * 0.093),
])
draw_colored_polygon(ribbon_inner, ivory)
draw_polyline(ribbon_outer + PackedVector2Array([ribbon_outer[0]]), Color("#b99a59"), 2.0)
for split_x in [0.465, 0.535]:
draw_line(Vector2(w * split_x, 0.0), Vector2(w * split_x, h * 0.093), Color("#b59d6c", 0.62), 1.0)
_draw_pause_icon(Vector2(w * 0.428, h * 0.045), h * 0.025, navy_inner)
_draw_speed_icon(Vector2(w * 0.492, h * 0.045), h * 0.023, navy_inner)
_draw_magnifier(Vector2(w * 0.570, h * 0.042), h * 0.020, navy_inner)
# Settings is an invisible hit target in the approved corner. The master has
# no competing visible gear beside the enemy count.
func _draw_field_language() -> void:
var w := size.x
var h := size.y
var gold := Color("#e0bd69", 0.92)
var ivory := Color("#f5ead0", 0.94)
var path_color := _event_color()
# A narrow field-attached objective bracket keeps the painted world open.
var objective := PackedVector2Array([
Vector2(w * 0.018, h * 0.142), Vector2(w * 0.278, h * 0.142),
Vector2(w * 0.294, h * 0.157), Vector2(w * 0.282, h * 0.238),
Vector2(w * 0.018, h * 0.238),
])
# The approved frame uses a field-attached bracket, not an objective card.
# Leave the painted ruins unobscured; only the corner lines remain visible.
draw_line(objective[0], objective[1], Color(gold, 0.78), 1.5)
draw_line(objective[1], objective[2], Color(gold, 0.46), 1.0)
draw_line(objective[4], objective[3], Color(gold, 0.46), 1.0)
draw_line(objective[0], objective[4], Color(gold, 0.66), 1.5)
draw_line(objective[2], objective[3], Color(gold, 0.30), 1.0)
# Small role/status markers remain attached to the actors.
for index in range(ally_anchors.size()):
_draw_actor_state_marker(_to_px(ally_anchors[index]), Color("#6fc0e7"), index == ally_anchors.size() - 1)
for index in range(enemy_anchors.size()):
_draw_actor_state_marker(_to_px(enemy_anchors[index]), Color("#dc6662"), index == 0)
if event_kind.is_empty() or source_side.is_empty() or target_side.is_empty():
if live_state == "waiting_for_next_event":
var waiting_center := Vector2(w * 0.50, h * 0.345)
draw_arc(waiting_center, w * 0.014, 0.0, TAU, 24, Color(ivory, 0.78), 2.0)
_draw_diamond(waiting_center, w * 0.0045, gold)
elif event_kind == "opening":
var opening_center := Vector2(w * 0.50, h * 0.365)
draw_arc(opening_center, w * 0.020, 0.0, TAU, 28, Color(gold, 0.68), 2.0)
_draw_diamond(opening_center, w * 0.006, ivory)
return
var source := _to_px(source_anchor)
var target := _to_px(target_anchor)
var bend := -h * 0.095 if source.x < target.x else h * 0.075
var path := _quadratic_path(source, target, bend)
for offset in [-4.0, 0.0, 4.0]:
var shifted := PackedVector2Array()
for point in path:
shifted.append(point + Vector2(0.0, offset))
draw_polyline(shifted, Color(path_color, 0.52 if offset != 0.0 else 0.98), 1.6 if offset != 0.0 else 3.0)
var direction := (path[-1] - path[-2]).normalized()
var perpendicular := Vector2(-direction.y, direction.x)
draw_colored_polygon(PackedVector2Array([
target,
target - direction * w * 0.018 + perpendicular * w * 0.008,
target - direction * w * 0.018 - perpendicular * w * 0.008,
]), path_color)
# A second small arrow in the corridor echoes the approved multi-stroke
# projection without introducing a separate event panel.
var mid_index := 11
var mid := path[mid_index]
var mid_direction := (path[mid_index + 1] - path[mid_index - 1]).normalized()
var mid_perpendicular := Vector2(-mid_direction.y, mid_direction.x)
draw_colored_polygon(PackedVector2Array([
mid,
mid - mid_direction * w * 0.012 + mid_perpendicular * w * 0.006,
mid - mid_direction * w * 0.012 - mid_perpendicular * w * 0.006,
]), Color(path_color, 0.82))
# The approved master uses a small cluster of parallel flight marks. Keep
# the extra strokes inside the action gap, without adding a diagnostic band.
var flight_mid := path[7]
var flight_direction := (path[8] - path[6]).normalized()
var flight_perpendicular := Vector2(-flight_direction.y, flight_direction.x)
for lane in [-1.0, 1.0]:
var lane_mid: Vector2 = flight_mid + flight_perpendicular * lane * w * 0.010
draw_line(lane_mid - flight_direction * w * 0.028, lane_mid + flight_direction * w * 0.028, Color(path_color, 0.62), 1.4)
draw_arc(source, w * 0.012, 0.0, TAU, 28, Color(path_color, 0.92), 2.0)
draw_circle(source, w * 0.005, Color(path_color, 0.55))
draw_circle(target, w * 0.029, Color(path_color, 0.13))
draw_arc(target, w * 0.029, 0.0, TAU, 36, Color(path_color, 0.92), 2.0)
# The urgent marker is tied to the target endpoint, never a central debug band.
var urgent := target - Vector2(0.0, h * 0.205)
draw_line(urgent + Vector2(0.0, h * 0.030), target - Vector2(0.0, h * 0.025), Color(path_color, 0.45), 3.0)
var urgent_shape := PackedVector2Array([
urgent + Vector2(0.0, -h * 0.036), urgent + Vector2(w * 0.020, 0.0),
urgent + Vector2(0.0, h * 0.036), urgent + Vector2(-w * 0.020, 0.0),
])
draw_colored_polygon(urgent_shape, Color(path_color, 0.94))
draw_polyline(urgent_shape + PackedVector2Array([urgent_shape[0]]), Color("#fff0d8"), 2.0)
draw_line(urgent - Vector2(0.0, h * 0.014), urgent + Vector2(0.0, h * 0.009), ivory, 3.0)
draw_circle(urgent + Vector2(0.0, h * 0.020), maxf(2.0, w * 0.0016), ivory)
func _draw_bottom_rail() -> void:
var w := size.x
var h := size.y
var top := h * 0.80
var gold := Color("#d0aa58", 0.96)
var rail := Color("#0e1723", 0.97)
var rail_inner := Color("#172434", 0.98)
var plate := Color("#1a2836", 0.84)
draw_rect(Rect2(0.0, top, w, h - top), rail)
draw_line(Vector2(0.0, top), Vector2(w, top), gold, 2.0)
draw_line(Vector2(0.0, top + h * 0.009), Vector2(w, top + h * 0.009), Color(gold, 0.28), 1.0)
draw_line(Vector2(0.0, h * 0.985), Vector2(w, h * 0.985), Color("#5b513c", 0.42), 1.0)
draw_rect(Rect2(w * 0.012, h * 0.835, w * 0.142, h * 0.132), rail_inner)
draw_style_box(_plate(Color("#131b25", 0.98), Color(gold, 0.78), 1, 3), Rect2(w * 0.018, h * 0.857, w * 0.125, h * 0.095))
_draw_hourglass(Vector2(w * 0.042, h * 0.905), h * 0.023, Color("#e6d5a8"))
var card_count := maxi(1, party_count)
var card_left := 0.170
var card_right := 0.885
var card_gap := clampf(0.012 - float(card_count) * 0.0008, 0.006, 0.012)
var card_width := (card_right - card_left - card_gap * float(card_count - 1)) / float(card_count)
for index in range(card_count):
var rect := Rect2(w * (card_left + index * (card_width + card_gap)), h * 0.812, w * card_width, h * 0.175)
var cut := clampf(rect.size.x * 0.055, 2.0, 7.0)
var card_shape := PackedVector2Array([
rect.position + Vector2(cut, 0.0), rect.position + Vector2(rect.size.x - cut, 0.0),
rect.position + Vector2(rect.size.x, cut), rect.position + Vector2(rect.size.x, rect.size.y - cut),
rect.position + Vector2(rect.size.x - cut, rect.size.y), rect.position + Vector2(cut, rect.size.y),
rect.position + Vector2(0.0, rect.size.y - cut), rect.position + Vector2(0.0, cut),
])
draw_colored_polygon(card_shape, plate)
draw_polyline(card_shape + PackedVector2Array([card_shape[0]]), Color(gold, 0.88), 1.2)
draw_polyline(PackedVector2Array([
rect.position + Vector2(cut + 2.0, 2.0),
rect.position + Vector2(rect.size.x - cut - 2.0, 2.0),
rect.position + Vector2(rect.size.x - 2.0, cut + 2.0),
rect.position + Vector2(rect.size.x - 2.0, rect.size.y - cut - 2.0),
rect.position + Vector2(rect.size.x - cut - 2.0, rect.size.y - 2.0),
rect.position + Vector2(cut + 2.0, rect.size.y - 2.0),
rect.position + Vector2(2.0, rect.size.y - cut - 2.0),
rect.position + Vector2(2.0, cut + 2.0),
rect.position + Vector2(cut + 2.0, 2.0),
]), Color("#f0d17d", 0.24), 1.0)
draw_line(rect.position + Vector2(rect.size.x * 0.06, rect.size.y * 0.71), rect.position + Vector2(rect.size.x * 0.94, rect.size.y * 0.71), Color(gold, 0.20), 1.0)
draw_rect(Rect2(rect.position + Vector2(rect.size.x * 0.055, rect.size.y * 0.745), Vector2(rect.size.x * 0.89, rect.size.y * 0.070)), Color("#071015", 0.92))
draw_rect(Rect2(rect.position + Vector2(rect.size.x * 0.055, rect.size.y * 0.862), Vector2(rect.size.x * 0.89, rect.size.y * 0.070)), Color("#071015", 0.92))
var badge_radius := clampf(rect.size.x * 0.105, h * 0.012, h * 0.028)
var badge := rect.position + Vector2(rect.size.x * 0.105, rect.size.y * 0.115)
_draw_diamond(badge, badge_radius, Color("#30291e"))
draw_arc(badge, badge_radius, 0.0, TAU, 4, gold, 1.5)
# The boundary command remains a compact rail glyph inside a generous hit area.
var command_center := Vector2(w * 0.943, h * 0.895)
draw_circle(command_center, h * 0.027, Color("#18283a", 0.84))
draw_arc(command_center, h * 0.027, 0.0, TAU, 28, Color(gold, 0.84), 1.5)
draw_line(command_center - Vector2(h * 0.010, 0.0), command_center + Vector2(h * 0.010, 0.0), Color("#e9dcb9"), 2.0)
draw_line(command_center + Vector2(h * 0.003, -h * 0.008), command_center + Vector2(h * 0.010, 0.0), Color("#e9dcb9"), 2.0)
draw_line(command_center + Vector2(h * 0.003, h * 0.008), command_center + Vector2(h * 0.010, 0.0), Color("#e9dcb9"), 2.0)
func _event_color() -> Color:
match event_kind:
"healing": return Color("#70c59b", 0.96)
"guard", "guarded_recovery": return Color("#73b8dc", 0.96)
"magic": return Color("#b58bd6", 0.96)
"knockout": return Color("#e16c67", 0.98)
"result": return Color("#dfbd68", 0.98)
return Color("#e2b75f", 0.96)
func _draw_actor_state_marker(center: Vector2, color: Color, emphasized: bool) -> void:
var radius := size.x * (0.0048 if not emphasized else 0.0062)
draw_circle(center, radius * 1.25, Color("#13202a", 0.62))
_draw_diamond(center, radius, color)
draw_arc(center, radius * 1.25, 0.0, TAU, 20, Color("#f4e6c3", 0.72), 1.0)
func _draw_crest(center: Vector2, radius: float, color: Color) -> void:
# Four tapered leaves read closer to the approved faction emblems than a
# generic gear/cross while remaining an engine-drawn, non-baked mark.
var leaf := func(angle: float) -> PackedVector2Array:
var direction := Vector2(cos(angle), sin(angle))
var side := Vector2(-direction.y, direction.x)
return PackedVector2Array([
center + direction * radius * 0.95,
center + direction * radius * 0.16 + side * radius * 0.34,
center - direction * radius * 0.08,
center + direction * radius * 0.16 - side * radius * 0.34,
])
for angle in [(-PI * 0.5), 0.0, (PI * 0.5), PI]:
draw_colored_polygon(leaf.call(angle), Color(color, 0.92))
draw_circle(center, radius * 0.16, Color("#29445c", 0.96))
draw_arc(center, radius * 0.68, 0.0, TAU, 16, Color(color, 0.78), 1.0)
func _draw_pause_icon(center: Vector2, radius: float, color: Color) -> void:
if playback_paused:
var triangle := PackedVector2Array([
center + Vector2(-radius * 0.42, -radius * 0.62),
center + Vector2(radius * 0.62, 0.0),
center + Vector2(-radius * 0.42, radius * 0.62),
])
draw_colored_polygon(triangle, color)
else:
draw_rect(Rect2(center - Vector2(radius * 0.48, radius * 0.62), Vector2(radius * 0.32, radius * 1.24)), color)
draw_rect(Rect2(center + Vector2(radius * 0.16, -radius * 0.62), Vector2(radius * 0.32, radius * 1.24)), color)
func _draw_speed_icon(center: Vector2, radius: float, color: Color) -> void:
var chevrons := 1 if playback_speed <= 1 else (2 if playback_speed <= 2 else 3)
for index in range(chevrons):
var offset := (float(index) - float(chevrons - 1) * 0.5) * radius * 0.72
var c := center + Vector2(offset, 0.0)
draw_colored_polygon(PackedVector2Array([
c + Vector2(-radius * 0.38, -radius * 0.62),
c + Vector2(radius * 0.42, 0.0),
c + Vector2(-radius * 0.38, radius * 0.62),
]), color)
func _draw_magnifier(center: Vector2, radius: float, color: Color) -> void:
draw_arc(center - Vector2(radius * 0.18, radius * 0.18), radius * 0.68, 0.0, TAU, 24, color, 3.0)
draw_line(center + Vector2(radius * 0.34, radius * 0.34), center + Vector2(radius * 0.95, radius * 0.95), color, 3.0)
func _draw_cog(center: Vector2, radius: float, color: Color, fill: Color) -> void:
draw_circle(center, radius * 1.12, Color(fill, 0.94))
for index in range(8):
var angle := TAU * float(index) / 8.0
var direction := Vector2(cos(angle), sin(angle))
draw_line(center + direction * radius * 0.82, center + direction * radius * 1.24, color, 2.0)
draw_arc(center, radius * 0.72, 0.0, TAU, 24, color, 2.0)
draw_circle(center, radius * 0.22, color)
func _draw_hourglass(center: Vector2, radius: float, color: Color) -> void:
var top_left := center + Vector2(-radius * 0.70, -radius)
var top_right := center + Vector2(radius * 0.70, -radius)
var bottom_left := center + Vector2(-radius * 0.70, radius)
var bottom_right := center + Vector2(radius * 0.70, radius)
draw_line(top_left, top_right, color, 2.0)
draw_line(bottom_left, bottom_right, color, 2.0)
draw_line(top_left, center + Vector2(radius * 0.18, 0.0), color, 2.0)
draw_line(top_right, center + Vector2(-radius * 0.18, 0.0), color, 2.0)
draw_line(center + Vector2(radius * 0.18, 0.0), bottom_left, color, 2.0)
draw_line(center + Vector2(-radius * 0.18, 0.0), bottom_right, color, 2.0)
draw_colored_polygon(PackedVector2Array([
center + Vector2(-radius * 0.16, radius * 0.12),
center + Vector2(radius * 0.16, radius * 0.12),
center + Vector2(radius * 0.30, radius * 0.58),
center + Vector2(-radius * 0.30, radius * 0.58),
]), Color(color, 0.72))
func _quadratic_path(source: Vector2, target: Vector2, bend: float) -> PackedVector2Array:
var points := PackedVector2Array()
var midpoint := (source + target) * 0.5 + Vector2(0.0, bend)
for index in range(21):
var t := float(index) / 20.0
var point := source * pow(1.0 - t, 2.0) + midpoint * 2.0 * (1.0 - t) * t + target * pow(t, 2.0)
points.append(point)
return points
func _to_px(point: Vector2) -> Vector2:
return Vector2(point.x * size.x, point.y * size.y)
func _draw_diamond(center: Vector2, radius: float, color: Color) -> void:
var points := PackedVector2Array([
center + Vector2(0.0, -radius),
center + Vector2(radius, 0.0),
center + Vector2(0.0, radius),
center + Vector2(-radius, 0.0),
])
draw_colored_polygon(points, color)
func _plate(background: Color, border: Color, border_width: int, radius: int) -> StyleBoxFlat:
var style := StyleBoxFlat.new()
style.bg_color = background
style.border_color = border
style.set_border_width_all(border_width)
style.set_corner_radius_all(radius)
return style