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 }