using Sandbox.UI; using System; namespace Editor; public partial class ListView { /// /// Rebuild the scrollbars and layout for the visible items /// protected override void Rebuild() { if ( !IsValid ) return; LayoutScrollbar(); LayoutItems(); Update(); } /// /// Work out how big the scrollbars need to be and layout the current PVS /// protected virtual void LayoutScrollbar() { var rect = CanvasRect; itemsPerRow = 1; if ( ItemSize.x > 0 ) itemsPerRow = ((rect.Width + ItemSpacing.x) / (ItemSize.x + ItemSpacing.x)).FloorToInt(); itemsPerRow = Math.Max( 1, itemsPerRow ); rowHeight = (ItemSize.y + ItemSpacing.y).CeilToInt(); rowCount = ((float)_items.Count() / (float)itemsPerRow).CeilToInt(); var canvasHeight = rowCount * ItemSize.y; if ( rowCount > 0 ) canvasHeight += (rowCount - 1) * ItemSpacing.y; VerticalScrollbar.Minimum = 0; VerticalScrollbar.Maximum = Math.Max( VerticalScrollbar.Minimum, (canvasHeight - rect.Height).CeilToInt() ); VerticalScrollbar.SingleStep = Math.Max( rowHeight, rect.Height * 0.33f ).CeilToInt(); VerticalScrollbar.PageStep = rect.Height.FloorToInt(); } protected virtual void LayoutItems() { // // Plenty of optimization here. // Like on scroll we probably don't need to lay the items out unless the scroll differs by rowheight amount // and this dictionary call here is probably pain. // but ultimately the repaint is going to take as long as this, and it's still pretty fast, so fuck it for now. // var old = ItemLayouts.ToList(); ItemLayouts.Clear(); if ( _items.Count == 0 ) return; var rect = CanvasRect; float scrollOffset = VerticalScrollbar.Value - rect.Top; float visibleHeight = Height; float rowHeight = ItemSize.y + ItemSpacing.y; float offset = scrollOffset % rowHeight; int firstVisibleRow = (scrollOffset / rowHeight).FloorToInt(); if ( firstVisibleRow < 0 ) firstVisibleRow = 0; int firstVisibleIndex = firstVisibleRow * itemsPerRow; int visibleRows = ((visibleHeight + offset) / ItemSize.y).CeilToInt(); int visibleItems = visibleRows * itemsPerRow; var subset = _items.Skip( firstVisibleIndex ).Take( visibleItems ); var itemRect = new Rect( 0, ItemSize ); if ( ItemSize.x <= 0 ) itemRect.Width = rect.Width; var col = 0; var row = 0; float rowStart = 0; float rowSpacing = ItemSpacing.x; float spareSpace = (rect.Width + ItemSpacing.x) - (itemRect.Width + ItemSpacing.x) * itemsPerRow; spareSpace = Math.Max( 0, spareSpace ); switch ( ItemAlign ) { case Align.Stretch: { itemRect.Width += spareSpace / itemsPerRow; break; } case Align.Center: { rowStart = spareSpace * 0.5f; break; } case Align.SpaceAround: { rowSpacing = (spareSpace / (itemsPerRow)); rowStart = rowSpacing / 2.0f; rowSpacing += ItemSpacing.x; break; } case Align.SpaceBetween: { if ( itemsPerRow > 1 ) { var items = Math.Min( _items.Count, itemsPerRow ); rowStart = 0; rowSpacing = (rect.Width - items * ItemSize.x) / Math.Max( 1, (items - 1) ); } break; } default: break; } foreach ( var item in subset ) { itemRect.Position = new Vector2( rect.Left + rowStart + col * (itemRect.Width + rowSpacing), row * (ItemSize.y + ItemSpacing.y) - offset ); var lo = old.FirstOrDefault( x => x.Object.Equals( item ) ); if ( lo == null ) { lo = new VirtualWidget(); lo.Selected = SelectedItems.Contains( item ); ItemScrollEnter?.Invoke( item ); } else { old.Remove( lo ); } lo.Object = item; lo.Rect = itemRect; lo.Row = firstVisibleRow + row; lo.Column = col; ItemLayouts.Add( lo ); col++; if ( col >= itemsPerRow ) { row++; col = 0; } } if ( ItemScrollExit is not null ) { // culling callback foreach ( var item in old ) { ItemScrollExit.Invoke( item.Object ); } } } }