using System.Numerics; using AcDream.App.Rendering; namespace AcDream.App.UI.Layout; /// /// Retains the shaped line projection of one value. /// Controllers publish semantic content; the cache reshapes only when that /// content or the text widget's layout/font inputs change. /// internal sealed class UiTextLayoutCache { private readonly UiText _target; private readonly Func> _shape; private readonly IEqualityComparer _comparer; private readonly Func? _source; private T _value = default!; private bool _hasValue; private bool _shaped; private IReadOnlyList _lines = Array.Empty(); private float _width; private float _padding; private Vector4 _defaultColor; private UiDatFont? _datFont; private BitmapFont? _bitmapFont; private IReadOnlyList? _fontColorPalette; public UiTextLayoutCache( UiText target, Func> shape, T initialValue, IEqualityComparer? comparer = null) : this(target, shape, source: null, comparer, initialize: true) { SetValue(initialValue); } public UiTextLayoutCache( UiText target, Func> shape, Func source, IEqualityComparer? comparer = null) : this( target, shape, source ?? throw new ArgumentNullException(nameof(source)), comparer, initialize: true) { } private UiTextLayoutCache( UiText target, Func> shape, Func? source, IEqualityComparer? comparer, bool initialize) { _ = initialize; _target = target ?? throw new ArgumentNullException(nameof(target)); _shape = shape ?? throw new ArgumentNullException(nameof(shape)); _source = source; _comparer = comparer ?? EqualityComparer.Default; } public Func> Provider => GetLines; public void SetValue(T value) { if (_hasValue && _comparer.Equals(_value, value)) return; _value = value; _hasValue = true; _shaped = false; } public void Invalidate() => _shaped = false; public IReadOnlyList GetLines() { if (_source is not null) SetValue(_source()); if (!_hasValue) return Array.Empty(); if (!_shaped || LayoutChanged()) { CaptureLayout(); _lines = _shape(_target, _value); _shaped = true; } return _lines; } private bool LayoutChanged() => _width != _target.Width || _padding != _target.Padding || _defaultColor != _target.DefaultColor || !ReferenceEquals(_datFont, _target.DatFont) || !ReferenceEquals(_bitmapFont, _target.Font) || !ReferenceEquals(_fontColorPalette, _target.FontColorPalette); private void CaptureLayout() { _width = _target.Width; _padding = _target.Padding; _defaultColor = _target.DefaultColor; _datFont = _target.DatFont; _bitmapFont = _target.Font; _fontColorPalette = _target.FontColorPalette; } }