upd: sync local changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7a30f4b8c5
commit
6884cd5374
36 files changed
+3363
-6
No files matched your search
@@ -0,0 +1,194 @@
|
||||
using Cthangover.Core.Utils;
|
||||
using Godot;
|
||||
|
||||
using Cthangover.Core.UI;
|
||||
|
||||
namespace Cthangover.Mods.Interface.Base.Lists
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public abstract partial class Widget : Control, IWidget
|
||||
{
|
||||
private bool isConstructed;
|
||||
|
||||
/// <summary>Syncs the internal visibility flag with the scene-file state and hides properly when the widget starts hidden.</summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
GameLogger.Log("WIDGET", $"_Ready: name={Name}, isVisible={isVisible}, GodotVisible={base.Visible}", LogLevel.Debug);
|
||||
isVisible = base.Visible;
|
||||
if (!isVisible)
|
||||
Hide();
|
||||
OnReady();
|
||||
}
|
||||
|
||||
/// <summary>Tears down a constructed widget when it leaves the tree, so hidden-while-freed widgets still release their resources.</summary>
|
||||
public override void _ExitTree()
|
||||
{
|
||||
if (isConstructed)
|
||||
OnceDestruct();
|
||||
OnExitTree();
|
||||
}
|
||||
|
||||
/// <summary>Engine callback; forwards to <see cref="OnInput"/> so mod subclasses receive input without their own engine bindings.</summary>
|
||||
public override void _Input(InputEvent @event) => OnInput(@event);
|
||||
|
||||
/// <summary>Forwarded from <see cref="_Ready"/>. Override for mod widget initialization.</summary>
|
||||
protected virtual void OnReady() { }
|
||||
|
||||
/// <summary>Forwarded from <see cref="_ExitTree"/>. Override for mod widget teardown.</summary>
|
||||
protected virtual void OnExitTree() { }
|
||||
|
||||
/// <summary>Forwarded from <see cref="_Input"/>. Override to handle input in mod widgets.</summary>
|
||||
protected virtual void OnInput(InputEvent @event) { }
|
||||
|
||||
/// <summary>Toggles visibility: calls <see cref="Hide"/> if visible, <see cref="Show"/> otherwise.</summary>
|
||||
public void Switch()
|
||||
{
|
||||
if (Visible)
|
||||
Hide();
|
||||
else
|
||||
Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes visible and runs lifecycle. On first call, <see cref="EnsureConstructed"/> fires <see cref="OnceConstruct"/>.
|
||||
/// Subsequent calls trigger <see cref="ShowConstruct"/> without reconstruction. Sets both the internal isVisible flag
|
||||
/// and Godot's native visibility, then explicitly shows the Body canvas item.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>Hides the widget, fires <see cref="HideDestruct"/> if constructed, sets internal and Godot visibility to false, and hides the Body canvas item.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Idempotent construction gate: calls <see cref="OnceConstruct"/> exactly once, on the first invocation.</summary>
|
||||
public void EnsureConstructed()
|
||||
{
|
||||
if (!isConstructed)
|
||||
{
|
||||
isConstructed = true;
|
||||
OnceConstruct();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called exactly once on the first <see cref="Show"/>. Override to perform one-time initialization.</summary>
|
||||
protected virtual void OnceConstruct()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Called once on tree exit if constructed. Override for one-time cleanup of resources allocated in <see cref="OnceConstruct"/>.</summary>
|
||||
protected virtual void OnceDestruct()
|
||||
{
|
||||
}
|
||||
|
||||
[Export] private bool isVisible = true;
|
||||
|
||||
[Export] private Node body;
|
||||
|
||||
/// <summary>
|
||||
/// Shadows Godot's built-in visibility. Setting to true calls <see cref="Show"/>, setting to false calls <see cref="Hide"/>.
|
||||
/// Maintains an independent <c>isVisible</c> flag because Godot's native visibility propagates through the parent chain,
|
||||
/// which would break the explicit lifecycle state machine.
|
||||
/// </summary>
|
||||
public new bool Visible
|
||||
{
|
||||
get => isVisible;
|
||||
set
|
||||
{
|
||||
if (isVisible == value)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
Show();
|
||||
else
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The rendered content subtree. May be a child node separate from the Rect layout root.</summary>
|
||||
public Node Body => body;
|
||||
|
||||
/// <summary>The Control used for layout. For Widget this is the node itself, enabling it to serve as both layout root and widget.</summary>
|
||||
public Control Rect => this;
|
||||
|
||||
/// <summary>Called each time <see cref="Show"/> runs after initial construction. Override to refresh state (e.g. reload data, re-enable input).</summary>
|
||||
protected virtual void ShowConstruct()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Called each time <see cref="Hide"/> runs when constructed. Override to clean up display-only state without destroying permanent resources.</summary>
|
||||
protected virtual void HideDestruct()
|
||||
{
|
||||
}
|
||||
|
||||
#if TOOLS
|
||||
/// <summary>Keeps the inspector in sync with the overridden visibility: touching <c>visible</c> or <c>isVisible</c> in the editor routes through the lifecycle.</summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user