github.com/utopiagio/gio@v0.0.8/layout/flex.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package layout
     4  
     5  import (
     6  	"image"
     7  
     8  	"github.com/utopiagio/gio/op"
     9  )
    10  
    11  // Flex lays out child elements along an axis,
    12  // according to alignment and weights.
    13  type Flex struct {
    14  	// Axis is the main axis, either Horizontal or Vertical.
    15  	Axis Axis
    16  	// Spacing controls the distribution of space left after
    17  	// layout.
    18  	Spacing Spacing
    19  	// Alignment is the alignment in the cross axis.
    20  	Alignment Alignment
    21  	// WeightSum is the sum of weights used for the weighted
    22  	// size of Flexed children. If WeightSum is zero, the sum
    23  	// of all Flexed weights is used.
    24  	WeightSum float32
    25  }
    26  
    27  // RNW Modified
    28  // hflex horizontal flex style. hflex=true->Flexed(hflex child) : hflex=false->Rigid(hflex child)
    29  // vflex vertical flex style. vflex=true->Flexed(vflex child) : vflex=false->Rigid(vflex child)
    30  // ********************************************
    31  // FlexChild is the descriptor for a Flex child.
    32  type FlexChild struct {
    33  	//flex 	bool
    34  	hflex   bool
    35  	vflex	bool
    36  	weight 	float32
    37  
    38  	widget Widget
    39  
    40  	// Scratch space.
    41  	call op.CallOp
    42  	dims Dimensions
    43  }
    44  
    45  // Spacing determine the spacing mode for a Flex.
    46  type Spacing uint8
    47  
    48  const (
    49  	// SpaceEnd leaves space at the end.
    50  	SpaceEnd Spacing = iota
    51  	// SpaceStart leaves space at the start.
    52  	SpaceStart
    53  	// SpaceSides shares space between the start and end.
    54  	SpaceSides
    55  	// SpaceAround distributes space evenly between children,
    56  	// with half as much space at the start and end.
    57  	SpaceAround
    58  	// SpaceBetween distributes space evenly between children,
    59  	// leaving no space at the start and end.
    60  	SpaceBetween
    61  	// SpaceEvenly distributes space evenly between children and
    62  	// at the start and end.
    63  	SpaceEvenly
    64  )
    65  
    66  // RNW Modified
    67  // hflex horizontal flex style
    68  // vflex vertical flex style
    69  // *********************************************
    70  // FlexControl returns a Flex child with a combination of flex or rigid axis
    71  func FlexControl(hflex bool, vflex bool, weight float32, widget Widget) FlexChild {
    72  	return FlexChild{
    73  		hflex:  hflex,
    74  		vflex: 	vflex,
    75  		weight: weight,
    76  		widget: widget,
    77  	}
    78  }
    79  
    80  // RNW Modified
    81  // hflex horizontal flex style defaults to false
    82  // vflex vertical flex style defaults to false
    83  // *********************************************
    84  // Rigid returns a Flex child and a maximal constraint of the
    85  // remaining space.
    86  func Rigid(widget Widget) FlexChild {
    87  	return FlexChild{
    88  		hflex:  false,
    89  		vflex: 	false,
    90  		weight: 0,
    91  		widget: widget,
    92  	}
    93  }
    94  
    95  // RNW Modified
    96  // hflex horizontal flex style defaults to true
    97  // vflex vertical flex style defaults to true
    98  // ********************************************
    99  // Flexed returns a Flex child forced to take up weight fraction of the
   100  // space left over from Rigid children. The fraction is weight
   101  // divided by either the weight sum of all Flexed children or the Flex
   102  // WeightSum if non zero.
   103  func Flexed(weight float32, widget Widget) FlexChild {
   104  	return FlexChild{
   105  		//flex: 	true,
   106  		hflex:  true,
   107  		vflex:	true,
   108  		weight: weight,
   109  		widget: widget,
   110  	}
   111  }
   112  
   113  // RNW Modified to enable fixed or expanded contexts in both axis
   114  // hflex horizontal flex style
   115  // vflex vertical flex style
   116  // if vflex == false {crossMin = 0}
   117  // **********************************************
   118  // Layout a list of children. The position of the children are
   119  // determined by the specified order, but Rigid children are laid out
   120  // before Flexed children.
   121  func (f Flex) Layout(gtx Context, children ...FlexChild) Dimensions {
   122  	var crossMinRigid int
   123  	size := 0
   124  	cs := gtx.Constraints
   125  	mainMin, mainMax := f.Axis.mainConstraint(cs)
   126  	crossMin, crossMax := f.Axis.crossConstraint(cs)
   127  
   128  	remaining := mainMax
   129  	var totalWeight float32
   130  	cgtx := gtx
   131  	// Lay out Rigid children.
   132  	for i, child := range children {
   133  		if child.hflex {
   134  			totalWeight += child.weight
   135  			continue
   136  		}
   137  		//log.Println("rigid child.idx =", i)
   138  		//log.Println("rigid child.hflex =", child.hflex)
   139  		macro := op.Record(gtx.Ops)
   140  		if child.vflex == false {
   141  			crossMinRigid = 0
   142  		} else {
   143  			crossMinRigid = crossMin
   144  		}
   145  		cgtx.Constraints = f.Axis.constraints(0, remaining, crossMinRigid, crossMax)
   146  		dims := child.widget(cgtx)
   147  		c := macro.Stop()
   148  		sz := f.Axis.Convert(dims.Size).X
   149  		size += sz
   150  		remaining -= sz
   151  		if remaining < 0 {
   152  			remaining = 0
   153  		}
   154  		children[i].call = c
   155  		children[i].dims = dims
   156  	}
   157  	if w := f.WeightSum; w != 0 {
   158  		totalWeight = w
   159  	}
   160  	// fraction is the rounding error from a Flex weighting.
   161  	var fraction float32
   162  	flexTotal := remaining
   163  	//log.Println("layout Flex......")
   164  	// Lay out Flexed children.
   165  	for i, child := range children {
   166  		if !child.hflex {
   167  			continue
   168  		}
   169  		var flexSize int
   170  		if remaining > 0 && totalWeight > 0 {
   171  			// Apply weight and add any leftover fraction from a
   172  			// previous Flexed.
   173  			childSize := float32(flexTotal) * child.weight / totalWeight
   174  			flexSize = int(childSize + fraction + .5)
   175  			fraction = childSize - float32(flexSize)
   176  			if flexSize > remaining {
   177  				flexSize = remaining
   178  			}
   179  		}
   180  		macro := op.Record(gtx.Ops)
   181  		if child.vflex == false {
   182  			crossMin = 0
   183  		}
   184  		cgtx.Constraints = f.Axis.constraints(flexSize, flexSize, crossMin, crossMax)
   185  		dims := child.widget(cgtx)
   186  		c := macro.Stop()
   187  		sz := f.Axis.Convert(dims.Size).X
   188  		size += sz
   189  		remaining -= sz
   190  		if remaining < 0 {
   191  			remaining = 0
   192  		}
   193  		children[i].call = c
   194  		children[i].dims = dims
   195  	}
   196  	maxCross := crossMin
   197  	var maxBaseline int
   198  	for _, child := range children {
   199  		if c := f.Axis.Convert(child.dims.Size).Y; c > maxCross {
   200  			maxCross = c
   201  		}
   202  		if b := child.dims.Size.Y - child.dims.Baseline; b > maxBaseline {
   203  			maxBaseline = b
   204  		}
   205  	}
   206  	var space int
   207  	if mainMin > size {
   208  		space = mainMin - size
   209  	}
   210  	var mainSize int
   211  	switch f.Spacing {
   212  	case SpaceSides:
   213  		mainSize += space / 2
   214  	case SpaceStart:
   215  		mainSize += space
   216  	case SpaceEvenly:
   217  		mainSize += space / (1 + len(children))
   218  	case SpaceAround:
   219  		if len(children) > 0 {
   220  			mainSize += space / (len(children) * 2)
   221  		}
   222  	}
   223  	for i, child := range children {
   224  		dims := child.dims
   225  		b := dims.Size.Y - dims.Baseline
   226  		var cross int
   227  		switch f.Alignment {
   228  		case End:
   229  			cross = maxCross - f.Axis.Convert(dims.Size).Y
   230  		case Middle:
   231  			cross = (maxCross - f.Axis.Convert(dims.Size).Y) / 2
   232  		case Baseline:
   233  			if f.Axis == Horizontal {
   234  				cross = maxBaseline - b
   235  			}
   236  		}
   237  		pt := f.Axis.Convert(image.Pt(mainSize, cross))
   238  		trans := op.Offset(pt).Push(gtx.Ops)
   239  		child.call.Add(gtx.Ops)
   240  		trans.Pop()
   241  		mainSize += f.Axis.Convert(dims.Size).X
   242  		if i < len(children)-1 {
   243  			switch f.Spacing {
   244  			case SpaceEvenly:
   245  				mainSize += space / (1 + len(children))
   246  			case SpaceAround:
   247  				if len(children) > 0 {
   248  					mainSize += space / len(children)
   249  				}
   250  			case SpaceBetween:
   251  				if len(children) > 1 {
   252  					mainSize += space / (len(children) - 1)
   253  				}
   254  			}
   255  		}
   256  	}
   257  	switch f.Spacing {
   258  	case SpaceSides:
   259  		mainSize += space / 2
   260  	case SpaceEnd:
   261  		mainSize += space
   262  	case SpaceEvenly:
   263  		mainSize += space / (1 + len(children))
   264  	case SpaceAround:
   265  		if len(children) > 0 {
   266  			mainSize += space / (len(children) * 2)
   267  		}
   268  	}
   269  	sz := f.Axis.Convert(image.Pt(mainSize, maxCross))
   270  	sz = cs.Constrain(sz)
   271  	return Dimensions{Size: sz, Baseline: sz.Y - maxBaseline}
   272  }
   273  
   274  func (s Spacing) String() string {
   275  	switch s {
   276  	case SpaceEnd:
   277  		return "SpaceEnd"
   278  	case SpaceStart:
   279  		return "SpaceStart"
   280  	case SpaceSides:
   281  		return "SpaceSides"
   282  	case SpaceAround:
   283  		return "SpaceAround"
   284  	case SpaceBetween:
   285  		return "SpaceAround"
   286  	case SpaceEvenly:
   287  		return "SpaceEvenly"
   288  	default:
   289  		panic("unreachable")
   290  	}
   291  }