github.com/jmigpin/editor@v1.6.0/util/uiutil/widget/startpercentlayout.go (about)

     1  package widget
     2  
     3  import (
     4  	"image"
     5  )
     6  
     7  // Start percent sets the child left X bound to the percent of the size.
     8  // This ensures a change in the percentage of a middle child doesn't affect the bounds of the other childs (ex: like causing a small adjustment when resizing).
     9  type StartPercentLayout struct {
    10  	ENode
    11  	YAxis            bool
    12  	MinimumChildSize int
    13  
    14  	minp float64
    15  	spm  map[Node]float64 // start percent map: between 0 and 1
    16  }
    17  
    18  func NewStartPercentLayout() *StartPercentLayout {
    19  	spl := &StartPercentLayout{
    20  		spm: make(map[Node]float64),
    21  	}
    22  
    23  	// ensure append uses at least this insertbefore implementation.
    24  	spl.Wrapper = spl
    25  
    26  	return spl
    27  }
    28  
    29  //----------
    30  
    31  func (spl *StartPercentLayout) Measure(hint image.Point) image.Point {
    32  	return hint
    33  }
    34  
    35  func (spl *StartPercentLayout) Layout() {
    36  	// translate axis
    37  	xya := XYAxis{spl.YAxis}
    38  	abounds := xya.Rectangle(&spl.Bounds)
    39  
    40  	// update minimum percentage
    41  	spl.minp = float64(spl.MinimumChildSize) / float64(abounds.Size().X)
    42  
    43  	// set sizes
    44  	dxf := float64(abounds.Dx())
    45  
    46  	spl.IterateWrappers2(func(child Node) {
    47  		sp := spl.sp(child)
    48  		ep := spl.ep(child)
    49  
    50  		var r image.Rectangle
    51  		cxs := abounds.Min.X + int(sp*dxf)
    52  		cxe := abounds.Min.X + int(ep*dxf)
    53  
    54  		// set bounds
    55  		r.Min = image.Point{cxs, abounds.Min.Y}
    56  		r.Max = image.Point{cxe, abounds.Max.Y}
    57  
    58  		// fix last child for rounding errors
    59  		if child == spl.LastChildWrapper() {
    60  			r.Max.X = abounds.Max.X
    61  		}
    62  
    63  		// translate axis
    64  		r2 := xya.Rectangle(&r)
    65  
    66  		r3 := r2.Intersect(spl.Bounds)
    67  		child.Embed().Bounds = r3
    68  	})
    69  }
    70  
    71  //----------
    72  
    73  func (spl *StartPercentLayout) InsertBefore(n Node, mark *EmbedNode) {
    74  	if mark != nil && spl.sp(mark.Wrapper) == 0.0 {
    75  		// insert after mark
    76  		spl.ENode.InsertBefore(n, mark.NextSibling())
    77  	} else {
    78  		spl.ENode.InsertBefore(n, mark)
    79  	}
    80  
    81  	ne := n.Embed()
    82  	if ne.PrevSiblingWrapper() != nil {
    83  		// share with previous sibling
    84  		s, e := spl.sp(ne.PrevSiblingWrapper()), spl.ep(n)
    85  		spl.setsp(n, s+(e-s)/2)
    86  	} else {
    87  		// first child
    88  		spl.setsp(n, 0)
    89  	}
    90  }
    91  
    92  func (spl *StartPercentLayout) Remove(n Node) {
    93  	delete(spl.spm, n)
    94  	spl.ENode.Remove(n)
    95  }
    96  
    97  //----------
    98  
    99  // start percent
   100  func (spl *StartPercentLayout) sp(n Node) float64 {
   101  	return spl.spm[n]
   102  }
   103  
   104  // end percent
   105  func (spl *StartPercentLayout) ep(n Node) float64 {
   106  	ns := n.Embed().NextSiblingWrapper()
   107  	if ns != nil {
   108  		return spl.sp(ns)
   109  	}
   110  	return 1.0
   111  }
   112  
   113  // start percent of previous
   114  func (spl *StartPercentLayout) spPrev(n Node) float64 {
   115  	ps := n.Embed().PrevSiblingWrapper()
   116  	if ps != nil {
   117  		return spl.sp(ps)
   118  	}
   119  	return 0.0
   120  }
   121  
   122  // set start percent
   123  func (spl *StartPercentLayout) setsp(n Node, v float64) {
   124  	if v < 0 {
   125  		v = 0
   126  	} else if v > 1 {
   127  		v = 1
   128  	}
   129  	spl.spm[n] = v
   130  
   131  	spl.MarkNeedsLayout()
   132  }
   133  
   134  //----------
   135  
   136  func (spl *StartPercentLayout) size(n Node) float64 {
   137  	return spl.ep(n) - spl.sp(n)
   138  }
   139  func (spl *StartPercentLayout) prevSize(n Node) float64 {
   140  	return spl.sp(n) - spl.spPrev(n)
   141  }
   142  
   143  //----------
   144  
   145  func (spl *StartPercentLayout) Resize(node Node, percent float64) {
   146  	spl.resize(node, percent)
   147  }
   148  
   149  func (spl *StartPercentLayout) resize(node Node, percent float64) {
   150  	s, e := spl.spPrev(node), spl.ep(node)
   151  
   152  	// add margins
   153  	s2, e2 := s, e
   154  	if node.Embed() != spl.FirstChild() {
   155  		s2 += spl.minp
   156  	}
   157  	e2 -= spl.minp
   158  
   159  	// squash in the middle
   160  	if node.Embed() != spl.FirstChild() && s2 > e2 {
   161  		spl.setsp(node, s+(e-s)/2)
   162  		return
   163  	}
   164  
   165  	// minimum
   166  	if percent < s2 {
   167  		spl.setsp(node, s2)
   168  		return
   169  	}
   170  	// maximum
   171  	if percent > e2 {
   172  		spl.setsp(node, e2)
   173  		return
   174  	}
   175  
   176  	spl.setsp(node, percent)
   177  }
   178  
   179  //----------
   180  
   181  func (spl *StartPercentLayout) ResizeWithMove(node Node, percent float64) {
   182  	minDir := true
   183  	sp := spl.sp(node)
   184  	if percent >= sp {
   185  		minDir = false
   186  	}
   187  	_ = spl.resizeWithMove(node, percent, minDir, true)
   188  }
   189  
   190  func (spl *StartPercentLayout) resizeWithMove(node Node, percent float64, minDir bool, pusher bool) bool {
   191  	// preemptively move node
   192  	moved := false
   193  	if minDir {
   194  		for n := node.Embed().PrevSiblingWrapper(); n != nil; n = n.Embed().PrevSiblingWrapper() {
   195  			s := spl.spPrev(n)
   196  			e := spl.sp(n)
   197  			if s < percent && percent < e {
   198  				// directly use EmbedNode remove/insertbefore
   199  				spl.ENode.Remove(node)
   200  				spl.ENode.InsertBefore(node, n.Embed())
   201  				moved = true
   202  				break
   203  			}
   204  		}
   205  	} else {
   206  		for n := node.Embed().NextSiblingWrapper(); n != nil; n = n.Embed().NextSiblingWrapper() {
   207  			s := spl.sp(n)
   208  			e := spl.ep(n)
   209  			if s < percent && percent < e {
   210  				// directly use EmbedNode remove/insertbefore
   211  				spl.ENode.Remove(node)
   212  				spl.ENode.InsertBefore(node, n.Embed().NextSibling())
   213  				moved = true
   214  				break
   215  			}
   216  		}
   217  	}
   218  
   219  	spl.resize(node, percent)
   220  
   221  	return moved
   222  }
   223  
   224  //----------
   225  
   226  func (spl *StartPercentLayout) SetPercentWithPush(node Node, percentPos float64) {
   227  	sp := spl.sp(node)
   228  	if percentPos > sp {
   229  		percent := percentPos - sp
   230  		_ = spl.incStartBy(node, percent)
   231  	} else {
   232  		percent := sp - percentPos
   233  		_ = spl.decStartBy(node, percent)
   234  	}
   235  }
   236  
   237  //----------
   238  
   239  func (spl *StartPercentLayout) SetSizePercentWithPush(node Node, sizePercent float64) {
   240  	size := spl.size(node)
   241  	if size < sizePercent {
   242  		d := sizePercent - size
   243  		// push next siblings
   244  		d -= spl.incStartBy(node.Embed().NextSiblingWrapper(), d)
   245  		// push prev siblings
   246  		_ = spl.decStartBy(node, d)
   247  	}
   248  }
   249  
   250  //----------
   251  
   252  func (spl *StartPercentLayout) incStartBy(node Node, percent float64) float64 {
   253  	if percent < 0.00001 {
   254  		return 0.0
   255  	}
   256  	if node == nil {
   257  		return 0.0
   258  	}
   259  	size := spl.size(node)
   260  	if size-percent >= spl.minp {
   261  		spl.setsp(node, spl.sp(node)+percent)
   262  		return percent
   263  	}
   264  
   265  	// at this node
   266  	w := size - spl.minp
   267  	if w < 0 {
   268  		w = 0
   269  	}
   270  	spl.setsp(node, spl.sp(node)+w)
   271  
   272  	// at siblings
   273  	w2 := spl.incStartBy(node.Embed().NextSiblingWrapper(), percent-w)
   274  	spl.setsp(node, spl.sp(node)+w2)
   275  
   276  	return w + w2
   277  }
   278  
   279  func (spl *StartPercentLayout) decStartBy(node Node, percent float64) float64 {
   280  	if percent < 0.00001 {
   281  		return 0.0
   282  	}
   283  	if node == nil {
   284  		return 0.0
   285  	}
   286  
   287  	minp := spl.minp
   288  	if node.Embed() == spl.FirstChild() {
   289  		minp = 0.0
   290  	}
   291  
   292  	size := spl.prevSize(node)
   293  	if size-percent >= minp {
   294  		spl.setsp(node, spl.sp(node)-percent)
   295  		return percent
   296  	}
   297  
   298  	// at this node
   299  	w := size - minp
   300  	if w < 0 {
   301  		w = 0
   302  	}
   303  	spl.setsp(node, spl.sp(node)-w)
   304  
   305  	// at siblings
   306  	w2 := spl.decStartBy(node.Embed().PrevSiblingWrapper(), percent-w)
   307  	spl.setsp(node, spl.sp(node)-w2)
   308  
   309  	return w + w2
   310  }
   311  
   312  //----------
   313  
   314  func (spl *StartPercentLayout) MaximizeNode(node Node) {
   315  	n := spl.ChildsLen()
   316  	sp := 0.0
   317  	i := 0
   318  	spl.IterateWrappers2(func(c Node) {
   319  		spl.setsp(c, sp)
   320  		if c == node {
   321  			sp = 1.0 - spl.minp*float64(n-(i+1))
   322  		} else {
   323  			sp += spl.minp
   324  		}
   325  		i++
   326  	})
   327  }
   328  
   329  //----------
   330  
   331  // Used for encoding/decoding only. (Ex: sessions)
   332  func (spl *StartPercentLayout) SetRawStartPercent(child Node, v float64) {
   333  	// detect oddly placed rows (two rows with same value due to manual edit of the sessions file)
   334  	for n, k := range spl.spm {
   335  		if n == child {
   336  			continue
   337  		}
   338  		if k == v {
   339  			// return and don't set the value, will keep the automatically assigned percent value uppon child creation. Fixes allowing rows to be hidden with the same percent value on restoring a session.
   340  			return
   341  		}
   342  	}
   343  
   344  	spl.setsp(child, v)
   345  }
   346  
   347  // Used for encoding/decoding only. (Ex: sessions)
   348  func (spl *StartPercentLayout) RawStartPercent(child Node) float64 {
   349  	return spl.sp(child)
   350  }