using Cthangover.Core.Scenes; using Cthangover.Core.UI.Event; using Cthangover.Core.UI.Theming; using Godot; using Cthangover.Core.UI.Messages; namespace Cthangover.Mods.Interface.Messages { /// /// Floating text message with auto-fade. Appears at a vertical position, /// stays visible for 3 seconds, then fades over 2/Speed seconds. Self- /// destructs (QueueFree) when fully transparent. Registers with /// SceneEventController for per-frame updates; unregisters on tree exit /// to prevent leaks. Speed affects only the fade duration, not the display /// duration, so faster speed = shorter fade = message disappears more quickly. /// public partial class UiMessage : Control, IOnUpdateEvent { [Export] private Label textField; /// Tint of the message text; defaults to the warning colour. [Export] public Color Color { get; set; } = UiTokens.Warning; /// Fade speed multiplier — 1 is normal; higher values shorten the fade-out. [Export] public float Speed { get; set; } = 1f; private float timestamp; /// /// The text content displayed by this message. Reads/writes the child /// textField label directly. /// public string Text { get => textField.Text; set => textField.Text = value; } /// /// Initializes the message: positions it, sets text/speed/color, records /// the start timestamp, and registers with the /// for per-frame updates. Called by . /// /// Vertical Y-offset in pixels. /// The message text. /// Fade speed multiplier. /// The text color. public void Setup(float pos, string text, float speed, Color color) { OffsetLeft = 50; OffsetRight = -10; Size = new Vector2(Size.X, 50); Position = new Vector2(0, pos + 200); Text = text; Speed = speed; Color = color; timestamp = (float)Godot.Time.GetTicksMsec() / 1000f; if (SceneContextNode.Instance != null) { var eventController = SceneContextNode.FindNode("EventController"); eventController?.AddUpdateEventListener(this); } } /// Detaches from the event controller so a freed message stops receiving updates. public override void _ExitTree() { if (SceneContextNode.Instance != null) { var eventController = SceneContextNode.FindNode("EventController"); eventController?.RemoveUpdateEventListener(this); } } /// /// Priority 1 — lightweight enough for many concurrent messages. /// public int Priority => 1; /// /// Per-frame update: displays for 3 seconds, then fades out over /// 2 / seconds. Self-destructs via /// QueueFree when fully transparent. /// public void OnUpdate() { var currentTime = (float)Godot.Time.GetTicksMsec() / 1000f; var elapsed = currentTime - timestamp; if (elapsed < 3f) return; var fadeDuration = 2f / Speed; var fadeElapsed = elapsed - 3f; var progress = Mathf.Clamp(fadeElapsed / fadeDuration, 0f, 1f); textField.SelfModulate = UiTokens.Alpha(Colors.White, 1f - progress); if (progress >= 1f) QueueFree(); } } }