using System;
using Cthangover.Core.Items;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Core.UI.Theming;
using Godot;
namespace Cthangover.Core.UI.Inventory
{
///
/// Interactive cell of the inventory grid. Unlike the old thin cell that
/// only drew an icon and a stack counter, this one owns the whole visual
/// language of a slot: the frame (normal/hover/selected), the type accent
/// dot, the short action tag and the dimmed state for inert items. It is
/// built entirely in code because the grid needs pixel-accurate
/// frame swapping that theme overrides in the
/// tscn pipeline cannot express.
///
/// Click handling lives inside the cell: it raises
/// after consuming the mouse event, instead of the panel subscribing to
/// GuiInput from outside, so event propagation is stopped by the
/// control that actually owns the click.
///
public partial class InventoryItemBehaviour : ListItem
{
/// Raised on a left-click after the cell consumed the event. The payload is the cell itself.
public event Action Clicked;
private static readonly StyleBoxFlat NormalFrame = MakeFrame(UiTokens.LineSoft, UiTokens.Surface1);
private static readonly StyleBoxFlat HoverFrame = MakeFrame(UiTokens.LineStrong, UiTokens.SurfaceHover);
private static readonly StyleBoxFlat SelectedFrame = MakeFrame(UiTokens.Accent, UiTokens.SurfaceSelected);
private static readonly StyleBoxFlat TagStyle = MakeTagStyle();
private static readonly Color IconDimmed = UiTokens.Alpha(UiTokens.White, 0.38f);
private Panel _frame;
private TextureRect _icon;
private Label _count;
private Panel _dot;
private StyleBoxFlat _dotStyle;
private PanelContainer _tag;
private Label _tagLabel;
private bool _built;
private bool _selected;
private bool _hovered;
/// Stops mouse propagation and wires hover/click handling before any model is bound.
public InventoryItemBehaviour()
{
MouseFilter = MouseFilterEnum.Stop;
GuiInput += OnGuiInput;
MouseEntered += OnMouseEntered;
MouseExited += OnMouseExited;
}
///
/// Binds the item stack and lazily builds the visual tree on first
/// use; repeated binds only refresh the rendered data.
///
public override void Construct(IItemContainer model)
{
base.Construct(model);
EnsureBuilt();
UpdateInfo();
}
///
/// Pushes the bound stack into the visuals: icon, stack counter
/// (hidden for singletons), type accent dot, action tag and the
/// dimmed state for items without a usable action.
///
public void UpdateInfo()
{
if (!_built || Model?.Item == null)
return;
var item = Model.Item;
_icon.Texture = item.Sprite;
_count.Text = Model.Count > 1 ? "×" + Model.Count : string.Empty;
_count.Visible = Model.Count > 1;
_dotStyle.BgColor = InventoryItemUi.AccentColor(item.ItemType);
var tagKey = InventoryItemUi.TagKey(item);
_tag.Visible = tagKey != null;
if (tagKey != null)
_tagLabel.Text = InventoryItemUi.Translate(tagKey);
var dimmed = !InventoryItemUi.IsUsable(item);
_icon.Modulate = dimmed ? IconDimmed : Colors.White;
_count.Modulate = dimmed ? IconDimmed : Colors.White;
}
///
/// Overrides what derived from the item's action,
/// for grids where the action is not the point — the trade window shows a
/// price in the corner tag (, null hides it) and
/// dims what cannot be traded. Call after .
///
public void Decorate(string tag, bool dimmed)
{
if (!_built)
return;
_tag.Visible = tag != null;
if (tag != null)
_tagLabel.Text = tag;
_icon.Modulate = dimmed ? IconDimmed : Colors.White;
_count.Modulate = dimmed ? IconDimmed : Colors.White;
}
/// Switches the frame to the gold selected state; the panel owns this flag.
public void SetSelected(bool selected)
{
_selected = selected;
ApplyFrame();
}
/// Removes the cell from the scene tree; the grid rebuilds cells instead of recycling them.
public override void Destruct()
{
QueueFree();
}
private void EnsureBuilt()
{
if (_built)
return;
_built = true;
_frame = new Panel();
_frame.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
_frame.AddThemeStyleboxOverride("panel", NormalFrame);
_frame.MouseFilter = MouseFilterEnum.Ignore;
AddChild(_frame);
_dot = new Panel();
_dot.SetAnchorsAndOffsetsPreset(LayoutPreset.TopLeft);
_dot.OffsetLeft = 6;
_dot.OffsetTop = 6;
_dot.OffsetRight = 13;
_dot.OffsetBottom = 13;
_dot.MouseFilter = MouseFilterEnum.Ignore;
_dotStyle = new StyleBoxFlat();
_dotStyle.CornerRadiusTopLeft = 2;
_dotStyle.CornerRadiusTopRight = 2;
_dotStyle.CornerRadiusBottomLeft = 2;
_dotStyle.CornerRadiusBottomRight = 2;
_dot.AddThemeStyleboxOverride("panel", _dotStyle);
AddChild(_dot);
_icon = new TextureRect();
_icon.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect);
_icon.OffsetLeft = 12;
_icon.OffsetTop = 12;
_icon.OffsetRight = -12;
_icon.OffsetBottom = -12;
_icon.ExpandMode = TextureRect.ExpandModeEnum.IgnoreSize;
_icon.StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered;
_icon.MouseFilter = MouseFilterEnum.Ignore;
AddChild(_icon);
_count = new Label();
_count.SetAnchorsAndOffsetsPreset(LayoutPreset.BottomRight);
_count.OffsetLeft = -70;
_count.OffsetTop = -26;
_count.OffsetRight = -6;
_count.OffsetBottom = -3;
_count.HorizontalAlignment = HorizontalAlignment.Right;
_count.VerticalAlignment = VerticalAlignment.Center;
_count.AddThemeFontSizeOverride("font_size", 13);
_count.AddThemeColorOverride("font_color", UiTokens.Stack);
_count.AddThemeColorOverride("font_outline_color", new Color(0f, 0f, 0f, 0.9f));
_count.AddThemeConstantOverride("outline_size", 4);
_count.MouseFilter = MouseFilterEnum.Ignore;
AddChild(_count);
var tagRow = new HBoxContainer();
tagRow.SetAnchorsAndOffsetsPreset(LayoutPreset.TopWide);
tagRow.OffsetLeft = 5;
tagRow.OffsetTop = 4;
tagRow.OffsetRight = -5;
tagRow.OffsetBottom = 24;
tagRow.Alignment = BoxContainer.AlignmentMode.End;
tagRow.MouseFilter = MouseFilterEnum.Ignore;
AddChild(tagRow);
_tag = new PanelContainer();
_tag.AddThemeStyleboxOverride("panel", TagStyle);
_tag.MouseFilter = MouseFilterEnum.Ignore;
tagRow.AddChild(_tag);
_tagLabel = new Label();
_tagLabel.AddThemeFontSizeOverride("font_size", 9);
_tagLabel.AddThemeColorOverride("font_color", UiTokens.AccentBright);
_tagLabel.MouseFilter = MouseFilterEnum.Ignore;
_tag.AddChild(_tagLabel);
}
private void OnGuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mb && mb.Pressed && mb.ButtonIndex == MouseButton.Left)
{
Clicked?.Invoke(this);
AcceptEvent();
}
}
private void OnMouseEntered()
{
_hovered = true;
ApplyFrame();
}
private void OnMouseExited()
{
_hovered = false;
ApplyFrame();
}
private void ApplyFrame()
{
if (!_built || _frame == null)
return;
_frame.AddThemeStyleboxOverride("panel",
_selected ? SelectedFrame : (_hovered ? HoverFrame : NormalFrame));
}
private static StyleBoxFlat MakeFrame(Color border, Color background)
{
var style = new StyleBoxFlat();
style.BgColor = background;
style.BorderColor = border;
style.BorderWidthLeft = 1;
style.BorderWidthRight = 1;
style.BorderWidthTop = 1;
style.BorderWidthBottom = 1;
style.CornerRadiusTopLeft = 6;
style.CornerRadiusTopRight = 6;
style.CornerRadiusBottomLeft = 6;
style.CornerRadiusBottomRight = 6;
return style;
}
private static StyleBoxFlat MakeTagStyle()
{
var style = new StyleBoxFlat();
style.BgColor = UiTokens.SlotTagBg;
style.BorderColor = UiTokens.AccentLine;
style.BorderWidthLeft = 1;
style.BorderWidthRight = 1;
style.BorderWidthTop = 1;
style.BorderWidthBottom = 1;
style.CornerRadiusTopLeft = 3;
style.CornerRadiusTopRight = 3;
style.CornerRadiusBottomLeft = 3;
style.CornerRadiusBottomRight = 3;
style.ContentMarginLeft = 5;
style.ContentMarginRight = 5;
style.ContentMarginTop = 1;
style.ContentMarginBottom = 1;
return style;
}
}
}