class_name SpeechBubble extends Node2D ## SpeechBubble - World-space speech bubble drawn in _draw() (Phase 3a). ## ## A child of the rig root at a fixed upward offset (SPEECH_BUBBLE_OFFSET), so ## it follows the figure and scales with the camera. Pure drawing, no ## hit-testing. Hidden by default. const FONT_SIZE := 28 const PADDING := Vector2(14.0, 10.0) const TAIL_HEIGHT := 12.0 const TAIL_WIDTH := 16.0 const MAX_WIDTH := 320.0 const BG_COLOR := Color(1.0, 1.0, 1.0, 0.95) const BORDER_COLOR := Color(0.0, 0.0, 0.0, 0.6) const TEXT_COLOR := Color(0.0, 0.0, 0.0, 1.0) var _text: String = "" var _bg_style: StyleBoxFlat func _init() -> void: visible = false _bg_style = StyleBoxFlat.new() _bg_style.bg_color = BG_COLOR _bg_style.border_color = BORDER_COLOR _bg_style.set_border_width_all(2) _bg_style.set_corner_radius_all(8) func show_text(text: String) -> void: _text = text visible = true queue_redraw() func hide_bubble() -> void: visible = false func _draw() -> void: if _text.is_empty(): return var font := ThemeDB.fallback_font var text_size := font.get_string_size(_text, HORIZONTAL_ALIGNMENT_LEFT, MAX_WIDTH, FONT_SIZE) var box_size := text_size + PADDING * 2.0 var box := Rect2(Vector2(-box_size.x * 0.5, -TAIL_HEIGHT - box_size.y), box_size) draw_style_box(_bg_style, box) draw_colored_polygon(PackedVector2Array([ Vector2(-TAIL_WIDTH * 0.5, -TAIL_HEIGHT), Vector2(TAIL_WIDTH * 0.5, -TAIL_HEIGHT), Vector2(0.0, 0.0), ]), BG_COLOR) draw_string(font, box.position + PADDING, _text, HORIZONTAL_ALIGNMENT_LEFT, MAX_WIDTH, FONT_SIZE, TEXT_COLOR)