extends RefCounted ## Compact translucent cards attached to objects in the world canvas. static func panel_style(accent: Color) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.bg_color = Color(0.018,0.041,0.063,0.84) style.border_color = Color(accent,0.55) style.set_border_width_all(1) style.border_width_top = 2 style.set_corner_radius_all(7) style.content_margin_left = 12 style.content_margin_right = 12 style.content_margin_top = 10 style.content_margin_bottom = 10 style.shadow_color = Color(0,0,0,0.28) style.shadow_size = 7 style.shadow_offset = Vector2(0,3) return style static func compact_button(button: Button, accent: Color) -> void: preload("res://scripts/ui/hud_button_style.gd").apply(button,accent) button.custom_minimum_size.y = 30 button.add_theme_font_size_override("font_size",11) for state in ["normal","hover","pressed","disabled"]: var style := button.get_theme_stylebox(state).duplicate() as StyleBoxFlat style.bg_color.a = 0.8 style.content_margin_top = 4 style.content_margin_bottom = 4 style.content_margin_left = 9 style.content_margin_right = 9 style.shadow_size = 0 style.set_corner_radius_all(4) button.add_theme_stylebox_override(state,style) static func hud_obstacles(hud: Node) -> Array[Rect2]: var boxes: Array[Rect2] = [] if hud == null: return boxes for name in ["CaptainPanel","CaptainEquipment","MissionObjectives","ShipDrawer","PassiveCaptainPanel","PassiveShipPanel"]: var control := hud.get_node_or_null(NodePath(name)) as Control if control != null and control.is_visible_in_tree(): boxes.append(control.get_global_rect().grow(6)) return boxes static func beside(center: Vector2, radius: float, extent: Vector2, viewport: Vector2, obstacles: Array[Rect2] = [], prefer_right := true, vertical_radius := -1.0) -> Vector2: var gap := 14.0 var vertical := radius if vertical_radius < 0 else vertical_radius var right := center + Vector2(radius+gap,-extent.y*0.5) var left := center - Vector2(radius+gap+extent.x,extent.y*0.5) var candidates: Array[Vector2] = [right,left] if not prefer_right: candidates.reverse() candidates.append(center+Vector2(-extent.x*0.5,-vertical-gap-extent.y)) candidates.append(center+Vector2(-extent.x*0.5,vertical+gap)) # Additional lanes prevent two cards from clipping one another's headers. for obstacle in obstacles: candidates.append(Vector2(center.x-extent.x*0.5,obstacle.position.y-gap-extent.y)) candidates.append(Vector2(center.x-extent.x*0.5,obstacle.end.y+gap)) var chosen := Vector2.ZERO var best := INF var low := Vector2(10,88) var high := Vector2(maxf(low.x,viewport.x-extent.x-10),maxf(low.y,viewport.y-extent.y-118)) for index in candidates.size(): var candidate := candidates[index] var clamped := candidate.clamp(low,high) var box := Rect2(clamped,extent) var score := candidate.distance_to(clamped) + index*8.0 # Avoid covering the object if an edge forces the card to change sides. var object_box := Rect2(center-Vector2(radius,vertical),Vector2(radius,vertical)*2) if box.intersects(object_box): score += box.intersection(object_box).get_area()*0.1 for obstacle in obstacles: if box.intersects(obstacle): score += box.intersection(obstacle).get_area() if score < best: best = score chosen = clamped return chosen