using System;
using System.Numerics;
using AcDream.App.UI.Layout;
namespace AcDream.App.UI;
///
/// Retail UIElement_Resizebar (element class 9,
/// UIElement_Resizebar::Register @0x0046B920 registers class 9 with the
/// LayoutDesc element factory). A leaf grip authored at exactly one edge or
/// corner of a window; retail authors 4 edge grips + 4 corner grips per
/// resizable window (main chat: 0x1000069B-0x100006A2, minus the
/// straight top strip which is a retail UIElement_Dragbar move handle
/// instead — see the class doc on
/// and docs/research/2026-08-09-chat-retail-window-shell.md §2.1/§2.3).
///
///
/// UIElement_Resizebar::StartMouseResizing @0x0046B7E0 reads four BOOL
/// attributes off the element — 0x2A=bottom, 0x2B=left,
/// 0x2C=right, 0x2D=top — and combines them into a
/// BorderLocation (acclient.h:4327).
/// is a verbatim port of that branch order. acdream's engine expresses resize
/// direction as an edge-flag bitmask () rather than
/// retail's nine-case enum; is the lossless projection —
/// every value maps to exactly one
/// combination and back.
///
///
///
/// A grip is a small (5px) leaf with no children; it exists primarily as a
/// precise hit region, but it IS authored with real border/corner media (the
/// 0x06006129-family sprites — CH6a/b REJECT-review BLOCKER 1,
/// docs/research/2026-08-10-ch6ab-review-findings.md: every one of the
/// seven live grips on the main chat window carries a non-zero DirectState
/// sprite, and drawing nothing left the window with only its 400×5 top strip
/// visible). A factory-built grip (,
/// Type 9) therefore draws its own DirectState media exactly like
/// would; a synthetic grip
/// built with the parameterless constructor (unit tests exercising only the
/// resize-drag behavior) carries no media and draws nothing, matching its
/// prior behavior. gives a directly-hit
/// grip's own priority over the generic proximity-based
/// heuristic, so e.g. the chat
/// window's top-left/top-right CORNER grips resize (including the Y/top axis)
/// while the plain top EDGE strip in between remains a pure move handle.
///
///
public sealed class UiResizeGrip : UiElement
{
private readonly ElementInfo? _info;
private readonly Func? _resolve;
/// Synthetic grip with no authored media — used by unit tests that
/// exercise only the resize-drag hit-testing/edge behavior.
public UiResizeGrip()
{
}
///
/// Factory-built grip (CH6a/b REJECT-review BLOCKER 1): carries its resolved
/// and sprite resolver so it draws its own
/// authored DirectState media, the same way every other dat-imported leaf
/// does. is left at the base
/// default — unlike 's
/// decoration default of — because a grip must stay
/// hit-testable to capture the resize drag.
///
public UiResizeGrip(ElementInfo info, Func resolve)
{
_info = info;
_resolve = resolve;
}
///
/// This grip's authored DirectState sprite file id (0 for a synthetic grip,
/// or an authored grip with no media). Exposed for conformance tests —
/// BLOCKER 1's regression guard is that every one of the seven live Type-9
/// grips resolves a non-zero value here.
///
public uint SpriteFile => _info is not null && _info.StateMedia.TryGetValue("", out var media)
? media.File
: 0u;
///
/// Draws this grip's own DirectState media exactly like
/// — tiled at native size (Normal), same
/// blend for Overlay/Alphablend since the sprite shader already
/// alpha-blends. A synthetic (parameterless-constructed) grip has no
/// resolver and draws nothing, matching its pre-BLOCKER-1 behavior.
///
protected override void OnDraw(UiRenderContext ctx)
{
if (_resolve is null) return;
uint file = SpriteFile;
if (file == 0u) return;
var (tex, tw, th) = _resolve(file);
if (tex == 0 || tw == 0 || th == 0) return;
ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One);
}
/// Retail BorderLocation (acclient.h:4327):
/// BORDER_NONE=0, BORDER_UL=1, BORDER_TOP=2, BORDER_UR=3, BORDER_RIGHT=4,
/// BORDER_LR=5, BORDER_BOTTOM=6, BORDER_LL=7, BORDER_LEFT=8.
public enum Border
{
None = 0,
UpperLeft = 1,
Top = 2,
UpperRight = 3,
Right = 4,
LowerRight = 5,
Bottom = 6,
LowerLeft = 7,
Left = 8,
}
/// Decoded retail BorderLocation for this grip.
public Border BorderLocation { get; set; }
///
/// Exact port of UIElement_Resizebar::StartMouseResizing @0x0046B7E0's
/// if/else-if chain (right beats left beats top beats bottom when more than
/// two bools are set, which retail's authored data never does — corners set
/// exactly two adjacent bools, edges set exactly one).
///
public static Border DecodeBorderLocation(bool bottom, bool left, bool right, bool top)
{
if (right) return top ? Border.UpperRight : bottom ? Border.LowerRight : Border.Right;
if (left) return top ? Border.UpperLeft : bottom ? Border.LowerLeft : Border.Left;
if (top) return Border.Top;
if (bottom) return Border.Bottom;
return Border.None;
}
/// The projection 's resize
/// drag state machine consumes — a lossless re-encoding of .
public ResizeEdges Edges => BorderLocation switch
{
Border.UpperLeft => ResizeEdges.Left | ResizeEdges.Top,
Border.Top => ResizeEdges.Top,
Border.UpperRight => ResizeEdges.Right | ResizeEdges.Top,
Border.Right => ResizeEdges.Right,
Border.LowerRight => ResizeEdges.Right | ResizeEdges.Bottom,
Border.Bottom => ResizeEdges.Bottom,
Border.LowerLeft => ResizeEdges.Left | ResizeEdges.Bottom,
Border.Left => ResizeEdges.Left,
_ => ResizeEdges.None,
};
}