class_name LuneEventFeed extends VBoxContainer ## Short event list: tower falls, champions, boss, critical Ancients, round results. At most ## five entries, newest on top; each fades after a while. A click on an entry with a place ## moves the camera there. It never lists every kill. signal focus_requested(target: Dictionary) const LIMIT: int = 5 const LIFETIME: float = 14.0 const LEVEL_COLORS: Dictionary = {"info": LuneUiTheme.INFO, "good": LuneUiTheme.GOOD, "warn": LuneUiTheme.WARN, "danger": LuneUiTheme.DANGER} var entries: Array[Dictionary] = [] var _clock: float = 0.0 func _init() -> void: add_theme_constant_override("separation", 3) mouse_filter = Control.MOUSE_FILTER_IGNORE ## level: info, good, warn, danger. target: {} or {"lane": …} / {"point": Vector2} / {"camera": name}. func push(text_value: String, level: String = "info", glyph: String = "", target: Dictionary = {}) -> void: for entry: Dictionary in entries: if entry.text == text_value and _clock - float(entry.born) < 4.0: return var button := Button.new() button.theme_type_variation = "HudRow" button.focus_mode = Control.FOCUS_NONE button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND if not target.is_empty() else Control.CURSOR_ARROW button.custom_minimum_size = Vector2(0, 30) var row := HBoxContainer.new() row.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) row.offset_left = 8 row.offset_right = -8 row.add_theme_constant_override("separation", 7) row.mouse_filter = Control.MOUSE_FILTER_IGNORE button.add_child(row) var tint: Color = LEVEL_COLORS.get(level, LuneUiTheme.INFO) LuneGlyph.make(glyph if not glyph.is_empty() else "star", tint, 15.0, row) var label := LuneUiTheme.label(text_value, "HudText", row) label.size_flags_horizontal = Control.SIZE_EXPAND_FILL label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS label.clip_text = true label.add_theme_color_override("font_color", tint.lerp(LuneUiTheme.TEXT, .45)) button.pressed.connect(func() -> void: if not target.is_empty(): focus_requested.emit(target)) LuneTooltip.attach(button, func() -> Dictionary: return {"title": text_value, "note": "Klick: Kamera dorthin" if not target.is_empty() else ""}) add_child(button) move_child(button, 0) entries.push_front({"text": text_value, "level": level, "born": _clock, "node": button}) while entries.size() > LIMIT: var old: Dictionary = entries.pop_back() (old.node as Node).queue_free() visible = true func clear_all() -> void: for entry: Dictionary in entries: (entry.node as Node).queue_free() entries.clear() func _process(delta: float) -> void: _clock += delta for index: int in range(entries.size() - 1, -1, -1): var entry: Dictionary = entries[index] var age: float = _clock - float(entry.born) var node: Control = entry.node if age > LIFETIME: node.queue_free() entries.remove_at(index) elif age > LIFETIME - 2.0: node.modulate.a = clampf((LIFETIME - age) / 2.0, 0.0, 1.0) visible = not entries.is_empty()