9fans.net/go@v0.0.5/draw/menuhit.go (about)

     1  package draw
     2  
     3  const (
     4  	menuMargin      = 4  /* outside to text */
     5  	menuBorder      = 2  /* outside to selection boxes */
     6  	menuBlackborder = 2  /* width of outlining border */
     7  	menuVspacing    = 2  /* extra spacing between lines of text */
     8  	menuMaxunscroll = 25 /* maximum #entries before scrolling turns on */
     9  	menuNscroll     = 20 /* number entries in scrolling part */
    10  	menuScrollwid   = 14 /* width of scroll bar */
    11  	menuGap         = 4  /* between text and scroll bar */
    12  )
    13  
    14  // A Menu describes a menu of items.
    15  //
    16  // The items are specified either in the static slice Item
    17  // or by a function Gen that can be called to generate the k'th item
    18  // (starting with k = 0). Gen should return text, true on success
    19  // and "", false when k is beyond the end of the menu.
    20  // LastHit records the previously selected menu item.
    21  type Menu struct {
    22  	Item    []string
    23  	Gen     func(k int, buf []byte) (text []byte, ok bool)
    24  	LastHit int
    25  
    26  	cache []byte
    27  }
    28  
    29  func (me *Menu) gen(k int) ([]byte, bool) {
    30  	buf, ok := me.Gen(k, me.cache[:0])
    31  	if buf != nil {
    32  		me.cache = buf
    33  	}
    34  	return buf, ok
    35  }
    36  
    37  // TODO hide in display
    38  var menu struct {
    39  	txt   *Image
    40  	back  *Image
    41  	high  *Image
    42  	bord  *Image
    43  	text  *Image
    44  	htext *Image
    45  }
    46  
    47  func menucolors(display *Display) {
    48  	/* Main tone is greenish, with negative selection */
    49  	menu.back = display.AllocImageMix(PaleGreen, White)
    50  	menu.high, _ = display.AllocImage(Rect(0, 0, 1, 1), display.ScreenImage.Pix, true, DarkGreen) /* dark green */
    51  	menu.bord, _ = display.AllocImage(Rect(0, 0, 1, 1), display.ScreenImage.Pix, true, MedGreen)  /* not as dark green */
    52  	if menu.back == nil || menu.high == nil || menu.bord == nil {
    53  		goto Error
    54  	}
    55  	menu.text = display.Black
    56  	menu.htext = menu.back
    57  	return
    58  
    59  Error:
    60  	menu.back.Free()
    61  	menu.high.Free()
    62  	menu.bord.Free()
    63  	menu.back = display.White
    64  	menu.high = display.Black
    65  	menu.bord = display.Black
    66  	menu.text = display.Black
    67  	menu.htext = display.White
    68  }
    69  
    70  /*
    71   * r is a rectangle holding the text elements.
    72   * return the rectangle, including its black edge, holding element i.
    73   */
    74  func menurect(display *Display, r Rectangle, i int) Rectangle {
    75  	if i < 0 {
    76  		return Rect(0, 0, 0, 0)
    77  	}
    78  	r.Min.Y += (display.Font.Height + menuVspacing) * i
    79  	r.Max.Y = r.Min.Y + display.Font.Height + menuVspacing
    80  	return r.Inset(menuBorder - menuMargin)
    81  }
    82  
    83  /*
    84   * r is a rectangle holding the text elements.
    85   * return the element number containing p.
    86   */
    87  func menusel(display *Display, r Rectangle, p Point) int {
    88  	r = r.Inset(menuMargin)
    89  	if !p.In(r) {
    90  		return -1
    91  	}
    92  	return (p.Y - r.Min.Y) / (display.Font.Height + menuVspacing)
    93  }
    94  
    95  func paintitem(m *Image, me *Menu, textr Rectangle, off, i int, highlight bool, save, restore *Image) {
    96  	if i < 0 {
    97  		return
    98  	}
    99  	display := m.Display
   100  	font := display.Font
   101  	r := menurect(display, textr, i)
   102  	if restore != nil {
   103  		m.Draw(r, restore, nil, restore.R.Min)
   104  		return
   105  	}
   106  	if save != nil {
   107  		save.Draw(save.R, m, nil, r.Min)
   108  	}
   109  	var item string
   110  	var itemBytes []byte
   111  	var width int
   112  	if me.Item != nil {
   113  		item = me.Item[i+off]
   114  		width = font.StringWidth(item)
   115  	} else {
   116  		itemBytes, _ = me.gen(i + off)
   117  		width = font.BytesWidth(itemBytes)
   118  	}
   119  	var pt Point
   120  	pt.X = (textr.Min.X + textr.Max.X - width) / 2
   121  	pt.Y = textr.Min.Y + i*(font.Height+menuVspacing)
   122  	back, text := menu.back, menu.text
   123  	if highlight {
   124  		back, text = menu.high, menu.htext
   125  	}
   126  	m.Draw(r, back, nil, pt)
   127  	if item != "" {
   128  		m.String(pt, text, pt, font, item)
   129  	} else {
   130  		m.Bytes(pt, text, pt, font, itemBytes)
   131  	}
   132  }
   133  
   134  /*
   135   * menur is a rectangle holding all the highlightable text elements.
   136   * track mouse while inside the box, return what's selected when button
   137   * is raised, -1 as soon as it leaves box.
   138   * invariant: nothing is highlighted on entry or exit.
   139   */
   140  func menuscan(m *Image, me *Menu, but int, mc *Mousectl, textr Rectangle, off, lasti int, save *Image) int {
   141  	paintitem(m, me, textr, off, lasti, true, save, nil)
   142  	for mc.Read(); mc.Buttons&(1<<(but-1)) != 0; mc.Read() {
   143  		i := menusel(m.Display, textr, mc.Point)
   144  		if i != -1 && i == lasti {
   145  			continue
   146  		}
   147  		paintitem(m, me, textr, off, lasti, false, nil, save)
   148  		if i == -1 {
   149  			return i
   150  		}
   151  		lasti = i
   152  		paintitem(m, me, textr, off, lasti, true, save, nil)
   153  	}
   154  	return lasti
   155  }
   156  
   157  func menupaint(m *Image, me *Menu, textr Rectangle, off, nitemdrawn int) {
   158  	m.Draw(textr.Inset(menuBorder-menuMargin), menu.back, nil, ZP)
   159  	for i := 0; i < nitemdrawn; i++ {
   160  		paintitem(m, me, textr, off, i, false, nil, nil)
   161  	}
   162  }
   163  
   164  func menuscrollpaint(m *Image, scrollr Rectangle, off, nitem, nitemdrawn int) {
   165  	m.Draw(scrollr, menu.back, nil, ZP)
   166  	r := scrollr
   167  	r.Min.Y = scrollr.Min.Y + (scrollr.Dy()*off)/nitem
   168  	r.Max.Y = scrollr.Min.Y + (scrollr.Dy()*(off+nitemdrawn))/nitem
   169  	if r.Max.Y < r.Min.Y+2 {
   170  		r.Max.Y = r.Min.Y + 2
   171  	}
   172  	m.Border(r, 1, menu.bord, ZP)
   173  	if menu.txt == nil {
   174  		display := m.Display
   175  		menu.txt, _ = display.AllocImage(Rect(0, 0, 1, 1), display.ScreenImage.Pix, true, DarkGreen) /* border color; BUG? */
   176  	}
   177  	if menu.txt != nil {
   178  		m.Draw(r.Inset(1), menu.txt, nil, ZP)
   179  	}
   180  }
   181  
   182  func MenuHit(but int, mc *Mousectl, me *Menu, scr *Screen) int {
   183  	/*
   184  		int nitemdrawn,  lasti, off, noff, wid;
   185  		int scrolling;
   186  		Rectangle r, menur,  textr, scrollr;
   187  		Image *b, *save, *backup;
   188  		Point pt;
   189  		char *item;
   190  	*/
   191  
   192  	display := mc.Display
   193  	screen := display.ScreenImage
   194  	font := display.Font
   195  
   196  	if menu.back == nil {
   197  		menucolors(display)
   198  	}
   199  	sc := screen.Clipr
   200  	screen.ReplClipr(false, screen.R)
   201  	maxwid := 0
   202  	nitem := 0
   203  	for ; ; nitem++ {
   204  		var w int
   205  		if me.Item != nil {
   206  			if nitem >= len(me.Item) {
   207  				break
   208  			}
   209  			w = font.StringWidth(me.Item[nitem])
   210  		} else {
   211  			buf, ok := me.gen(nitem)
   212  			if !ok {
   213  				break
   214  			}
   215  			w = font.BytesWidth(buf)
   216  		}
   217  		if w > maxwid {
   218  			maxwid = w
   219  		}
   220  	}
   221  
   222  	if me.LastHit < 0 || me.LastHit >= nitem {
   223  		me.LastHit = 0
   224  	}
   225  
   226  	screenitem := (screen.R.Dy() - 10) / (font.Height + menuVspacing)
   227  	scrolling := false
   228  	nitemdrawn := nitem
   229  	wid := maxwid
   230  	off := 0
   231  	lasti := me.LastHit
   232  	if nitem > menuMaxunscroll || nitem > screenitem {
   233  		scrolling = true
   234  		nitemdrawn = menuNscroll
   235  		if nitemdrawn > screenitem {
   236  			nitemdrawn = screenitem
   237  		}
   238  		wid = maxwid + menuGap + menuScrollwid
   239  		off = me.LastHit - nitemdrawn/2
   240  		if off < 0 {
   241  			off = 0
   242  		}
   243  		if off > nitem-nitemdrawn {
   244  			off = nitem - nitemdrawn
   245  		}
   246  		lasti = me.LastHit - off
   247  	}
   248  	r := Rect(0, 0, wid, nitemdrawn*(font.Height+menuVspacing)).Inset(-menuMargin)
   249  	r = r.Sub(Pt(wid/2, lasti*(font.Height+menuVspacing)+font.Height/2))
   250  	r = r.Add(mc.Point)
   251  	pt := ZP
   252  	if r.Max.X > screen.R.Max.X {
   253  		pt.X = screen.R.Max.X - r.Max.X
   254  	}
   255  	if r.Max.Y > screen.R.Max.Y {
   256  		pt.Y = screen.R.Max.Y - r.Max.Y
   257  	}
   258  	if r.Min.X < screen.R.Min.X {
   259  		pt.X = screen.R.Min.X - r.Min.X
   260  	}
   261  	if r.Min.Y < screen.R.Min.Y {
   262  		pt.Y = screen.R.Min.Y - r.Min.Y
   263  	}
   264  	menur := r.Add(pt)
   265  	var textr Rectangle
   266  	textr.Max.X = menur.Max.X - menuMargin
   267  	textr.Min.X = textr.Max.X - maxwid
   268  	textr.Min.Y = menur.Min.Y + menuMargin
   269  	textr.Max.Y = textr.Min.Y + nitemdrawn*(font.Height+menuVspacing)
   270  	var scrollr Rectangle
   271  	if scrolling {
   272  		scrollr = menur.Inset(menuBorder)
   273  		scrollr.Max.X = scrollr.Min.X + menuScrollwid
   274  	}
   275  
   276  	var b *Image
   277  	var backup *Image
   278  	if scr != nil {
   279  		b, _ = allocwindow(nil, scr, menur, RefBackup, White)
   280  		if b == nil {
   281  			b = screen
   282  		}
   283  		backup = nil
   284  	} else {
   285  		b = screen
   286  		backup, _ = display.AllocImage(menur, screen.Pix, false, White)
   287  		if backup != nil {
   288  			backup.Draw(menur, screen, nil, menur.Min)
   289  		}
   290  	}
   291  	b.Draw(menur, menu.back, nil, ZP)
   292  	b.Border(menur, menuBlackborder, menu.bord, ZP)
   293  	save, _ := display.AllocImage(menurect(display, textr, 0), screen.Pix, false, White)
   294  	r = menurect(display, textr, lasti)
   295  	display.MoveCursor(r.Min.Add(r.Max).Div(2))
   296  	menupaint(b, me, textr, off, nitemdrawn)
   297  	if scrolling {
   298  		menuscrollpaint(b, scrollr, off, nitem, nitemdrawn)
   299  	}
   300  	for mc.Buttons&(1<<(but-1)) != 0 {
   301  		lasti = menuscan(b, me, but, mc, textr, off, lasti, save)
   302  		if lasti >= 0 {
   303  			break
   304  		}
   305  		for !mc.In(textr) && (mc.Buttons&(1<<(but-1))) != 0 {
   306  			if scrolling && mc.In(scrollr) {
   307  				noff := ((mc.Y - scrollr.Min.Y) * nitem) / scrollr.Dy()
   308  				noff -= nitemdrawn / 2
   309  				if noff < 0 {
   310  					noff = 0
   311  				}
   312  				if noff > nitem-nitemdrawn {
   313  					noff = nitem - nitemdrawn
   314  				}
   315  				if noff != off {
   316  					off = noff
   317  					menupaint(b, me, textr, off, nitemdrawn)
   318  					menuscrollpaint(b, scrollr, off, nitem, nitemdrawn)
   319  				}
   320  			}
   321  			mc.Read()
   322  		}
   323  	}
   324  	if b != screen {
   325  		b.Free()
   326  	}
   327  	if backup != nil {
   328  		screen.Draw(menur, backup, nil, menur.Min)
   329  		backup.Free()
   330  	}
   331  	save.Free()
   332  	screen.ReplClipr(false, sc)
   333  	display.Flush()
   334  	if lasti >= 0 {
   335  		me.LastHit = lasti + off
   336  		return me.LastHit
   337  	}
   338  	return -1
   339  }