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, }; }