Files
mod_ui_base/src/Messages/UiMessage.cs
T
2026-10-16 16:18:16 +00:00

105 lines
3.7 KiB
C#

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
{
/// <summary>
/// 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.
/// </summary>
public partial class UiMessage : Control, IOnUpdateEvent
{
[Export] private Label textField;
/// <summary>Tint of the message text; defaults to the warning colour.</summary>
[Export] public Color Color { get; set; } = UiTokens.Warning;
/// <summary>Fade speed multiplier — 1 is normal; higher values shorten the fade-out.</summary>
[Export] public float Speed { get; set; } = 1f;
private float timestamp;
/// <summary>
/// The text content displayed by this message. Reads/writes the child
/// <c>textField</c> label directly.
/// </summary>
public string Text
{
get => textField.Text;
set => textField.Text = value;
}
/// <summary>
/// Initializes the message: positions it, sets text/speed/color, records
/// the start timestamp, and registers with the <see cref="SceneEventController"/>
/// for per-frame updates. Called by <see cref="MessagesHelper"/>.
/// </summary>
/// <param name="pos">Vertical Y-offset in pixels.</param>
/// <param name="text">The message text.</param>
/// <param name="speed">Fade speed multiplier.</param>
/// <param name="color">The text color.</param>
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<SceneEventController>("EventController");
eventController?.AddUpdateEventListener(this);
}
}
/// <summary>Detaches from the event controller so a freed message stops receiving updates.</summary>
public override void _ExitTree()
{
if (SceneContextNode.Instance != null)
{
var eventController = SceneContextNode.FindNode<SceneEventController>("EventController");
eventController?.RemoveUpdateEventListener(this);
}
}
/// <summary>
/// Priority <c>1</c> — lightweight enough for many concurrent messages.
/// </summary>
public int Priority => 1;
/// <summary>
/// Per-frame update: displays for 3 seconds, then fades out over
/// <c>2 / <see cref="Speed"/></c> seconds. Self-destructs via
/// <c>QueueFree</c> when fully transparent.
/// </summary>
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();
}
}
}