using Cthangover.Core.Utils;
using Godot;
using Cthangover.Core.UI;
namespace Cthangover.Mods.Interface.Base.Lists
{
///
/// Base widget with a lazy construct/destruct lifecycle around Show/Hide.
/// On first Show(), OnceConstruct() fires once via EnsureConstructed — subsequent
/// Show/Hide calls trigger ShowConstruct/HideDestruct instead, allowing repeated
/// state reset without full reconstruction. Overrides Godot's built-in Visible
/// property to intercept visibility changes and route them through the lifecycle.
/// Maintains an independent isVisible flag because Godot's visibility has quirks
/// (e.g. parent visibility propagation) that would break the state machine.
/// The #if TOOLS _ValidateProperty syncs the editor inspector with the override.
///
public abstract partial class Widget : Control, IWidget
{
private bool isConstructed;
/// Syncs the internal visibility flag with the scene-file state and hides properly when the widget starts hidden.
public override void _Ready()
{
GameLogger.Log("WIDGET", $"_Ready: name={Name}, isVisible={isVisible}, GodotVisible={base.Visible}", LogLevel.Debug);
isVisible = base.Visible;
if (!isVisible)
Hide();
OnReady();
}
/// Tears down a constructed widget when it leaves the tree, so hidden-while-freed widgets still release their resources.
public override void _ExitTree()
{
if (isConstructed)
OnceDestruct();
OnExitTree();
}
/// Engine callback; forwards to so mod subclasses receive input without their own engine bindings.
public override void _Input(InputEvent @event) => OnInput(@event);
/// Forwarded from . Override for mod widget initialization.
protected virtual void OnReady() { }
/// Forwarded from . Override for mod widget teardown.
protected virtual void OnExitTree() { }
/// Forwarded from . Override to handle input in mod widgets.
protected virtual void OnInput(InputEvent @event) { }
/// Toggles visibility: calls if visible, otherwise.
public void Switch()
{
if (Visible)
Hide();
else
Show();
}
///
/// Makes visible and runs lifecycle. On first call, fires .
/// Subsequent calls trigger without reconstruction. Sets both the internal isVisible flag
/// and Godot's native visibility, then explicitly shows the Body canvas item.
///
public new virtual void Show()
{
GameLogger.Log("WIDGET", $"Show ENTER: name={Name}, isVisible={isVisible}, GodotVisible={base.Visible}", LogLevel.Debug);
var wasConstructed = isConstructed;
EnsureConstructed();
if (Visible)
{
if (!wasConstructed)
ShowConstruct();
GameLogger.Log("WIDGET", $"Show SKIP: already visible", LogLevel.Debug);
return;
}
ShowConstruct();
isVisible = true;
base.Visible = true;
var canvasBody = Body as CanvasItem;
if (canvasBody != null)
canvasBody.Visible = true;
(Body as Control)?.QueueRedraw();
GameLogger.Log("WIDGET", $"Show EXIT: name={Name}, isVisible={isVisible}, GodotVisible={base.Visible}", LogLevel.Debug);
}
/// Hides the widget, fires if constructed, sets internal and Godot visibility to false, and hides the Body canvas item.
public new virtual void Hide()
{
if (!Visible)
return;
if (isConstructed)
HideDestruct();
isVisible = false;
base.Visible = false;
var canvasBody = Body as CanvasItem;
if (canvasBody != null)
canvasBody.Visible = false;
}
/// Idempotent construction gate: calls exactly once, on the first invocation.
public void EnsureConstructed()
{
if (!isConstructed)
{
isConstructed = true;
OnceConstruct();
}
}
/// Called exactly once on the first . Override to perform one-time initialization.
protected virtual void OnceConstruct()
{
}
/// Called once on tree exit if constructed. Override for one-time cleanup of resources allocated in .
protected virtual void OnceDestruct()
{
}
[Export] private bool isVisible = true;
[Export] private Node body;
///
/// Shadows Godot's built-in visibility. Setting to true calls , setting to false calls .
/// Maintains an independent isVisible flag because Godot's native visibility propagates through the parent chain,
/// which would break the explicit lifecycle state machine.
///
public new bool Visible
{
get => isVisible;
set
{
if (isVisible == value)
return;
if (value)
Show();
else
Hide();
}
}
/// The rendered content subtree. May be a child node separate from the Rect layout root.
public Node Body => body;
/// The Control used for layout. For Widget this is the node itself, enabling it to serve as both layout root and widget.
public Control Rect => this;
/// Called each time runs after initial construction. Override to refresh state (e.g. reload data, re-enable input).
protected virtual void ShowConstruct()
{
}
/// Called each time runs when constructed. Override to clean up display-only state without destroying permanent resources.
protected virtual void HideDestruct()
{
}
#if TOOLS
/// Keeps the inspector in sync with the overridden visibility: touching visible or isVisible in the editor routes through the lifecycle.
public override void _ValidateProperty(Godot.Collections.Dictionary property)
{
base._ValidateProperty(property);
if (property.TryGetValue("visible", out var _))
{
isVisible = base.Visible;
if (isVisible)
Show();
else
Hide();
}
else if (property.TryGetValue("isVisible", out var _))
{
if (isVisible)
Show();
else
Hide();
}
}
#endif
}
}