github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/framework/framework.go (about)

     1  package framework
     2  
     3  import (
     4  	"errors"
     5  	"image"
     6  	"image/color"
     7  	"math"
     8  	"strconv"
     9  	"sync/atomic"
    10  
    11  	"github.com/gop9/olt/framework/command"
    12  	"github.com/gop9/olt/framework/font"
    13  	"github.com/gop9/olt/framework/label"
    14  	"github.com/gop9/olt/framework/rect"
    15  	nstyle "github.com/gop9/olt/framework/style"
    16  	gkey "github.com/gop9/olt/gio/io/key"
    17  	"github.com/gop9/olt/gio/io/pointer"
    18  )
    19  
    20  ///////////////////////////////////////////////////////////////////////////////////
    21  // CONTEXT & PANELS
    22  ///////////////////////////////////////////////////////////////////////////////////
    23  
    24  type UpdateFn func(*Window)
    25  
    26  type Window struct {
    27  	LastWidgetBounds rect.Rect
    28  	Data             interface{}
    29  	title            string
    30  	ctx              *context
    31  	idx              int
    32  	flags            WindowFlags
    33  	Bounds           rect.Rect
    34  	Scrollbar        image.Point
    35  	cmds             command.Buffer
    36  	widgets          widgetBuffer
    37  	layout           *panel
    38  	close, first     bool
    39  	moving           bool
    40  	scaling          bool
    41  	// trigger rectangle of nonblocking windows
    42  	header rect.Rect
    43  	// root of the node tree
    44  	rootNode *treeNode
    45  	// current tree node see TreePush/TreePop
    46  	curNode *treeNode
    47  	// parent window of a popup
    48  	parent *Window
    49  	// helper windows to implement groups
    50  	groupWnd map[string]*Window
    51  	// editor of the active property widget (see PropertyInt, PropertyFloat)
    52  	editor *TextEditor
    53  	// update function
    54  	updateFn      UpdateFn
    55  	usingSub      bool
    56  	began         bool
    57  	rowCtor       rowConstructor
    58  	menuItemWidth int
    59  	lastLayoutCnt int
    60  	adjust        map[int]map[int]*adjustCol
    61  	undockedSz    image.Point
    62  }
    63  
    64  type FittingWidthFn func(width int)
    65  
    66  type adjustCol struct {
    67  	id    int
    68  	font  font.Face
    69  	width int
    70  	first bool
    71  }
    72  
    73  type treeNode struct {
    74  	Open     bool
    75  	Children map[string]*treeNode
    76  	Parent   *treeNode
    77  }
    78  
    79  type panel struct {
    80  	Cnt            int
    81  	Flags          WindowFlags
    82  	Bounds         rect.Rect
    83  	Offset         *image.Point
    84  	AtX            int
    85  	AtY            int
    86  	MaxX           int
    87  	Width          int
    88  	Height         int
    89  	FooterH        int
    90  	HeaderH        int
    91  	Border         int
    92  	Clip           rect.Rect
    93  	Menu           menuState
    94  	Row            rowLayout
    95  	ReservedHeight int
    96  }
    97  
    98  type menuState struct {
    99  	X      int
   100  	Y      int
   101  	W      int
   102  	H      int
   103  	Offset image.Point
   104  }
   105  
   106  type rowLayout struct {
   107  	Type         int
   108  	Index        int
   109  	Index2       int
   110  	CalcMaxWidth bool
   111  	Height       int
   112  	Columns      int
   113  	Ratio        []float64
   114  	WidthArr     []int
   115  	ItemWidth    int
   116  	ItemRatio    float64
   117  	ItemHeight   int
   118  	ItemOffset   int
   119  	Filled       float64
   120  	Item         rect.Rect
   121  	TreeDepth    int
   122  
   123  	DynamicFreeX, DynamicFreeY, DynamicFreeW, DynamicFreeH float64
   124  }
   125  
   126  type WindowFlags int
   127  
   128  const (
   129  	WindowBorder WindowFlags = 1 << iota
   130  	WindowBorderHeader
   131  	WindowMovable
   132  	WindowScalable
   133  	WindowClosable
   134  	WindowDynamic
   135  	WindowNoScrollbar
   136  	WindowNoHScrollbar
   137  	WindowTitle
   138  	WindowContextualReplace
   139  	WindowNonmodal
   140  
   141  	windowSub
   142  	windowGroup
   143  	windowPopup
   144  	windowNonblock
   145  	windowContextual
   146  	windowCombo
   147  	windowMenu
   148  	windowTooltip
   149  	windowEnabled
   150  	windowHDynamic
   151  	windowDocked
   152  
   153  	WindowDefaultFlags = WindowBorder | WindowMovable | WindowScalable | WindowClosable | WindowTitle
   154  )
   155  
   156  func createTreeNode(initialState bool, parent *treeNode) *treeNode {
   157  	return &treeNode{initialState, map[string]*treeNode{}, parent}
   158  }
   159  
   160  func createWindow(ctx *context, title string) *Window {
   161  	rootNode := createTreeNode(false, nil)
   162  	r := &Window{ctx: ctx, title: title, rootNode: rootNode, curNode: rootNode, groupWnd: map[string]*Window{}, first: true}
   163  	r.rowCtor.win = r
   164  	r.widgets.cur = make(map[rect.Rect]frozenWidget)
   165  	return r
   166  }
   167  
   168  type frozenWidget struct {
   169  	ws         nstyle.WidgetStates
   170  	frameCount int
   171  }
   172  
   173  type widgetBuffer struct {
   174  	cur        map[rect.Rect]frozenWidget
   175  	frameCount int
   176  }
   177  
   178  func (wbuf *widgetBuffer) PrevState(bounds rect.Rect) nstyle.WidgetStates {
   179  	return wbuf.cur[bounds].ws
   180  }
   181  
   182  func (wbuf *widgetBuffer) Add(ws nstyle.WidgetStates, bounds rect.Rect) {
   183  	wbuf.cur[bounds] = frozenWidget{ws, wbuf.frameCount}
   184  }
   185  
   186  func (wbuf *widgetBuffer) reset() {
   187  	for k, v := range wbuf.cur {
   188  		if v.frameCount != wbuf.frameCount {
   189  			delete(wbuf.cur, k)
   190  		}
   191  	}
   192  	wbuf.frameCount++
   193  }
   194  
   195  func (w *Window) Master() MasterWindow {
   196  	return w.ctx.mw
   197  }
   198  
   199  func (win *Window) style() *nstyle.Window {
   200  	switch {
   201  	case win.flags&windowCombo != 0:
   202  		return &win.ctx.Style.ComboWindow
   203  	case win.flags&windowContextual != 0:
   204  		return &win.ctx.Style.ContextualWindow
   205  	case win.flags&windowMenu != 0:
   206  		return &win.ctx.Style.MenuWindow
   207  	case win.flags&windowGroup != 0:
   208  		return &win.ctx.Style.GroupWindow
   209  	case win.flags&windowTooltip != 0:
   210  		return &win.ctx.Style.TooltipWindow
   211  	default:
   212  		return &win.ctx.Style.NormalWindow
   213  	}
   214  }
   215  
   216  func (win *Window) WindowStyle() *nstyle.Window {
   217  	return win.style()
   218  }
   219  
   220  func panelBegin(ctx *context, win *Window, title string) {
   221  	in := &ctx.Input
   222  	style := &ctx.Style
   223  	font := style.Font
   224  	wstyle := win.style()
   225  
   226  	// window dragging
   227  	if win.moving {
   228  		if in == nil || !in.Mouse.Down(pointer.ButtonLeft) {
   229  			if win.flags&windowDocked == 0 && in != nil {
   230  				win.ctx.DockedWindows.Dock(win, in.Mouse.Pos, win.ctx.Windows[0].Bounds, win.ctx.Style.Scaling)
   231  			}
   232  			win.moving = false
   233  		} else {
   234  			win.move(in.Mouse.Delta, in.Mouse.Pos)
   235  		}
   236  	}
   237  
   238  	win.usingSub = false
   239  	in.Mouse.clip = nk_null_rect
   240  	layout := win.layout
   241  
   242  	window_padding := wstyle.Padding
   243  	item_spacing := wstyle.Spacing
   244  	scaler_size := wstyle.ScalerSize
   245  
   246  	*layout = panel{}
   247  
   248  	/* panel space with border */
   249  	if win.flags&WindowBorder != 0 {
   250  		layout.Bounds = shrinkRect(win.Bounds, wstyle.Border)
   251  	} else {
   252  		layout.Bounds = win.Bounds
   253  	}
   254  
   255  	/* setup panel */
   256  	layout.Border = layout.Bounds.X - win.Bounds.X
   257  
   258  	layout.AtX = layout.Bounds.X
   259  	layout.AtY = layout.Bounds.Y
   260  	layout.Width = layout.Bounds.W
   261  	layout.Height = layout.Bounds.H
   262  	layout.MaxX = 0
   263  	layout.Row.Index = 0
   264  	layout.Row.Index2 = 0
   265  	layout.Row.CalcMaxWidth = false
   266  	layout.Row.Columns = 0
   267  	layout.Row.Height = 0
   268  	layout.Row.Ratio = nil
   269  	layout.Row.ItemWidth = 0
   270  	layout.Row.ItemRatio = 0
   271  	layout.Row.TreeDepth = 0
   272  	layout.Flags = win.flags
   273  	layout.ReservedHeight = 0
   274  	win.lastLayoutCnt = 0
   275  
   276  	for _, cols := range win.adjust {
   277  		for _, col := range cols {
   278  			col.first = false
   279  		}
   280  	}
   281  
   282  	/* calculate window header */
   283  	if win.flags&windowMenu != 0 || win.flags&windowContextual != 0 {
   284  		layout.HeaderH = window_padding.Y
   285  		layout.Row.Height = window_padding.Y
   286  	} else {
   287  		layout.HeaderH = window_padding.Y
   288  		layout.Row.Height = window_padding.Y
   289  	}
   290  
   291  	/* calculate window footer height */
   292  	if win.flags&windowNonblock == 0 && (((win.flags&WindowNoScrollbar == 0) && (win.flags&WindowNoHScrollbar == 0)) || (win.flags&WindowScalable != 0)) {
   293  		layout.FooterH = scaler_size.Y + wstyle.FooterPadding.Y
   294  	} else {
   295  		layout.FooterH = 0
   296  	}
   297  
   298  	/* calculate the window size */
   299  	if win.flags&WindowNoScrollbar == 0 {
   300  		layout.Width = layout.Bounds.W - wstyle.ScrollbarSize.X
   301  	}
   302  	layout.Height = layout.Bounds.H - (layout.HeaderH + window_padding.Y)
   303  	layout.Height -= layout.FooterH
   304  
   305  	/* window header */
   306  
   307  	var dwh drawableWindowHeader
   308  	dwh.Dynamic = layout.Flags&WindowDynamic != 0
   309  	dwh.Bounds = layout.Bounds
   310  	dwh.HeaderActive = (win.idx != 0) && (win.flags&WindowTitle != 0)
   311  	dwh.LayoutWidth = layout.Width
   312  	dwh.Style = win.style()
   313  
   314  	var closeButton rect.Rect
   315  
   316  	if dwh.HeaderActive {
   317  		/* calculate header bounds */
   318  		dwh.Header.X = layout.Bounds.X
   319  		dwh.Header.Y = layout.Bounds.Y
   320  		dwh.Header.W = layout.Bounds.W
   321  
   322  		/* calculate correct header height */
   323  		layout.HeaderH = FontHeight(font) + 2.0*wstyle.Header.Padding.Y
   324  
   325  		layout.HeaderH += 2.0 * wstyle.Header.LabelPadding.Y
   326  		layout.Row.Height += layout.HeaderH
   327  		dwh.Header.H = layout.HeaderH
   328  
   329  		/* update window height */
   330  		layout.Height = layout.Bounds.H - (dwh.Header.H + 2*item_spacing.Y)
   331  
   332  		layout.Height -= layout.FooterH
   333  
   334  		dwh.Hovered = ctx.Input.Mouse.HoveringRect(dwh.Header)
   335  		dwh.Focused = win.toplevel()
   336  
   337  		/* window header title */
   338  		t := FontWidth(font, title)
   339  
   340  		dwh.Label.X = dwh.Header.X + wstyle.Header.Padding.X
   341  		dwh.Label.X += wstyle.Header.LabelPadding.X
   342  		dwh.Label.Y = dwh.Header.Y + wstyle.Header.LabelPadding.Y
   343  		dwh.Label.H = FontHeight(font) + 2*wstyle.Header.LabelPadding.Y
   344  		dwh.Label.W = t + 2*wstyle.Header.Spacing.X
   345  		dwh.LayoutHeaderH = layout.HeaderH
   346  		dwh.RowHeight = layout.Row.Height
   347  		dwh.Title = title
   348  
   349  		win.widgets.Add(nstyle.WidgetStateInactive, layout.Bounds)
   350  		dwh.Draw(&win.ctx.Style, &win.cmds)
   351  
   352  		// window close button
   353  		closeButton.Y = dwh.Header.Y + wstyle.Header.Padding.Y
   354  		closeButton.H = layout.HeaderH - 2*wstyle.Header.Padding.Y
   355  		closeButton.W = closeButton.H
   356  		if win.flags&WindowClosable != 0 {
   357  			if wstyle.Header.Align == nstyle.HeaderRight {
   358  				closeButton.X = (dwh.Header.W + dwh.Header.X) - (closeButton.W + wstyle.Header.Padding.X)
   359  				dwh.Header.W -= closeButton.W + wstyle.Header.Spacing.X + wstyle.Header.Padding.X
   360  			} else {
   361  				closeButton.X = dwh.Header.X + wstyle.Header.Padding.X
   362  				dwh.Header.X += closeButton.W + wstyle.Header.Spacing.X + wstyle.Header.Padding.X
   363  			}
   364  
   365  			if doButton(win, label.S(wstyle.Header.CloseSymbol), closeButton, &wstyle.Header.CloseButton, in, false) {
   366  				win.close = true
   367  			}
   368  		}
   369  	} else {
   370  		dwh.LayoutHeaderH = layout.HeaderH
   371  		dwh.RowHeight = layout.Row.Height
   372  		win.widgets.Add(nstyle.WidgetStateInactive, layout.Bounds)
   373  		dwh.Draw(&win.ctx.Style, &win.cmds)
   374  	}
   375  
   376  	if (win.flags&WindowMovable != 0) && win.toplevel() {
   377  		var move rect.Rect
   378  		move.X = win.Bounds.X
   379  		move.Y = win.Bounds.Y
   380  		move.W = win.Bounds.W
   381  		move.H = FontHeight(font) + 2.0*wstyle.Header.Padding.Y + 2.0*wstyle.Header.LabelPadding.Y
   382  
   383  		if in.Mouse.IsClickDownInRect(pointer.ButtonLeft, move, true) && !in.Mouse.IsClickDownInRect(pointer.ButtonLeft, closeButton, true) {
   384  			win.moving = true
   385  		}
   386  	}
   387  
   388  	var dwb drawableWindowBody
   389  
   390  	dwb.NoScrollbar = win.flags&WindowNoScrollbar != 0
   391  	dwb.Style = win.style()
   392  
   393  	/* calculate and set the window clipping rectangle*/
   394  	if win.flags&WindowDynamic == 0 {
   395  		layout.Clip.X = layout.Bounds.X + window_padding.X
   396  		layout.Clip.W = layout.Width - 2*window_padding.X
   397  	} else {
   398  		layout.Clip.X = layout.Bounds.X
   399  		layout.Clip.W = layout.Width
   400  	}
   401  
   402  	layout.Clip.H = layout.Bounds.H - (layout.FooterH + layout.HeaderH)
   403  	// FooterH already includes the window padding
   404  	layout.Clip.H -= window_padding.Y
   405  	layout.Clip.Y = layout.Bounds.Y
   406  
   407  	/* combo box and menu do not have header space */
   408  	if win.flags&windowCombo == 0 && win.flags&windowMenu == 0 {
   409  		layout.Clip.Y += layout.HeaderH
   410  	}
   411  
   412  	clip := unify(win.cmds.Clip, layout.Clip)
   413  	layout.Clip = clip
   414  
   415  	dwb.Bounds = layout.Bounds
   416  	dwb.LayoutWidth = layout.Width
   417  	dwb.Clip = layout.Clip
   418  	win.cmds.Clip = dwb.Clip
   419  	win.widgets.Add(nstyle.WidgetStateInactive, dwb.Bounds)
   420  	dwb.Draw(&win.ctx.Style, &win.cmds)
   421  
   422  	layout.Row.Type = layoutInvalid
   423  }
   424  
   425  func (win *Window) specialPanelBegin() {
   426  	win.began = true
   427  	w := win.Master()
   428  	ctx := w.context()
   429  	if win.flags&windowContextual != 0 {
   430  		prevbody := win.Bounds
   431  		prevbody.H = win.layout.Height
   432  		// if the contextual menu ended up with its bottom right corner outside
   433  		// the main window's bounds and it could be moved to be inside the main
   434  		// window by popping it a different way do it.
   435  		// Since the size of the contextual menu is only knowable after displaying
   436  		// it once this must be done on the second frame.
   437  		max := ctx.Windows[0].Bounds.Max()
   438  		if (win.header.H <= 0 || win.header.W <= 0 || win.header.Contains(prevbody.Min())) && ((prevbody.Max().X > max.X) || (prevbody.Max().Y > max.Y)) && (win.Bounds.X-prevbody.W >= 0) && (win.Bounds.Y-prevbody.H >= 0) {
   439  			win.Bounds.X = win.Bounds.X - prevbody.W
   440  			win.Bounds.Y = win.Bounds.Y - prevbody.H
   441  		}
   442  	}
   443  
   444  	if win.flags&windowHDynamic != 0 && !win.first {
   445  		uw := win.menuItemWidth + 2*win.style().Padding.X + 2*win.style().Border
   446  		if uw < win.Bounds.W {
   447  			win.Bounds.W = uw
   448  		}
   449  	}
   450  
   451  	if win.flags&windowCombo != 0 && win.flags&WindowDynamic != 0 {
   452  		prevbody := win.Bounds
   453  		prevbody.H = win.layout.Height
   454  		// If the combo window ends up with the right corner below the
   455  		// main winodw's lower bound make it non-dynamic and resize it to its
   456  		// maximum possible size that will show the whole combo box.
   457  		max := ctx.Windows[0].Bounds.Max()
   458  		if prevbody.Y+prevbody.H > max.Y {
   459  			prevbody.H = max.Y - prevbody.Y
   460  			win.Bounds = prevbody
   461  			win.flags &= ^windowCombo
   462  		}
   463  	}
   464  
   465  	if win.flags&windowNonblock != 0 && !win.first {
   466  		/* check if user clicked outside the popup and close if so */
   467  		in_panel := ctx.Input.Mouse.IsClickInRect(pointer.ButtonLeft, win.ctx.Windows[0].layout.Bounds)
   468  		prevbody := win.Bounds
   469  		prevbody.H = win.layout.Height
   470  		in_body := ctx.Input.Mouse.IsClickInRect(pointer.ButtonLeft, prevbody)
   471  		in_header := ctx.Input.Mouse.IsClickInRect(pointer.ButtonLeft, win.header)
   472  		if !in_body && in_panel || in_header {
   473  			win.close = true
   474  		}
   475  	}
   476  
   477  	if win.flags&windowPopup != 0 {
   478  		win.cmds.PushScissor(nk_null_rect)
   479  
   480  		panelBegin(ctx, win, win.title)
   481  		win.layout.Offset = &win.Scrollbar
   482  	}
   483  
   484  	if win.first && (win.flags&windowContextual != 0 || win.flags&windowHDynamic != 0) {
   485  		ctx.trashFrame = true
   486  	}
   487  
   488  	win.first = false
   489  }
   490  
   491  var nk_null_rect = rect.Rect{-8192.0, -8192.0, 16384.0, 16384.0}
   492  
   493  func panelEnd(ctx *context, window *Window) {
   494  	var footer = rect.Rect{0, 0, 0, 0}
   495  
   496  	layout := window.layout
   497  	style := &ctx.Style
   498  	in := &Input{}
   499  	if window.toplevel() {
   500  		ctx.Input.Mouse.clip = nk_null_rect
   501  		in = &ctx.Input
   502  	}
   503  	outclip := nk_null_rect
   504  	if window.flags&windowGroup != 0 {
   505  		outclip = window.parent.cmds.Clip
   506  	}
   507  	window.cmds.PushScissor(outclip)
   508  
   509  	wstyle := window.style()
   510  
   511  	/* cache configuration data */
   512  	item_spacing := wstyle.Spacing
   513  	window_padding := wstyle.Padding
   514  	scrollbar_size := wstyle.ScrollbarSize
   515  	scaler_size := wstyle.ScalerSize
   516  
   517  	/* update the current cursor Y-position to point over the last added widget */
   518  	layout.AtY += layout.Row.Height
   519  
   520  	/* draw footer and fill empty spaces inside a dynamically growing panel */
   521  	if layout.Flags&WindowDynamic != 0 {
   522  		layout.Height = layout.AtY - layout.Bounds.Y
   523  		layout.Height = min(layout.Height, layout.Bounds.H)
   524  
   525  		// fill horizontal scrollbar space
   526  		{
   527  			var bounds rect.Rect
   528  			bounds.X = window.Bounds.X
   529  			bounds.Y = layout.AtY - item_spacing.Y
   530  			bounds.W = window.Bounds.W
   531  			bounds.H = window.Bounds.Y + layout.Height + item_spacing.Y + window.style().Padding.Y - bounds.Y
   532  
   533  			window.cmds.FillRect(bounds, 0, wstyle.Background)
   534  		}
   535  
   536  		if (layout.Offset.X == 0) || (layout.Flags&WindowNoScrollbar != 0) {
   537  			/* special case for dynamic windows without horizontal scrollbar
   538  			 * or hidden scrollbars */
   539  			footer.X = window.Bounds.X
   540  			footer.Y = window.Bounds.Y + layout.Height + item_spacing.Y + window.style().Padding.Y
   541  			footer.W = window.Bounds.W + scrollbar_size.X
   542  			layout.FooterH = 0
   543  			footer.H = 0
   544  
   545  			if (layout.Offset.X == 0) && layout.Flags&WindowNoScrollbar == 0 {
   546  				/* special case for windows like combobox, menu require draw call
   547  				 * to fill the empty scrollbar background */
   548  				var bounds rect.Rect
   549  				bounds.X = layout.Bounds.X + layout.Width
   550  				bounds.Y = layout.Clip.Y
   551  				bounds.W = scrollbar_size.X
   552  				bounds.H = layout.Height
   553  
   554  				window.cmds.FillRect(bounds, 0, wstyle.Background)
   555  			}
   556  		} else {
   557  			/* dynamic window with visible scrollbars and therefore bigger footer */
   558  			footer.X = window.Bounds.X
   559  			footer.W = window.Bounds.W + scrollbar_size.X
   560  			footer.H = layout.FooterH
   561  			if (layout.Flags&windowCombo != 0) || (layout.Flags&windowMenu != 0) || (layout.Flags&windowContextual != 0) {
   562  				footer.Y = window.Bounds.Y + layout.Height
   563  			} else {
   564  				footer.Y = window.Bounds.Y + layout.Height + layout.FooterH
   565  			}
   566  			window.cmds.FillRect(footer, 0, wstyle.Background)
   567  
   568  			if layout.Flags&windowCombo == 0 && layout.Flags&windowMenu == 0 {
   569  				/* fill empty scrollbar space */
   570  				var bounds rect.Rect
   571  				bounds.X = layout.Bounds.X
   572  				bounds.Y = window.Bounds.Y + layout.Height
   573  				bounds.W = layout.Bounds.W
   574  				bounds.H = layout.Row.Height
   575  				window.cmds.FillRect(bounds, 0, wstyle.Background)
   576  			}
   577  		}
   578  	}
   579  
   580  	/* scrollbars */
   581  	if layout.Flags&WindowNoScrollbar == 0 {
   582  		var bounds rect.Rect
   583  		var scroll_target float64
   584  		var scroll_offset float64
   585  		var scroll_step float64
   586  		var scroll_inc float64
   587  		{
   588  			/* vertical scrollbar */
   589  			bounds.X = layout.Bounds.X + layout.Width
   590  			bounds.Y = layout.Clip.Y
   591  			bounds.W = scrollbar_size.Y
   592  			bounds.H = layout.Clip.H
   593  			if layout.Flags&WindowBorder != 0 {
   594  				bounds.H -= 1
   595  			}
   596  
   597  			scroll_offset = float64(layout.Offset.Y)
   598  			scroll_step = float64(layout.Clip.H) * 0.10
   599  			scroll_inc = float64(layout.Clip.H) * 0.01
   600  			scroll_target = float64(layout.AtY - layout.Clip.Y)
   601  			scroll_offset = doScrollbarv(window, bounds, layout.Bounds, scroll_offset, scroll_target, scroll_step, scroll_inc, &ctx.Style.Scrollv, in, style.Font)
   602  			layout.Offset.Y = int(scroll_offset)
   603  		}
   604  		if layout.Flags&WindowNoHScrollbar == 0 {
   605  			/* horizontal scrollbar */
   606  			bounds.X = layout.Bounds.X + window_padding.X
   607  			if layout.Flags&windowSub != 0 {
   608  				bounds.H = scrollbar_size.X
   609  				bounds.Y = layout.Bounds.Y
   610  				if layout.Flags&WindowBorder != 0 {
   611  					bounds.Y++
   612  				}
   613  				bounds.Y += layout.HeaderH + layout.Menu.H + layout.Height
   614  				bounds.W = layout.Clip.W
   615  			} else if layout.Flags&WindowDynamic != 0 {
   616  				bounds.H = min(scrollbar_size.X, layout.FooterH)
   617  				bounds.W = layout.Bounds.W
   618  				bounds.Y = footer.Y
   619  			} else {
   620  				bounds.H = min(scrollbar_size.X, layout.FooterH)
   621  				bounds.Y = layout.Bounds.Y + window.Bounds.H
   622  				bounds.Y -= max(layout.FooterH, scrollbar_size.X)
   623  				bounds.W = layout.Width - 2*window_padding.X
   624  			}
   625  
   626  			scroll_offset = float64(layout.Offset.X)
   627  			scroll_target = float64(layout.MaxX - bounds.X)
   628  			scroll_step = float64(layout.MaxX) * 0.05
   629  			scroll_inc = float64(layout.MaxX) * 0.005
   630  			scroll_offset = doScrollbarh(window, bounds, scroll_offset, scroll_target, scroll_step, scroll_inc, &ctx.Style.Scrollh, in, style.Font)
   631  			layout.Offset.X = int(scroll_offset)
   632  		}
   633  	}
   634  
   635  	var dsab drawableScalerAndBorders
   636  	dsab.Style = window.style()
   637  	dsab.Bounds = window.Bounds
   638  	dsab.Border = layout.Border
   639  	dsab.HeaderH = layout.HeaderH
   640  
   641  	/* scaler */
   642  	if layout.Flags&WindowScalable != 0 {
   643  		dsab.DrawScaler = true
   644  
   645  		dsab.ScalerRect.W = max(0, scaler_size.X)
   646  		dsab.ScalerRect.H = max(0, scaler_size.Y)
   647  		dsab.ScalerRect.X = (layout.Bounds.X + layout.Bounds.W) - (window_padding.X + dsab.ScalerRect.W)
   648  		/* calculate scaler bounds */
   649  		if layout.Flags&WindowDynamic != 0 {
   650  			dsab.ScalerRect.Y = footer.Y + layout.FooterH - scaler_size.Y
   651  		} else {
   652  			dsab.ScalerRect.Y = layout.Bounds.Y + layout.Bounds.H - (scaler_size.Y + window_padding.Y)
   653  		}
   654  
   655  		/* do window scaling logic */
   656  		if window.toplevel() {
   657  			if window.scaling {
   658  				if in == nil || !in.Mouse.Down(pointer.ButtonLeft) {
   659  					window.scaling = false
   660  				} else {
   661  					window.scale(in.Mouse.Delta)
   662  				}
   663  			} else if in != nil && in.Mouse.IsClickDownInRect(pointer.ButtonLeft, dsab.ScalerRect, true) {
   664  				window.scaling = true
   665  			}
   666  		}
   667  	}
   668  
   669  	/* window border */
   670  	if layout.Flags&WindowBorder != 0 {
   671  		dsab.DrawBorders = true
   672  
   673  		if layout.Flags&WindowDynamic != 0 {
   674  			dsab.PaddingY = layout.FooterH + footer.Y
   675  		} else {
   676  			dsab.PaddingY = layout.Bounds.Y + layout.Bounds.H
   677  		}
   678  		/* select correct border color */
   679  		dsab.BorderColor = wstyle.BorderColor
   680  
   681  		/* draw border between header and window body */
   682  		if window.flags&WindowBorderHeader != 0 {
   683  			dsab.DrawHeaderBorder = true
   684  		}
   685  	}
   686  
   687  	window.widgets.Add(nstyle.WidgetStateInactive, dsab.Bounds)
   688  	dsab.Draw(&window.ctx.Style, &window.cmds)
   689  
   690  	layout.Flags |= windowEnabled
   691  	window.flags = layout.Flags
   692  
   693  	/* helper to make sure you have a 'nk_tree_push'
   694  	 * for every 'nk_tree_pop' */
   695  	if layout.Row.TreeDepth != 0 {
   696  		panic("Some TreePush not closed by TreePop")
   697  	}
   698  }
   699  
   700  // MenubarBegin adds a menubar to the current window.
   701  // A menubar is an area displayed at the top of the window that is unaffected by scrolling.
   702  // Remember to call MenubarEnd when you are done adding elements to the menubar.
   703  func (win *Window) MenubarBegin() {
   704  	layout := win.layout
   705  
   706  	layout.Menu.X = layout.AtX
   707  	layout.Menu.Y = layout.Bounds.Y + layout.HeaderH
   708  	layout.Menu.W = layout.Width
   709  	layout.Menu.Offset = *layout.Offset
   710  	layout.Offset.Y = 0
   711  }
   712  
   713  func (win *Window) move(delta image.Point, pos image.Point) {
   714  	if win.flags&windowDocked != 0 {
   715  		if delta.X != 0 && delta.Y != 0 {
   716  			win.ctx.DockedWindows.Undock(win)
   717  		}
   718  		return
   719  	}
   720  	if canDock, bounds := win.ctx.DockedWindows.Dock(nil, pos, win.ctx.Windows[0].Bounds, win.ctx.Style.Scaling); canDock {
   721  		win.ctx.finalCmds.FillRect(bounds, 0, color.RGBA{0x0, 0x0, 0x50, 0x50})
   722  	}
   723  	win.Bounds.X = win.Bounds.X + delta.X
   724  	win.Bounds.X = clampInt(0, win.Bounds.X, win.ctx.Windows[0].Bounds.X+win.ctx.Windows[0].Bounds.W-FontHeight(win.ctx.Style.Font))
   725  	win.Bounds.Y = win.Bounds.Y + delta.Y
   726  	win.Bounds.Y = clampInt(0, win.Bounds.Y, win.ctx.Windows[0].Bounds.Y+win.ctx.Windows[0].Bounds.H-FontHeight(win.ctx.Style.Font))
   727  }
   728  
   729  func (win *Window) scale(delta image.Point) {
   730  	if win.flags&windowDocked != 0 {
   731  		win.ctx.DockedWindows.Scale(win, delta, win.ctx.Style.Scaling)
   732  		return
   733  	}
   734  	window_size := win.style().MinSize
   735  	win.Bounds.W = max(window_size.X, win.Bounds.W+delta.X)
   736  
   737  	/* dragging in y-direction is only possible if static window */
   738  	if win.layout.Flags&WindowDynamic == 0 {
   739  		win.Bounds.H = max(window_size.Y, win.Bounds.H+delta.Y)
   740  	}
   741  }
   742  
   743  // MenubarEnd signals that all widgets have been added to the menubar.
   744  func (win *Window) MenubarEnd() {
   745  	layout := win.layout
   746  
   747  	layout.Menu.H = layout.AtY - layout.Menu.Y
   748  	layout.Clip.Y = layout.Bounds.Y + layout.HeaderH + layout.Menu.H + layout.Row.Height
   749  	layout.Height -= layout.Menu.H
   750  	*layout.Offset = layout.Menu.Offset
   751  	layout.Clip.H -= layout.Menu.H + layout.Row.Height
   752  	layout.AtY = layout.Menu.Y + layout.Menu.H
   753  	win.cmds.PushScissor(layout.Clip)
   754  }
   755  
   756  func (win *Window) widget() (valid bool, bounds rect.Rect, calcFittingWidth FittingWidthFn) {
   757  	/* allocate space  and check if the widget needs to be updated and drawn */
   758  	calcFittingWidth = panelAllocSpace(&bounds, win)
   759  
   760  	if !win.layout.Clip.Intersect(&bounds) {
   761  		return false, bounds, calcFittingWidth
   762  	}
   763  
   764  	return (bounds.W > 0 && bounds.H > 0), bounds, calcFittingWidth
   765  }
   766  
   767  func (win *Window) widgetFitting(item_padding image.Point) (valid bool, bounds rect.Rect) {
   768  	/* update the bounds to stand without padding  */
   769  	style := win.style()
   770  	layout := win.layout
   771  	valid, bounds, _ = win.widget()
   772  	if layout.Row.Index == 1 {
   773  		bounds.W += style.Padding.X
   774  		bounds.X -= style.Padding.X
   775  	} else {
   776  		bounds.X -= item_padding.X
   777  	}
   778  
   779  	if layout.Row.Columns > 0 && layout.Row.Index == layout.Row.Columns {
   780  		bounds.W += style.Padding.X
   781  	} else {
   782  		bounds.W += item_padding.X
   783  	}
   784  	return valid, bounds
   785  }
   786  
   787  func panelAllocSpace(bounds *rect.Rect, win *Window) FittingWidthFn {
   788  	if win.usingSub {
   789  		panic(UsingSubErr)
   790  	}
   791  	/* check if the end of the row has been hit and begin new row if so */
   792  	layout := win.layout
   793  	if layout.Row.Columns > 0 && layout.Row.Index >= layout.Row.Columns {
   794  		panelAllocRow(win)
   795  	}
   796  
   797  	/* calculate widget position and size */
   798  	layoutWidgetSpace(bounds, win.ctx, win, true)
   799  
   800  	win.LastWidgetBounds = *bounds
   801  
   802  	layout.Row.Index++
   803  
   804  	if win.layout.Row.CalcMaxWidth {
   805  		col := win.adjust[win.layout.Cnt][win.layout.Row.Index2-1]
   806  		return func(width int) {
   807  			if width > col.width {
   808  				col.width = width
   809  			}
   810  		}
   811  	}
   812  	return nil
   813  }
   814  
   815  func panelAllocRow(win *Window) {
   816  	layout := win.layout
   817  	spacing := win.style().Spacing
   818  	row_height := layout.Row.Height - spacing.Y
   819  	panelLayout(win.ctx, win, row_height, layout.Row.Columns, 0)
   820  }
   821  
   822  func panelLayout(ctx *context, win *Window, height int, cols int, cnt int) {
   823  	/* prefetch some configuration data */
   824  	layout := win.layout
   825  
   826  	style := win.style()
   827  	item_spacing := style.Spacing
   828  
   829  	if height == 0 {
   830  		height = layout.Height - (layout.AtY - layout.Bounds.Y) - 1
   831  		if layout.Row.Index != 0 && (win.flags&windowPopup == 0) {
   832  			height -= layout.Row.Height
   833  		} else {
   834  			height -= item_spacing.Y
   835  		}
   836  		if layout.ReservedHeight > 0 {
   837  			height -= layout.ReservedHeight
   838  		}
   839  	}
   840  
   841  	/* update the current row and set the current row layout */
   842  	layout.Cnt = cnt
   843  	layout.Row.Index = 0
   844  	layout.Row.Index2 = 0
   845  	layout.Row.CalcMaxWidth = false
   846  
   847  	layout.AtY += layout.Row.Height
   848  	layout.Row.Columns = cols
   849  	layout.Row.Height = height + item_spacing.Y
   850  	layout.Row.ItemOffset = 0
   851  	if layout.Flags&WindowDynamic != 0 {
   852  		win.cmds.FillRect(rect.Rect{layout.Bounds.X, layout.AtY, layout.Bounds.W, height + item_spacing.Y}, 0, style.Background)
   853  	}
   854  }
   855  
   856  const (
   857  	layoutDynamicFixed = iota
   858  	layoutDynamicFree
   859  	layoutDynamic
   860  	layoutStaticFree
   861  	layoutStatic
   862  	layoutInvalid
   863  )
   864  
   865  var InvalidLayoutErr = errors.New("invalid layout")
   866  var UsingSubErr = errors.New("parent window used while populating a sub window")
   867  
   868  func layoutWidgetSpace(bounds *rect.Rect, ctx *context, win *Window, modify bool) {
   869  	layout := win.layout
   870  
   871  	/* cache some configuration data */
   872  	style := win.style()
   873  	spacing := style.Spacing
   874  	padding := style.Padding
   875  
   876  	/* calculate the usable panel space */
   877  	panel_padding := 2 * padding.X
   878  
   879  	panel_spacing := int(float64(layout.Row.Columns-1) * float64(spacing.X))
   880  	panel_space := layout.Width - panel_padding - panel_spacing
   881  
   882  	/* calculate the width of one item inside the current layout space */
   883  	item_offset := 0
   884  	item_width := 0
   885  	item_spacing := 0
   886  
   887  	switch layout.Row.Type {
   888  	case layoutInvalid:
   889  		panic(InvalidLayoutErr)
   890  	case layoutDynamicFixed:
   891  		/* scaling fixed size widgets item width */
   892  		item_width = int(float64(panel_space) / float64(layout.Row.Columns))
   893  
   894  		item_offset = layout.Row.Index * item_width
   895  		item_spacing = layout.Row.Index * spacing.X
   896  	case layoutDynamicFree:
   897  		/* panel width depended free widget placing */
   898  		bounds.X = layout.AtX + int(float64(layout.Width)*layout.Row.DynamicFreeX)
   899  		bounds.X -= layout.Offset.X
   900  		bounds.Y = layout.AtY + int(float64(layout.Row.Height)*layout.Row.DynamicFreeY)
   901  		bounds.Y -= layout.Offset.Y
   902  		bounds.W = int(float64(layout.Width) * layout.Row.DynamicFreeW)
   903  		bounds.H = int(float64(layout.Row.Height) * layout.Row.DynamicFreeH)
   904  		return
   905  	case layoutDynamic:
   906  		/* scaling arrays of panel width ratios for every widget */
   907  		var ratio float64
   908  		if layout.Row.Ratio[layout.Row.Index] < 0 {
   909  			ratio = layout.Row.ItemRatio
   910  		} else {
   911  			ratio = layout.Row.Ratio[layout.Row.Index]
   912  		}
   913  
   914  		item_spacing = layout.Row.Index * spacing.X
   915  		item_width = int(ratio * float64(panel_space))
   916  		item_offset = layout.Row.ItemOffset
   917  		if modify {
   918  			layout.Row.ItemOffset += item_width
   919  			layout.Row.Filled += ratio
   920  		}
   921  	case layoutStaticFree:
   922  		/* free widget placing */
   923  		atx, aty := layout.AtX, layout.AtY
   924  		if atx < layout.Clip.X {
   925  			atx = layout.Clip.X
   926  		}
   927  		if aty < layout.Clip.Y {
   928  			aty = layout.Clip.Y
   929  		}
   930  
   931  		bounds.X = atx + layout.Row.Item.X
   932  
   933  		bounds.W = layout.Row.Item.W
   934  		if ((bounds.X + bounds.W) > layout.MaxX) && modify {
   935  			layout.MaxX = (bounds.X + bounds.W)
   936  		}
   937  		bounds.X -= layout.Offset.X
   938  		bounds.Y = aty + layout.Row.Item.Y
   939  		bounds.Y -= layout.Offset.Y
   940  		bounds.H = layout.Row.Item.H
   941  		return
   942  	case layoutStatic:
   943  		/* non-scaling array of panel pixel width for every widget */
   944  		item_spacing = layout.Row.Index * spacing.X
   945  
   946  		if len(layout.Row.WidthArr) > 0 {
   947  			item_width = layout.Row.WidthArr[layout.Row.Index]
   948  		} else {
   949  			item_width = layout.Row.ItemWidth
   950  		}
   951  		item_offset = layout.Row.ItemOffset
   952  		if modify {
   953  			layout.Row.ItemOffset += item_width
   954  		}
   955  
   956  	default:
   957  		panic("internal error unknown layout")
   958  	}
   959  
   960  	/* set the bounds of the newly allocated widget */
   961  	bounds.W = item_width
   962  
   963  	bounds.H = layout.Row.Height - spacing.Y
   964  	bounds.Y = layout.AtY - layout.Offset.Y
   965  	bounds.X = layout.AtX + item_offset + item_spacing + padding.X
   966  	if ((bounds.X + bounds.W) > layout.MaxX) && modify {
   967  		layout.MaxX = bounds.X + bounds.W
   968  	}
   969  	bounds.X -= layout.Offset.X
   970  }
   971  
   972  func (ctx *context) scale(x int) int {
   973  	return int(float64(x) * ctx.Style.Scaling)
   974  }
   975  
   976  func rowLayoutCtr(win *Window, height int, cols int, width int) {
   977  	/* update the current row and set the current row layout */
   978  	panelLayout(win.ctx, win, height, cols, 0)
   979  	win.layout.Row.Type = layoutDynamicFixed
   980  
   981  	win.layout.Row.ItemWidth = width
   982  	win.layout.Row.ItemRatio = 0.0
   983  	win.layout.Row.Ratio = nil
   984  	win.layout.Row.ItemOffset = 0
   985  	win.layout.Row.Filled = 0
   986  }
   987  
   988  // Reserves space for num rows of the specified height at the bottom
   989  // of the panel.
   990  // If a row of height == 0  is inserted it will take reserved space
   991  // into account.
   992  func (win *Window) LayoutReserveRow(height int, num int) {
   993  	win.LayoutReserveRowScaled(win.ctx.scale(height), num)
   994  }
   995  
   996  // Like LayoutReserveRow but with a scaled height.
   997  func (win *Window) LayoutReserveRowScaled(height int, num int) {
   998  	win.layout.ReservedHeight += height*num + win.style().Spacing.Y*num
   999  }
  1000  
  1001  // Changes row layout and starts a new row.
  1002  // Use the returned value to configure the new row layout:
  1003  //  win.Row(10).Static(100, 120, 100)
  1004  // If height == 0 all the row is stretched to fill all the remaining space.
  1005  func (win *Window) Row(height int) *rowConstructor {
  1006  	win.rowCtor.height = win.ctx.scale(height)
  1007  	return &win.rowCtor
  1008  }
  1009  
  1010  // Same as Row but with scaled units.
  1011  func (win *Window) RowScaled(height int) *rowConstructor {
  1012  	win.rowCtor.height = height
  1013  	return &win.rowCtor
  1014  }
  1015  
  1016  type rowConstructor struct {
  1017  	win    *Window
  1018  	height int
  1019  }
  1020  
  1021  // Starts new row that has cols columns of equal width that automatically
  1022  // resize to fill the available space.
  1023  func (ctr *rowConstructor) Dynamic(cols int) {
  1024  	rowLayoutCtr(ctr.win, ctr.height, cols, 0)
  1025  }
  1026  
  1027  // Starts new row with a fixed number of columns of width proportional
  1028  // to the size of the window.
  1029  func (ctr *rowConstructor) Ratio(ratio ...float64) {
  1030  	layout := ctr.win.layout
  1031  	panelLayout(ctr.win.ctx, ctr.win, ctr.height, len(ratio), 0)
  1032  
  1033  	/* calculate width of undefined widget ratios */
  1034  	r := 0.0
  1035  	n_undef := 0
  1036  	layout.Row.Ratio = ratio
  1037  	for i := range ratio {
  1038  		if ratio[i] < 0.0 {
  1039  			n_undef++
  1040  		} else {
  1041  			r += ratio[i]
  1042  		}
  1043  	}
  1044  
  1045  	r = saturateFloat(1.0 - r)
  1046  	layout.Row.Type = layoutDynamic
  1047  	layout.Row.ItemWidth = 0
  1048  	layout.Row.ItemRatio = 0.0
  1049  	if r > 0 && n_undef > 0 {
  1050  		layout.Row.ItemRatio = (r / float64(n_undef))
  1051  	}
  1052  
  1053  	layout.Row.ItemOffset = 0
  1054  	layout.Row.Filled = 0
  1055  
  1056  }
  1057  
  1058  // Starts new row with a fixed number of columns with the specfieid widths.
  1059  // If no widths are specified the row will never autowrap
  1060  // and the width of the next widget can be specified using
  1061  // LayoutSetWidth/LayoutSetWidthScaled/LayoutFitWidth.
  1062  func (ctr *rowConstructor) Static(width ...int) {
  1063  	for i := range width {
  1064  		width[i] = ctr.win.ctx.scale(width[i])
  1065  	}
  1066  
  1067  	ctr.StaticScaled(width...)
  1068  }
  1069  
  1070  func (win *Window) staticZeros(width []int) {
  1071  	layout := win.layout
  1072  
  1073  	nzero := 0
  1074  	used := 0
  1075  	for i := range width {
  1076  		if width[i] == 0 {
  1077  			nzero++
  1078  		}
  1079  		used += width[i]
  1080  	}
  1081  
  1082  	if nzero > 0 {
  1083  		style := win.style()
  1084  		spacing := style.Spacing
  1085  		padding := style.Padding
  1086  		panel_padding := 2 * padding.X
  1087  		panel_spacing := int(float64(len(width)-1) * float64(spacing.X))
  1088  		panel_space := layout.Width - panel_padding - panel_spacing
  1089  
  1090  		unused := panel_space - used
  1091  
  1092  		zerowidth := unused / nzero
  1093  
  1094  		for i := range width {
  1095  			if width[i] == 0 {
  1096  				width[i] = zerowidth
  1097  			}
  1098  		}
  1099  	}
  1100  }
  1101  
  1102  // Like Static but with scaled sizes.
  1103  func (ctr *rowConstructor) StaticScaled(width ...int) {
  1104  	layout := ctr.win.layout
  1105  
  1106  	cnt := 0
  1107  
  1108  	if len(width) == 0 {
  1109  		if len(layout.Row.WidthArr) == 0 {
  1110  			cnt = layout.Cnt
  1111  		} else {
  1112  			cnt = ctr.win.lastLayoutCnt + 1
  1113  			ctr.win.lastLayoutCnt = cnt
  1114  		}
  1115  	}
  1116  
  1117  	panelLayout(ctr.win.ctx, ctr.win, ctr.height, len(width), cnt)
  1118  
  1119  	ctr.win.staticZeros(width)
  1120  
  1121  	layout.Row.WidthArr = width
  1122  	layout.Row.Type = layoutStatic
  1123  	layout.Row.ItemWidth = 0
  1124  	layout.Row.ItemRatio = 0.0
  1125  	layout.Row.ItemOffset = 0
  1126  	layout.Row.Filled = 0
  1127  }
  1128  
  1129  // Reset static row
  1130  func (win *Window) LayoutResetStatic(width ...int) {
  1131  	for i := range width {
  1132  		width[i] = win.ctx.scale(width[i])
  1133  	}
  1134  	win.LayoutResetStaticScaled(width...)
  1135  }
  1136  
  1137  func (win *Window) LayoutResetStaticScaled(width ...int) {
  1138  	layout := win.layout
  1139  	if layout.Row.Type != layoutStatic {
  1140  		panic(WrongLayoutErr)
  1141  	}
  1142  	win.staticZeros(width)
  1143  	layout.Row.Index = 0
  1144  	layout.Row.Index2 = 0
  1145  	layout.Row.CalcMaxWidth = false
  1146  	layout.Row.Columns = len(width)
  1147  	layout.Row.WidthArr = width
  1148  	layout.Row.ItemWidth = 0
  1149  	layout.Row.ItemRatio = 0.0
  1150  	layout.Row.ItemOffset = 0
  1151  	layout.Row.Filled = 0
  1152  }
  1153  
  1154  // Starts new row that will contain widget_count widgets.
  1155  // The size and position of widgets inside this row will be specified
  1156  // by callling LayoutSpacePush/LayoutSpacePushScaled.
  1157  func (ctr *rowConstructor) SpaceBegin(widget_count int) (bounds rect.Rect) {
  1158  	layout := ctr.win.layout
  1159  	panelLayout(ctr.win.ctx, ctr.win, ctr.height, widget_count, 0)
  1160  	layout.Row.Type = layoutStaticFree
  1161  
  1162  	layout.Row.Ratio = nil
  1163  	layout.Row.ItemWidth = 0
  1164  	layout.Row.ItemRatio = 0.0
  1165  	layout.Row.ItemOffset = 0
  1166  	layout.Row.Filled = 0
  1167  
  1168  	style := ctr.win.style()
  1169  	spacing := style.Spacing
  1170  	padding := style.Padding
  1171  
  1172  	bounds.W = layout.Width - 2*padding.X
  1173  	bounds.H = layout.Row.Height - spacing.Y
  1174  
  1175  	return bounds
  1176  }
  1177  
  1178  // Starts new row that will contain widget_count widgets.
  1179  // The size and position of widgets inside this row will be specified
  1180  // by callling LayoutSpacePushRatio.
  1181  func (ctr *rowConstructor) SpaceBeginRatio(widget_count int) {
  1182  	layout := ctr.win.layout
  1183  	panelLayout(ctr.win.ctx, ctr.win, ctr.height, widget_count, 0)
  1184  	layout.Row.Type = layoutDynamicFree
  1185  
  1186  	layout.Row.Ratio = nil
  1187  	layout.Row.ItemWidth = 0
  1188  	layout.Row.ItemRatio = 0.0
  1189  	layout.Row.ItemOffset = 0
  1190  	layout.Row.Filled = 0
  1191  }
  1192  
  1193  // LayoutSetWidth adds a new column with the specified width to a static
  1194  // layout.
  1195  func (win *Window) LayoutSetWidth(width int) {
  1196  	layout := win.layout
  1197  	if layout.Row.Type != layoutStatic || len(layout.Row.WidthArr) > 0 {
  1198  		panic(WrongLayoutErr)
  1199  	}
  1200  	layout.Row.Index2++
  1201  	layout.Row.CalcMaxWidth = false
  1202  	layout.Row.ItemWidth = win.ctx.scale(width)
  1203  }
  1204  
  1205  // LayoutSetWidthScaled adds a new column width the specified scaled width
  1206  // to a static layout.
  1207  func (win *Window) LayoutSetWidthScaled(width int) {
  1208  	layout := win.layout
  1209  	if layout.Row.Type != layoutStatic || len(layout.Row.WidthArr) > 0 {
  1210  		panic(WrongLayoutErr)
  1211  	}
  1212  	layout.Row.Index2++
  1213  	layout.Row.CalcMaxWidth = false
  1214  	layout.Row.ItemWidth = width
  1215  }
  1216  
  1217  // LayoutFitWidth adds a new column to a static layout.
  1218  // The width of the column will be large enough to fit the largest widget
  1219  // exactly. The largest widget will only be calculated once per id, if the
  1220  // dataset changes the id should change.
  1221  func (win *Window) LayoutFitWidth(id int, minwidth int) {
  1222  	layout := win.layout
  1223  	if layout.Row.Type != layoutStatic || len(layout.Row.WidthArr) > 0 {
  1224  		panic(WrongLayoutErr)
  1225  	}
  1226  	if win.adjust == nil {
  1227  		win.adjust = make(map[int]map[int]*adjustCol)
  1228  	}
  1229  	adjust, ok := win.adjust[layout.Cnt]
  1230  	if !ok {
  1231  		adjust = make(map[int]*adjustCol)
  1232  		win.adjust[layout.Cnt] = adjust
  1233  	}
  1234  	col, ok := adjust[layout.Row.Index2]
  1235  	if !ok || col.id != id || col.font != win.ctx.Style.Font {
  1236  		if !ok {
  1237  			col = &adjustCol{id: id, width: minwidth}
  1238  			win.adjust[layout.Cnt][layout.Row.Index2] = col
  1239  		}
  1240  		col.id = id
  1241  		col.font = win.ctx.Style.Font
  1242  		col.width = minwidth
  1243  		col.first = true
  1244  		win.ctx.trashFrame = true
  1245  		win.LayoutSetWidth(minwidth)
  1246  		layout.Row.CalcMaxWidth = true
  1247  		return
  1248  	}
  1249  	win.LayoutSetWidthScaled(col.width)
  1250  	layout.Row.CalcMaxWidth = col.first
  1251  }
  1252  
  1253  var WrongLayoutErr = errors.New("Command not available with current layout")
  1254  
  1255  // Sets position and size of the next widgets in a Space row layout
  1256  func (win *Window) LayoutSpacePush(rect rect.Rect) {
  1257  	if win.layout.Row.Type != layoutStaticFree {
  1258  		panic(WrongLayoutErr)
  1259  	}
  1260  	rect.X = win.ctx.scale(rect.X)
  1261  	rect.Y = win.ctx.scale(rect.Y)
  1262  	rect.W = win.ctx.scale(rect.W)
  1263  	rect.H = win.ctx.scale(rect.H)
  1264  	win.layout.Row.Item = rect
  1265  }
  1266  
  1267  // Like LayoutSpacePush but with scaled units
  1268  func (win *Window) LayoutSpacePushScaled(rect rect.Rect) {
  1269  	if win.layout.Row.Type != layoutStaticFree {
  1270  		panic(WrongLayoutErr)
  1271  	}
  1272  	win.layout.Row.Item = rect
  1273  }
  1274  
  1275  // Sets position and size of the next widgets in a Space row layout
  1276  func (win *Window) LayoutSpacePushRatio(x, y, w, h float64) {
  1277  	if win.layout.Row.Type != layoutDynamicFree {
  1278  		panic(WrongLayoutErr)
  1279  	}
  1280  	win.layout.Row.DynamicFreeX, win.layout.Row.DynamicFreeY, win.layout.Row.DynamicFreeH, win.layout.Row.DynamicFreeW = x, y, w, h
  1281  }
  1282  
  1283  func (win *Window) layoutPeek(bounds *rect.Rect) {
  1284  	layout := win.layout
  1285  	y := layout.AtY
  1286  	off := layout.Row.ItemOffset
  1287  	index := layout.Row.Index
  1288  	if layout.Row.Columns > 0 && layout.Row.Index >= layout.Row.Columns {
  1289  		layout.AtY += layout.Row.Height
  1290  		layout.Row.ItemOffset = 0
  1291  		layout.Row.Index = 0
  1292  		layout.Row.Index2 = 0
  1293  	}
  1294  
  1295  	layoutWidgetSpace(bounds, win.ctx, win, false)
  1296  	layout.AtY = y
  1297  	layout.Row.ItemOffset = off
  1298  	layout.Row.Index = index
  1299  }
  1300  
  1301  // Returns the position and size of the next widget that will be
  1302  // added to the current row.
  1303  // Note that the return value is in scaled units.
  1304  func (win *Window) WidgetBounds() rect.Rect {
  1305  	var bounds rect.Rect
  1306  	win.layoutPeek(&bounds)
  1307  	return bounds
  1308  }
  1309  
  1310  // Returns remaining available height of win in scaled units.
  1311  func (win *Window) LayoutAvailableHeight() int {
  1312  	return win.layout.Clip.H - (win.layout.AtY - win.layout.Bounds.Y) - win.style().Spacing.Y - win.layout.Row.Height
  1313  }
  1314  
  1315  func (win *Window) LayoutAvailableWidth() int {
  1316  	switch win.layout.Row.Type {
  1317  	case layoutDynamicFree, layoutStaticFree:
  1318  		return win.layout.Clip.W
  1319  	default:
  1320  		style := win.style()
  1321  		panel_spacing := int(float64(win.layout.Row.Columns-1) * float64(style.Spacing.X))
  1322  		return win.layout.Width - style.Padding.X*2 - panel_spacing - win.layout.AtX
  1323  	}
  1324  }
  1325  
  1326  // Will return (false, false) if the last widget is visible, (true,
  1327  // false) if it is above the visible area, (false, true) if it is
  1328  // below the visible area
  1329  func (win *Window) Invisible() (above, below bool) {
  1330  	return win.LastWidgetBounds.Y < win.layout.Clip.Y, win.LastWidgetBounds.Y+win.LastWidgetBounds.H > (win.layout.Clip.Y + win.layout.Clip.H)
  1331  }
  1332  
  1333  func (win *Window) At() image.Point {
  1334  	return image.Point{win.layout.AtX - win.layout.Clip.X, win.layout.AtY - win.layout.Clip.Y}
  1335  }
  1336  
  1337  ///////////////////////////////////////////////////////////////////////////////////
  1338  // TREE
  1339  ///////////////////////////////////////////////////////////////////////////////////
  1340  
  1341  type TreeType int
  1342  
  1343  const (
  1344  	TreeNode TreeType = iota
  1345  	TreeTab
  1346  )
  1347  
  1348  func (win *Window) TreePush(type_ TreeType, title string, initialOpen bool) bool {
  1349  	return win.TreePushNamed(type_, title, title, initialOpen)
  1350  }
  1351  
  1352  // Creates a new collapsable section inside win. Returns true
  1353  // when the section is open. Widgets that are inside this collapsable
  1354  // section should be added to win only when this function returns true.
  1355  // Once you are done adding elements to the collapsable section
  1356  // call TreePop.
  1357  // Initial_open will determine whether this collapsable section
  1358  // will be initially open.
  1359  // Type_ will determine the style of this collapsable section.
  1360  func (win *Window) TreePushNamed(type_ TreeType, name, title string, initial_open bool) bool {
  1361  	labelBounds, _, ok := win.TreePushCustom(type_, name, initial_open)
  1362  
  1363  	style := win.style()
  1364  	z := &win.ctx.Style
  1365  	out := &win.cmds
  1366  
  1367  	var text textWidget
  1368  	if type_ == TreeTab {
  1369  		var background *nstyle.Item = &z.Tab.Background
  1370  		if background.Type == nstyle.ItemImage {
  1371  			text.Background = color.RGBA{0, 0, 0, 0}
  1372  		} else {
  1373  			text.Background = background.Data.Color
  1374  		}
  1375  	} else {
  1376  		text.Background = style.Background
  1377  	}
  1378  
  1379  	text.Text = z.Tab.Text
  1380  	widgetText(out, labelBounds, title, &text, "LC", z.Font)
  1381  
  1382  	return ok
  1383  }
  1384  
  1385  func (win *Window) TreePushCustom(type_ TreeType, name string, initial_open bool) (bounds rect.Rect, out *command.Buffer, ok bool) {
  1386  	/* cache some data */
  1387  	layout := win.layout
  1388  	style := &win.ctx.Style
  1389  	panel_padding := win.style().Padding
  1390  
  1391  	if type_ == TreeTab {
  1392  		/* calculate header bounds and draw background */
  1393  		panelLayout(win.ctx, win, FontHeight(style.Font)+2*style.Tab.Padding.Y, 1, 0)
  1394  		win.layout.Row.Type = layoutDynamicFixed
  1395  		win.layout.Row.ItemWidth = 0
  1396  		win.layout.Row.ItemRatio = 0.0
  1397  		win.layout.Row.Ratio = nil
  1398  		win.layout.Row.ItemOffset = 0
  1399  		win.layout.Row.Filled = 0
  1400  	}
  1401  
  1402  	widget_state, header, _ := win.widget()
  1403  
  1404  	/* find or create tab persistent state (open/closed) */
  1405  
  1406  	node := win.curNode.Children[name]
  1407  	if node == nil {
  1408  		node = createTreeNode(initial_open, win.curNode)
  1409  		win.curNode.Children[name] = node
  1410  	}
  1411  
  1412  	/* update node state */
  1413  	in := &Input{}
  1414  	if win.toplevel() {
  1415  		if widget_state {
  1416  			in = &win.ctx.Input
  1417  			in.Mouse.clip = win.cmds.Clip
  1418  		}
  1419  	}
  1420  
  1421  	ws := win.widgets.PrevState(header)
  1422  	if buttonBehaviorDo(&ws, header, in, false) {
  1423  		node.Open = !node.Open
  1424  	}
  1425  
  1426  	/* calculate the triangle bounds */
  1427  	var sym rect.Rect
  1428  	sym.H = FontHeight(style.Font)
  1429  	sym.W = sym.H
  1430  	sym.Y = header.Y + style.Tab.Padding.Y
  1431  	sym.X = header.X + panel_padding.X + style.Tab.Padding.X
  1432  
  1433  	win.widgets.Add(ws, header)
  1434  	labelBounds := drawTreeNode(win, win.style(), type_, header, sym)
  1435  
  1436  	/* calculate the triangle points and draw triangle */
  1437  	symbolType := style.Tab.SymMaximize
  1438  	if node.Open {
  1439  		symbolType = style.Tab.SymMinimize
  1440  	}
  1441  	styleButton := &style.Tab.NodeButton
  1442  	if type_ == TreeTab {
  1443  		styleButton = &style.Tab.TabButton
  1444  	}
  1445  	doButton(win, label.S(symbolType), sym, styleButton, in, false)
  1446  
  1447  	out = &win.cmds
  1448  	if !widget_state {
  1449  		out = nil
  1450  	}
  1451  
  1452  	/* increase x-axis cursor widget position pointer */
  1453  	if node.Open {
  1454  		layout.AtX = header.X + layout.Offset.X + style.Tab.Indent
  1455  		layout.Width = max(layout.Width, 2*panel_padding.X)
  1456  		layout.Width -= (style.Tab.Indent + panel_padding.X)
  1457  		layout.Row.TreeDepth++
  1458  		win.curNode = node
  1459  		return labelBounds, out, true
  1460  	} else {
  1461  		return labelBounds, out, false
  1462  	}
  1463  }
  1464  
  1465  func (win *Window) treeOpenClose(open bool, path []string) {
  1466  	node := win.curNode
  1467  	for i := range path {
  1468  		var ok bool
  1469  		node, ok = node.Children[path[i]]
  1470  		if !ok {
  1471  			return
  1472  		}
  1473  	}
  1474  	if node != nil {
  1475  		node.Open = open
  1476  	}
  1477  }
  1478  
  1479  // Opens the collapsable section specified by path
  1480  func (win *Window) TreeOpen(path ...string) {
  1481  	win.treeOpenClose(true, path)
  1482  }
  1483  
  1484  // Closes the collapsable section specified by path
  1485  func (win *Window) TreeClose(path ...string) {
  1486  	win.treeOpenClose(false, path)
  1487  }
  1488  
  1489  // Returns true if the specified node is open
  1490  func (win *Window) TreeIsOpen(name string) bool {
  1491  	node := win.curNode.Children[name]
  1492  	if node != nil {
  1493  		return node.Open
  1494  	}
  1495  	return false
  1496  }
  1497  
  1498  // TreePop signals that the program is done adding elements to the
  1499  // current collapsable section.
  1500  func (win *Window) TreePop() {
  1501  	layout := win.layout
  1502  	panel_padding := win.style().Padding
  1503  	layout.AtX -= panel_padding.X + win.ctx.Style.Tab.Indent
  1504  	layout.Width += panel_padding.X + win.ctx.Style.Tab.Indent
  1505  	if layout.Row.TreeDepth == 0 {
  1506  		panic("TreePop called without opened tree nodes")
  1507  	}
  1508  	win.curNode = win.curNode.Parent
  1509  	layout.Row.TreeDepth--
  1510  }
  1511  
  1512  ///////////////////////////////////////////////////////////////////////////////////
  1513  // NON-INTERACTIVE WIDGETS
  1514  ///////////////////////////////////////////////////////////////////////////////////
  1515  
  1516  // LabelColored draws a text label with the specified background color.
  1517  func (win *Window) LabelColored(str string, alignment label.Align, color color.RGBA) {
  1518  	var bounds rect.Rect
  1519  	var text textWidget
  1520  
  1521  	style := &win.ctx.Style
  1522  	fitting := panelAllocSpace(&bounds, win)
  1523  	item_padding := style.Text.Padding
  1524  	if fitting != nil {
  1525  		fitting(2*item_padding.X + FontWidth(win.ctx.Style.Font, str))
  1526  	}
  1527  
  1528  	text.Padding.X = item_padding.X
  1529  	text.Padding.Y = item_padding.Y
  1530  	text.Background = win.style().Background
  1531  	text.Text = color
  1532  	win.widgets.Add(nstyle.WidgetStateInactive, bounds)
  1533  	widgetText(&win.cmds, bounds, str, &text, alignment, win.ctx.Style.Font)
  1534  
  1535  }
  1536  
  1537  // LabelWrapColored draws a text label with the specified background
  1538  // color autowrappping the text.
  1539  func (win *Window) LabelWrapColored(str string, color color.RGBA) {
  1540  	var bounds rect.Rect
  1541  	var text textWidget
  1542  
  1543  	style := &win.ctx.Style
  1544  	panelAllocSpace(&bounds, win)
  1545  	item_padding := style.Text.Padding
  1546  
  1547  	text.Padding.X = item_padding.X
  1548  	text.Padding.Y = item_padding.Y
  1549  	text.Background = win.style().Background
  1550  	text.Text = color
  1551  	win.widgets.Add(nstyle.WidgetStateInactive, bounds)
  1552  	widgetTextWrap(&win.cmds, bounds, []rune(str), &text, win.ctx.Style.Font)
  1553  }
  1554  
  1555  // Label draws a text label.
  1556  func (win *Window) Label(str string, alignment label.Align) {
  1557  	win.LabelColored(str, alignment, win.ctx.Style.Text.Color)
  1558  }
  1559  
  1560  // LabelWrap draws a text label, autowrapping its contents.
  1561  func (win *Window) LabelWrap(str string) {
  1562  	win.LabelWrapColored(str, win.ctx.Style.Text.Color)
  1563  }
  1564  
  1565  // Image draws an image.
  1566  func (win *Window) Image(img *image.RGBA) {
  1567  	s, bounds, fitting := win.widget()
  1568  	if fitting != nil {
  1569  		fitting(img.Bounds().Dx())
  1570  	}
  1571  	if !s {
  1572  		return
  1573  	}
  1574  	win.widgets.Add(nstyle.WidgetStateInactive, bounds)
  1575  	win.cmds.DrawImage(bounds, img)
  1576  }
  1577  
  1578  // Spacing adds empty space
  1579  func (win *Window) Spacing(cols int) {
  1580  	for i := 0; i < cols; i++ {
  1581  		win.widget()
  1582  	}
  1583  }
  1584  
  1585  // CustomState returns the widget state of a custom widget.
  1586  func (win *Window) CustomState() nstyle.WidgetStates {
  1587  	bounds := win.WidgetBounds()
  1588  	s := true
  1589  	if !win.layout.Clip.Intersect(&bounds) {
  1590  		s = false
  1591  	}
  1592  
  1593  	ws := win.widgets.PrevState(bounds)
  1594  	basicWidgetStateControl(&ws, win.inputMaybe(s), bounds)
  1595  	return ws
  1596  }
  1597  
  1598  // Custom adds a custom widget.
  1599  func (win *Window) Custom(state nstyle.WidgetStates) (bounds rect.Rect, out *command.Buffer) {
  1600  	var s bool
  1601  
  1602  	if s, bounds, _ = win.widget(); !s {
  1603  		return
  1604  	}
  1605  	prevstate := win.widgets.PrevState(bounds)
  1606  	exitstate := basicWidgetStateControl(&prevstate, win.inputMaybe(s), bounds)
  1607  	if state != nstyle.WidgetStateActive {
  1608  		state = exitstate
  1609  	}
  1610  	win.widgets.Add(state, bounds)
  1611  	return bounds, &win.cmds
  1612  }
  1613  
  1614  func (win *Window) Commands() *command.Buffer {
  1615  	return &win.cmds
  1616  }
  1617  
  1618  ///////////////////////////////////////////////////////////////////////////////////
  1619  // BUTTON
  1620  ///////////////////////////////////////////////////////////////////////////////////
  1621  
  1622  func buttonBehaviorDo(state *nstyle.WidgetStates, r rect.Rect, i *Input, repeat bool) (ret bool) {
  1623  	exitstate := basicWidgetStateControl(state, i, r)
  1624  
  1625  	if *state == nstyle.WidgetStateActive {
  1626  		if exitstate == nstyle.WidgetStateHovered {
  1627  			if repeat {
  1628  				ret = i.Mouse.Down(pointer.ButtonLeft)
  1629  			} else {
  1630  				ret = i.Mouse.Released(pointer.ButtonLeft)
  1631  			}
  1632  		}
  1633  		if !i.Mouse.Down(pointer.ButtonLeft) {
  1634  			*state = exitstate
  1635  		}
  1636  	}
  1637  
  1638  	return ret
  1639  }
  1640  
  1641  func symbolWidth(sym label.SymbolType, font font.Face) int {
  1642  	switch sym {
  1643  	case label.SymbolX:
  1644  		return FontWidth(font, "x")
  1645  	case label.SymbolUnderscore:
  1646  		return FontWidth(font, "_")
  1647  	case label.SymbolPlus:
  1648  		return FontWidth(font, "+")
  1649  	case label.SymbolMinus:
  1650  		return FontWidth(font, "-")
  1651  	default:
  1652  		return FontWidth(font, "M")
  1653  	}
  1654  }
  1655  
  1656  func buttonWidth(lbl label.Label, style *nstyle.Button, font font.Face) int {
  1657  	w := 2*style.Padding.X + 2*style.TouchPadding.X + 2*style.Border
  1658  	switch lbl.Kind {
  1659  	case label.TextLabel:
  1660  		w += FontWidth(font, lbl.Text)
  1661  	case label.SymbolLabel:
  1662  		w += symbolWidth(lbl.Symbol, font)
  1663  	case label.ImageLabel:
  1664  		w += lbl.Img.Bounds().Dx() + 2*style.ImagePadding.X
  1665  	case label.SymbolTextLabel:
  1666  		w += FontWidth(font, lbl.Text) + symbolWidth(lbl.Symbol, font) + 2*style.Padding.X
  1667  	case label.ImageTextLabel:
  1668  	}
  1669  	return w
  1670  }
  1671  
  1672  func doButton(win *Window, lbl label.Label, r rect.Rect, style *nstyle.Button, in *Input, repeat bool) bool {
  1673  	out := win.widgets
  1674  	if lbl.Kind == label.ColorLabel {
  1675  		button := *style
  1676  		button.Normal = nstyle.MakeItemColor(lbl.Color)
  1677  		button.Hover = nstyle.MakeItemColor(lbl.Color)
  1678  		button.Active = nstyle.MakeItemColor(lbl.Color)
  1679  		button.Padding = image.Point{0, 0}
  1680  		style = &button
  1681  	}
  1682  
  1683  	/* calculate button content space */
  1684  	var content rect.Rect
  1685  	content.X = r.X + style.Padding.X + style.Border
  1686  	content.Y = r.Y + style.Padding.Y + style.Border
  1687  	content.W = r.W - 2*style.Padding.X + style.Border
  1688  	content.H = r.H - 2*style.Padding.Y + style.Border
  1689  
  1690  	/* execute button behavior */
  1691  	var bounds rect.Rect
  1692  	bounds.X = r.X - style.TouchPadding.X
  1693  	bounds.Y = r.Y - style.TouchPadding.Y
  1694  	bounds.W = r.W + 2*style.TouchPadding.X
  1695  	bounds.H = r.H + 2*style.TouchPadding.Y
  1696  	state := out.PrevState(bounds)
  1697  	ok := buttonBehaviorDo(&state, bounds, in, repeat)
  1698  
  1699  	switch lbl.Kind {
  1700  	case label.TextLabel:
  1701  		if lbl.Align == "" {
  1702  			lbl.Align = "CC"
  1703  		}
  1704  		out.Add(state, bounds)
  1705  		drawTextButton(win, bounds, content, state, style, lbl.Text, lbl.Align)
  1706  
  1707  	case label.SymbolLabel:
  1708  		out.Add(state, bounds)
  1709  		drawSymbolButton(win, bounds, content, state, style, lbl.Symbol)
  1710  
  1711  	case label.ImageLabel:
  1712  		content.X += style.ImagePadding.X
  1713  		content.Y += style.ImagePadding.Y
  1714  		content.W -= 2 * style.ImagePadding.X
  1715  		content.H -= 2 * style.ImagePadding.Y
  1716  
  1717  		out.Add(state, bounds)
  1718  		drawImageButton(win, bounds, content, state, style, lbl.Img)
  1719  
  1720  	case label.SymbolTextLabel:
  1721  		if lbl.Align == "" {
  1722  			lbl.Align = "CC"
  1723  		}
  1724  		font := win.ctx.Style.Font
  1725  		var tri rect.Rect
  1726  		tri.Y = content.Y + (content.H / 2) - FontHeight(font)/2
  1727  		tri.W = FontHeight(font)
  1728  		tri.H = FontHeight(font)
  1729  		if lbl.Align[0] == 'L' {
  1730  			tri.X = (content.X + content.W) - (2*style.Padding.X + tri.W)
  1731  			tri.X = max(tri.X, 0)
  1732  		} else {
  1733  			tri.X = content.X + 2*style.Padding.X
  1734  		}
  1735  
  1736  		out.Add(state, bounds)
  1737  		drawTextSymbolButton(win, bounds, content, tri, state, style, lbl.Text, lbl.Symbol)
  1738  
  1739  	case label.ImageTextLabel:
  1740  		if lbl.Align == "" {
  1741  			lbl.Align = "CC"
  1742  		}
  1743  		var icon rect.Rect
  1744  		icon.Y = bounds.Y + style.Padding.Y
  1745  		icon.H = bounds.H - 2*style.Padding.Y
  1746  		icon.W = icon.H
  1747  		if lbl.Align[0] == 'L' {
  1748  			icon.X = (bounds.X + bounds.W) - (2*style.Padding.X + icon.W)
  1749  			icon.X = max(icon.X, 0)
  1750  		} else {
  1751  			icon.X = bounds.X + 2*style.Padding.X
  1752  		}
  1753  
  1754  		icon.X += style.ImagePadding.X
  1755  		icon.Y += style.ImagePadding.Y
  1756  		icon.W -= 2 * style.ImagePadding.X
  1757  		icon.H -= 2 * style.ImagePadding.Y
  1758  
  1759  		out.Add(state, bounds)
  1760  		drawTextImageButton(win, bounds, content, icon, state, style, lbl.Text, lbl.Img)
  1761  
  1762  	case label.ColorLabel:
  1763  		out.Add(state, bounds)
  1764  		drawSymbolButton(win, bounds, bounds, state, style, label.SymbolNone)
  1765  
  1766  	}
  1767  
  1768  	return ok
  1769  }
  1770  
  1771  // Button adds a button
  1772  func (win *Window) Button(lbl label.Label, repeat bool) bool {
  1773  	style := &win.ctx.Style
  1774  	state, bounds, fitting := win.widget()
  1775  	if fitting != nil {
  1776  		buttonWidth(lbl, &style.Button, style.Font)
  1777  	}
  1778  	if !state {
  1779  		return false
  1780  	}
  1781  	in := win.inputMaybe(state)
  1782  	return doButton(win, lbl, bounds, &style.Button, in, repeat)
  1783  }
  1784  
  1785  func (win *Window) ButtonText(text string) bool {
  1786  	return win.Button(label.T(text), false)
  1787  }
  1788  
  1789  ///////////////////////////////////////////////////////////////////////////////////
  1790  // SELECTABLE
  1791  ///////////////////////////////////////////////////////////////////////////////////
  1792  
  1793  func selectableWidth(str string, style *nstyle.Selectable, font font.Face) int {
  1794  	return 2*style.Padding.X + 2*style.TouchPadding.X + FontWidth(font, str)
  1795  }
  1796  
  1797  func doSelectable(win *Window, bounds rect.Rect, str string, align label.Align, value *bool, style *nstyle.Selectable, in *Input) bool {
  1798  	if str == "" {
  1799  		return false
  1800  	}
  1801  	old_value := *value
  1802  
  1803  	/* remove padding */
  1804  	var touch rect.Rect
  1805  	touch.X = bounds.X - style.TouchPadding.X
  1806  	touch.Y = bounds.Y - style.TouchPadding.Y
  1807  	touch.W = bounds.W + style.TouchPadding.X*2
  1808  	touch.H = bounds.H + style.TouchPadding.Y*2
  1809  
  1810  	/* update button */
  1811  	state := win.widgets.PrevState(bounds)
  1812  	if buttonBehaviorDo(&state, touch, in, false) {
  1813  		*value = !*value
  1814  	}
  1815  
  1816  	win.widgets.Add(state, bounds)
  1817  	drawSelectable(win, state, style, *value, bounds, str, align)
  1818  	return old_value != *value
  1819  }
  1820  
  1821  // SelectableLabel adds a selectable label. Value is a pointer
  1822  // to a flag that will be changed to reflect the selected state of
  1823  // this label.
  1824  // Returns true when the label is clicked.
  1825  func (win *Window) SelectableLabel(str string, align label.Align, value *bool) bool {
  1826  	style := &win.ctx.Style
  1827  	state, bounds, fitting := win.widget()
  1828  	if fitting != nil {
  1829  		fitting(selectableWidth(str, &style.Selectable, style.Font))
  1830  	}
  1831  	if !state {
  1832  		return false
  1833  	}
  1834  	in := win.inputMaybe(state)
  1835  	return doSelectable(win, bounds, str, align, value, &style.Selectable, in)
  1836  }
  1837  
  1838  ///////////////////////////////////////////////////////////////////////////////////
  1839  // SCROLLBARS
  1840  ///////////////////////////////////////////////////////////////////////////////////
  1841  
  1842  type orientation int
  1843  
  1844  const (
  1845  	vertical orientation = iota
  1846  	horizontal
  1847  )
  1848  
  1849  func scrollbarBehavior(state *nstyle.WidgetStates, in *Input, scroll, cursor, empty0, empty1 rect.Rect, scroll_offset float64, target float64, scroll_step float64, o orientation) float64 {
  1850  	exitstate := basicWidgetStateControl(state, in, cursor)
  1851  
  1852  	if *state == nstyle.WidgetStateActive {
  1853  		if !in.Mouse.Down(pointer.ButtonLeft) {
  1854  			*state = exitstate
  1855  		} else {
  1856  			switch o {
  1857  			case vertical:
  1858  				pixel := in.Mouse.Delta.Y
  1859  				delta := (float64(pixel) / float64(scroll.H)) * target
  1860  				scroll_offset = clampFloat(0, scroll_offset+delta, target-float64(scroll.H))
  1861  			case horizontal:
  1862  				pixel := in.Mouse.Delta.X
  1863  				delta := (float64(pixel) / float64(scroll.W)) * target
  1864  				scroll_offset = clampFloat(0, scroll_offset+delta, target-float64(scroll.W))
  1865  			}
  1866  		}
  1867  	} else if in.Mouse.IsClickInRect(pointer.ButtonLeft, empty0) {
  1868  		switch o {
  1869  		case vertical:
  1870  			scroll_offset -= float64(scroll.H)
  1871  		case horizontal:
  1872  			scroll_offset -= float64(scroll.W)
  1873  		}
  1874  
  1875  		if scroll_offset < 0 {
  1876  			scroll_offset = 0
  1877  		}
  1878  	} else if in.Mouse.IsClickInRect(pointer.ButtonLeft, empty1) {
  1879  		var max float64
  1880  		switch o {
  1881  		case vertical:
  1882  			scroll_offset += float64(scroll.H)
  1883  			max = target - float64(scroll.H)
  1884  		case horizontal:
  1885  			scroll_offset += float64(scroll.W)
  1886  			max = target - float64(scroll.W)
  1887  		}
  1888  
  1889  		if scroll_offset > max {
  1890  			scroll_offset = max
  1891  		}
  1892  	}
  1893  
  1894  	return scroll_offset
  1895  }
  1896  
  1897  func scrollwheelBehavior(win *Window, scroll, scrollwheel_bounds rect.Rect, scroll_offset, target, scroll_step float64) float64 {
  1898  	in := win.scrollwheelInput()
  1899  
  1900  	if ((in.Mouse.ScrollDelta < 0) || (in.Mouse.ScrollDelta > 0)) && in.Mouse.HoveringRect(scrollwheel_bounds) {
  1901  		/* update cursor by mouse scrolling */
  1902  		old_scroll_offset := scroll_offset
  1903  		scroll_offset = scroll_offset + scroll_step*float64(-in.Mouse.ScrollDelta)
  1904  		scroll_offset = clampFloat(0, scroll_offset, target-float64(scroll.H))
  1905  		used_delta := (scroll_offset - old_scroll_offset) / scroll_step
  1906  		residual := float64(in.Mouse.ScrollDelta) + used_delta
  1907  		if residual < 0 {
  1908  			in.Mouse.ScrollDelta = int(math.Ceil(residual))
  1909  		} else {
  1910  			in.Mouse.ScrollDelta = int(math.Floor(residual))
  1911  		}
  1912  	}
  1913  	return scroll_offset
  1914  }
  1915  
  1916  func doScrollbarv(win *Window, scroll, scrollwheel_bounds rect.Rect, offset float64, target float64, step float64, button_pixel_inc float64, style *nstyle.Scrollbar, in *Input, font font.Face) float64 {
  1917  	var cursor rect.Rect
  1918  	var scroll_step float64
  1919  	var scroll_offset float64
  1920  	var scroll_off float64
  1921  	var scroll_ratio float64
  1922  
  1923  	if scroll.W < 1 {
  1924  		scroll.W = 1
  1925  	}
  1926  
  1927  	if scroll.H < 2*scroll.W {
  1928  		scroll.H = 2 * scroll.W
  1929  	}
  1930  
  1931  	if target <= float64(scroll.H) {
  1932  		return 0
  1933  	}
  1934  
  1935  	/* optional scrollbar buttons */
  1936  	if style.ShowButtons {
  1937  		var button rect.Rect
  1938  		button.X = scroll.X
  1939  		button.W = scroll.W
  1940  		button.H = scroll.W
  1941  
  1942  		scroll_h := float64(scroll.H - 2*button.H)
  1943  		scroll_step = minFloat(step, button_pixel_inc)
  1944  
  1945  		/* decrement button */
  1946  		button.Y = scroll.Y
  1947  
  1948  		if doButton(win, label.S(style.DecSymbol), button, &style.DecButton, in, true) {
  1949  			offset = offset - scroll_step
  1950  		}
  1951  
  1952  		/* increment button */
  1953  		button.Y = scroll.Y + scroll.H - button.H
  1954  
  1955  		if doButton(win, label.S(style.IncSymbol), button, &style.IncButton, in, true) {
  1956  			offset = offset + scroll_step
  1957  		}
  1958  
  1959  		scroll.Y = scroll.Y + button.H
  1960  		scroll.H = int(scroll_h)
  1961  	}
  1962  
  1963  	/* calculate scrollbar constants */
  1964  	scroll_step = minFloat(step, float64(scroll.H))
  1965  
  1966  	scroll_offset = clampFloat(0, offset, target-float64(scroll.H))
  1967  	scroll_ratio = float64(scroll.H) / target
  1968  	scroll_off = scroll_offset / target
  1969  
  1970  	originalScroll := scroll
  1971  
  1972  	/* calculate scrollbar cursor bounds */
  1973  	cursor.H = int(scroll_ratio*float64(scroll.H) - 2)
  1974  	if minh := FontHeight(font); cursor.H < minh {
  1975  		cursor.H = minh
  1976  		scroll.H -= minh
  1977  	}
  1978  	cursor.Y = scroll.Y + int(scroll_off*float64(scroll.H)) + 1
  1979  	cursor.W = scroll.W - 2
  1980  	cursor.X = scroll.X + 1
  1981  
  1982  	emptyNorth := scroll
  1983  	emptyNorth.H = cursor.Y - scroll.Y
  1984  
  1985  	emptySouth := scroll
  1986  	emptySouth.Y = cursor.Y + cursor.H
  1987  	emptySouth.H = (scroll.Y + scroll.H) - emptySouth.Y
  1988  
  1989  	/* update scrollbar */
  1990  	out := &win.widgets
  1991  	state := out.PrevState(scroll)
  1992  	scroll_offset = scrollbarBehavior(&state, in, scroll, cursor, emptyNorth, emptySouth, scroll_offset, target, scroll_step, vertical)
  1993  	scroll_offset = scrollwheelBehavior(win, originalScroll, scrollwheel_bounds, scroll_offset, target, scroll_step)
  1994  
  1995  	scroll_off = scroll_offset / target
  1996  	cursor.Y = scroll.Y + int(scroll_off*float64(scroll.H))
  1997  
  1998  	out.Add(state, scroll)
  1999  	drawScrollbar(win, state, style, originalScroll, cursor)
  2000  
  2001  	return scroll_offset
  2002  }
  2003  
  2004  func doScrollbarh(win *Window, scroll rect.Rect, offset float64, target float64, step float64, button_pixel_inc float64, style *nstyle.Scrollbar, in *Input, font font.Face) float64 {
  2005  	var cursor rect.Rect
  2006  	var scroll_step float64
  2007  	var scroll_offset float64
  2008  	var scroll_off float64
  2009  	var scroll_ratio float64
  2010  
  2011  	/* scrollbar background */
  2012  	if scroll.H < 1 {
  2013  		scroll.H = 1
  2014  	}
  2015  
  2016  	if scroll.W < 2*scroll.H {
  2017  		scroll.Y = 2 * scroll.H
  2018  	}
  2019  
  2020  	if target <= float64(scroll.W) {
  2021  		return 0
  2022  	}
  2023  
  2024  	/* optional scrollbar buttons */
  2025  	if style.ShowButtons {
  2026  		var scroll_w float64
  2027  		var button rect.Rect
  2028  		button.Y = scroll.Y
  2029  		button.W = scroll.H
  2030  		button.H = scroll.H
  2031  
  2032  		scroll_w = float64(scroll.W - 2*button.W)
  2033  		scroll_step = minFloat(step, button_pixel_inc)
  2034  
  2035  		/* decrement button */
  2036  		button.X = scroll.X
  2037  
  2038  		if doButton(win, label.S(style.DecSymbol), button, &style.DecButton, in, true) {
  2039  			offset = offset - scroll_step
  2040  		}
  2041  
  2042  		/* increment button */
  2043  		button.X = scroll.X + scroll.W - button.W
  2044  
  2045  		if doButton(win, label.S(style.IncSymbol), button, &style.IncButton, in, true) {
  2046  			offset = offset + scroll_step
  2047  		}
  2048  
  2049  		scroll.X = scroll.X + button.W
  2050  		scroll.W = int(scroll_w)
  2051  	}
  2052  
  2053  	/* calculate scrollbar constants */
  2054  	scroll_step = minFloat(step, float64(scroll.W))
  2055  
  2056  	scroll_offset = clampFloat(0, offset, target-float64(scroll.W))
  2057  	scroll_ratio = float64(scroll.W) / target
  2058  	scroll_off = scroll_offset / target
  2059  
  2060  	/* calculate cursor bounds */
  2061  	cursor.W = int(scroll_ratio*float64(scroll.W) - 2)
  2062  	cursor.X = scroll.X + int(scroll_off*float64(scroll.W)) + 1
  2063  	cursor.H = scroll.H - 2
  2064  	cursor.Y = scroll.Y + 1
  2065  
  2066  	emptyWest := scroll
  2067  	emptyWest.W = cursor.X - scroll.X
  2068  
  2069  	emptyEast := scroll
  2070  	emptyEast.X = cursor.X + cursor.W
  2071  	emptyEast.W = (scroll.X + scroll.W) - emptyEast.X
  2072  
  2073  	/* update scrollbar */
  2074  	out := &win.widgets
  2075  	state := out.PrevState(scroll)
  2076  	scroll_offset = scrollbarBehavior(&state, in, scroll, cursor, emptyWest, emptyEast, scroll_offset, target, scroll_step, horizontal)
  2077  
  2078  	scroll_off = scroll_offset / target
  2079  	cursor.X = scroll.X + int(scroll_off*float64(scroll.W))
  2080  
  2081  	out.Add(state, scroll)
  2082  	drawScrollbar(win, state, style, scroll, cursor)
  2083  
  2084  	return scroll_offset
  2085  }
  2086  
  2087  ///////////////////////////////////////////////////////////////////////////////////
  2088  // TOGGLE BOXES
  2089  ///////////////////////////////////////////////////////////////////////////////////
  2090  
  2091  type toggleType int
  2092  
  2093  const (
  2094  	toggleCheck = toggleType(iota)
  2095  	toggleOption
  2096  )
  2097  
  2098  func toggleBehavior(in *Input, b rect.Rect, state *nstyle.WidgetStates, active bool) bool {
  2099  	//TODO: rewrite using basicWidgetStateControl
  2100  	if in.Mouse.HoveringRect(b) {
  2101  		*state = nstyle.WidgetStateHovered
  2102  	} else {
  2103  		*state = nstyle.WidgetStateInactive
  2104  	}
  2105  	if *state == nstyle.WidgetStateHovered && in.Mouse.Clicked(pointer.ButtonLeft, b) {
  2106  		*state = nstyle.WidgetStateActive
  2107  		active = !active
  2108  	}
  2109  
  2110  	return active
  2111  }
  2112  
  2113  func toggleWidth(str string, type_ toggleType, style *nstyle.Toggle, font font.Face) int {
  2114  	w := 2*style.Padding.X + 2*style.TouchPadding.X + FontWidth(font, str)
  2115  	sw := FontHeight(font) + style.Padding.X
  2116  	w += sw
  2117  	if type_ == toggleOption {
  2118  		w += sw / 4
  2119  	} else {
  2120  		w += sw / 6
  2121  	}
  2122  	return w
  2123  }
  2124  
  2125  func doToggle(win *Window, r rect.Rect, active bool, str string, type_ toggleType, style *nstyle.Toggle, in *Input, font font.Face) bool {
  2126  	var bounds rect.Rect
  2127  	var select_ rect.Rect
  2128  	var cursor rect.Rect
  2129  	var label rect.Rect
  2130  	var cursor_pad int
  2131  
  2132  	r.W = max(r.W, FontHeight(font)+2*style.Padding.X)
  2133  	r.H = max(r.H, FontHeight(font)+2*style.Padding.Y)
  2134  
  2135  	/* add additional touch padding for touch screen devices */
  2136  	bounds.X = r.X - style.TouchPadding.X
  2137  	bounds.Y = r.Y - style.TouchPadding.Y
  2138  	bounds.W = r.W + 2*style.TouchPadding.X
  2139  	bounds.H = r.H + 2*style.TouchPadding.Y
  2140  
  2141  	/* calculate the selector space */
  2142  	select_.W = min(r.H, FontHeight(font)+style.Padding.Y)
  2143  
  2144  	select_.H = select_.W
  2145  	select_.X = r.X + style.Padding.X
  2146  	select_.Y = r.Y + (r.H/2 - select_.H/2)
  2147  	if type_ == toggleOption {
  2148  		cursor_pad = select_.W / 4
  2149  	} else {
  2150  		cursor_pad = select_.H / 6
  2151  	}
  2152  
  2153  	/* calculate the bounds of the cursor inside the selector */
  2154  	select_.H = max(select_.W, cursor_pad*2)
  2155  
  2156  	cursor.H = select_.H - cursor_pad*2
  2157  	cursor.W = cursor.H
  2158  	cursor.X = select_.X + cursor_pad
  2159  	cursor.Y = select_.Y + cursor_pad
  2160  
  2161  	/* label behind the selector */
  2162  	label.X = r.X + select_.W + style.Padding.X*2
  2163  	label.Y = select_.Y
  2164  	label.W = max(r.X+r.W, label.X+style.Padding.X)
  2165  	label.W -= (label.X + style.Padding.X)
  2166  	label.H = select_.W
  2167  
  2168  	/* update selector */
  2169  	state := win.widgets.PrevState(bounds)
  2170  	active = toggleBehavior(in, bounds, &state, active)
  2171  
  2172  	win.widgets.Add(state, r)
  2173  	drawTogglebox(win, type_, state, style, active, label, select_, cursor, str)
  2174  
  2175  	return active
  2176  }
  2177  
  2178  // OptionText adds a radio button to win. If is_active is true the
  2179  // radio button will be drawn selected. Returns true when the button
  2180  // is clicked once.
  2181  // You are responsible for ensuring that only one radio button is selected at once.
  2182  func (win *Window) OptionText(text string, is_active bool) bool {
  2183  	style := &win.ctx.Style
  2184  	state, bounds, fitting := win.widget()
  2185  	if fitting != nil {
  2186  		fitting(toggleWidth(text, toggleOption, &style.Option, style.Font))
  2187  	}
  2188  	if !state {
  2189  		return false
  2190  	}
  2191  	in := win.inputMaybe(state)
  2192  	is_active = doToggle(win, bounds, is_active, text, toggleOption, &style.Option, in, style.Font)
  2193  	return is_active
  2194  }
  2195  
  2196  // CheckboxText adds a checkbox button to win. Active will contain
  2197  // the checkbox value.
  2198  // Returns true when value changes.
  2199  func (win *Window) CheckboxText(text string, active *bool) bool {
  2200  	state, bounds, fitting := win.widget()
  2201  	if fitting != nil {
  2202  		fitting(toggleWidth(text, toggleCheck, &win.ctx.Style.Checkbox, win.ctx.Style.Font))
  2203  	}
  2204  	if !state {
  2205  		return false
  2206  	}
  2207  	in := win.inputMaybe(state)
  2208  	old_active := *active
  2209  	*active = doToggle(win, bounds, *active, text, toggleCheck, &win.ctx.Style.Checkbox, in, win.ctx.Style.Font)
  2210  	return *active != old_active
  2211  }
  2212  
  2213  ///////////////////////////////////////////////////////////////////////////////////
  2214  // SLIDER
  2215  ///////////////////////////////////////////////////////////////////////////////////
  2216  
  2217  func sliderBehavior(state *nstyle.WidgetStates, cursor *rect.Rect, in *Input, style *nstyle.Slider, bounds rect.Rect, slider_min float64, slider_value float64, slider_max float64, slider_step float64, slider_steps int) float64 {
  2218  	exitstate := basicWidgetStateControl(state, in, bounds)
  2219  
  2220  	if *state == nstyle.WidgetStateActive {
  2221  		if !in.Mouse.Down(pointer.ButtonLeft) {
  2222  			*state = exitstate
  2223  		} else {
  2224  			d := in.Mouse.Pos.X - (cursor.X + cursor.W/2.0)
  2225  			var pxstep float64 = float64(bounds.W-(2*style.Padding.X)) / float64(slider_steps)
  2226  
  2227  			if math.Abs(float64(d)) >= pxstep {
  2228  				steps := float64(int(math.Abs(float64(d)) / pxstep))
  2229  				if d > 0 {
  2230  					slider_value += slider_step * steps
  2231  				} else {
  2232  					slider_value -= slider_step * steps
  2233  				}
  2234  				slider_value = clampFloat(slider_min, slider_value, slider_max)
  2235  			}
  2236  		}
  2237  	}
  2238  
  2239  	return slider_value
  2240  }
  2241  
  2242  func doSlider(win *Window, bounds rect.Rect, minval float64, val float64, maxval float64, step float64, style *nstyle.Slider, in *Input) float64 {
  2243  	var slider_range float64
  2244  	var cursor_offset float64
  2245  	var cursor rect.Rect
  2246  
  2247  	/* remove padding from slider bounds */
  2248  	bounds.X = bounds.X + style.Padding.X
  2249  
  2250  	bounds.Y = bounds.Y + style.Padding.Y
  2251  	bounds.H = max(bounds.H, 2*style.Padding.Y)
  2252  	bounds.W = max(bounds.W, 1+bounds.H+2*style.Padding.X)
  2253  	bounds.H -= 2 * style.Padding.Y
  2254  	bounds.W -= 2 * style.Padding.Y
  2255  
  2256  	/* optional buttons */
  2257  	if style.ShowButtons {
  2258  		var button rect.Rect
  2259  		button.Y = bounds.Y
  2260  		button.W = bounds.H
  2261  		button.H = bounds.H
  2262  
  2263  		/* decrement button */
  2264  		button.X = bounds.X
  2265  
  2266  		if doButton(win, label.S(style.DecSymbol), button, &style.DecButton, in, false) {
  2267  			val -= step
  2268  		}
  2269  
  2270  		/* increment button */
  2271  		button.X = (bounds.X + bounds.W) - button.W
  2272  
  2273  		if doButton(win, label.S(style.IncSymbol), button, &style.IncButton, in, false) {
  2274  			val += step
  2275  		}
  2276  
  2277  		bounds.X = bounds.X + button.W + style.Spacing.X
  2278  		bounds.W = bounds.W - (2*button.W + 2*style.Spacing.X)
  2279  	}
  2280  
  2281  	/* make sure the provided values are correct */
  2282  	slider_value := clampFloat(minval, val, maxval)
  2283  	slider_range = maxval - minval
  2284  	slider_steps := int(slider_range / step)
  2285  
  2286  	/* calculate slider virtual cursor bounds */
  2287  	cursor_offset = (slider_value - minval) / step
  2288  
  2289  	cursor.H = bounds.H
  2290  	tempW := float64(bounds.W) / float64(slider_steps+1)
  2291  	cursor.W = int(tempW)
  2292  	cursor.X = bounds.X + int((tempW * cursor_offset))
  2293  	cursor.Y = bounds.Y
  2294  
  2295  	out := &win.widgets
  2296  	state := out.PrevState(bounds)
  2297  	slider_value = sliderBehavior(&state, &cursor, in, style, bounds, minval, slider_value, maxval, step, slider_steps)
  2298  	out.Add(state, bounds)
  2299  	drawSlider(win, state, style, bounds, cursor, minval, slider_value, maxval)
  2300  	return slider_value
  2301  }
  2302  
  2303  // Adds a slider with a floating point value to win.
  2304  // Returns true when the slider's value is changed.
  2305  func (win *Window) SliderFloat(min_value float64, value *float64, max_value float64, value_step float64) bool {
  2306  	style := &win.ctx.Style
  2307  	state, bounds, _ := win.widget()
  2308  	if !state {
  2309  		return false
  2310  	}
  2311  	in := win.inputMaybe(state)
  2312  
  2313  	old_value := *value
  2314  	*value = doSlider(win, bounds, min_value, old_value, max_value, value_step, &style.Slider, in)
  2315  	return old_value > *value || old_value < *value
  2316  }
  2317  
  2318  // Adds a slider with an integer value to win.
  2319  // Returns true when the slider's value changes.
  2320  func (win *Window) SliderInt(min int, val *int, max int, step int) bool {
  2321  	value := float64(*val)
  2322  	ret := win.SliderFloat(float64(min), &value, float64(max), float64(step))
  2323  	*val = int(value)
  2324  	return ret
  2325  }
  2326  
  2327  ///////////////////////////////////////////////////////////////////////////////////
  2328  // PROGRESS BAR
  2329  ///////////////////////////////////////////////////////////////////////////////////
  2330  
  2331  func progressBehavior(state *nstyle.WidgetStates, in *Input, r rect.Rect, maxval int, value int, modifiable bool) int {
  2332  	if !modifiable {
  2333  		*state = nstyle.WidgetStateInactive
  2334  		return value
  2335  	}
  2336  
  2337  	exitstate := basicWidgetStateControl(state, in, r)
  2338  
  2339  	if *state == nstyle.WidgetStateActive {
  2340  		if !in.Mouse.Down(pointer.ButtonLeft) {
  2341  			*state = exitstate
  2342  		} else {
  2343  			ratio := maxFloat(0, float64(in.Mouse.Pos.X-r.X)) / float64(r.W)
  2344  			value = int(float64(maxval) * ratio)
  2345  			if value < 0 {
  2346  				value = 0
  2347  			}
  2348  		}
  2349  	}
  2350  
  2351  	if maxval > 0 && value > maxval {
  2352  		value = maxval
  2353  	}
  2354  	return value
  2355  }
  2356  
  2357  func doProgress(win *Window, bounds rect.Rect, value int, maxval int, modifiable bool, style *nstyle.Progress, in *Input) int {
  2358  	var prog_scale float64
  2359  	var cursor rect.Rect
  2360  
  2361  	/* calculate progressbar cursor */
  2362  	cursor = padRect(bounds, style.Padding)
  2363  	prog_scale = float64(value) / float64(maxval)
  2364  	cursor.W = int(float64(cursor.W) * prog_scale)
  2365  
  2366  	/* update progressbar */
  2367  	if value > maxval {
  2368  		value = maxval
  2369  	}
  2370  
  2371  	state := win.widgets.PrevState(bounds)
  2372  	value = progressBehavior(&state, in, bounds, maxval, value, modifiable)
  2373  	win.widgets.Add(state, bounds)
  2374  	drawProgress(win, state, style, bounds, cursor, value, maxval)
  2375  
  2376  	return value
  2377  }
  2378  
  2379  // Adds a progress bar to win. if is_modifiable is true the progress
  2380  // bar will be user modifiable through click-and-drag.
  2381  // Returns true when the progress bar values is modified.
  2382  func (win *Window) Progress(cur *int, maxval int, is_modifiable bool) bool {
  2383  	style := &win.ctx.Style
  2384  	state, bounds, _ := win.widget()
  2385  	if !state {
  2386  		return false
  2387  	}
  2388  
  2389  	in := win.inputMaybe(state)
  2390  	old_value := *cur
  2391  	*cur = doProgress(win, bounds, *cur, maxval, is_modifiable, &style.Progress, in)
  2392  	return *cur != old_value
  2393  }
  2394  
  2395  ///////////////////////////////////////////////////////////////////////////////////
  2396  // PROPERTY
  2397  ///////////////////////////////////////////////////////////////////////////////////
  2398  
  2399  type FilterFunc func(c rune) bool
  2400  
  2401  func FilterDefault(c rune) bool {
  2402  	return true
  2403  }
  2404  
  2405  func FilterDecimal(c rune) bool {
  2406  	return !((c < '0' || c > '9') && c != '-')
  2407  }
  2408  
  2409  func FilterFloat(c rune) bool {
  2410  	return !((c < '0' || c > '9') && c != '.' && c != '-')
  2411  }
  2412  
  2413  func (ed *TextEditor) propertyBehavior(ws *nstyle.WidgetStates, in *Input, property rect.Rect, label rect.Rect, edit rect.Rect, empty rect.Rect) (drag bool, delta int) {
  2414  	if ed.propertyStatus == propertyDefault {
  2415  		if buttonBehaviorDo(ws, edit, in, false) {
  2416  			ed.propertyStatus = propertyEdit
  2417  		} else if in.Mouse.IsClickDownInRect(pointer.ButtonLeft, label, true) {
  2418  			ed.propertyStatus = propertyDrag
  2419  		} else if in.Mouse.IsClickDownInRect(pointer.ButtonLeft, empty, true) {
  2420  			ed.propertyStatus = propertyDrag
  2421  		}
  2422  	}
  2423  
  2424  	if ed.propertyStatus == propertyDrag {
  2425  		if in.Mouse.Released(pointer.ButtonLeft) {
  2426  			ed.propertyStatus = propertyDefault
  2427  		} else {
  2428  			delta = in.Mouse.Delta.X
  2429  			drag = true
  2430  		}
  2431  	}
  2432  
  2433  	if ed.propertyStatus == propertyDefault {
  2434  		if in.Mouse.HoveringRect(property) {
  2435  			*ws = nstyle.WidgetStateHovered
  2436  		} else {
  2437  			*ws = nstyle.WidgetStateInactive
  2438  		}
  2439  	} else {
  2440  		*ws = nstyle.WidgetStateActive
  2441  	}
  2442  
  2443  	return
  2444  }
  2445  
  2446  type doPropertyRet int
  2447  
  2448  const (
  2449  	doPropertyStay = doPropertyRet(iota)
  2450  	doPropertyInc
  2451  	doPropertyDec
  2452  	doPropertyDrag
  2453  	doPropertySet
  2454  )
  2455  
  2456  func digits(n int) int {
  2457  	if n <= 0 {
  2458  		n = 1
  2459  	}
  2460  	return int(math.Log2(float64(n)) + 1)
  2461  }
  2462  
  2463  func propertyWidth(max int, style *nstyle.Property, font font.Face) int {
  2464  	return 2*FontHeight(font)/2 + digits(max)*FontWidth(font, "0") + 4*style.Padding.X + 2*style.Border
  2465  }
  2466  
  2467  func (win *Window) doProperty(property rect.Rect, name string, text string, filter FilterFunc, in *Input) (ret doPropertyRet, delta int, ed *TextEditor) {
  2468  	ret = doPropertyStay
  2469  	style := &win.ctx.Style.Property
  2470  	font := win.ctx.Style.Font
  2471  
  2472  	// left decrement button
  2473  	var left rect.Rect
  2474  	left.H = FontHeight(font) / 2
  2475  	left.W = left.H
  2476  	left.X = property.X + style.Border + style.Padding.X
  2477  	left.Y = property.Y + style.Border + property.H/2.0 - left.H/2
  2478  
  2479  	// text label
  2480  	size := FontWidth(font, name)
  2481  	var lblrect rect.Rect
  2482  	lblrect.X = left.X + left.W + style.Padding.X
  2483  	lblrect.W = size + 2*style.Padding.X
  2484  	lblrect.Y = property.Y + style.Border
  2485  	lblrect.H = property.H - 2*style.Border
  2486  
  2487  	/* right increment button */
  2488  	var right rect.Rect
  2489  	right.Y = left.Y
  2490  	right.W = left.W
  2491  	right.H = left.H
  2492  	right.X = property.X + property.W - (right.W + style.Padding.X)
  2493  
  2494  	ws := win.widgets.PrevState(property)
  2495  	oldws := ws
  2496  	if ws == nstyle.WidgetStateActive && win.editor != nil {
  2497  		ed = win.editor
  2498  	} else {
  2499  		ed = &TextEditor{}
  2500  		ed.init(win)
  2501  		ed.Buffer = []rune(text)
  2502  	}
  2503  
  2504  	size = FontWidth(font, string(ed.Buffer)) + FontWidth(font, "i")
  2505  
  2506  	/* edit */
  2507  	var edit rect.Rect
  2508  	edit.W = size + 2*style.Padding.X
  2509  	edit.X = right.X - (edit.W + style.Padding.X)
  2510  	edit.Y = property.Y + style.Border + 1
  2511  	edit.H = property.H - (2*style.Border + 2)
  2512  
  2513  	/* empty left space activator */
  2514  	var empty rect.Rect
  2515  	empty.W = edit.X - (lblrect.X + lblrect.W)
  2516  	empty.X = lblrect.X + lblrect.W
  2517  	empty.Y = property.Y
  2518  	empty.H = property.H
  2519  
  2520  	/* update property */
  2521  	old := ed.propertyStatus == propertyEdit
  2522  
  2523  	drag, delta := ed.propertyBehavior(&ws, in, property, lblrect, edit, empty)
  2524  	if drag {
  2525  		ret = doPropertyDrag
  2526  	}
  2527  	if ws == nstyle.WidgetStateActive {
  2528  		ed.Active = true
  2529  		win.editor = ed
  2530  	} else if oldws == nstyle.WidgetStateActive {
  2531  		win.editor = nil
  2532  	}
  2533  	ed.win.widgets.Add(ws, property)
  2534  	drawProperty(ed.win, style, property, lblrect, ws, name)
  2535  
  2536  	/* execute right and left button  */
  2537  	if doButton(ed.win, label.S(style.SymLeft), left, &style.DecButton, in, false) {
  2538  		ret = doPropertyDec
  2539  	}
  2540  	if doButton(ed.win, label.S(style.SymRight), right, &style.IncButton, in, false) {
  2541  		ret = doPropertyInc
  2542  	}
  2543  
  2544  	active := ed.propertyStatus == propertyEdit
  2545  	if !old && active {
  2546  		/* property has been activated so setup buffer */
  2547  		ed.Cursor = len(ed.Buffer)
  2548  	}
  2549  	ed.Flags = EditAlwaysInsertMode | EditNoHorizontalScroll
  2550  	ed.doEdit(edit, &style.Edit, in, false, false, false)
  2551  	active = ed.Active
  2552  
  2553  	if active && in.Keyboard.Pressed(gkey.CodeReturnEnter) {
  2554  		active = !active
  2555  	}
  2556  
  2557  	if old && !active {
  2558  		/* property is now not active so convert edit text to value*/
  2559  		ed.propertyStatus = propertyDefault
  2560  		ed.Active = false
  2561  		ret = doPropertySet
  2562  	}
  2563  
  2564  	return
  2565  }
  2566  
  2567  // Adds a property widget to win for floating point properties.
  2568  // A property widget will display a text label, a small text editor
  2569  // for the property value and one up and one down button.
  2570  // The value can be modified by editing the text value, by clicking
  2571  // the up/down buttons (which will increase/decrease the value by step)
  2572  // or by clicking and dragging over the label.
  2573  // Returns true when the property's value is changed
  2574  func (win *Window) PropertyFloat(name string, min float64, val *float64, max, step, inc_per_pixel float64, prec int) (changed bool) {
  2575  	s, bounds, fitting := win.widget()
  2576  	if fitting != nil {
  2577  		fitting(propertyWidth(int(max+1), &win.ctx.Style.Property, win.ctx.Style.Font))
  2578  	}
  2579  	if !s {
  2580  		return
  2581  	}
  2582  	in := win.inputMaybe(s)
  2583  	text := strconv.FormatFloat(*val, 'G', prec, 32)
  2584  	ret, delta, ed := win.doProperty(bounds, name, text, FilterFloat, in)
  2585  	switch ret {
  2586  	case doPropertyDec:
  2587  		*val -= step
  2588  	case doPropertyInc:
  2589  		*val += step
  2590  	case doPropertyDrag:
  2591  		*val += float64(delta) * inc_per_pixel
  2592  	case doPropertySet:
  2593  		*val, _ = strconv.ParseFloat(string(ed.Buffer), 64)
  2594  	}
  2595  	changed = ret != doPropertyStay
  2596  	if changed {
  2597  		*val = clampFloat(min, *val, max)
  2598  	}
  2599  	return
  2600  }
  2601  
  2602  // Same as PropertyFloat but with integer values.
  2603  func (win *Window) PropertyInt(name string, min int, val *int, max, step, inc_per_pixel int) (changed bool) {
  2604  	s, bounds, fitting := win.widget()
  2605  	if fitting != nil {
  2606  		fitting(propertyWidth(max, &win.ctx.Style.Property, win.ctx.Style.Font))
  2607  	}
  2608  	if !s {
  2609  		return
  2610  	}
  2611  	in := win.inputMaybe(s)
  2612  	text := strconv.Itoa(*val)
  2613  	ret, delta, ed := win.doProperty(bounds, name, text, FilterDecimal, in)
  2614  	switch ret {
  2615  	case doPropertyDec:
  2616  		*val -= step
  2617  	case doPropertyInc:
  2618  		*val += step
  2619  	case doPropertyDrag:
  2620  		*val += delta * inc_per_pixel
  2621  	case doPropertySet:
  2622  		*val, _ = strconv.Atoi(string(ed.Buffer))
  2623  	}
  2624  	changed = ret != doPropertyStay
  2625  	if changed {
  2626  		*val = clampInt(min, *val, max)
  2627  	}
  2628  	return
  2629  }
  2630  
  2631  ///////////////////////////////////////////////////////////////////////////////////
  2632  // POPUP
  2633  ///////////////////////////////////////////////////////////////////////////////////
  2634  
  2635  func (ctx *context) nonblockOpen(flags WindowFlags, body rect.Rect, header rect.Rect, updateFn UpdateFn) *Window {
  2636  	popup := createWindow(ctx, "")
  2637  	popup.idx = len(ctx.Windows)
  2638  	popup.updateFn = updateFn
  2639  	ctx.Windows = append(ctx.Windows, popup)
  2640  
  2641  	popup.Bounds = body
  2642  	popup.layout = &panel{}
  2643  	popup.flags = flags
  2644  	popup.flags |= WindowBorder | windowPopup
  2645  	popup.flags |= WindowDynamic | windowSub
  2646  	popup.flags |= windowNonblock
  2647  
  2648  	popup.header = header
  2649  
  2650  	if updateFn == nil {
  2651  		popup.specialPanelBegin()
  2652  	}
  2653  	return popup
  2654  }
  2655  
  2656  func (ctx *context) popupOpen(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) {
  2657  	popup := createWindow(ctx, title)
  2658  	popup.idx = len(ctx.Windows)
  2659  	popup.updateFn = updateFn
  2660  	if updateFn == nil {
  2661  		panic("nil update function")
  2662  	}
  2663  	ctx.Windows = append(ctx.Windows, popup)
  2664  	ctx.dockedWindowFocus = 0
  2665  
  2666  	if scale {
  2667  		rect.X = ctx.scale(rect.X)
  2668  		rect.Y = ctx.scale(rect.Y)
  2669  		rect.W = ctx.scale(rect.W)
  2670  		rect.H = ctx.scale(rect.H)
  2671  	}
  2672  
  2673  	if ((rect.X+rect.W <= 0) && (rect.Y+rect.H <= 0)) || ((rect.X >= ctx.Windows[0].Bounds.W) && (rect.Y >= ctx.Windows[0].Bounds.H)) {
  2674  		// out of bounds
  2675  		rect.X = 0
  2676  		rect.Y = 0
  2677  	}
  2678  
  2679  	if rect.X == 0 && rect.Y == 0 && flags&WindowNonmodal != 0 {
  2680  		rect.X, rect.Y = ctx.autoPosition()
  2681  	}
  2682  
  2683  	popup.Bounds = rect
  2684  	popup.layout = &panel{}
  2685  	popup.flags = flags | WindowBorder | windowSub | windowPopup
  2686  }
  2687  
  2688  func (ctx *context) autoPosition() (int, int) {
  2689  	x, y := ctx.autopos.X, ctx.autopos.Y
  2690  
  2691  	z := FontHeight(ctx.Style.Font) + 2.0*ctx.Style.NormalWindow.Header.Padding.Y
  2692  
  2693  	ctx.autopos.X += ctx.scale(z)
  2694  	ctx.autopos.Y += ctx.scale(z)
  2695  
  2696  	if ctx.Windows[0].Bounds.W != 0 && ctx.Windows[0].Bounds.H != 0 {
  2697  		if ctx.autopos.X >= ctx.Windows[0].Bounds.W || ctx.autopos.Y >= ctx.Windows[0].Bounds.H {
  2698  			ctx.autopos.X = 0
  2699  			ctx.autopos.Y = 0
  2700  		}
  2701  	}
  2702  
  2703  	return x, y
  2704  }
  2705  
  2706  // Programmatically closes this window
  2707  func (win *Window) Close() {
  2708  	if win.idx != 0 {
  2709  		win.close = true
  2710  	}
  2711  }
  2712  
  2713  ///////////////////////////////////////////////////////////////////////////////////
  2714  // CONTEXTUAL
  2715  ///////////////////////////////////////////////////////////////////////////////////
  2716  
  2717  // Opens a contextual menu with maximum size equal to 'size'.
  2718  // Specify size == image.Point{} if you want a menu big enough to fit its larges MenuItem
  2719  func (win *Window) ContextualOpen(flags WindowFlags, size image.Point, trigger_bounds rect.Rect, updateFn UpdateFn) *Window {
  2720  	if popup := win.ctx.Windows[len(win.ctx.Windows)-1]; popup.header == trigger_bounds {
  2721  		popup.specialPanelBegin()
  2722  		return popup
  2723  	}
  2724  	if size == (image.Point{}) {
  2725  		size.X = nk_null_rect.W
  2726  		size.Y = nk_null_rect.H
  2727  		flags = flags | windowHDynamic
  2728  	}
  2729  	size.X = win.ctx.scale(size.X)
  2730  	size.Y = win.ctx.scale(size.Y)
  2731  	if trigger_bounds.W > 0 && trigger_bounds.H > 0 {
  2732  		if !win.Input().Mouse.Clicked(pointer.ButtonRight, trigger_bounds) {
  2733  			return nil
  2734  		}
  2735  	}
  2736  
  2737  	var body rect.Rect
  2738  	body.X = win.ctx.Input.Mouse.Pos.X
  2739  	body.Y = win.ctx.Input.Mouse.Pos.Y
  2740  	body.W = size.X
  2741  	body.H = size.Y
  2742  
  2743  	if flags&WindowContextualReplace != 0 {
  2744  		if popup := win.ctx.Windows[len(win.ctx.Windows)-1]; popup.flags&windowContextual != 0 {
  2745  			body.X = popup.Bounds.X
  2746  			body.Y = popup.Bounds.Y
  2747  		}
  2748  	}
  2749  
  2750  	atomic.AddInt32(&win.ctx.changed, 1)
  2751  	return win.ctx.nonblockOpen(flags|windowContextual|WindowNoScrollbar, body, trigger_bounds, updateFn)
  2752  }
  2753  
  2754  // MenuItem adds a menu item
  2755  func (win *Window) MenuItem(lbl label.Label) bool {
  2756  	style := &win.ctx.Style
  2757  	state, bounds := win.widgetFitting(style.ContextualButton.Padding)
  2758  	if !state {
  2759  		return false
  2760  	}
  2761  
  2762  	if win.flags&windowHDynamic != 0 {
  2763  		w := FontWidth(style.Font, lbl.Text) + 2*style.ContextualButton.Padding.X
  2764  		if w > win.menuItemWidth {
  2765  			win.menuItemWidth = w
  2766  		}
  2767  	}
  2768  
  2769  	in := win.inputMaybe(state)
  2770  	if doButton(win, lbl, bounds, &style.ContextualButton, in, false) {
  2771  		win.Close()
  2772  		return true
  2773  	}
  2774  
  2775  	return false
  2776  }
  2777  
  2778  ///////////////////////////////////////////////////////////////////////////////////
  2779  // TOOLTIP
  2780  ///////////////////////////////////////////////////////////////////////////////////
  2781  
  2782  const tooltipWindowTitle = "__##Tooltip##__"
  2783  
  2784  // Displays a tooltip window.
  2785  func (win *Window) TooltipOpen(width int, scale bool, updateFn UpdateFn) {
  2786  	in := &win.ctx.Input
  2787  
  2788  	if scale {
  2789  		width = win.ctx.scale(width)
  2790  	}
  2791  
  2792  	var bounds rect.Rect
  2793  	bounds.W = width
  2794  	bounds.H = nk_null_rect.H
  2795  	bounds.X = (in.Mouse.Pos.X + 1)
  2796  	bounds.Y = (in.Mouse.Pos.Y + 1)
  2797  
  2798  	win.ctx.popupOpen(tooltipWindowTitle, WindowDynamic|WindowNoScrollbar|windowTooltip, bounds, false, updateFn)
  2799  }
  2800  
  2801  // Shows a tooltip window containing the specified text.
  2802  func (win *Window) Tooltip(text string) {
  2803  	if text == "" {
  2804  		return
  2805  	}
  2806  
  2807  	/* fetch configuration data */
  2808  	padding := win.ctx.Style.TooltipWindow.Padding
  2809  	item_spacing := win.ctx.Style.TooltipWindow.Spacing
  2810  
  2811  	/* calculate size of the text and tooltip */
  2812  	text_width := FontWidth(win.ctx.Style.Font, text) + win.ctx.scale(4*padding.X) + win.ctx.scale(2*item_spacing.X)
  2813  	text_height := FontHeight(win.ctx.Style.Font)
  2814  
  2815  	win.TooltipOpen(text_width, false, func(tw *Window) {
  2816  		tw.RowScaled(text_height).Dynamic(1)
  2817  		tw.Label(text, "LC")
  2818  	})
  2819  }
  2820  
  2821  ///////////////////////////////////////////////////////////////////////////////////
  2822  // COMBO-BOX
  2823  ///////////////////////////////////////////////////////////////////////////////////
  2824  
  2825  // Adds a drop-down list to win.
  2826  func (win *Window) Combo(lbl label.Label, height int, updateFn UpdateFn) *Window {
  2827  	s, header, _ := win.widget()
  2828  	if !s {
  2829  		return nil
  2830  	}
  2831  
  2832  	in := win.inputMaybe(s)
  2833  	state := win.widgets.PrevState(header)
  2834  	is_clicked := buttonBehaviorDo(&state, header, in, false)
  2835  
  2836  	switch lbl.Kind {
  2837  	case label.ColorLabel:
  2838  		win.widgets.Add(state, header)
  2839  		drawComboColor(win, state, header, is_clicked, lbl.Color)
  2840  	case label.ImageLabel:
  2841  		win.widgets.Add(state, header)
  2842  		drawComboImage(win, state, header, is_clicked, lbl.Img)
  2843  	case label.ImageTextLabel:
  2844  		win.widgets.Add(state, header)
  2845  		drawComboImageText(win, state, header, is_clicked, lbl.Text, lbl.Img)
  2846  	case label.SymbolLabel:
  2847  		win.widgets.Add(state, header)
  2848  		drawComboSymbol(win, state, header, is_clicked, lbl.Symbol)
  2849  	case label.SymbolTextLabel:
  2850  		win.widgets.Add(state, header)
  2851  		drawComboSymbolText(win, state, header, is_clicked, lbl.Symbol, lbl.Text)
  2852  	case label.TextLabel:
  2853  		win.widgets.Add(state, header)
  2854  		drawComboText(win, state, header, is_clicked, lbl.Text)
  2855  	}
  2856  
  2857  	if popup := win.ctx.Windows[len(win.ctx.Windows)-1]; updateFn == nil && popup.header == header {
  2858  		popup.specialPanelBegin()
  2859  		return popup
  2860  	}
  2861  
  2862  	if !is_clicked {
  2863  		return nil
  2864  	}
  2865  
  2866  	height = win.ctx.scale(height)
  2867  
  2868  	var body rect.Rect
  2869  	body.X = header.X
  2870  	body.W = header.W
  2871  	body.Y = header.Y + header.H - 1
  2872  	body.H = height
  2873  
  2874  	return win.ctx.nonblockOpen(windowCombo, body, header, updateFn)
  2875  }
  2876  
  2877  // Adds a drop-down list to win. The contents are specified by items,
  2878  // with selected being the index of the selected item.
  2879  func (win *Window) ComboSimple(items []string, selected int, item_height int) int {
  2880  	if len(items) == 0 {
  2881  		return selected
  2882  	}
  2883  
  2884  	item_height = win.ctx.scale(item_height)
  2885  	item_padding := win.ctx.Style.Combo.ButtonPadding.Y
  2886  	window_padding := win.style().Padding.Y
  2887  	max_height := (len(items)+1)*item_height + item_padding*3 + window_padding*2
  2888  	if w := win.Combo(label.T(items[selected]), max_height, nil); w != nil {
  2889  		w.RowScaled(item_height).Dynamic(1)
  2890  		for i := range items {
  2891  			if w.MenuItem(label.TA(items[i], "LC")) {
  2892  				selected = i
  2893  			}
  2894  		}
  2895  	}
  2896  	return selected
  2897  }
  2898  
  2899  ///////////////////////////////////////////////////////////////////////////////////
  2900  // MENU
  2901  ///////////////////////////////////////////////////////////////////////////////////
  2902  
  2903  // Adds a menu to win with a text label.
  2904  // If width == 0 the width will be automatically adjusted to fit the largest MenuItem
  2905  func (win *Window) Menu(lbl label.Label, width int, updateFn UpdateFn) *Window {
  2906  	state, header, fitting := win.widget()
  2907  	if fitting != nil {
  2908  		fitting(buttonWidth(lbl, &win.ctx.Style.MenuButton, win.ctx.Style.Font))
  2909  	}
  2910  	if !state {
  2911  		return nil
  2912  	}
  2913  
  2914  	in := &Input{}
  2915  	if win.toplevel() {
  2916  		in = &win.ctx.Input
  2917  		in.Mouse.clip = win.cmds.Clip
  2918  	}
  2919  	is_clicked := doButton(win, lbl, header, &win.ctx.Style.MenuButton, in, false)
  2920  
  2921  	if popup := win.ctx.Windows[len(win.ctx.Windows)-1]; updateFn == nil && popup.header == header {
  2922  		popup.specialPanelBegin()
  2923  		return popup
  2924  	}
  2925  
  2926  	if !is_clicked {
  2927  		return nil
  2928  	}
  2929  
  2930  	flags := windowMenu | WindowNoScrollbar
  2931  
  2932  	width = win.ctx.scale(width)
  2933  
  2934  	if width == 0 {
  2935  		width = nk_null_rect.W
  2936  		flags = flags | windowHDynamic
  2937  	}
  2938  
  2939  	var body rect.Rect
  2940  	body.X = header.X
  2941  	body.W = width
  2942  	body.Y = header.Y + header.H
  2943  	body.H = (win.layout.Bounds.Y + win.layout.Bounds.H) - body.Y
  2944  
  2945  	return win.ctx.nonblockOpen(flags, body, header, updateFn)
  2946  }
  2947  
  2948  ///////////////////////////////////////////////////////////////////////////////////
  2949  // GROUPS
  2950  ///////////////////////////////////////////////////////////////////////////////////
  2951  
  2952  // Creates a group of widgets.
  2953  // Group are useful for creating lists as well as splitting a main
  2954  // window into tiled subwindows.
  2955  // Items that you want to add to the group should be added to the
  2956  // returned window.
  2957  func (win *Window) GroupBegin(title string, flags WindowFlags) *Window {
  2958  	sw := win.groupWnd[title]
  2959  	if sw == nil {
  2960  		sw = createWindow(win.ctx, title)
  2961  		sw.parent = win
  2962  		win.groupWnd[title] = sw
  2963  		sw.Scrollbar.X = 0
  2964  		sw.Scrollbar.Y = 0
  2965  		sw.layout = &panel{}
  2966  	}
  2967  
  2968  	sw.curNode = sw.rootNode
  2969  	sw.widgets.reset()
  2970  	sw.cmds.Reset()
  2971  	sw.idx = win.idx
  2972  
  2973  	sw.cmds.Clip = win.cmds.Clip
  2974  
  2975  	state, bounds, _ := win.widget()
  2976  	if !state {
  2977  		return nil
  2978  	}
  2979  
  2980  	flags |= windowSub | windowGroup
  2981  
  2982  	if win.flags&windowEnabled != 0 {
  2983  		flags |= windowEnabled
  2984  	}
  2985  
  2986  	sw.Bounds = bounds
  2987  	sw.flags = flags
  2988  
  2989  	panelBegin(win.ctx, sw, title)
  2990  
  2991  	sw.layout.Offset = &sw.Scrollbar
  2992  
  2993  	win.usingSub = true
  2994  
  2995  	return sw
  2996  }
  2997  
  2998  // Signals that you are done adding widgets to a group.
  2999  func (win *Window) GroupEnd() {
  3000  	panelEnd(win.ctx, win)
  3001  	win.parent.usingSub = false
  3002  
  3003  	// immediate drawing
  3004  	win.parent.cmds.Commands = append(win.parent.cmds.Commands, win.cmds.Commands...)
  3005  }