Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das HUD im Unity-Projekt (../luneraria-unity). - HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse, Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails, Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben - Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie - Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715; verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern - Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md - HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
5.1 KiB
GDScript
141 lines
5.1 KiB
GDScript
class_name LuneTooltip
|
|
extends PanelContainer
|
|
## The one tooltip of the HUD. A single panel is reused for every hover: it appears after a
|
|
## short delay next to the hovered control, stays inside the window, never takes mouse input
|
|
## and hides as soon as the control is left, hidden, disabled for input, freed or clicked.
|
|
## Content is a small dictionary: title, subtitle, stats ([glyph, name, value] rows), text,
|
|
## note and warn. Components register with `LuneTooltip.attach(control, builder)`.
|
|
|
|
const DELAY: float = .28
|
|
const QUICK: float = .05
|
|
const WIDTH: float = 300.0
|
|
|
|
static var current: LuneTooltip
|
|
|
|
var _owner: Control
|
|
var _builder: Callable
|
|
var _wait: float = 0.0
|
|
var _last_hide: float = -10.0
|
|
var _clock: float = 0.0
|
|
var _column: VBoxContainer
|
|
## Last pointer position from input events (canvas coordinates); real and synthetic motion.
|
|
var _pointer: Vector2 = Vector2(-1, -1)
|
|
|
|
func _init() -> void:
|
|
theme_type_variation = "HudTooltip"
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
visible = false
|
|
z_index = 100
|
|
_column = VBoxContainer.new()
|
|
_column.add_theme_constant_override("separation", 4)
|
|
_column.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
add_child(_column)
|
|
current = self
|
|
|
|
static func attach(control: Control, builder: Callable) -> void:
|
|
control.mouse_entered.connect(func() -> void:
|
|
if current != null and is_instance_valid(current):
|
|
current.hover(control, builder))
|
|
control.mouse_exited.connect(func() -> void:
|
|
if current != null and is_instance_valid(current):
|
|
current.leave(control))
|
|
control.gui_input.connect(func(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton and event.pressed and current != null and is_instance_valid(current):
|
|
current.leave(control))
|
|
|
|
func hover(control: Control, builder: Callable) -> void:
|
|
if _pointer.x < 0.0:
|
|
_pointer = control.get_global_mouse_position()
|
|
_owner = control
|
|
_builder = builder
|
|
_wait = QUICK if _clock - _last_hide < .6 else DELAY
|
|
visible = false
|
|
|
|
func leave(control: Control) -> void:
|
|
if control == _owner:
|
|
_hide()
|
|
|
|
func _hide() -> void:
|
|
if visible:
|
|
_last_hide = _clock
|
|
_owner = null
|
|
visible = false
|
|
|
|
## Refreshes the text of an open tooltip (values changed while hovering).
|
|
func refresh(control: Control) -> void:
|
|
if control == _owner and visible:
|
|
_show()
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if event is InputEventMouse:
|
|
_pointer = (event as InputEventMouse).position
|
|
|
|
func _process(delta: float) -> void:
|
|
_clock += delta
|
|
if _owner == null:
|
|
return
|
|
if not is_instance_valid(_owner) or not _owner.is_visible_in_tree() or not _owner.get_global_rect().has_point(_pointer):
|
|
_hide()
|
|
return
|
|
if not visible:
|
|
_wait -= delta
|
|
if _wait <= 0.0:
|
|
_show()
|
|
|
|
func _show() -> void:
|
|
var content: Dictionary = _builder.call() if _builder.is_valid() else {}
|
|
if content.is_empty():
|
|
visible = false
|
|
return
|
|
for child: Node in _column.get_children():
|
|
child.free()
|
|
if content.has("title"):
|
|
var head := HBoxContainer.new()
|
|
head.add_theme_constant_override("separation", 6)
|
|
head.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_column.add_child(head)
|
|
if content.has("glyph"):
|
|
LuneGlyph.make(str(content.glyph), content.get("glyph_color", LuneUiTheme.BRONZE_LIGHT), 18.0, head)
|
|
LuneUiTheme.label(str(content.title), "HudValue", head)
|
|
if content.has("subtitle"):
|
|
LuneUiTheme.label(str(content.subtitle), "HudMuted", _column)
|
|
var stats: Array = content.get("stats", [])
|
|
if not stats.is_empty():
|
|
var grid := GridContainer.new()
|
|
grid.columns = 3
|
|
grid.add_theme_constant_override("h_separation", 8)
|
|
grid.add_theme_constant_override("v_separation", 2)
|
|
grid.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_column.add_child(grid)
|
|
for row: Array in stats:
|
|
LuneGlyph.make(str(row[0]), LuneUiTheme.MUTED, 14.0, grid)
|
|
LuneUiTheme.label(str(row[1]), "HudMuted", grid)
|
|
var value := LuneUiTheme.label(str(row[2]), "HudText", grid)
|
|
value.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
for key: String in ["text", "note", "warn"]:
|
|
if content.has(key) and not str(content[key]).is_empty():
|
|
var line := LuneUiTheme.label(str(content[key]), "HudText" if key == "text" else "HudMuted", _column)
|
|
line.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
line.custom_minimum_size.x = WIDTH - 24.0
|
|
if key == "warn":
|
|
line.add_theme_color_override("font_color", LuneUiTheme.DANGER)
|
|
visible = true
|
|
reset_size()
|
|
custom_minimum_size.x = 0.0
|
|
await get_tree().process_frame
|
|
if not visible or _owner == null or not is_instance_valid(_owner):
|
|
return
|
|
_place()
|
|
|
|
func _place() -> void:
|
|
reset_size()
|
|
var parent_rect: Rect2 = (get_parent() as Control).get_global_rect() if get_parent() is Control else get_viewport_rect()
|
|
var anchor: Rect2 = _owner.get_global_rect()
|
|
var scale_factor: float = get_global_transform().get_scale().x
|
|
var extent: Vector2 = size * scale_factor
|
|
var at := Vector2(anchor.get_center().x - extent.x * .5, anchor.position.y - extent.y - 8.0)
|
|
if at.y < parent_rect.position.y + 6.0:
|
|
at.y = anchor.end.y + 8.0
|
|
at.x = clampf(at.x, parent_rect.position.x + 6.0, parent_rect.end.x - extent.x - 6.0)
|
|
at.y = clampf(at.y, parent_rect.position.y + 6.0, parent_rect.end.y - extent.y - 6.0)
|
|
global_position = at
|