9fans.net/go@v0.0.5/draw/frame/frinsert.go (about) 1 package frame 2 3 import ( 4 "9fans.net/go/draw" 5 ) 6 7 // bxscan splits text into boxes inside tmpf starting at *ppt, 8 // and reports the position at its end. 9 // 10 // It updates ppt to the position of the start of the text. 11 func (f *Frame) bxscan(tmpf *Frame, text []rune, ppt *draw.Point) draw.Point { 12 tmpf.R = f.R 13 tmpf.B = f.B 14 tmpf.Font = f.Font 15 tmpf.MaxTab = f.MaxTab 16 tmpf.box = nil 17 tmpf.NumChars = 0 18 tmpf.Cols = f.Cols 19 tmpf.MaxLines = f.MaxLines 20 nl := 0 21 for len(text) > 0 && nl <= tmpf.MaxLines { 22 tmpf.box = append(tmpf.box, box{}) 23 b := &tmpf.box[len(tmpf.box)-1] 24 c := text[0] 25 if c == '\t' || c == '\n' { 26 b.bc = c 27 b.wid = 5000 28 if c == '\t' { 29 b.minwid = tmpf.Font.StringWidth(" ") 30 } 31 b.nrune = -1 32 if c == '\n' { 33 nl++ 34 } 35 tmpf.NumChars++ 36 text = text[1:] 37 } else { 38 nr := 0 39 w := 0 40 for nr < len(text) { 41 c := text[nr] 42 if c == '\t' || c == '\n' || nr > 256 { // 256 is arbitrary cutoff to keep boxes small 43 break 44 } 45 w += tmpf.Font.RunesWidth(text[nr : nr+1]) 46 nr++ 47 } 48 b.bytes, text = []byte(string(text[:nr])), text[nr:] 49 b.wid = w 50 b.nrune = nr 51 tmpf.NumChars += nr 52 } 53 } 54 // Adjust for line wrap before calling draw so that 55 // the starting position agrees with that of draw. 56 // TODO we could call tmpf.cklinewrap0 instead, 57 // which would arguably be clearer. 58 f.cklinewrap0(ppt, &tmpf.box[0]) 59 return tmpf.draw(*ppt) 60 } 61 62 // chop removes any boxes in f that extend beyond 63 // its bounds, starting the calculation at box index bn 64 // that's at rune index p. It updates f.NumChars and f.NumLines. 65 // 66 // This is only called when we know that the frame needs to be 67 // truncated. 68 func (f *Frame) chop(pt draw.Point, p, bn int) { 69 for ; ; bn++ { 70 if bn >= len(f.box) { 71 drawerror(f.Display, "endofframe") 72 } 73 b := &f.box[bn] 74 f.cklinewrap(&pt, b) 75 if pt.Y >= f.R.Max.Y { 76 break 77 } 78 p += b.NRUNE() 79 f.advance(&pt, b) 80 } 81 f.NumChars = p 82 f.NumLines = f.MaxLines 83 if bn < len(f.box) { // BUG 84 f.delbox(bn, len(f.box)-1) 85 } 86 } 87 88 // Insert inserts text into f starting at rune index p0. 89 // Tabs and newlines are handled by the library, 90 // but all other characters, including control characters, are just displayed. 91 // For example, backspaces are printed; to erase a character, use frdelete. 92 func (f *Frame) Insert(text []rune, p int) { 93 p0 := p 94 if p0 > f.NumChars || len(text) == 0 || f.B == nil { 95 return 96 } 97 // Find the index of the box that starts at the insertion point, 98 // splitting the box that's there if needed. 99 n0 := f.findbox(0, 0, p0) 100 // cn0 tracks the rune index of box that's being processed. 101 cn0 := p0 102 // nn0 remembers the index of the starting box (we update 103 // n0 as we scan through during the calculations). 104 nn0 := n0 105 106 // Find the point at the end of the box just before the insertion point. 107 pt0 := f.ptofcharnb(p0, n0) 108 // ppt0 remembers the starting point. 109 ppt0 := pt0 110 opt0 := pt0 111 112 // Scan the text into a temporary frame starting at the point 113 // we know that it will be inserted. This tells us where the 114 // text will end up. 115 var tmpf Frame 116 pt1 := f.bxscan(&tmpf, text, &ppt0) 117 ppt1 := pt1 118 if n0 < len(f.box) { 119 b := &f.box[n0] 120 f.cklinewrap(&pt0, b) // for frdrawsel 121 f.cklinewrap0(&ppt1, b) 122 } 123 f.modified = true 124 125 // ppt0 and ppt1 are start and end of insertion 126 // as they will appear when insertion is complete. 127 // pt0 is current location of insertion position (p0). 128 // pt1 is terminal point (without line wrap) of insertion. 129 if f.P0 == f.P1 { 130 f.Tick(f.PointOf(f.P0), false) 131 } 132 // Find point where old and new x's line up. 133 // Invariants: 134 // - pt0 is where the next box (b, n0) is now 135 // - pt1 is where it will be after the insertion 136 // If pt1 goes off the rectangle, we can toss everything from there on. 137 var pts []draw.Point 138 for pt1.X != pt0.X && pt1.Y != f.R.Max.Y && n0 < len(f.box) { 139 b := &f.box[n0] 140 f.cklinewrap(&pt0, b) 141 f.cklinewrap0(&pt1, b) 142 if b.nrune > 0 { 143 n := f.canfit(pt1, b) 144 if n == 0 { 145 drawerror(f.Display, "canfit==0") 146 } 147 if n != b.nrune { 148 f.splitbox(n0, n) 149 b = &f.box[n0] 150 } 151 } 152 // has a text box overflowed off the frame? 153 if pt1.Y == f.R.Max.Y { 154 break 155 } 156 pts = append(pts, pt0, pt1) 157 f.advance(&pt0, b) 158 pt1.X += f.newwid(pt1, b) 159 cn0 += b.NRUNE() 160 n0++ 161 } 162 if pt1.Y > f.R.Max.Y { 163 drawerror(f.Display, "frinsert pt1 too far") 164 } 165 if pt1.Y == f.R.Max.Y && n0 < len(f.box) { 166 f.NumChars -= f.strlen(n0) 167 f.delbox(n0, len(f.box)-1) 168 } 169 if n0 == len(f.box) { 170 f.NumLines = (pt1.Y - f.R.Min.Y) / f.Font.Height 171 if pt1.X > f.R.Min.X { 172 f.NumLines++ 173 } 174 } else if pt1.Y != pt0.Y { 175 y := f.R.Max.Y 176 q0 := pt0.Y + f.Font.Height 177 q1 := pt1.Y + f.Font.Height 178 f.NumLines += (q1 - q0) / f.Font.Height 179 if f.NumLines > f.MaxLines { 180 f.chop(ppt1, p0, nn0) 181 } 182 if pt1.Y < y { 183 r := f.R 184 r.Min.Y = q1 185 r.Max.Y = y 186 if q1 < y { 187 f.B.Draw(r, f.B, nil, draw.Pt(f.R.Min.X, q0)) 188 } 189 r.Min = pt1 190 r.Max.X = pt1.X + (f.R.Max.X - pt0.X) 191 r.Max.Y = q1 192 f.B.Draw(r, f.B, nil, pt0) 193 } 194 } 195 196 // Move the old stuff down to make room. 197 // The draws above moved everything down after the point where x's lined up. 198 // The loop below will move the stuff between the insertion and the draws. 199 y := pt1.Y 200 if y != f.R.Max.Y { 201 y = 0 202 } 203 for bn := n0 - 1; len(pts) > 0; bn, pts = bn-1, pts[:len(pts)-2] { 204 b := &f.box[bn] 205 pt := pts[len(pts)-1] 206 if b.nrune > 0 { 207 r := draw.Rectangle{Min: pt, Max: pt} 208 r.Max.X += b.wid 209 r.Max.Y += f.Font.Height 210 f.B.Draw(r, f.B, nil, pts[len(pts)-2]) 211 // clear bit hanging off right 212 if len(pts) == 2 && pt.Y > pt0.Y { 213 // first new char is bigger than first char we're displacing, 214 // causing line wrap. ugly special case. 215 r := draw.Rectangle{Min: opt0, Max: opt0} 216 r.Max.X = f.R.Max.X 217 r.Max.Y += f.Font.Height 218 var col *draw.Image 219 if f.P0 <= cn0 && cn0 < f.P1 { // b+1 is inside selection 220 col = f.Cols[HIGH] 221 } else { 222 col = f.Cols[BACK] 223 } 224 f.B.Draw(r, col, nil, r.Min) 225 } else if pt.Y < y { 226 r := draw.Rectangle{Min: pt, Max: pt} 227 r.Min.X += b.wid 228 r.Max.X = f.R.Max.X 229 r.Max.Y += f.Font.Height 230 var col *draw.Image 231 if f.P0 <= cn0 && cn0 < f.P1 { // b+1 is inside selection 232 col = f.Cols[HIGH] 233 } else { 234 col = f.Cols[BACK] 235 } 236 f.B.Draw(r, col, nil, r.Min) 237 } 238 y = pt.Y 239 cn0 -= b.nrune 240 } else { 241 r := draw.Rectangle{Min: pt, Max: pt} 242 r.Max.X += b.wid 243 r.Max.Y += f.Font.Height 244 if r.Max.X >= f.R.Max.X { 245 r.Max.X = f.R.Max.X 246 } 247 cn0-- 248 var col *draw.Image 249 if f.P0 <= cn0 && cn0 < f.P1 { // b is inside selection 250 col = f.Cols[HIGH] 251 } else { 252 col = f.Cols[BACK] 253 } 254 f.B.Draw(r, col, nil, r.Min) 255 y = 0 256 if pt.X == f.R.Min.X { 257 y = pt.Y 258 } 259 } 260 } 261 // insertion can extend the selection, so the condition here is different. 262 var col, tcol *draw.Image 263 if f.P0 < p0 && p0 <= f.P1 { 264 col = f.Cols[HIGH] 265 tcol = f.Cols[HTEXT] 266 } else { 267 col = f.Cols[BACK] 268 tcol = f.Cols[TEXT] 269 } 270 f.SelectPaint(ppt0, ppt1, col) 271 tmpf.drawtext(ppt0, tcol, col) 272 f.addbox(nn0, len(tmpf.box)) 273 copy(f.box[nn0:], tmpf.box) 274 if nn0 > 0 && f.box[nn0-1].nrune >= 0 && ppt0.X-f.box[nn0-1].wid >= f.R.Min.X { 275 // There's some text just before the insertion point. Make sure we clean up from there. 276 // TODO when can the start of that box ever be outside the frame rectangle bounds? 277 nn0-- 278 ppt0.X -= f.box[nn0].wid 279 } 280 n0 += len(tmpf.box) 281 if n0 < len(f.box)-1 { 282 n0++ 283 } 284 f.clean(ppt0, nn0, n0) 285 f.NumChars += tmpf.NumChars 286 if f.P0 >= p0 { 287 f.P0 += tmpf.NumChars 288 } 289 if f.P0 > f.NumChars { 290 f.P0 = f.NumChars 291 } 292 if f.P1 >= p0 { 293 f.P1 += tmpf.NumChars 294 } 295 if f.P1 > f.NumChars { 296 f.P1 = f.NumChars 297 } 298 if f.P0 == f.P1 { 299 f.Tick(f.PointOf(f.P0), true) 300 } 301 }