using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; namespace ClassicAssist.UI.Controls { /* * https://stackoverflow.com/questions/8004981/how-to-make-wpf-wrappanel-child-items-to-stretch */ public class StretchyWrapPanel : Panel { public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register( nameof( ItemWidth ), typeof( double ), typeof( StretchyWrapPanel ), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure ) ); public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register( nameof( ItemHeight ), typeof( double ), typeof( StretchyWrapPanel ), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure ) ); public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner( typeof( StretchyWrapPanel ), new FrameworkPropertyMetadata( Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure, OnOrientationChanged ) ); public static readonly DependencyProperty StretchProportionallyProperty = DependencyProperty.Register( nameof( StretchProportionally ), typeof( bool ), typeof( StretchyWrapPanel ), new PropertyMetadata( true, OnStretchProportionallyChanged ) ); private Orientation _orientation = Orientation.Horizontal; private bool _stretchProportionally = true; [TypeConverter( typeof( LengthConverter ) )] public double ItemHeight { get => (double) GetValue( ItemHeightProperty ); set => SetValue( ItemHeightProperty, value ); } [TypeConverter( typeof( LengthConverter ) )] public double ItemWidth { get => (double) GetValue( ItemWidthProperty ); set => SetValue( ItemWidthProperty, value ); } public Orientation Orientation { get => _orientation; set => SetValue( OrientationProperty, value ); } public bool StretchProportionally { get => _stretchProportionally; set => SetValue( StretchProportionallyProperty, value ); } private static void OnOrientationChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) { ( (StretchyWrapPanel) d )._orientation = (Orientation) e.NewValue; } private static void OnStretchProportionallyChanged( DependencyObject o, DependencyPropertyChangedEventArgs e ) { ( (StretchyWrapPanel) o )._stretchProportionally = (bool) e.NewValue; } protected override Size MeasureOverride( Size constraint ) { UVSize curLineSize = new UVSize( Orientation ); UVSize panelSize = new UVSize( Orientation ); UVSize uvConstraint = new UVSize( Orientation, constraint.Width, constraint.Height ); double itemWidth = ItemWidth; double itemHeight = ItemHeight; bool itemWidthSet = !double.IsNaN( itemWidth ); bool itemHeightSet = !double.IsNaN( itemHeight ); Size childConstraint = new Size( itemWidthSet ? itemWidth : constraint.Width, itemHeightSet ? itemHeight : constraint.Height ); UIElementCollection children = InternalChildren; for ( int i = 0, count = children.Count; i < count; i++ ) { UIElement child = children[i]; if ( child == null ) { continue; } // Flow passes its own constrint to children child.Measure( childConstraint ); // This is the size of the child in UV space UVSize sz = new UVSize( Orientation, itemWidthSet ? itemWidth : child.DesiredSize.Width, itemHeightSet ? itemHeight : child.DesiredSize.Height ); if ( curLineSize.U + sz.U > uvConstraint.U ) { // Need to switch to another line panelSize.U = Math.Max( curLineSize.U, panelSize.U ); panelSize.V += curLineSize.V; curLineSize = sz; if ( sz.U > uvConstraint.U ) { // The element is wider then the constrint - give it a separate line panelSize.U = Math.Max( sz.U, panelSize.U ); panelSize.V += sz.V; curLineSize = new UVSize( Orientation ); } } else { // Continue to accumulate a line curLineSize.U += sz.U; curLineSize.V = Math.Max( sz.V, curLineSize.V ); } } // The last line size, if any should be added panelSize.U = Math.Max( curLineSize.U, panelSize.U ); panelSize.V += curLineSize.V; // Go from UV space to W/H space return new Size( panelSize.Width, panelSize.Height ); } protected override Size ArrangeOverride( Size finalSize ) { int firstInLine = 0; double itemWidth = ItemWidth; double itemHeight = ItemHeight; double accumulatedV = 0; double itemU = Orientation == Orientation.Horizontal ? itemWidth : itemHeight; UVSize curLineSize = new UVSize( Orientation ); UVSize uvFinalSize = new UVSize( Orientation, finalSize.Width, finalSize.Height ); bool itemWidthSet = !double.IsNaN( itemWidth ); bool itemHeightSet = !double.IsNaN( itemHeight ); bool useItemU = Orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet; UIElementCollection children = InternalChildren; for ( int i = 0, count = children.Count; i < count; i++ ) { UIElement child = children[i]; if ( child == null ) { continue; } UVSize sz = new UVSize( Orientation, itemWidthSet ? itemWidth : child.DesiredSize.Width, itemHeightSet ? itemHeight : child.DesiredSize.Height ); if ( curLineSize.U + sz.U > uvFinalSize.U ) { // Need to switch to another line if ( !useItemU && StretchProportionally ) { ArrangeLineProportionally( accumulatedV, curLineSize.V, firstInLine, i, uvFinalSize.Width ); } else { ArrangeLine( accumulatedV, curLineSize.V, firstInLine, i, true, useItemU ? itemU : uvFinalSize.Width / Math.Max( 1, i - firstInLine - 1 ) ); } accumulatedV += curLineSize.V; curLineSize = sz; if ( sz.U > uvFinalSize.U ) { // The element is wider then the constraint - give it a separate line // Switch to next line which only contain one element if ( !useItemU && StretchProportionally ) { ArrangeLineProportionally( accumulatedV, sz.V, i, ++i, uvFinalSize.Width ); } else { ArrangeLine( accumulatedV, sz.V, i, ++i, true, useItemU ? itemU : uvFinalSize.Width ); } accumulatedV += sz.V; curLineSize = new UVSize( Orientation ); } firstInLine = i; } else { // Continue to accumulate a line curLineSize.U += sz.U; curLineSize.V = Math.Max( sz.V, curLineSize.V ); } } // Arrange the last line, if any if ( firstInLine < children.Count ) { if ( !useItemU && StretchProportionally ) { ArrangeLineProportionally( accumulatedV, curLineSize.V, firstInLine, children.Count, uvFinalSize.Width ); } else { ArrangeLine( accumulatedV, curLineSize.V, firstInLine, children.Count, true, useItemU ? itemU : uvFinalSize.Width / Math.Max( 1, children.Count - firstInLine - 1 ) ); } } return finalSize; } private void ArrangeLineProportionally( double v, double lineV, int start, int end, double limitU ) { double u = 0d; bool horizontal = Orientation == Orientation.Horizontal; UIElementCollection children = InternalChildren; double total = 0d; for ( int i = start; i < end; i++ ) { total += horizontal ? children[i].DesiredSize.Width : children[i].DesiredSize.Height; } double uMultipler = limitU / total; for ( int i = start; i < end; i++ ) { UIElement child = children[i]; if ( child != null ) { UVSize childSize = new UVSize( Orientation, child.DesiredSize.Width, child.DesiredSize.Height ); double layoutSlotU = childSize.U * uMultipler; child.Arrange( new Rect( horizontal ? u : v, horizontal ? v : u, horizontal ? layoutSlotU : lineV, horizontal ? lineV : layoutSlotU ) ); u += layoutSlotU; } } } private void ArrangeLine( double v, double lineV, int start, int end, bool useItemU, double itemU ) { double u = 0d; bool horizontal = Orientation == Orientation.Horizontal; UIElementCollection children = InternalChildren; for ( int i = start; i < end; i++ ) { UIElement child = children[i]; if ( child != null ) { UVSize childSize = new UVSize( Orientation, child.DesiredSize.Width, child.DesiredSize.Height ); double layoutSlotU = useItemU ? itemU : childSize.U; child.Arrange( new Rect( horizontal ? u : v, horizontal ? v : u, horizontal ? layoutSlotU : lineV, horizontal ? lineV : layoutSlotU ) ); u += layoutSlotU; } } } private struct UVSize { internal UVSize( Orientation orientation, double width, double height ) { U = V = 0d; _orientation = orientation; Width = width; Height = height; } internal UVSize( Orientation orientation ) { U = V = 0d; _orientation = orientation; } internal double U; internal double V; private readonly Orientation _orientation; internal double Width { get => _orientation == Orientation.Horizontal ? U : V; private set { if ( _orientation == Orientation.Horizontal ) { U = value; } else { V = value; } } } internal double Height { get => _orientation == Orientation.Horizontal ? V : U; private set { if ( _orientation == Orientation.Horizontal ) { V = value; } else { U = value; } } } } } }