├── .gopherci.yml ├── .travis.yml ├── README.md ├── caret ├── caret.go ├── caret_test.go └── whole_word.go ├── chanwriter.go ├── data └── fonts │ ├── Helvetica Neue.png │ ├── Menlo.png │ └── Tahoma.png ├── event ├── event.go ├── eventtype_string.go └── virtualcategory_string.go ├── font.go ├── go.mod ├── highlight.go ├── install-into-bundle.sh ├── main.go ├── page ├── events │ ├── index.html │ └── main.go ├── page1 │ └── main.go ├── page2 │ ├── font.go │ └── main.go └── terminal │ ├── font.go │ └── main.go ├── pkg ├── analysis │ ├── generated_detection.go │ ├── generated_detection_test.go │ └── testdata │ │ ├── generated_0.go.txt │ │ ├── handcrafted_0.go.txt │ │ └── handcrafted_1.go.txt ├── exp11 │ ├── main.go │ └── sortimports.go ├── exp12 │ └── main.go ├── exp13 │ └── main.go ├── exp14 │ └── main.go ├── gist4727543 │ ├── main.go │ └── main_test.go ├── gist5504644 │ ├── compat.go │ ├── go14.go │ └── main.go ├── gist5645828 │ └── main.go ├── gist6003701 │ ├── main.go │ └── main_test.go ├── gist7480523 │ ├── import_path_found.go │ ├── main.go │ └── main_test.go ├── gist7576154 │ └── main.go ├── gist7651991 │ └── main.go ├── gist7802150 │ ├── main.go │ ├── main_test.go │ ├── play_0.go │ └── play_1.go ├── gist8018045 │ ├── main.go │ └── main_test.go ├── httpstoppable │ └── httpstoppable.go ├── legacyvcs │ ├── git.go │ ├── hg.go │ └── vcs.go ├── markdown_http │ └── markdown.go ├── multilinecontent │ ├── multilinecontent.go │ └── reverse.go ├── u10 │ └── main.go └── u6 │ └── main.go ├── tail-log.sh └── text_test.go /.gopherci.yml: -------------------------------------------------------------------------------- 1 | apt_packages: 2 | - libgl1-mesa-dev 3 | - xorg-dev 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | addons: 3 | apt_packages: 4 | - libgl1-mesa-dev 5 | - xorg-dev 6 | language: go 7 | go: 8 | - 1.x 9 | - master 10 | env: 11 | - GO111MODULE=on 12 | matrix: 13 | allow_failures: 14 | - go: master 15 | fast_finish: true 16 | install: 17 | - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening. 18 | script: 19 | - git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 20 | - diff -u <(echo -n) <(gofmt -d -s .) 21 | - go vet ./... 22 | - go test -v -race ./... 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conception-go 2 | ============= 3 | 4 | [](https://travis-ci.org/shurcooL-legacy/Conception-go) [](https://godoc.org/github.com/shurcooL-legacy/Conception-go) 5 | 6 | This is a work in progress Go implementation of [Conception](https://github.com/shurcooL/Conception#demonstration). 7 | 8 | Conception is an experimental project. It's a platform for researching software development tools and techniques. It is driven by a set of guiding principles. Conception-go targets Go development. 9 | 10 | My "short term" goal is to implement some of the ideas shown off in Bret Victor's [Learnable Programming](http://worrydream.com/LearnableProgramming/) article. Another goal is to try to beat my current Sublime Text + GoSublime development environment setup (which sets a really high bar). 11 | 12 | Watch this repo to follow the project's development. 13 | 14 | Screenshot 15 | ---------- 16 | 17 | A screenshot showing off a single aspect of the project that was recently added or improved. 18 | 19 |  20 | 21 | Installation 22 | ------------ 23 | 24 | ### macOS 25 | 26 | ```bash 27 | # Install latest Go, git (if you don't already have them). 28 | ... 29 | 30 | # Step back and enjoy 2 commands that install Conception-go. 31 | git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 32 | (cd; GO111MODULE=on go install github.com/shurcooL-legacy/Conception-go) 33 | 34 | # Run it. 35 | $(go env GOPATH)/bin/Conception-go 36 | ``` 37 | 38 | ### Linux 39 | 40 | ```bash 41 | # Install latest Go (if you don't already have it). 42 | sudo apt-get install --yes curl 43 | curl -L https://golang.org/dl/go1.13.7.linux-amd64.tar.gz | sudo tar zx -C /usr/local/ 44 | export PATH="$PATH:/usr/local/go/bin" 45 | 46 | # Install git, OpenGL headers (if you don't already have them). 47 | sudo apt-get install --yes git 48 | sudo apt-get install --yes libgl1-mesa-dev xorg-dev # OpenGL headers. 49 | 50 | # Step back and enjoy 2 commands that install Conception-go. 51 | git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 52 | (cd; GO111MODULE=on go install github.com/shurcooL-legacy/Conception-go) 53 | 54 | # Run it. 55 | $(go env GOPATH)/bin/Conception-go 56 | ``` 57 | 58 | License 59 | ------- 60 | 61 | - [MIT License](https://opensource.org/licenses/mit-license.php) 62 | -------------------------------------------------------------------------------- /caret/caret.go: -------------------------------------------------------------------------------- 1 | // Package caret contains functionality related to text caret state and operations. 2 | package caret 3 | 4 | import ( 5 | "sort" 6 | "strings" 7 | 8 | "github.com/go-gl/mathgl/mgl64" 9 | intmath "github.com/pkg/math" 10 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 11 | ) 12 | 13 | type ContentLine interface { 14 | Start() uint32 15 | End() uint32 16 | Length() uint32 17 | } 18 | 19 | type MultilineContentI interface { 20 | Content() string 21 | LenContent() int 22 | LongestLine() uint32 // Line length 23 | 24 | Line(lineIndex int) ContentLine 25 | LenLines() int 26 | 27 | gist7802150.ViewGroupI 28 | } 29 | 30 | // ===== 31 | 32 | func ExpandedLength(s string, currentAdvance uint32) (expandedLineLength uint32) { 33 | segments := strings.Split(s, "\t") 34 | var advance uint32 = currentAdvance 35 | for segmentIndex, segment := range segments { 36 | advance += uint32(len(segment)) 37 | if segmentIndex != len(segments)-1 { 38 | advance += 4 - (advance % 4) 39 | } 40 | } 41 | return advance - currentAdvance 42 | } 43 | 44 | func ExpandedToLogical(s string, expanded uint32) uint32 { 45 | var logical uint32 46 | var advance uint32 47 | var smallestDifference int32 = int32(expanded) - 0 48 | for charIndex, char := range []byte(s) { 49 | if char == '\t' { 50 | advance += 4 - (advance % 4) 51 | } else { 52 | advance++ 53 | } 54 | 55 | difference := int32(advance) - int32(expanded) 56 | if difference < 0 { 57 | difference *= -1 58 | } 59 | if difference < smallestDifference { 60 | smallestDifference = difference 61 | logical = uint32(charIndex + 1) 62 | } 63 | } 64 | 65 | return logical 66 | } 67 | 68 | // --- 69 | 70 | // TODO: Rename. 71 | type caretPositionInternal struct { 72 | w MultilineContentI 73 | lineIndex int 74 | positionWithinLine uint32 75 | targetExpandedX uint32 76 | 77 | gist7802150.DepNode2Manual 78 | } 79 | 80 | func (cp *caretPositionInternal) NotifyContentChanged() { 81 | if cp.lineIndex > cp.w.LenLines()-1 { 82 | cp.Move(+3) 83 | } else if cp.positionWithinLine > cp.w.Line(cp.lineIndex).Length() { 84 | cp.Move(+2) 85 | } 86 | } 87 | 88 | func (cp *caretPositionInternal) Logical() uint32 { 89 | return cp.w.Line(cp.lineIndex).Start() + cp.positionWithinLine 90 | } 91 | 92 | func (cp *caretPositionInternal) tryMoveH(direction direction, jumpWords bool) { 93 | switch direction { 94 | case Backward: 95 | if cp.Logical() >= 1 { 96 | if jumpWords { 97 | // Skip spaces to the left 98 | LookAt := cp.Logical() 99 | for LookAt > 0 && !isCoreCharacter(cp.w.Content()[LookAt-1]) { 100 | LookAt-- 101 | } 102 | // Skip non-spaces to the left 103 | for LookAt > 0 && isCoreCharacter(cp.w.Content()[LookAt-1]) { 104 | LookAt-- 105 | } 106 | 107 | cp.willMoveH(int32(LookAt) - int32(cp.Logical())) 108 | } else { 109 | cp.willMoveH(-1) 110 | } 111 | } 112 | case Forward: 113 | if cp.Logical() < uint32(cp.w.LenContent()) { 114 | if jumpWords { 115 | // Skip spaces to the right 116 | LookAt := cp.Logical() 117 | for LookAt < uint32(cp.w.LenContent()) && !isCoreCharacter(cp.w.Content()[LookAt]) { 118 | LookAt++ 119 | } 120 | // Skip non-spaces to the right 121 | for LookAt < uint32(cp.w.LenContent()) && isCoreCharacter(cp.w.Content()[LookAt]) { 122 | LookAt++ 123 | } 124 | 125 | cp.willMoveH(int32(LookAt) - int32(cp.Logical())) 126 | } else { 127 | cp.willMoveH(+1) 128 | } 129 | } 130 | } 131 | } 132 | 133 | type direction int8 134 | 135 | const ( 136 | Backward direction = -1 137 | Forward direction = +1 138 | ) 139 | 140 | // expandSelection goes in direction over core characters only. 141 | func (cp *caretPositionInternal) expandSelection(direction direction) { 142 | switch direction { 143 | case Backward: 144 | if cp.Logical() > 0 { 145 | // Skip non-spaces to the left. 146 | lookAt := cp.Logical() 147 | for lookAt > 0 && isCoreCharacter(cp.w.Content()[lookAt-1]) { 148 | lookAt-- 149 | } 150 | 151 | cp.willMoveH(int32(lookAt) - int32(cp.Logical())) 152 | } 153 | case Forward: 154 | if cp.Logical() < uint32(cp.w.LenContent()) { 155 | // Skip non-spaces to the right. 156 | lookAt := cp.Logical() 157 | for lookAt < uint32(cp.w.LenContent()) && isCoreCharacter(cp.w.Content()[lookAt]) { 158 | lookAt++ 159 | } 160 | 161 | cp.willMoveH(int32(lookAt) - int32(cp.Logical())) 162 | } 163 | } 164 | } 165 | 166 | // Moves caret horizontally by amount. It doesn't do bounds checking, so it's 167 | // the caller's responsibility to ensure it's a legal amount to move by. 168 | // 169 | // Pre-conditions: 170 | // - Moving caret by amount should result in a valid position. 171 | func (cp *caretPositionInternal) willMoveH(amount int32) { 172 | switch { 173 | case amount < 0: 174 | absAmount := uint32(-amount) 175 | for absAmount != 0 { 176 | if cp.positionWithinLine >= absAmount { 177 | cp.positionWithinLine -= absAmount 178 | absAmount = 0 179 | } else { //if cp.lineIndex > 0 180 | absAmount -= 1 + cp.positionWithinLine 181 | cp.lineIndex-- 182 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 183 | } 184 | } 185 | case amount > 0: 186 | absAmount := uint32(amount) 187 | for absAmount != 0 { 188 | if cp.positionWithinLine+absAmount <= cp.w.Line(cp.lineIndex).Length() { 189 | cp.positionWithinLine += absAmount 190 | absAmount = 0 191 | } else { //if cp.lineIndex < some_max 192 | absAmount -= 1 + cp.w.Line(cp.lineIndex).Length() - cp.positionWithinLine 193 | cp.lineIndex++ 194 | cp.positionWithinLine = 0 195 | } 196 | } 197 | default: 198 | // There's no change, so don't do anything else 199 | return 200 | } 201 | 202 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 203 | 204 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 205 | } 206 | 207 | func (cp *caretPositionInternal) tryMoveV(direction direction, jumpWords bool) { 208 | switch direction { 209 | case Backward: 210 | if cp.lineIndex > 0 { 211 | if jumpWords { 212 | for cp.lineIndex > 0 { 213 | cp.lineIndex-- 214 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 215 | if line == "" { 216 | break 217 | } 218 | } 219 | cp.positionWithinLine = 0 220 | } else { 221 | cp.lineIndex-- 222 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 223 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 224 | } 225 | 226 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 227 | } else { 228 | cp.Move(-2) 229 | } 230 | case Forward: 231 | if cp.lineIndex < cp.w.LenLines()-1 { 232 | if jumpWords { 233 | for cp.lineIndex < cp.w.LenLines()-1 { 234 | cp.lineIndex++ 235 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 236 | if line == "" { 237 | break 238 | } 239 | } 240 | cp.positionWithinLine = 0 241 | } else { 242 | cp.lineIndex++ 243 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 244 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 245 | } 246 | 247 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 248 | } else { 249 | cp.Move(+2) 250 | } 251 | } 252 | } 253 | 254 | func (cp *caretPositionInternal) MoveTo(other *caretPositionInternal) { 255 | cp.lineIndex = other.lineIndex 256 | cp.positionWithinLine = other.positionWithinLine 257 | cp.targetExpandedX = other.targetExpandedX 258 | 259 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 260 | } 261 | 262 | func (cp *caretPositionInternal) Compare(other *caretPositionInternal) int8 { 263 | if cp.lineIndex < other.lineIndex { 264 | return -1 265 | } else if cp.lineIndex > other.lineIndex { 266 | return +1 267 | } else { 268 | if cp.positionWithinLine < other.positionWithinLine { 269 | return -1 270 | } else if cp.positionWithinLine > other.positionWithinLine { 271 | return +1 272 | } else { 273 | return 0 274 | } 275 | } 276 | } 277 | 278 | // TODO: Change amount to a proper type with 4 values, etc. to avoid confusion with other funcs where amount can be an arbitrary number. 279 | // TODO: Rename to JumpTo or something to indicate it's a method that can never fail. 280 | // Move jumps the caret position to a new position. This operation never fails, but may not have any effect. 281 | // If amount is ±1, move by 1 character within same line if possible. 282 | // If amount is ±2, jump to start/end of line. 283 | // If amount is ±3, jump to start/end of content. 284 | func (cp *caretPositionInternal) Move(amount int8) { 285 | originalPosition := *cp 286 | 287 | switch amount { 288 | // Move by 1 character within same line, if possible. 289 | case -1: 290 | if cp.positionWithinLine > 0 { 291 | cp.positionWithinLine-- 292 | } 293 | case +1: 294 | if cp.positionWithinLine < cp.w.Line(cp.lineIndex).Length() { 295 | cp.positionWithinLine++ 296 | } 297 | case -2: 298 | cp.positionWithinLine = 0 299 | case +2: 300 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 301 | case -3: 302 | cp.lineIndex = 0 303 | cp.positionWithinLine = 0 304 | case +3: 305 | cp.lineIndex = cp.w.LenLines() - 1 306 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 307 | } 308 | 309 | if cp.Compare(&originalPosition) == 0 { 310 | // There's no change, so don't do anything else 311 | return 312 | } 313 | 314 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 315 | 316 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 317 | } 318 | 319 | func (cp *caretPositionInternal) SetPositionFromLogical(pos mgl64.Vec2) { 320 | if pos[1] < 0 { 321 | cp.lineIndex = 0 322 | } else if pos[1] >= float64(cp.w.LenLines()) { 323 | cp.lineIndex = cp.w.LenLines() - 1 324 | } else { 325 | cp.lineIndex = int(pos[1]) 326 | } 327 | 328 | if pos[0] < 0 { 329 | cp.targetExpandedX = 0 330 | } else { 331 | cp.targetExpandedX = uint32(pos[0] + 0.5) 332 | } 333 | 334 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 335 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 336 | 337 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 338 | } 339 | 340 | // TrySetPositionAtLineIndex places caret at beginning of lineIndex line. It accepts out of range line indicies. 341 | func (cp *caretPositionInternal) TrySetPositionAtLineIndex(lineIndex int) { 342 | if lineIndex < 0 { 343 | lineIndex = 0 344 | } else if lineIndex > cp.w.LenLines()-1 { 345 | lineIndex = cp.w.LenLines() - 1 346 | } 347 | 348 | cp.lineIndex = lineIndex 349 | cp.positionWithinLine = 0 350 | cp.targetExpandedX = 0 351 | 352 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 353 | } 354 | 355 | func (cp *caretPositionInternal) TrySet(position uint32) { 356 | if position > uint32(cp.w.LenContent()) { 357 | position = uint32(cp.w.LenContent()) 358 | } 359 | 360 | cp.lineIndex = sort.Search(cp.w.LenLines()-1, func(lineIndex int) bool { 361 | return cp.w.Line(lineIndex+1).Start() > position 362 | }) 363 | cp.positionWithinLine = position - cp.w.Line(cp.lineIndex).Start() 364 | 365 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 366 | 367 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 368 | } 369 | 370 | // expandedPosition returns logical character units. 371 | // Multiply by (fontWidth, fontHeight) to get physical coords. 372 | func (cp *caretPositionInternal) expandedPosition() (x uint32, y uint32) { 373 | expandedCaretPosition := ExpandedLength(cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).Start()+cp.positionWithinLine], 0) 374 | 375 | return expandedCaretPosition, uint32(cp.lineIndex) 376 | } 377 | 378 | // HACK 379 | func (cp *caretPositionInternal) SetHint(caretPosition uint32, beginLineIndex int) (x uint32, y uint32) { 380 | caretPosition -= cp.w.Line(beginLineIndex).Start() 381 | caretLine := beginLineIndex 382 | for caretPosition > cp.w.Line(caretLine).Length() { 383 | caretPosition -= cp.w.Line(caretLine).Length() + 1 384 | caretLine++ 385 | } 386 | expandedCaretPosition := ExpandedLength(cp.w.Content()[cp.w.Line(caretLine).Start():cp.w.Line(caretLine).Start()+caretPosition], 0) 387 | 388 | cp.targetExpandedX, y = expandedCaretPosition, uint32(caretLine) 389 | 390 | return cp.targetExpandedX, y 391 | } 392 | 393 | func (cp *caretPositionInternal) SaveState() *caretPositionInternal { 394 | return &caretPositionInternal{ 395 | lineIndex: cp.lineIndex, 396 | positionWithinLine: cp.positionWithinLine, 397 | targetExpandedX: cp.targetExpandedX, 398 | } 399 | } 400 | 401 | func isCoreCharacter(character byte) bool { 402 | return (('a' <= character && character <= 'z') || 403 | ('A' <= character && character <= 'Z') || 404 | ('0' <= character && character <= '9') || 405 | '_' == character) 406 | } 407 | 408 | // --- 409 | 410 | type CaretPosition struct { 411 | w MultilineContentI 412 | caretPosition *caretPositionInternal 413 | selectionPosition *caretPositionInternal 414 | 415 | gist7802150.DepNode2 416 | } 417 | 418 | func NewCaretPosition(mc MultilineContentI) *CaretPosition { 419 | cp := &CaretPosition{ 420 | w: mc, 421 | caretPosition: &caretPositionInternal{w: mc}, 422 | selectionPosition: &caretPositionInternal{w: mc}, 423 | } 424 | cp.AddSources(cp.caretPosition, cp.selectionPosition) 425 | return cp 426 | } 427 | 428 | func (cp *CaretPosition) Update() {} 429 | 430 | // TODO: Should DepNode2 be used instead of this custom func? 431 | func (cp *CaretPosition) NotifyContentChanged() { 432 | cp.caretPosition.NotifyContentChanged() 433 | cp.selectionPosition.NotifyContentChanged() 434 | } 435 | 436 | func (cp *CaretPosition) Logical() uint32 { 437 | return cp.caretPosition.Logical() 438 | } 439 | 440 | func (cp *CaretPosition) SelectionRange() (start, end uint32) { 441 | min := intmath.MinUint32(cp.caretPosition.Logical(), cp.selectionPosition.Logical()) 442 | max := intmath.MaxUint32(cp.caretPosition.Logical(), cp.selectionPosition.Logical()) 443 | return min, max 444 | } 445 | 446 | func (cp *CaretPosition) SelectionRange2() (start, end *caretPositionInternal) { 447 | if cp.caretPosition.Compare(cp.selectionPosition) <= 0 { 448 | return cp.caretPosition, cp.selectionPosition 449 | } else { 450 | return cp.selectionPosition, cp.caretPosition 451 | } 452 | } 453 | 454 | func (cp *CaretPosition) AnySelection() bool { 455 | return cp.caretPosition.Compare(cp.selectionPosition) != 0 456 | } 457 | 458 | func (cp *CaretPosition) TryMoveH(direction direction, leaveSelection, jumpWords bool) { 459 | min, max := cp.SelectionRange2() 460 | 461 | switch direction { 462 | case Backward: 463 | if cp.AnySelection() && !leaveSelection { 464 | max.MoveTo(min) 465 | } else { 466 | if cp.caretPosition.Logical() >= 1 { // TODO: Where should this check happen 467 | cp.caretPosition.tryMoveH(direction, jumpWords) 468 | 469 | if !leaveSelection { 470 | cp.selectionPosition.MoveTo(cp.caretPosition) 471 | } 472 | } 473 | } 474 | case Forward: 475 | if cp.AnySelection() && !leaveSelection { 476 | min.MoveTo(max) 477 | } else { 478 | if cp.caretPosition.Logical() < uint32(cp.w.LenContent()) { // TODO: Where should this check happen 479 | cp.caretPosition.tryMoveH(direction, jumpWords) 480 | 481 | if !leaveSelection { 482 | cp.selectionPosition.MoveTo(cp.caretPosition) 483 | } 484 | } 485 | } 486 | } 487 | } 488 | 489 | // HACK: leaveSelection is currently an optional bool parameter 490 | // TODO: Change amount to a proper type with 4 values, etc. to avoid confusion with other funcs where amount can be an arbitrary number. 491 | // TODO: Rename to JumpTo or something to indicate it's a method that can never fail. 492 | // Move jumps the caret position to a new position. This operation never fails, but may not have any effect. 493 | // If amount is ±1, move by 1 character within same line if possible. 494 | // If amount is ±2, jump to start/end of line. 495 | // If amount is ±3, jump to start/end of content. 496 | func (cp *CaretPosition) Move(amount int8, leaveSelectionOptional ...bool) { 497 | // HACK, TODO: Make leaveSelection a required parameter? 498 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 499 | 500 | cp.caretPosition.Move(amount) 501 | 502 | if !leaveSelection { 503 | cp.selectionPosition.MoveTo(cp.caretPosition) 504 | } 505 | } 506 | 507 | func (cp *CaretPosition) MoveTo(target *caretPositionInternal) { 508 | cp.caretPosition.MoveTo(target) 509 | cp.selectionPosition.MoveTo(target) 510 | } 511 | 512 | func (cp *CaretPosition) TryMoveV(direction direction, leaveSelection, jumpWords bool) { 513 | cp.caretPosition.tryMoveV(direction, jumpWords) 514 | 515 | if !leaveSelection { 516 | cp.selectionPosition.MoveTo(cp.caretPosition) 517 | } 518 | } 519 | 520 | func (cp *CaretPosition) SetPositionFromLogical(pos mgl64.Vec2, leaveSelectionOptional ...bool) { 521 | // HACK, TODO: Make leaveSelection a required parameter? 522 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 523 | 524 | cp.caretPosition.SetPositionFromLogical(pos) 525 | 526 | if !leaveSelection { 527 | cp.selectionPosition.MoveTo(cp.caretPosition) 528 | } 529 | } 530 | 531 | func (cp *CaretPosition) Backspace() { 532 | cp.CreateSelectionIfNone(-1) 533 | cp.ReplaceSelectionWith("") 534 | } 535 | 536 | func (cp *CaretPosition) SelectAll() { 537 | // TODO: Not move the view 538 | cp.Move(-3) 539 | cp.Move(+3, true) 540 | } 541 | 542 | func (cp *CaretPosition) CreateSelectionIfNone(amount int32) { 543 | if !cp.AnySelection() { 544 | if (amount < 0 && cp.caretPosition.Logical() >= uint32(-amount)) || 545 | (amount > 0 && cp.caretPosition.Logical()+uint32(amount) <= uint32(cp.w.LenContent())) { 546 | 547 | cp.caretPosition.willMoveH(amount) 548 | } 549 | } 550 | } 551 | 552 | func (cp *CaretPosition) CreateSelectionLineIfNone() { 553 | if !cp.AnySelection() { 554 | cp.selectionPosition.Move(-2) 555 | cp.caretPosition.Move(+2) 556 | cp.caretPosition.tryMoveH(+1, false) 557 | } 558 | } 559 | 560 | // Replaces selection with string s and moves caret to end of s. 561 | func (cp *CaretPosition) ReplaceSelectionWith(s string) { 562 | selStart, selEnd := cp.SelectionRange2() 563 | gist7802150.SetViewGroup(cp.w, cp.w.Content()[:selStart.Logical()]+s+cp.w.Content()[selEnd.Logical():]) 564 | selStart.willMoveH(int32(len(s))) 565 | selEnd.MoveTo(selStart) 566 | } 567 | 568 | // Gets the selection content. 569 | func (cp *CaretPosition) GetSelectionContent() (selectionContent string) { 570 | selStart, selEnd := cp.SelectionRange2() 571 | return cp.w.Content()[selStart.Logical():selEnd.Logical()] 572 | } 573 | 574 | // TrySetPositionAtLineIndex places caret at beginning of lineIndex line. It accepts out of range line indicies. 575 | func (cp *CaretPosition) TrySetPositionAtLineIndex(lineIndex int) { 576 | cp.caretPosition.TrySetPositionAtLineIndex(lineIndex) 577 | cp.selectionPosition.MoveTo(cp.caretPosition) 578 | } 579 | 580 | // TODO: Change api to ask for an caretPositionInternal instance? 581 | func (cp *CaretPosition) TrySet(position uint32, leaveSelectionOptional ...bool) { 582 | // HACK, TODO: Make leaveSelection a required parameter? 583 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 584 | 585 | cp.caretPosition.TrySet(position) 586 | 587 | if !leaveSelection { 588 | cp.selectionPosition.MoveTo(cp.caretPosition) 589 | } 590 | } 591 | 592 | // SetSelection sets a selection starting at start of length. Indices must be valid. 593 | // TODO: Optimise by not doing the "try" logic. 594 | func (cp *CaretPosition) SetSelection(start, length uint32) { 595 | cp.TrySet(start) 596 | cp.caretPosition.willMoveH(int32(length)) 597 | } 598 | 599 | func (cp *CaretPosition) ExpandSelectionToWord() { 600 | switch 1 { 601 | case 0: 602 | if cp.AnySelection() { 603 | return 604 | } 605 | case 1: 606 | // Try it out so that it always works, even if there's already a selection... 607 | cp.selectionPosition, cp.caretPosition = cp.SelectionRange2() 608 | } 609 | 610 | cp.selectionPosition.expandSelection(-1) 611 | cp.caretPosition.expandSelection(+1) 612 | } 613 | 614 | // ExpandedPosition returns logical character units of primary cursor position. 615 | // Multiply by (fontWidth, fontHeight) to get physical coords. 616 | func (cp *CaretPosition) ExpandedPosition() (x, y uint32) { 617 | return cp.caretPosition.expandedPosition() 618 | } 619 | 620 | // LineNumber returns line number of primary caret. Line number is line index + 1, so it starts at 1. 621 | func (cp *CaretPosition) LineNumber() int { 622 | return cp.caretPosition.lineIndex + 1 623 | } 624 | 625 | // lineIndexSpan returns line indicies that are spanned by the selection. 626 | func (cp *CaretPosition) lineIndexSpan() (first, last int) { 627 | min := intmath.MinInt(cp.caretPosition.lineIndex, cp.selectionPosition.lineIndex) 628 | max := intmath.MaxInt(cp.caretPosition.lineIndex, cp.selectionPosition.lineIndex) 629 | return min, max 630 | } 631 | 632 | // DecreaseIndent decreases indent of lines that fall within selection by 1 tab (for lines where it's possible). 633 | func (cp *CaretPosition) DecreaseIndent() { 634 | selStart, selEnd := cp.SelectionRange2() 635 | first, last := cp.lineIndexSpan() 636 | 637 | newContent := cp.w.Content()[:cp.w.Line(first).Start()] 638 | for lineIndex := first; lineIndex <= last; lineIndex++ { 639 | contentLine := cp.w.Line(lineIndex) 640 | 641 | if lineIndex != first { 642 | newContent += "\n" 643 | } 644 | line := cp.w.Content()[contentLine.Start():contentLine.End()] 645 | if strings.HasPrefix(line, "\t") { 646 | line = line[1:] // Trim leading tab. 647 | 648 | if lineIndex == first { 649 | selStart.Move(-1) 650 | } 651 | if lineIndex == last { 652 | selEnd.Move(-1) 653 | } 654 | } 655 | newContent += line 656 | } 657 | newContent += cp.w.Content()[cp.w.Line(last).End():] 658 | 659 | gist7802150.SetViewGroup(cp.w, newContent) 660 | } 661 | 662 | // IncreaseIndent increases indent of all lines that fall within selection by 1 tab. 663 | func (cp *CaretPosition) IncreaseIndent() { 664 | selStart, selEnd := cp.SelectionRange2() 665 | first, last := cp.lineIndexSpan() 666 | 667 | newContent := cp.w.Content()[:cp.w.Line(first).Start()] 668 | for lineIndex := first; lineIndex <= last; lineIndex++ { 669 | contentLine := cp.w.Line(lineIndex) 670 | 671 | if lineIndex != first { 672 | newContent += "\n" 673 | } 674 | newContent += "\t" + cp.w.Content()[contentLine.Start():contentLine.End()] 675 | } 676 | newContent += cp.w.Content()[cp.w.Line(last).End():] 677 | 678 | gist7802150.SetViewGroup(cp.w, newContent) 679 | 680 | // Safe to use willMoveH after SetViewGroup, since we know each line will always get a character longer. 681 | selStart.willMoveH(1) 682 | selEnd.willMoveH(1) 683 | } 684 | 685 | // LeadingTabCount returns number of leading tabs on current line. 686 | func (cp *CaretPosition) LeadingTabCount() int { 687 | tabCount := 0 688 | 689 | lineToCaret := cp.w.Content()[cp.w.Line(cp.caretPosition.lineIndex).Start():cp.Logical()] 690 | 691 | for _, c := range lineToCaret { 692 | if c == '\t' { 693 | tabCount++ 694 | } else { 695 | break 696 | } 697 | } 698 | 699 | return tabCount 700 | } 701 | 702 | func (cp *CaretPosition) SaveState() CaretPosition { 703 | return CaretPosition{ 704 | caretPosition: cp.caretPosition.SaveState(), 705 | selectionPosition: cp.selectionPosition.SaveState(), 706 | } 707 | } 708 | 709 | func (cp *CaretPosition) RestoreState(state CaretPosition) { 710 | // TODO: Add support for gracefully handling content changing since the time state was saved. 711 | cp.caretPosition.MoveTo(state.caretPosition) 712 | cp.selectionPosition.MoveTo(state.selectionPosition) 713 | } 714 | -------------------------------------------------------------------------------- /caret/caret_test.go: -------------------------------------------------------------------------------- 1 | package caret_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/caret" 7 | "github.com/shurcooL-legacy/Conception-go/pkg/multilinecontent" 8 | "github.com/shurcooL/go-goon" 9 | ) 10 | 11 | const sample = `package main 12 | 13 | import "fmt" 14 | 15 | func main() { 16 | fmt.Println("Hello world.") 17 | fmt.Println("How are you?") 18 | 19 | if 1 == 1 { 20 | fmt.Println("Good!") 21 | } 22 | } 23 | ` 24 | 25 | func ExampleCaretPosition() { 26 | mc := multilinecontent.NewString(sample) 27 | cp := caret.NewCaretPosition(mc) 28 | cp.SetSelection(43, 45) 29 | 30 | fmt.Println(cp.AnySelection()) 31 | goon.Dump(cp.GetSelectionContent()) 32 | 33 | // Output: 34 | // true 35 | // (string)("fmt.Println(\"Hello world.\")\n\tfmt.Println(\"How") 36 | } 37 | -------------------------------------------------------------------------------- /caret/whole_word.go: -------------------------------------------------------------------------------- 1 | package caret 2 | 3 | // TODO: Implement IsWholeWord more efficiently without relying on virtual CaretPosition, just use linear content access. 4 | // But reuse tryMoveH jump condition logic for word boundaries. 5 | 6 | func IsWholeWord(content MultilineContentI, caretPosition *CaretPosition) bool { 7 | if !caretPosition.AnySelection() { 8 | return false 9 | } 10 | 11 | start, end := caretPosition.SelectionRange2() 12 | 13 | // Figure out if the selection is a whole word. 14 | x := &caretPositionInternal{w: content} 15 | y := &caretPositionInternal{w: content} 16 | x.MoveTo(start) 17 | y.MoveTo(end) 18 | x.tryMoveH(+1, true) 19 | y.tryMoveH(-1, true) 20 | 21 | return (y.Compare(start) == 0 && x.Compare(end) == 0) 22 | } 23 | 24 | func IsWholeWord2(content MultilineContentI, start, length uint32) bool { 25 | cp := NewCaretPosition(content) 26 | cp.SetSelection(start, length) 27 | 28 | return IsWholeWord(content, cp) 29 | } 30 | -------------------------------------------------------------------------------- /chanwriter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ChanWriter chan []byte 4 | 5 | func (cw ChanWriter) Write(p []byte) (n int, err error) { 6 | // Make a copy of p in order to avoid retaining it. 7 | b := make([]byte, len(p)) 8 | copy(b, p) 9 | cw <- b 10 | return len(b), nil 11 | } 12 | -------------------------------------------------------------------------------- /data/fonts/Helvetica Neue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Helvetica Neue.png -------------------------------------------------------------------------------- /data/fonts/Menlo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Menlo.png -------------------------------------------------------------------------------- /data/fonts/Tahoma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Tahoma.png -------------------------------------------------------------------------------- /event/event.go: -------------------------------------------------------------------------------- 1 | // Package event is a package for common event handling. 2 | package event 3 | 4 | import ( 5 | "io" 6 | "time" 7 | 8 | "github.com/go-gl/mathgl/mgl64" 9 | ) 10 | 11 | type VirtualCategory uint8 12 | 13 | const ( 14 | Typing VirtualCategory = iota 15 | Pointing 16 | Windowing 17 | ) 18 | 19 | //go:generate stringer -type=VirtualCategory 20 | 21 | type Pointer struct { 22 | VirtualCategory VirtualCategory 23 | Mapping Widgeters // Always reflects current pointer state. 24 | OriginMapping Widgeters // Updated only when pointer is moved while not active (e.g., where mouse button was first pressed down). 25 | State PointerState 26 | } 27 | 28 | func (this *Pointer) Render() { 29 | /*switch { 30 | case this.VirtualCategory == POINTING && len(this.State.Axes) >= 2: 31 | // Prevent pointer from being drawn when the OS mouse pointer is visible. 32 | { 33 | // HACK 34 | var windowSize [2]int 35 | if globalWindow != nil { 36 | windowSize[0], windowSize[1], _ = globalWindow.GetSize() 37 | } 38 | 39 | // HACK: OS X specific. 40 | const border = 3 41 | if this.State.Axes[1] < 0 || this.State.Axes[0] < border || this.State.Axes[0] >= float64(windowSize[0])-border || this.State.Axes[1] >= float64(windowSize[1])-border { 42 | break 43 | } 44 | } 45 | 46 | gl.PushMatrix() 47 | defer gl.PopMatrix() 48 | gl.Translated(float64(nearInt64(this.State.Axes[0]))+0.5, float64(nearInt64(this.State.Axes[1]))+0.5, 0) 49 | 50 | const size float64 = 12 51 | gl.Color3d(1, 1, 1) 52 | gl.Begin(gl.TRIANGLE_FAN) 53 | gl.Vertex2d(0, 0) 54 | gl.Vertex2d(0, size) 55 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 56 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 57 | gl.End() 58 | 59 | gl.Begin(gl.LINE_LOOP) 60 | gl.Color3d(0, 0, 0) 61 | gl.Vertex2d(0, 0) 62 | gl.Vertex2d(0, size) 63 | gl.Color3d(0.75, 0.75, 0.75) 64 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 65 | gl.Color3d(0, 0, 0) 66 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 67 | gl.End() 68 | }*/ 69 | } 70 | 71 | type PointerState struct { 72 | Buttons []bool // True means pressed down. 73 | Axes []float64 74 | 75 | Time time.Time 76 | } 77 | 78 | // A pointer is defined to be active if any of its buttons are pressed down. 79 | func (ps *PointerState) IsActive() bool { 80 | for _, button := range ps.Buttons { 81 | if button { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | func (ps *PointerState) Button(button int) bool { 89 | if button >= len(ps.Buttons) { 90 | return false 91 | } 92 | return ps.Buttons[button] 93 | } 94 | 95 | type EventType uint8 96 | 97 | const ( 98 | ButtonEvent EventType = iota 99 | CharacterEvent 100 | SliderEvent 101 | AxisEvent 102 | PointerActivation 103 | PointerDeactivation 104 | ) 105 | 106 | //go:generate stringer -type=EventType 107 | 108 | type eventTypeSet map[EventType]struct{} 109 | 110 | func (s eventTypeSet) Has(et EventType) bool { 111 | _, ok := s[et] 112 | return ok 113 | } 114 | 115 | type InputEvent struct { 116 | Pointer *Pointer 117 | EventTypes eventTypeSet 118 | InputID uint16 119 | // TODO: Add pointers to BeforeState and AfterState? 120 | 121 | Buttons []bool // First button has id InputID, second has id InputID+1, etc. 122 | // TODO: Characters? Split into distinct event types, bundle up in an event frame based on time? 123 | Sliders []float64 124 | Axes []float64 125 | ModifierKey uint8 // TODO: Think about the best solution here. 126 | } 127 | 128 | /*func (e InputEvent) String() string { 129 | return spew.Sdump(e) 130 | }*/ 131 | 132 | func ProcessInputEventQueue(inputEventQueue []InputEvent, widget Widgeter) []InputEvent { 133 | for len(inputEventQueue) > 0 { 134 | /*inputEvent := inputEventQueue[0] 135 | 136 | //fmt.Println(inputEvent) 137 | //spew.Dump(inputEvent)*/ 138 | 139 | inputEvent := inputEventQueue[0] 140 | 141 | // TODO: Calculate whether a pointing pointer moved relative to canvas in a better way... what if canvas is moved via keyboard, etc. 142 | pointingPointerMovedRelativeToCanvas := inputEvent.Pointer.VirtualCategory == Pointing && 143 | (inputEvent.EventTypes.Has(AxisEvent) && inputEvent.InputID == 0 || inputEvent.EventTypes.Has(SliderEvent) && inputEvent.InputID == 2) 144 | 145 | if pointingPointerMovedRelativeToCanvas { 146 | LocalPosition := mgl64.Vec2{inputEvent.Pointer.State.Axes[0], inputEvent.Pointer.State.Axes[1]} 147 | 148 | // Clear previously hit widgets. 149 | for _, widget := range inputEvent.Pointer.Mapping { 150 | delete(widget.HoverPointers(), inputEvent.Pointer) 151 | } 152 | inputEvent.Pointer.Mapping = []Widgeter{} 153 | 154 | // Recalculate currently hit widgets. 155 | inputEvent.Pointer.Mapping = append(inputEvent.Pointer.Mapping, widget.Hit(LocalPosition)...) 156 | for _, widget := range inputEvent.Pointer.Mapping { 157 | widget.HoverPointers()[inputEvent.Pointer] = true 158 | } 159 | } 160 | 161 | // Populate OriginMapping (but only when pointer is moved while not active, and this isn't a deactivation since that's handled below). 162 | if pointingPointerMovedRelativeToCanvas && 163 | !inputEvent.EventTypes.Has(PointerDeactivation) && !inputEvent.Pointer.State.IsActive() { 164 | 165 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 166 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 167 | } 168 | 169 | for _, widget := range inputEvent.Pointer.OriginMapping { 170 | widget.ProcessEvent(inputEvent) 171 | } 172 | 173 | // Populate OriginMapping (but only upon pointer deactivation event). 174 | if inputEvent.Pointer.VirtualCategory == Pointing && inputEvent.EventTypes.Has(PointerDeactivation) { 175 | 176 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 177 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 178 | } 179 | 180 | inputEventQueue = inputEventQueue[1:] 181 | } 182 | 183 | return inputEventQueue 184 | } 185 | 186 | func EnqueueInputEvent(inputEventQueue []InputEvent, inputEvent InputEvent) []InputEvent { 187 | preStateActive := inputEvent.Pointer.State.IsActive() 188 | 189 | { 190 | if inputEvent.EventTypes.Has(ButtonEvent) { 191 | // Extend slice if needed. 192 | neededSize := int(inputEvent.InputID) + len(inputEvent.Buttons) 193 | if neededSize > len(inputEvent.Pointer.State.Buttons) { 194 | inputEvent.Pointer.State.Buttons = append(inputEvent.Pointer.State.Buttons, make([]bool, neededSize-len(inputEvent.Pointer.State.Buttons))...) 195 | } 196 | 197 | copy(inputEvent.Pointer.State.Buttons[inputEvent.InputID:], inputEvent.Buttons) 198 | } 199 | 200 | if inputEvent.EventTypes.Has(AxisEvent) { 201 | // Extend slice if needed. 202 | neededSize := int(inputEvent.InputID) + len(inputEvent.Axes) 203 | if neededSize > len(inputEvent.Pointer.State.Axes) { 204 | inputEvent.Pointer.State.Axes = append(inputEvent.Pointer.State.Axes, make([]float64, neededSize-len(inputEvent.Pointer.State.Axes))...) 205 | } 206 | 207 | copy(inputEvent.Pointer.State.Axes[inputEvent.InputID:], inputEvent.Axes) 208 | } 209 | 210 | inputEvent.Pointer.State.Time = time.Now() 211 | } 212 | 213 | postStateActive := inputEvent.Pointer.State.IsActive() 214 | 215 | switch { 216 | case !preStateActive && postStateActive: 217 | inputEvent.EventTypes[PointerActivation] = struct{}{} 218 | case preStateActive && !postStateActive: 219 | inputEvent.EventTypes[PointerDeactivation] = struct{}{} 220 | } 221 | 222 | return append(inputEventQueue, inputEvent) 223 | } 224 | 225 | // --- 226 | 227 | func nearInt64(value float64) int64 { 228 | if value >= 0 { 229 | return int64(value + 0.5) 230 | } else { 231 | return int64(value - 0.5) 232 | } 233 | } 234 | 235 | // ===== 236 | 237 | // --- 238 | 239 | type ChangeListener interface { 240 | NotifyChange() 241 | } 242 | 243 | type ChangeListenerFunc func() 244 | 245 | func (f ChangeListenerFunc) NotifyChange() { f() } 246 | 247 | // --- 248 | 249 | type DepNodeI interface { 250 | AddChangeListener(l ChangeListener) 251 | } 252 | 253 | type DepNode struct { 254 | changeListeners []ChangeListener 255 | } 256 | 257 | func (this *DepNode) AddChangeListener(l ChangeListener) { 258 | this.changeListeners = append(this.changeListeners, l) 259 | 260 | l.NotifyChange() // TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run. 261 | } 262 | 263 | // Pre-condition: l is a change listener that exists 264 | func (this *DepNode) RemoveChangeListener(l ChangeListener) { 265 | for i := range this.changeListeners { 266 | if this.changeListeners[i] == l { 267 | // Delete. 268 | copy(this.changeListeners[i:], this.changeListeners[i+1:]) 269 | this.changeListeners[len(this.changeListeners)-1] = nil 270 | this.changeListeners = this.changeListeners[:len(this.changeListeners)-1] 271 | //println("removed ith element of originally this many", i, len(this.changeListeners)+1) 272 | return 273 | } 274 | } 275 | panic("RemoveChangeListener: ChangeListener to be deleted wasn't found.") 276 | } 277 | 278 | func (this *DepNode) NotifyAllListeners() { 279 | // TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run. 280 | for _, changeListener := range this.changeListeners { 281 | changeListener.NotifyChange() 282 | } 283 | } 284 | 285 | // --- 286 | 287 | type Widgeter interface { 288 | PollLogic() 289 | io.Closer 290 | Layout() 291 | LayoutNeeded() 292 | Render() 293 | Hit(mgl64.Vec2) []Widgeter 294 | ProcessEvent(InputEvent) // TODO: Upgrade to MatchEventQueue() or so. 295 | ContainsWidget(widget, target Widgeter) bool // Returns true if target is widget or within it. 296 | 297 | Pos() *mgl64.Vec2 298 | Size() *mgl64.Vec2 299 | HoverPointers() map[*Pointer]bool 300 | Parent() Widgeter 301 | SetParent(Widgeter) 302 | 303 | ParentToLocal(mgl64.Vec2) mgl64.Vec2 304 | 305 | //DepNodeI 306 | } 307 | 308 | type Widgeters []Widgeter 309 | 310 | func (widgets Widgeters) ContainsWidget(target Widgeter) bool { 311 | for _, widget := range widgets { 312 | if widget.ContainsWidget(widget, target) { 313 | return true 314 | } 315 | } 316 | return false 317 | } 318 | -------------------------------------------------------------------------------- /event/eventtype_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=EventType; DO NOT EDIT 2 | 3 | package event 4 | 5 | import "fmt" 6 | 7 | const _EventType_name = "BUTTON_EVENTCHARACTER_EVENTSLIDER_EVENTAXIS_EVENTPOINTER_ACTIVATIONPOINTER_DEACTIVATION" 8 | 9 | var _EventType_index = [...]uint8{0, 12, 27, 39, 49, 67, 87} 10 | 11 | func (i EventType) String() string { 12 | if i+1 >= EventType(len(_EventType_index)) { 13 | return fmt.Sprintf("EventType(%d)", i) 14 | } 15 | return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /event/virtualcategory_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=VirtualCategory; DO NOT EDIT 2 | 3 | package event 4 | 5 | import "fmt" 6 | 7 | const _VirtualCategory_name = "TYPINGPOINTINGWINDOWING" 8 | 9 | var _VirtualCategory_index = [...]uint8{0, 6, 14, 23} 10 | 11 | func (i VirtualCategory) String() string { 12 | if i+1 >= VirtualCategory(len(_VirtualCategory_index)) { 13 | return fmt.Sprintf("VirtualCategory(%d)", i) 14 | } 15 | return _VirtualCategory_name[_VirtualCategory_index[i]:_VirtualCategory_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/go-gl/gl/v2.1/gl" 13 | "github.com/go-gl/mathgl/mgl64" 14 | 15 | "github.com/shurcooL-legacy/Conception-go/caret" 16 | ) 17 | 18 | var oFontBase, oFontBackground uint32 19 | var lodBias float64 = -66.67 20 | 21 | // FontOptions specifies the properties of the font. 22 | type FontOptions uint8 23 | 24 | const ( 25 | Regular FontOptions = iota 26 | Bold 27 | Italic 28 | BoldItalic 29 | ) 30 | 31 | // IsBold returns true if the font has the bold property set. 32 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 33 | 34 | // IsItalic returns true if the font has the italic property set. 35 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 36 | 37 | type OpenGLStream struct { 38 | pos mgl64.Vec2 39 | lineStartX float64 40 | advance uint32 41 | 42 | FontOptions FontOptions 43 | BorderColor *mgl64.Vec3 // nil means no border color. 44 | BackgroundColor *mgl64.Vec3 // nil means no background color. 45 | ShowInvisibles bool 46 | } 47 | 48 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 49 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 50 | } 51 | 52 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 53 | o.pos = pos 54 | o.lineStartX = pos[0] 55 | o.advance = 0 56 | } 57 | 58 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 59 | o.pos = pos.Add(mgl64.Vec2{float64(x * fontWidth), float64(y * fontHeight)}) 60 | o.lineStartX = pos[0] 61 | o.advance = x 62 | } 63 | 64 | func (o *OpenGLStream) PrintText(s string) { 65 | for { 66 | end := strings.Index(s, "\n") 67 | 68 | length := len(s) 69 | if end != -1 { 70 | length = end 71 | } 72 | o.PrintLine(s[:length]) 73 | 74 | if end == -1 { 75 | break 76 | } else { 77 | //o.NewLine() 78 | o.PrintSegment(" ") // Newline 79 | o.pos[1] += fontHeight 80 | o.advanceReset() 81 | s = s[end+1:] 82 | } 83 | } 84 | } 85 | 86 | // Input shouldn't have newlines 87 | func (o *OpenGLStream) PrintLine(s string) { 88 | if o.BorderColor != nil { 89 | gl.PushAttrib(gl.CURRENT_BIT) 90 | 91 | expandedLineLength := caret.ExpandedLength(s, o.advance) 92 | 93 | backgroundColor := nearlyWhiteColor 94 | if o.BackgroundColor != nil { 95 | backgroundColor = *o.BackgroundColor 96 | } 97 | 98 | DrawInnerRoundedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 99 | 100 | gl.PopAttrib() 101 | } 102 | 103 | segments := strings.Split(s, "\t") 104 | for index, segment := range segments { 105 | o.PrintSegment(segment) 106 | o.advanceBy(uint32(len(segment))) 107 | if index+1 < len(segments) { 108 | tabSpaces := 4 - (o.advance % 4) 109 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 110 | if o.ShowInvisibles { 111 | gl.PushAttrib(gl.CURRENT_BIT) 112 | DrawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 113 | gl.PopAttrib() 114 | } 115 | o.advanceBy(tabSpaces) 116 | } 117 | } 118 | } 119 | 120 | func (o *OpenGLStream) advanceBy(amount uint32) { 121 | o.advance += amount 122 | o.afterAdvance() 123 | } 124 | func (o *OpenGLStream) advanceReset() { 125 | o.advance = 0 126 | o.afterAdvance() 127 | } 128 | func (o *OpenGLStream) afterAdvance() { 129 | o.pos[0] = o.lineStartX + float64(fontWidth*o.advance) 130 | } 131 | 132 | // Shouldn't have tabs nor newlines 133 | func (o *OpenGLStream) PrintSegment(s string) { 134 | if s == "" { 135 | return 136 | } 137 | 138 | if o.BackgroundColor != nil && o.BorderColor == nil { 139 | gl.PushAttrib(gl.CURRENT_BIT) 140 | gl.Color3dv(&o.BackgroundColor[0]) 141 | gl.PushMatrix() 142 | gl.Translated(o.pos[0], o.pos[1], 0) 143 | for range s { 144 | gl.CallList(oFontBackground) 145 | } 146 | gl.PopMatrix() 147 | gl.PopAttrib() 148 | } 149 | 150 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 151 | 152 | gl.Enable(gl.BLEND) 153 | defer gl.Disable(gl.BLEND) 154 | gl.Enable(gl.TEXTURE_2D) 155 | defer gl.Disable(gl.TEXTURE_2D) 156 | 157 | gl.PushMatrix() 158 | gl.Translated(o.pos[0], o.pos[1], 0) 159 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 160 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 161 | gl.PopMatrix() 162 | 163 | //checkGLError() 164 | } 165 | 166 | // --- 167 | 168 | const fontWidth, fontHeight = 6, 12 169 | 170 | func InitFont() { 171 | LoadTexture(filepath.Join("data", "fonts", "Menlo.png")) 172 | 173 | oFontBase = gl.GenLists(32 + 96*4) 174 | for i := 0; i < 96*4; i++ { 175 | const shiftX, shiftY = float64(1.0 / 16), float64(1.0 / 6 / 4) 176 | 177 | indexX, indexY := i%16, i/16 178 | 179 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 180 | gl.Begin(gl.QUADS) 181 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY)*shiftY) 182 | gl.Vertex2i(0, 0) 183 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY)*shiftY) 184 | gl.Vertex2i(fontWidth, 0) 185 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY+1)*shiftY) 186 | gl.Vertex2i(fontWidth, fontHeight) 187 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY+1)*shiftY) 188 | gl.Vertex2i(0, fontHeight) 189 | gl.End() 190 | gl.Translated(fontWidth, 0.0, 0.0) 191 | gl.EndList() 192 | } 193 | 194 | oFontBackground = gl.GenLists(1) 195 | gl.NewList(oFontBackground, gl.COMPILE) 196 | gl.Begin(gl.QUADS) 197 | gl.Vertex2i(0, 0) 198 | gl.Vertex2i(0, fontHeight) 199 | gl.Vertex2i(fontWidth, fontHeight) 200 | gl.Vertex2i(fontWidth, 0) 201 | gl.End() 202 | gl.Translated(fontWidth, 0.0, 0.0) 203 | gl.EndList() 204 | 205 | checkGLError() 206 | } 207 | 208 | func DeinitFont() { 209 | gl.DeleteLists(oFontBase, 32+96*4) 210 | gl.DeleteLists(oFontBackground, 1) 211 | } 212 | 213 | func LoadTexture(path string) { 214 | //fmt.Printf("Trying to load texture %q: ", path) 215 | 216 | // Open the file 217 | file, err := os.Open(path) 218 | if err != nil { 219 | fmt.Println(os.Getwd()) 220 | log.Fatal(err) 221 | } 222 | defer file.Close() 223 | 224 | // Decode the image 225 | img, _, err := image.Decode(file) 226 | if err != nil { 227 | log.Fatal(err) 228 | } 229 | 230 | bounds := img.Bounds() 231 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 232 | 233 | var format int 234 | var pixPointer *uint8 235 | switch img := img.(type) { 236 | case *image.RGBA: 237 | format = gl.RGBA 238 | pixPointer = &img.Pix[0] 239 | case *image.NRGBA: 240 | format = gl.RGBA 241 | pixPointer = &img.Pix[0] 242 | case *image.Gray: 243 | format = gl.ALPHA 244 | pixPointer = &img.Pix[0] 245 | default: 246 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 247 | } 248 | 249 | var texture uint32 250 | gl.GenTextures(1, &texture) 251 | gl.BindTexture(gl.TEXTURE_2D, texture) 252 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 253 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 254 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 255 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 256 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 257 | checkGLError() 258 | } 259 | 260 | func checkGLError() { 261 | err := gl.GetError() 262 | if err != 0 { 263 | panic(fmt.Errorf("GL error: %v", err)) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shurcooL-legacy/Conception-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 7 | github.com/fsnotify/fsnotify v1.4.7 // indirect 8 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 9 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 10 | github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a 11 | github.com/gopherjs/gopherjs v0.0.0-20200209144316-f9cef593def5 // indirect 12 | github.com/goxjs/gl v0.0.0-20171128034433-dc8f4a9a3c9c 13 | github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838 14 | github.com/kisielk/gotool v1.0.0 // indirect 15 | github.com/mattn/go-runewidth v0.0.8 16 | github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d 17 | github.com/microcosm-cc/bluemonday v1.0.2 // indirect 18 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 // indirect 19 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab // indirect 20 | github.com/pkg/math v0.0.0-20141027224758-f2ed9e40e245 21 | github.com/sergi/go-diff v1.1.0 22 | github.com/shurcooL-legacy/octicons v0.0.0-20180602233653-10c8018fdc76 23 | github.com/shurcooL/frontend v0.0.0-20191223011957-441c9e127002 24 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 25 | github.com/shurcooL/go v0.0.0-20191216061654-b114cc39af9f 26 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 27 | github.com/shurcooL/gopherjslib v0.0.0-20200209150458-30f639db26d4 // indirect 28 | github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect 29 | github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b 30 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 31 | github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 32 | github.com/shurcooL/markdownfmt v0.0.0-20191117054414-21fe95c248e9 33 | github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect 34 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 35 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 36 | github.com/sourcegraph/go-diff v0.5.1 37 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e 38 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 39 | golang.org/x/tools v0.0.0-20200207224406-61798d64f025 40 | gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 41 | honnef.co/go/js/dom v0.0.0-20190526011328-ebc4cf92d81f // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /install-into-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GO111MODULE=on GOPATH="$HOME/Library/Caches/go" GOBIN="$HOME/Dropbox/Applications/Conception.app/Contents/MacOS" go install -v 4 | -------------------------------------------------------------------------------- /page/events/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /page/events/main.go: -------------------------------------------------------------------------------- 1 | // events is a test program for printing basic events. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | 8 | "github.com/goxjs/gl" 9 | "github.com/goxjs/glfw" 10 | "github.com/shurcooL-legacy/Conception-go/event" 11 | ) 12 | 13 | var mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 14 | var keyboardPointer = &event.Pointer{VirtualCategory: event.Typing} 15 | 16 | func init() { 17 | runtime.LockOSThread() 18 | } 19 | 20 | func main() { 21 | if err := glfw.Init(gl.ContextWatcher); err != nil { 22 | panic(err) 23 | } 24 | defer glfw.Terminate() 25 | 26 | window, err := glfw.CreateWindow(400, 400, "", nil, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | window.MakeContextCurrent() 31 | 32 | glfw.SwapInterval(1) // Vsync. 33 | 34 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 35 | gl.Viewport(0, 0, framebufferSize0, framebufferSize1) 36 | 37 | var windowSize [2]int 38 | windowSize[0], windowSize[1] = w.GetSize() 39 | _, _ = windowSize[0], windowSize[1] 40 | 41 | /*inputEvent := InputEvent{ 42 | Pointer: windowPointer, 43 | EventTypes: map[EventType]struct{}{AXIS_EVENT: struct{}{}}, 44 | InputId: 0, 45 | Buttons: nil, 46 | Sliders: nil, 47 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 48 | } 49 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent)*/ 50 | } 51 | { 52 | var framebufferSize [2]int 53 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 54 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 55 | } 56 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 57 | 58 | var inputEventQueue []event.InputEvent 59 | 60 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 61 | inputEvent := event.InputEvent{ 62 | Pointer: mousePointer, 63 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 64 | InputID: 0, 65 | Buttons: nil, 66 | Sliders: []float64{xdelta, ydelta}, 67 | } 68 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 69 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 70 | inputEvent.Axes = []float64{xpos, ypos} 71 | } 72 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 73 | }) 74 | 75 | window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 76 | inputEvent := event.InputEvent{ 77 | Pointer: mousePointer, 78 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 79 | InputID: 2, 80 | Buttons: nil, 81 | Sliders: []float64{yoff, xoff}, 82 | Axes: nil, 83 | } 84 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 85 | }) 86 | 87 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 88 | inputEvent := event.InputEvent{ 89 | Pointer: mousePointer, 90 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 91 | InputID: uint16(button), 92 | Buttons: []bool{action != glfw.Release}, 93 | Sliders: nil, 94 | Axes: nil, 95 | ModifierKey: uint8(mods), 96 | } 97 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 98 | }) 99 | 100 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 101 | inputEvent := event.InputEvent{ 102 | Pointer: keyboardPointer, 103 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 104 | InputID: uint16(key), 105 | Buttons: []bool{action != glfw.Release}, 106 | Sliders: nil, 107 | Axes: nil, 108 | ModifierKey: uint8(mods), 109 | } 110 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 111 | 112 | // HACK. 113 | switch { 114 | case key == glfw.Key1 && action == glfw.Press: 115 | window.SetInputMode(glfw.CursorMode, glfw.CursorNormal) 116 | case key == glfw.Key2 && action == glfw.Press: 117 | window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) 118 | case key == glfw.Key3 && action == glfw.Press: 119 | window.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 120 | } 121 | }) 122 | 123 | window.SetCharCallback(func(w *glfw.Window, char rune) { 124 | inputEvent := event.InputEvent{ 125 | Pointer: keyboardPointer, 126 | EventTypes: map[event.EventType]struct{}{event.CharacterEvent: {}}, 127 | InputID: uint16(char), 128 | Buttons: nil, 129 | Sliders: nil, 130 | Axes: nil, 131 | } 132 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 133 | }) 134 | 135 | gl.ClearColor(0.85, 0.85, 0.85, 1) 136 | 137 | for !window.ShouldClose() { 138 | glfw.PollEvents() 139 | 140 | // Process Input. 141 | inputEventQueue = processInputEventQueue(inputEventQueue) 142 | 143 | gl.Clear(gl.COLOR_BUFFER_BIT) 144 | 145 | mousePointer.Render() 146 | 147 | window.SwapBuffers() 148 | runtime.Gosched() 149 | } 150 | } 151 | 152 | func processInputEventQueue(inputEventQueue []event.InputEvent) []event.InputEvent { 153 | for len(inputEventQueue) > 0 { 154 | inputEvent := inputEventQueue[0] 155 | 156 | fmt.Println(inputEvent) 157 | //spew.Dump(inputEvent) 158 | 159 | inputEventQueue = inputEventQueue[1:] 160 | } 161 | 162 | return inputEventQueue 163 | } 164 | -------------------------------------------------------------------------------- /page/page1/main.go: -------------------------------------------------------------------------------- 1 | // page1 is an experimental page for trying out a gesture recognition system. 2 | package main 3 | 4 | import ( 5 | "log" 6 | "math" 7 | "math/rand" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/go-gl/gl/v2.1/gl" 12 | "github.com/go-gl/mathgl/mgl64" 13 | "github.com/goxjs/glfw" 14 | ) 15 | 16 | // TODO: Remove these 17 | var globalWindow *glfw.Window 18 | 19 | var mousePointer *Pointer 20 | 21 | func init() { 22 | runtime.LockOSThread() 23 | } 24 | 25 | type nopContextWatcher struct{} 26 | 27 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 28 | func (nopContextWatcher) OnDetach() {} 29 | 30 | func main() { 31 | if err := glfw.Init(nopContextWatcher{}); err != nil { 32 | panic(err) 33 | } 34 | defer glfw.Terminate() 35 | 36 | window, err := glfw.CreateWindow(1536, 960, "", nil, nil) 37 | if err != nil { 38 | panic(err) 39 | } 40 | globalWindow = window 41 | window.MakeContextCurrent() 42 | 43 | window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) 44 | 45 | if err := gl.Init(); nil != err { 46 | panic(err) 47 | } 48 | 49 | glfw.SwapInterval(1) // Vsync. 50 | 51 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 52 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 53 | 54 | var windowSize [2]int 55 | windowSize[0], windowSize[1] = w.GetSize() 56 | 57 | // Update the projection matrix 58 | gl.MatrixMode(gl.PROJECTION) 59 | gl.LoadIdentity() 60 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 61 | gl.MatrixMode(gl.MODELVIEW) 62 | 63 | /*inputEvent := InputEvent{ 64 | Pointer: windowPointer, 65 | EventTypes: map[EventType]bool{AXIS_EVENT: true}, 66 | InputId: 0, 67 | Buttons: nil, 68 | Sliders: nil, 69 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 70 | } 71 | inputEventQueue = EnqueueInputEvent(inputEvent, inputEventQueue)*/ 72 | } 73 | { 74 | var framebufferSize [2]int 75 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 76 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 77 | } 78 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 79 | 80 | var inputEventQueue []InputEvent 81 | mousePointer = &Pointer{VirtualCategory: POINTING} 82 | 83 | var lastMousePos mgl64.Vec2 84 | lastMousePos[0], lastMousePos[1] = window.GetCursorPos() 85 | MousePos := func(w *glfw.Window, x, y float64) { 86 | //fmt.Println("MousePos:", x, y) 87 | 88 | inputEvent := InputEvent{ 89 | Pointer: mousePointer, 90 | EventTypes: map[EventType]bool{SLIDER_EVENT: true, AXIS_EVENT: true}, 91 | InputId: 0, 92 | Buttons: nil, 93 | Sliders: []float64{x - lastMousePos[0], y - lastMousePos[1]}, // TODO: Do this in a pointer general way? 94 | Axes: []float64{x, y}, 95 | } 96 | lastMousePos[0] = x 97 | lastMousePos[1] = y 98 | inputEventQueue = EnqueueInputEvent(inputEvent, inputEventQueue) 99 | } 100 | window.SetCursorPosCallback(MousePos) 101 | MousePos(window, lastMousePos[0], lastMousePos[1]) 102 | 103 | gl.ClearColor(0.85, 0.85, 0.85, 1) 104 | 105 | rand.Seed(4) 106 | var widget = newMultitouchTestBoxWidget(mgl64.Vec2{600, 300}, rand.Intn(6)) 107 | var widget2 = newMultitouchTestBoxWidget(mgl64.Vec2{600 + 210, 300 + 210}, rand.Intn(6)) 108 | var widget3 = newMultitouchTestBoxWidget(mgl64.Vec2{600 + 210, 300}, rand.Intn(6)) 109 | var widget4 = newMultitouchTestBoxWidget(mgl64.Vec2{600, 300 + 210}, rand.Intn(6)) 110 | 111 | go func() { 112 | <-time.After(5 * time.Second) 113 | log.Println("trigger!") 114 | widget.color++ // HACK: Racy. 115 | 116 | glfw.PostEmptyEvent() 117 | }() 118 | 119 | for !window.ShouldClose() { 120 | //glfw.PollEvents() 121 | glfw.WaitEvents() 122 | 123 | // Process Input. 124 | inputEventQueue = ProcessInputEventQueue(inputEventQueue) 125 | 126 | gl.Clear(gl.COLOR_BUFFER_BIT) 127 | 128 | widget.Render() 129 | widget2.Render() 130 | widget3.Render() 131 | widget4.Render() 132 | 133 | mousePointer.Render() 134 | 135 | window.SwapBuffers() 136 | log.Println("swapped buffers") 137 | runtime.Gosched() 138 | } 139 | } 140 | 141 | type MultitouchTestBoxWidget struct { 142 | pos mgl64.Vec2 143 | color int 144 | 145 | buffer uint32 146 | } 147 | 148 | func newMultitouchTestBoxWidget(pos mgl64.Vec2, color int) MultitouchTestBoxWidget { 149 | var buffer uint32 150 | gl.GenBuffers(1, &buffer) 151 | gl.BindBuffer(gl.ARRAY_BUFFER, buffer) 152 | vertices := []float32{ 153 | 0, 0, 154 | 0, 200, 155 | 200, 200, 156 | 200, 0, 157 | } 158 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 159 | 160 | return MultitouchTestBoxWidget{pos: pos, color: color, buffer: buffer} 161 | } 162 | 163 | func (w *MultitouchTestBoxWidget) Render() { 164 | colors := [...]mgl64.Vec3{ 165 | {0 / 255.0, 140 / 255.0, 0 / 255.0}, 166 | {0 / 255.0, 98 / 255.0, 140 / 255.0}, 167 | {194 / 255.0, 74 / 255.0, 0 / 255.0}, 168 | {89 / 255.0, 0 / 255.0, 140 / 255.0}, 169 | {191 / 255.0, 150 / 255.0, 0 / 255.0}, 170 | {140 / 255.0, 0 / 255.0, 0 / 255.0}, 171 | } 172 | 173 | backgroundColor := colors[w.color] 174 | 175 | //borderColor := backgroundColor 176 | 177 | switch 1 { 178 | case 0: 179 | DrawBorderlessBox(w.pos, mgl64.Vec2{200, 200}, backgroundColor) 180 | case 1: 181 | gl.PushMatrix() 182 | gl.Translated(w.pos[0], w.pos[1], 0) 183 | gl.Color3dv(&backgroundColor[0]) 184 | 185 | gl.EnableClientState(gl.VERTEX_ARRAY) 186 | gl.BindBuffer(gl.ARRAY_BUFFER, w.buffer) 187 | gl.VertexPointer(2, gl.FLOAT, 0, nil) 188 | 189 | gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) 190 | 191 | gl.PopMatrix() 192 | } 193 | } 194 | 195 | // --- 196 | 197 | func DrawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 198 | gl.Color3dv(&backgroundColor[0]) 199 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 200 | } 201 | 202 | func DrawBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 203 | DrawBorderlessBox(pos.Add(mgl64.Vec2{-1, -1}), size.Add(mgl64.Vec2{2, 2}), borderColor) 204 | DrawBorderlessBox(pos, size, backgroundColor) 205 | } 206 | 207 | // ===== 208 | 209 | type VirtualCategory uint8 210 | 211 | const ( 212 | TYPING VirtualCategory = iota 213 | POINTING 214 | WINDOWING 215 | ) 216 | 217 | type Pointer struct { 218 | VirtualCategory VirtualCategory 219 | //Mapping Widgeters // Always reflects current pointer state. 220 | //OriginMapping Widgeters // Updated only when pointer is moved while not active (e.g., where mouse button was first pressed down). 221 | State PointerState 222 | } 223 | 224 | func (this *Pointer) Render() { 225 | switch { 226 | case this.VirtualCategory == POINTING && len(this.State.Axes) >= 2: 227 | // Prevent pointer from being drawn when the OS mouse pointer is visible. 228 | { 229 | // HACK 230 | var windowSize [2]int 231 | if globalWindow != nil { 232 | windowSize[0], windowSize[1] = globalWindow.GetSize() 233 | } 234 | 235 | // HACK: OS X specific. 236 | const border = 3 237 | if this.State.Axes[1] < 0 || this.State.Axes[0] < border || this.State.Axes[0] >= float64(windowSize[0])-border || this.State.Axes[1] >= float64(windowSize[1])-border { 238 | break 239 | } 240 | } 241 | 242 | gl.PushMatrix() 243 | defer gl.PopMatrix() 244 | gl.Translated(float64(NearInt64(this.State.Axes[0]))+0.5, float64(NearInt64(this.State.Axes[1]))+0.5, 0) 245 | 246 | const size float64 = 12 * 40 / 40 247 | gl.Color3d(1, 1, 1) 248 | gl.Begin(gl.TRIANGLE_FAN) 249 | gl.Vertex2d(0, 0) 250 | gl.Vertex2d(0, size) 251 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 252 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 253 | gl.End() 254 | 255 | gl.Begin(gl.LINE_LOOP) 256 | gl.Color3d(0, 0, 0) 257 | gl.Vertex2d(0, 0) 258 | gl.Vertex2d(0, size) 259 | gl.Color3d(0.75, 0.75, 0.75) 260 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 261 | gl.Color3d(0, 0, 0) 262 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 263 | gl.End() 264 | } 265 | } 266 | 267 | type PointerState struct { 268 | Buttons []bool // True means pressed down 269 | Axes []float64 270 | 271 | Timestamp int64 272 | } 273 | 274 | // A pointer is defined to be active if any of its buttons are pressed down 275 | func (ps *PointerState) IsActive() bool { 276 | //IsAnyButtonsPressed() 277 | for _, button := range ps.Buttons { 278 | if button { 279 | return true 280 | } 281 | } 282 | 283 | return false 284 | } 285 | 286 | func (ps *PointerState) Button(button int) bool { 287 | if button < len(ps.Buttons) { 288 | return ps.Buttons[button] 289 | } else { 290 | return false 291 | } 292 | } 293 | 294 | type EventType uint8 295 | 296 | const ( 297 | BUTTON_EVENT EventType = iota 298 | CHARACTER_EVENT 299 | SLIDER_EVENT 300 | AXIS_EVENT 301 | POINTER_ACTIVATION 302 | POINTER_DEACTIVATION 303 | ) 304 | 305 | type InputEvent struct { 306 | Pointer *Pointer 307 | EventTypes map[EventType]bool 308 | InputId uint16 309 | // TODO: Add pointers to BeforeState and AfterState? 310 | 311 | Buttons []bool 312 | // TODO: Characters? Split into distinct event types, bundle up in an event frame based on time? 313 | Sliders []float64 314 | Axes []float64 315 | ModifierKey glfw.ModifierKey // HACK 316 | } 317 | 318 | func ProcessInputEventQueue(inputEventQueue []InputEvent) []InputEvent { 319 | for len(inputEventQueue) > 0 { 320 | /*inputEvent := inputEventQueue[0] 321 | 322 | // TODO: Calculate whether a pointing pointer moved relative to canvas in a better way... what if canvas is moved via keyboard, etc. 323 | pointingPointerMovedRelativeToCanvas := inputEvent.Pointer.VirtualCategory == POINTING && 324 | (inputEvent.EventTypes[AXIS_EVENT] && inputEvent.InputId == 0 || inputEvent.EventTypes[SLIDER_EVENT] && inputEvent.InputId == 2) 325 | 326 | if pointingPointerMovedRelativeToCanvas { 327 | LocalPosition := mgl64.Vec2{float64(inputEvent.Pointer.State.Axes[0]), float64(inputEvent.Pointer.State.Axes[1])} 328 | 329 | // Clear previously hit widgets 330 | for _, widget := range inputEvent.Pointer.Mapping { 331 | delete(widget.HoverPointers(), inputEvent.Pointer) 332 | } 333 | inputEvent.Pointer.Mapping = []Widgeter{} 334 | 335 | // Recalculate currently hit widgets 336 | inputEvent.Pointer.Mapping = append(inputEvent.Pointer.Mapping, widget.Hit(LocalPosition)...) 337 | for _, widget := range inputEvent.Pointer.Mapping { 338 | widget.HoverPointers()[inputEvent.Pointer] = true 339 | } 340 | } 341 | 342 | // Populate OriginMapping (but only when pointer is moved while not active, and this isn't a deactivation since that's handled below) 343 | if pointingPointerMovedRelativeToCanvas && 344 | !inputEvent.EventTypes[POINTER_DEACTIVATION] && !inputEvent.Pointer.State.IsActive() { 345 | 346 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 347 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 348 | } 349 | 350 | for _, widget := range inputEvent.Pointer.OriginMapping { 351 | widget.ProcessEvent(inputEvent) 352 | } 353 | 354 | // Populate OriginMapping (but only upon pointer deactivation event) 355 | if inputEvent.Pointer.VirtualCategory == POINTING && inputEvent.EventTypes[POINTER_DEACTIVATION] { 356 | 357 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 358 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 359 | }*/ 360 | 361 | inputEventQueue = inputEventQueue[1:] 362 | } 363 | 364 | inputEventQueue = []InputEvent{} 365 | 366 | return inputEventQueue 367 | } 368 | 369 | func EnqueueInputEvent(inputEvent InputEvent, inputEventQueue []InputEvent) []InputEvent { 370 | //fmt.Printf("%#v\n", inputEvent) 371 | 372 | preStateActive := inputEvent.Pointer.State.IsActive() 373 | 374 | { 375 | if inputEvent.EventTypes[BUTTON_EVENT] { 376 | // Extend slice if needed 377 | neededSize := int(inputEvent.InputId) + len(inputEvent.Buttons) 378 | if neededSize > len(inputEvent.Pointer.State.Buttons) { 379 | inputEvent.Pointer.State.Buttons = append(inputEvent.Pointer.State.Buttons, make([]bool, neededSize-len(inputEvent.Pointer.State.Buttons))...) 380 | } 381 | 382 | copy(inputEvent.Pointer.State.Buttons[inputEvent.InputId:], inputEvent.Buttons) 383 | } 384 | 385 | if inputEvent.EventTypes[AXIS_EVENT] { 386 | // Extend slice if needed 387 | neededSize := int(inputEvent.InputId) + len(inputEvent.Axes) 388 | if neededSize > len(inputEvent.Pointer.State.Axes) { 389 | inputEvent.Pointer.State.Axes = append(inputEvent.Pointer.State.Axes, make([]float64, neededSize-len(inputEvent.Pointer.State.Axes))...) 390 | } 391 | 392 | copy(inputEvent.Pointer.State.Axes[inputEvent.InputId:], inputEvent.Axes) 393 | } 394 | 395 | inputEvent.Pointer.State.Timestamp = time.Now().UnixNano() 396 | } 397 | 398 | postStateActive := inputEvent.Pointer.State.IsActive() 399 | 400 | switch { 401 | case !preStateActive && postStateActive: 402 | inputEvent.EventTypes[POINTER_ACTIVATION] = true 403 | case preStateActive && !postStateActive: 404 | inputEvent.EventTypes[POINTER_DEACTIVATION] = true 405 | } 406 | 407 | return append(inputEventQueue, inputEvent) 408 | } 409 | 410 | // ===== 411 | 412 | func NearInt64(value float64) int64 { 413 | if value >= 0 { 414 | return int64(value + 0.5) 415 | } else { 416 | return int64(value - 0.5) 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /page/page2/font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/go-gl/gl/v2.1/gl" 13 | "github.com/go-gl/mathgl/mgl64" 14 | 15 | "github.com/shurcooL-legacy/Conception-go/caret" 16 | ) 17 | 18 | var oFontBase, oFontBackground uint32 19 | var lodBias float64 = -66.67 20 | 21 | var selectedTextColor = mgl64.Vec3{195 / 255.0, 212 / 255.0, 242 / 255.0} 22 | var selectedTextDarkColor = selectedTextColor.Mul(0.75) 23 | 24 | // FontOptions specifies the properties of the font. 25 | type FontOptions uint8 26 | 27 | const ( 28 | Regular FontOptions = iota 29 | Bold 30 | Italic 31 | BoldItalic 32 | ) 33 | 34 | // IsBold returns true if the font has the bold property set. 35 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 36 | 37 | // IsItalic returns true if the font has the italic property set. 38 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 39 | 40 | type OpenGLStream struct { 41 | pos mgl64.Vec2 42 | lineStartX float64 43 | advance uint32 44 | 45 | FontOptions FontOptions 46 | BorderColor *mgl64.Vec3 // nil means no border color. 47 | BackgroundColor *mgl64.Vec3 // nil means no background color. 48 | ShowInvisibles bool 49 | } 50 | 51 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 52 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 53 | } 54 | 55 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 56 | o.pos = pos 57 | o.lineStartX = pos[0] 58 | o.advance = 0 59 | } 60 | 61 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 62 | o.pos = pos.Add(mgl64.Vec2{float64(x) * fontWidth, float64(y) * fontHeight}) 63 | o.lineStartX = pos[0] 64 | o.advance = x 65 | } 66 | 67 | func (o *OpenGLStream) PrintText(s string) { 68 | for { 69 | end := strings.Index(s, "\n") 70 | 71 | length := len(s) 72 | if end != -1 { 73 | length = end 74 | } 75 | o.PrintLine(s[:length]) 76 | 77 | if end == -1 { 78 | break 79 | } else { 80 | //o.NewLine() 81 | o.PrintSegment(" ") // Newline 82 | o.pos[1] += fontHeight 83 | o.advanceReset() 84 | s = s[end+1:] 85 | } 86 | } 87 | } 88 | 89 | // Input shouldn't have newlines 90 | func (o *OpenGLStream) PrintLine(s string) { 91 | if o.BorderColor != nil { 92 | gl.PushAttrib(gl.CURRENT_BIT) 93 | 94 | expandedLineLength := caret.ExpandedLength(s, o.advance) 95 | 96 | backgroundColor := nearlyWhiteColor 97 | if o.BackgroundColor != nil { 98 | backgroundColor = *o.BackgroundColor 99 | } 100 | 101 | drawInnerSlicedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 102 | 103 | gl.PopAttrib() 104 | } 105 | 106 | segments := strings.Split(s, "\t") 107 | for index, segment := range segments { 108 | o.PrintSegment(segment) 109 | o.advanceBy(uint32(len(segment))) 110 | if index+1 < len(segments) { 111 | tabSpaces := 4 - (o.advance % 4) 112 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 113 | if o.ShowInvisibles { 114 | gl.PushAttrib(gl.CURRENT_BIT) 115 | drawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 116 | gl.PopAttrib() 117 | } 118 | o.advanceBy(tabSpaces) 119 | } 120 | } 121 | } 122 | 123 | func (o *OpenGLStream) advanceBy(amount uint32) { 124 | o.advance += amount 125 | o.afterAdvance() 126 | } 127 | func (o *OpenGLStream) advanceReset() { 128 | o.advance = 0 129 | o.afterAdvance() 130 | } 131 | func (o *OpenGLStream) afterAdvance() { 132 | o.pos[0] = o.lineStartX + fontWidth*float64(o.advance) 133 | } 134 | 135 | // Shouldn't have tabs nor newlines 136 | func (o *OpenGLStream) PrintSegment(s string) { 137 | if s == "" { 138 | return 139 | } 140 | 141 | if o.BackgroundColor != nil && o.BorderColor == nil { 142 | gl.PushAttrib(gl.CURRENT_BIT) 143 | gl.Color3dv(&o.BackgroundColor[0]) 144 | gl.PushMatrix() 145 | gl.Translated(o.pos[0], o.pos[1], 0) 146 | for range s { 147 | gl.CallList(oFontBackground) 148 | } 149 | gl.PopMatrix() 150 | gl.PopAttrib() 151 | } 152 | 153 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 154 | 155 | gl.Enable(gl.BLEND) 156 | defer gl.Disable(gl.BLEND) 157 | gl.Enable(gl.TEXTURE_2D) 158 | defer gl.Disable(gl.TEXTURE_2D) 159 | 160 | gl.PushMatrix() 161 | gl.Translated(o.pos[0], o.pos[1], 0) 162 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 163 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 164 | gl.PopMatrix() 165 | 166 | //checkGLError() 167 | } 168 | 169 | // --- 170 | 171 | //const fontHeight = 150 172 | const fontHeight = 13.1 173 | const fontWidth = fontHeight * (1312.0 / 900) * 6.0 / 16 174 | 175 | var shiftXs = [6][17]float64{{0, 0.025914634146341462, 0.051067073170731704, 0.09070121951219512, 0.14329268292682926, 0.19588414634146342, 0.29039634146341464, 0.34984756097560976, 0.37652439024390244, 0.4009146341463415, 0.4253048780487805, 0.45884146341463417, 0.5152439024390244, 0.541920731707317, 0.5785060975609756, 0.604420731707317, 0.6364329268292683}, {0, 0.052591463414634144, 0.10518292682926829, 0.15777439024390244, 0.21036585365853658, 0.2629573170731707, 0.3155487804878049, 0.36814024390243905, 0.42073170731707316, 0.4725609756097561, 0.5251524390243902, 0.551829268292683, 0.5777439024390244, 0.6349085365853658, 0.6913109756097561, 0.7484756097560976, 0.8010670731707317}, {0, 0.07545731707317073, 0.13719512195121952, 0.20121951219512196, 0.2698170731707317, 0.3361280487804878, 0.3940548780487805, 0.4481707317073171, 0.5198170731707317, 0.5884146341463414, 0.6128048780487805, 0.6615853658536586, 0.7248475609756098, 0.7774390243902439, 0.8597560975609756, 0.9283536585365854, 1}, {0, 0.06097560975609756, 0.13338414634146342, 0.19817073170731708, 0.25914634146341464, 0.3132621951219512, 0.38185975609756095, 0.43902439024390244, 0.5266768292682927, 0.5846036585365854, 0.645579268292683, 0.7035060975609756, 0.7278963414634146, 0.7591463414634146, 0.7842987804878049, 0.8407012195121951, 0.8879573170731707}, {0, 0.021341463414634148, 0.07164634146341463, 0.12804878048780488, 0.17835365853658536, 0.2347560975609756, 0.2850609756097561, 0.3132621951219512, 0.3673780487804878, 0.41996951219512196, 0.4413109756097561, 0.46189024390243905, 0.5114329268292683, 0.5320121951219512, 0.6128048780487805, 0.6653963414634146, 0.7195121951219512}, {0, 0.056402439024390245, 0.11204268292682927, 0.14329268292682926, 0.19054878048780488, 0.22027439024390244, 0.2728658536585366, 0.3201219512195122, 0.39176829268292684, 0.4413109756097561, 0.4885670731707317, 0.5335365853658537, 0.5647865853658537, 0.5861280487804879, 0.6173780487804879, 0.6745426829268293, 0.7004573170731707}} 176 | 177 | func InitFont() { 178 | LoadTexture(filepath.Join("data", "fonts", "Helvetica Neue.png")) 179 | 180 | oFontBase = gl.GenLists(32 + 96) 181 | for i := 0; i < 96; i++ { 182 | const shiftY = float64(1.0 / 6) 183 | 184 | indexX, indexY := i%16, i/16 185 | 186 | charWidth := shiftXs[indexY][indexX+1] - shiftXs[indexY][indexX] 187 | 188 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 189 | gl.Begin(gl.QUADS) 190 | gl.TexCoord2d(shiftXs[indexY][indexX], float64(indexY)*shiftY) 191 | gl.Vertex2d(0, 0) 192 | gl.TexCoord2d(shiftXs[indexY][indexX+1], float64(indexY)*shiftY) 193 | gl.Vertex2d(fontWidth*charWidth/float64(1.0/16), 0) 194 | gl.TexCoord2d(shiftXs[indexY][indexX+1], float64(indexY+1)*shiftY) 195 | gl.Vertex2d(fontWidth*charWidth/float64(1.0/16), fontHeight) 196 | gl.TexCoord2d(shiftXs[indexY][indexX], float64(indexY+1)*shiftY) 197 | gl.Vertex2d(0, fontHeight) 198 | gl.End() 199 | gl.Translated(fontWidth*charWidth/float64(1.0/16), 0.0, 0.0) 200 | gl.EndList() 201 | } 202 | 203 | oFontBackground = gl.GenLists(1) 204 | gl.NewList(oFontBackground, gl.COMPILE) 205 | gl.Begin(gl.QUADS) 206 | gl.Vertex2d(0, 0) 207 | gl.Vertex2d(0, fontHeight) 208 | gl.Vertex2d(fontWidth, fontHeight) 209 | gl.Vertex2d(fontWidth, 0) 210 | gl.End() 211 | gl.Translated(fontWidth, 0.0, 0.0) 212 | gl.EndList() 213 | 214 | checkGLError() 215 | } 216 | 217 | func DeinitFont() { 218 | gl.DeleteLists(oFontBase, 32+96) 219 | gl.DeleteLists(oFontBackground, 1) 220 | } 221 | 222 | func LoadTexture(path string) { 223 | //fmt.Printf("Trying to load texture %q: ", path) 224 | 225 | // Open the file 226 | file, err := os.Open(path) 227 | if err != nil { 228 | fmt.Println(os.Getwd()) 229 | log.Fatal(err) 230 | } 231 | defer file.Close() 232 | 233 | // Decode the image 234 | img, _, err := image.Decode(file) 235 | if err != nil { 236 | log.Fatal(err) 237 | } 238 | 239 | bounds := img.Bounds() 240 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 241 | 242 | var format int 243 | var pixPointer *uint8 244 | switch img := img.(type) { 245 | case *image.RGBA: 246 | format = gl.RGBA 247 | pixPointer = &img.Pix[0] 248 | case *image.NRGBA: 249 | format = gl.RGBA 250 | pixPointer = &img.Pix[0] 251 | case *image.Gray: 252 | format = gl.ALPHA 253 | pixPointer = &img.Pix[0] 254 | default: 255 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 256 | } 257 | 258 | var texture uint32 259 | gl.GenTextures(1, &texture) 260 | gl.BindTexture(gl.TEXTURE_2D, texture) 261 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 262 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 263 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 264 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 265 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 266 | checkGLError() 267 | } 268 | 269 | // ===== 270 | 271 | func drawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 272 | gl.Color3dv(&backgroundColor[0]) 273 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 274 | } 275 | 276 | func checkGLError() { 277 | err := gl.GetError() 278 | if err != 0 { 279 | panic(fmt.Errorf("GL error: %v", err)) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /page/page2/main.go: -------------------------------------------------------------------------------- 1 | // page2 is an experimental page for trying out different fonts and small UI widgets. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "go/build" 7 | "log" 8 | "math" 9 | "os" 10 | "runtime" 11 | "time" 12 | 13 | "github.com/go-gl/gl/v2.1/gl" 14 | "github.com/go-gl/mathgl/mgl64" 15 | "github.com/goxjs/glfw" 16 | "github.com/shurcooL-legacy/Conception-go/event" 17 | "github.com/shurcooL/go-goon" 18 | ) 19 | 20 | // HACK: Play with various offsets for font rendering. 21 | var softwareUpdateTextOffset mgl64.Vec2 22 | 23 | var boxUpdated bool 24 | 25 | func drawBox() { 26 | /*gl.Translated(50, 100, 0) 27 | gl.Color3d(201.0/255, 201.0/255, 201.0/255) 28 | gl.Recti(0, 0, 106, 18) 29 | if !boxUpdated { 30 | gl.Color3d(1, 1, 1) 31 | } else { 32 | gl.Color3d(0, 1, 0) 33 | } 34 | gl.Recti(0+1, 0+1, 106-1, 18-1)*/ 35 | 36 | c := mgl64.Vec3{1, 1, 1} 37 | if boxUpdated { 38 | c = mgl64.Vec3{42.0 / 255, 154.0 / 255, 254.0 / 255} 39 | } 40 | 41 | drawInnerRoundedBox(mgl64.Vec2{50, 100}, 42 | mgl64.Vec2{106, 18}, 43 | c.Mul(201.0/255), 44 | c) 45 | } 46 | 47 | func drawInnerRoundedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 48 | if size[0] == 0 || size[1] == 0 { 49 | return 50 | } 51 | 52 | const totalSlices = 4 * 16 53 | const borderWidth = 1 54 | const radius = 2.5 + borderWidth 55 | var x = float64(totalSlices) 56 | 57 | gl.Color3dv(&backgroundColor[0]) 58 | gl.Begin(gl.TRIANGLE_STRIP) 59 | for i := 0; i <= totalSlices/4; i++ { 60 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 61 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(totalSlices-i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(totalSlices-i)/x)*(radius-borderWidth)) 62 | } 63 | for i := totalSlices / 4; i <= totalSlices/2; i++ { 64 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 65 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(totalSlices-i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(totalSlices-i)/x)*(radius-borderWidth)) 66 | } 67 | gl.End() 68 | 69 | gl.Color3dv(&borderColor[0]) 70 | gl.Begin(gl.TRIANGLE_STRIP) 71 | gl.Vertex2d(pos[0]+radius, pos[1]) 72 | gl.Vertex2d(pos[0]+radius, pos[1]+borderWidth) 73 | for i := 0; i <= totalSlices/4; i++ { 74 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+radius-math.Cos(Tau*float64(i)/x)*radius) 75 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 76 | } 77 | for i := totalSlices / 4; i <= totalSlices/2; i++ { 78 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*radius) 79 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 80 | } 81 | for i := totalSlices / 2; i <= totalSlices*3/4; i++ { 82 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*radius) 83 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 84 | } 85 | for i := totalSlices * 3 / 4; i <= totalSlices; i++ { 86 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+radius-math.Cos(Tau*float64(i)/x)*radius) 87 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 88 | } 89 | gl.End() 90 | } 91 | 92 | func drawInnerSlicedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 93 | if size[0] == 0 || size[1] == 0 { 94 | return 95 | } 96 | 97 | const OuterDistance = 2.5 98 | gl.Begin(gl.POLYGON) 99 | gl.Color3dv(&borderColor[0]) 100 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]) 101 | gl.Vertex2d(pos[0], pos[1]+OuterDistance) 102 | gl.Vertex2d(pos[0], pos[1]-OuterDistance+size[1]) 103 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]+size[1]) 104 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]+size[1]) 105 | gl.Vertex2d(pos[0]+size[0], pos[1]-OuterDistance+size[1]) 106 | gl.Vertex2d(pos[0]+size[0], pos[1]+OuterDistance) 107 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]) 108 | gl.End() 109 | 110 | const InnerDistance = OuterDistance + (math.Sqrt2 - 1) 111 | gl.Begin(gl.POLYGON) 112 | gl.Color3dv(&backgroundColor[0]) 113 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]+1) 114 | gl.Vertex2d(pos[0]+1, pos[1]+InnerDistance) 115 | gl.Vertex2d(pos[0]+1, pos[1]-InnerDistance+size[1]) 116 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]-1+size[1]) 117 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]-1+size[1]) 118 | gl.Vertex2d(pos[0]-1+size[0], pos[1]-InnerDistance+size[1]) 119 | gl.Vertex2d(pos[0]-1+size[0], pos[1]+InnerDistance) 120 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]+1) 121 | gl.End() 122 | } 123 | 124 | // Tau is the constant τ, which equals to 6.283185... or 2π. 125 | // Reference: https://oeis.org/A019692 126 | const Tau = 2 * math.Pi 127 | 128 | func drawSpinner(spinner int) { 129 | gl.PushMatrix() 130 | gl.Translated(30.5, 30.5, 0) 131 | gl.Rotated(float64(spinner), 0, 0, 1) 132 | gl.Color3d(0, 0, 0) 133 | gl.Rectd(-0.5, -10.5, 0.5, 10.5) 134 | gl.PopMatrix() 135 | } 136 | 137 | func init() { 138 | runtime.LockOSThread() 139 | } 140 | 141 | // Set the working directory to the root of Conception-go package, so that its assets can be accessed. 142 | func init() { 143 | // importPathToDir resolves the absolute path from importPath. 144 | // There doesn't need to be a valid Go package inside that import path, 145 | // but the directory must exist. 146 | importPathToDir := func(importPath string) (string, error) { 147 | p, err := build.Import(importPath, "", build.FindOnly) 148 | if err != nil { 149 | return "", err 150 | } 151 | return p.Dir, nil 152 | } 153 | 154 | dir, err := importPathToDir("github.com/shurcooL-legacy/Conception-go") 155 | if err != nil { 156 | log.Fatalln("Unable to find github.com/shurcooL-legacy/Conception-go package in your GOPATH, it's needed to load assets:", err) 157 | } 158 | err = os.Chdir(dir) 159 | if err != nil { 160 | log.Fatalln("os.Chdir:", err) 161 | } 162 | } 163 | 164 | type nopContextWatcher struct{} 165 | 166 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 167 | func (nopContextWatcher) OnDetach() {} 168 | 169 | func main() { 170 | if err := glfw.Init(nopContextWatcher{}); err != nil { 171 | panic(err) 172 | } 173 | defer glfw.Terminate() 174 | 175 | glfw.WindowHint(glfw.Samples, 8) // Anti-aliasing. 176 | 177 | window, err := glfw.CreateWindow(400, 400, "", nil, nil) 178 | if err != nil { 179 | panic(err) 180 | } 181 | window.MakeContextCurrent() 182 | 183 | if err := gl.Init(); err != nil { 184 | panic(err) 185 | } 186 | 187 | glfw.SwapInterval(1) // Vsync. 188 | 189 | InitFont() 190 | defer DeinitFont() 191 | 192 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 193 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 194 | 195 | var windowSize [2]int 196 | windowSize[0], windowSize[1] = w.GetSize() 197 | 198 | // Update the projection matrix. 199 | gl.MatrixMode(gl.PROJECTION) 200 | gl.LoadIdentity() 201 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 202 | gl.MatrixMode(gl.MODELVIEW) 203 | } 204 | { 205 | var framebufferSize [2]int 206 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 207 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 208 | } 209 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 210 | 211 | var inputEventQueue []event.InputEvent 212 | mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 213 | 214 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 215 | inputEvent := event.InputEvent{ 216 | Pointer: mousePointer, 217 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 218 | InputID: 0, 219 | Buttons: nil, 220 | Sliders: []float64{xdelta, ydelta}, 221 | } 222 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 223 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 224 | inputEvent.Axes = []float64{xpos, ypos} 225 | } 226 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 227 | }) 228 | 229 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 230 | inputEvent := event.InputEvent{ 231 | Pointer: mousePointer, 232 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 233 | InputID: uint16(button), 234 | Buttons: []bool{action != glfw.Release}, 235 | Sliders: nil, 236 | Axes: nil, 237 | ModifierKey: uint8(mods), 238 | } 239 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 240 | }) 241 | 242 | // HACK: Play with various offsets for font rendering. 243 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 244 | switch { 245 | case key == glfw.KeyLeft && action != glfw.Release: 246 | softwareUpdateTextOffset[0] -= 0.01 247 | case key == glfw.KeyRight && action != glfw.Release: 248 | softwareUpdateTextOffset[0] += 0.01 249 | case key == glfw.KeyUp && action != glfw.Release: 250 | softwareUpdateTextOffset[1] -= 0.01 251 | case key == glfw.KeyDown && action != glfw.Release: 252 | softwareUpdateTextOffset[1] += 0.01 253 | } 254 | }) 255 | defer func() { 256 | goon.DumpExpr(softwareUpdateTextOffset) 257 | }() 258 | 259 | go func() { 260 | <-time.After(5 * time.Second) 261 | log.Println("trigger!") 262 | boxUpdated = true 263 | 264 | glfw.PostEmptyEvent() 265 | }() 266 | 267 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) // For font. 268 | 269 | gl.ClearColor(247.0/255, 247.0/255, 247.0/255, 1) 270 | 271 | var spinner int 272 | 273 | var widgets []event.Widgeter 274 | 275 | widgets = append(widgets, NewButtonWidget(mgl64.Vec2{50, 200}, func() { fmt.Println("button triggered") })) 276 | 277 | for !window.ShouldClose() && glfw.Press != window.GetKey(glfw.KeyEscape) { 278 | glfw.WaitEvents() 279 | 280 | // Process Input. 281 | inputEventQueue = event.ProcessInputEventQueue(inputEventQueue, widgets[0]) 282 | 283 | gl.Clear(gl.COLOR_BUFFER_BIT) 284 | gl.LoadIdentity() 285 | 286 | for _, widget := range widgets { 287 | widget.Render() 288 | } 289 | 290 | drawSpinner(spinner) 291 | spinner++ 292 | 293 | drawBox() 294 | 295 | gl.Color3d(1, 0, 0) 296 | NewOpenGLStream(mgl64.Vec2{50, 300}).PrintText(` !"#$%&'()*+,-./ 297 | 0123456789:;<=>? 298 | @ABCDEFGHIJKLMNO 299 | PQRSTUVWXYZ[\]^_ 300 | ` + "`" + `abcdefghijklmno 301 | pqrstuvwxyz{|}~`) 302 | 303 | window.SwapBuffers() 304 | log.Println("swapped buffers") 305 | 306 | //runtime.Gosched() 307 | } 308 | } 309 | 310 | // ===== 311 | 312 | var mousePointer *event.Pointer 313 | 314 | // --- 315 | 316 | type Widget struct { 317 | pos mgl64.Vec2 318 | size mgl64.Vec2 319 | hoverPointers map[*event.Pointer]bool 320 | parent event.Widgeter 321 | 322 | //DepNode 323 | } 324 | 325 | func NewWidget(pos, size mgl64.Vec2) Widget { 326 | return Widget{pos: pos, size: size, hoverPointers: map[*event.Pointer]bool{}} 327 | } 328 | 329 | func (_ *Widget) PollLogic() {} 330 | func (_ *Widget) Close() error { return nil } 331 | func (w *Widget) Layout() { 332 | if w.parent != nil { 333 | w.parent.Layout() 334 | } 335 | } 336 | func (_ *Widget) LayoutNeeded() {} 337 | func (_ *Widget) Render() {} 338 | func (w *Widget) Hit(ParentPosition mgl64.Vec2) []event.Widgeter { 339 | LocalPosition := w.ParentToLocal(ParentPosition) 340 | 341 | Hit := (LocalPosition[0] >= 0 && 342 | LocalPosition[1] >= 0 && 343 | LocalPosition[0] <= w.size[0] && 344 | LocalPosition[1] <= w.size[1]) 345 | 346 | if Hit { 347 | return []event.Widgeter{w} 348 | } else { 349 | return nil 350 | } 351 | } 352 | func (w *Widget) ProcessEvent(inputEvent event.InputEvent) {} 353 | func (_ *Widget) ContainsWidget(widget, target event.Widgeter) bool { 354 | return widget == target 355 | } 356 | 357 | func (w *Widget) Pos() *mgl64.Vec2 { return &w.pos } 358 | func (w *Widget) Size() *mgl64.Vec2 { return &w.size } 359 | 360 | func (w *Widget) HoverPointers() map[*event.Pointer]bool { 361 | return w.hoverPointers 362 | } 363 | 364 | func (w *Widget) Parent() event.Widgeter { return w.parent } 365 | func (w *Widget) SetParent(p event.Widgeter) { w.parent = p } 366 | 367 | func (w *Widget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) { 368 | return ParentPosition.Sub(w.pos) 369 | } 370 | 371 | type WidgeterS struct{ event.Widgeter } 372 | 373 | func (w WidgeterS) GlobalToParent(GlobalPosition mgl64.Vec2) (ParentPosition mgl64.Vec2) { 374 | switch w.Parent() { 375 | case nil: 376 | ParentPosition = GlobalPosition 377 | default: 378 | ParentPosition = WidgeterS{w.Parent()}.GlobalToLocal(GlobalPosition) 379 | } 380 | return ParentPosition 381 | } 382 | func (w WidgeterS) GlobalToLocal(GlobalPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) { 383 | return w.ParentToLocal(WidgeterS{w}.GlobalToParent(GlobalPosition)) 384 | } 385 | 386 | // --- 387 | 388 | type ButtonWidget struct { 389 | Widget 390 | action func() 391 | } 392 | 393 | func NewButtonWidget(pos mgl64.Vec2, action func()) *ButtonWidget { 394 | w := &ButtonWidget{Widget: NewWidget(pos, mgl64.Vec2{106, 18})} 395 | //w := &ButtonWidget{Widget: NewWidget(pos, mgl64.Vec2{122, 18})} 396 | w.setAction(action) 397 | 398 | return w 399 | } 400 | 401 | func (w *ButtonWidget) setAction(action func()) { 402 | w.action = action 403 | } 404 | 405 | var ( 406 | nearlyWhiteColor = mgl64.Vec3{0.975, 0.975, 0.975} 407 | grayColor = mgl64.Vec3{0.75, 0.75, 0.75} 408 | highlightColor = mgl64.Vec3{0.898, 0.765, 0.396} // Yellowish on-hover border color. 409 | ) 410 | 411 | func (w *ButtonWidget) Render() { 412 | // HACK: Brute-force check the mouse pointer if it contains this widget 413 | isOriginHit := false 414 | for _, hit := range mousePointer.OriginMapping { 415 | if w == hit { 416 | isOriginHit = true 417 | break 418 | } 419 | } 420 | isHit := len(w.HoverPointers()) > 0 421 | 422 | // HACK: Assumes mousePointer rather than considering all connected pointing pointers 423 | if isOriginHit && mousePointer.State.IsActive() && isHit { 424 | //DrawGBox(w.pos, w.size) 425 | //drawInnerRoundedBox(w.pos, w.size, highlightColor, grayColor) 426 | c := mgl64.Vec3{42.0 / 255, 154.0 / 255, 254.0 / 255} 427 | drawInnerRoundedBox(w.pos, w.size, c.Mul(201.0/255), c) 428 | gl.Color3d(1, 1, 1) 429 | //} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit { 430 | // //DrawYBox(w.pos, w.size) 431 | // drawInnerRoundedBox(w.pos, w.size, highlightColor, nearlyWhiteColor) 432 | } else { 433 | //DrawNBox(w.pos, w.size) 434 | //drawInnerRoundedBox(w.pos, w.size, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor) 435 | c := mgl64.Vec3{1, 1, 1} 436 | drawInnerRoundedBox(w.pos, w.size, c.Mul(201.0/255), c) 437 | gl.Color3d(0, 0, 0) 438 | } 439 | 440 | NewOpenGLStream(w.pos.Add(mgl64.Vec2{8, 3}).Add(softwareUpdateTextOffset)).PrintText("Software Update...") 441 | } 442 | func (w *ButtonWidget) Hit(ParentPosition mgl64.Vec2) []event.Widgeter { 443 | if len(w.Widget.Hit(ParentPosition)) > 0 { 444 | return []event.Widgeter{w} 445 | } else { 446 | return nil 447 | } 448 | } 449 | 450 | func (w *ButtonWidget) ProcessEvent(inputEvent event.InputEvent) { 451 | //if _, buttonEvent := inputEvent.EventTypes[event.BUTTON_EVENT]; inputEvent.Pointer.VirtualCategory == event.POINTING && buttonEvent && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false && 452 | if inputEvent.Pointer.VirtualCategory == event.Pointing && inputEvent.EventTypes.Has(event.ButtonEvent) && inputEvent.InputID == 0 && inputEvent.Buttons[0] == false && 453 | inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true 454 | inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer) 455 | 456 | if w.action != nil { 457 | w.action() 458 | //println(GetSourceAsString(w.action)) 459 | 460 | w.Layout() 461 | //w.NotifyAllListeners() 462 | } 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /page/terminal/font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "math" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/go-gl/gl/v2.1/gl" 14 | "github.com/go-gl/mathgl/mgl64" 15 | 16 | "github.com/shurcooL-legacy/Conception-go/caret" 17 | ) 18 | 19 | var oFontBase, oFontBackground uint32 20 | var lodBias = -66.67 21 | 22 | var selectedTextColor = mgl64.Vec3{195 / 255.0, 212 / 255.0, 242 / 255.0} 23 | var selectedTextDarkColor = selectedTextColor.Mul(0.75) 24 | 25 | // FontOptions specifies the properties of the font. 26 | type FontOptions uint8 27 | 28 | const ( 29 | Regular FontOptions = iota 30 | Bold 31 | Italic 32 | BoldItalic 33 | ) 34 | 35 | // IsBold returns true if the font has the bold property set. 36 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 37 | 38 | // IsItalic returns true if the font has the italic property set. 39 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 40 | 41 | type OpenGLStream struct { 42 | pos mgl64.Vec2 43 | lineStartX float64 44 | advance uint32 45 | 46 | FontOptions FontOptions 47 | BorderColor *mgl64.Vec3 // nil means no border color. 48 | BackgroundColor *mgl64.Vec3 // nil means no background color. 49 | ShowInvisibles bool 50 | } 51 | 52 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 53 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 54 | } 55 | 56 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 57 | o.pos = pos 58 | o.lineStartX = pos[0] 59 | o.advance = 0 60 | } 61 | 62 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 63 | o.pos = pos.Add(mgl64.Vec2{float64(x) * fontWidth, float64(y) * fontHeight}) 64 | o.lineStartX = pos[0] 65 | o.advance = x 66 | } 67 | 68 | func (o *OpenGLStream) PrintText(s string) { 69 | for { 70 | end := strings.Index(s, "\n") 71 | 72 | length := len(s) 73 | if end != -1 { 74 | length = end 75 | } 76 | o.PrintLine(s[:length]) 77 | 78 | if end == -1 { 79 | break 80 | } else { 81 | //o.NewLine() 82 | o.PrintSegment(" ") // Newline 83 | o.pos[1] += fontHeight 84 | o.advanceReset() 85 | s = s[end+1:] 86 | } 87 | } 88 | } 89 | 90 | // Input shouldn't have newlines 91 | func (o *OpenGLStream) PrintLine(s string) { 92 | if o.BorderColor != nil { 93 | gl.PushAttrib(gl.CURRENT_BIT) 94 | 95 | expandedLineLength := caret.ExpandedLength(s, o.advance) 96 | 97 | backgroundColor := nearlyWhiteColor 98 | if o.BackgroundColor != nil { 99 | backgroundColor = *o.BackgroundColor 100 | } 101 | 102 | drawInnerSlicedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 103 | 104 | gl.PopAttrib() 105 | } 106 | 107 | segments := strings.Split(s, "\t") 108 | for index, segment := range segments { 109 | o.PrintSegment(segment) 110 | o.advanceBy(uint32(len(segment))) 111 | if index+1 < len(segments) { 112 | tabSpaces := 4 - (o.advance % 4) 113 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 114 | if o.ShowInvisibles { 115 | gl.PushAttrib(gl.CURRENT_BIT) 116 | drawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 117 | gl.PopAttrib() 118 | } 119 | o.advanceBy(tabSpaces) 120 | } 121 | } 122 | } 123 | 124 | func (o *OpenGLStream) advanceBy(amount uint32) { 125 | o.advance += amount 126 | o.afterAdvance() 127 | } 128 | func (o *OpenGLStream) advanceReset() { 129 | o.advance = 0 130 | o.afterAdvance() 131 | } 132 | func (o *OpenGLStream) afterAdvance() { 133 | o.pos[0] = o.lineStartX + fontWidth*float64(o.advance) 134 | } 135 | 136 | // Shouldn't have tabs nor newlines 137 | func (o *OpenGLStream) PrintSegment(s string) { 138 | if s == "" { 139 | return 140 | } 141 | 142 | if o.BackgroundColor != nil && o.BorderColor == nil { 143 | gl.PushAttrib(gl.CURRENT_BIT) 144 | gl.Color3dv(&o.BackgroundColor[0]) 145 | gl.PushMatrix() 146 | gl.Translated(o.pos[0], o.pos[1], 0) 147 | for range s { 148 | gl.CallList(oFontBackground) 149 | } 150 | gl.PopMatrix() 151 | gl.PopAttrib() 152 | } 153 | 154 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 155 | 156 | gl.Enable(gl.BLEND) 157 | defer gl.Disable(gl.BLEND) 158 | gl.Enable(gl.TEXTURE_2D) 159 | defer gl.Disable(gl.TEXTURE_2D) 160 | 161 | gl.PushMatrix() 162 | gl.Translated(o.pos[0], o.pos[1], 0) 163 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 164 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 165 | gl.PopMatrix() 166 | 167 | //checkGLError() 168 | } 169 | 170 | // --- 171 | 172 | const fontWidth, fontHeight = 6, 12 173 | 174 | func InitFont() { 175 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 176 | 177 | LoadTexture(filepath.Join("data", "fonts", "Menlo.png")) 178 | 179 | oFontBase = gl.GenLists(32 + 96*4) 180 | for i := 0; i < 96*4; i++ { 181 | const shiftX, shiftY = float64(1.0 / 16), float64(1.0 / 6 / 4) 182 | 183 | indexX, indexY := i%16, i/16 184 | 185 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 186 | gl.Begin(gl.QUADS) 187 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY)*shiftY) 188 | gl.Vertex2i(0, 0) 189 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY)*shiftY) 190 | gl.Vertex2i(fontWidth, 0) 191 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY+1)*shiftY) 192 | gl.Vertex2i(fontWidth, fontHeight) 193 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY+1)*shiftY) 194 | gl.Vertex2i(0, fontHeight) 195 | gl.End() 196 | gl.Translated(fontWidth, 0.0, 0.0) 197 | gl.EndList() 198 | } 199 | 200 | oFontBackground = gl.GenLists(1) 201 | gl.NewList(oFontBackground, gl.COMPILE) 202 | gl.Begin(gl.QUADS) 203 | gl.Vertex2i(0, 0) 204 | gl.Vertex2i(0, fontHeight) 205 | gl.Vertex2i(fontWidth, fontHeight) 206 | gl.Vertex2i(fontWidth, 0) 207 | gl.End() 208 | gl.Translated(fontWidth, 0.0, 0.0) 209 | gl.EndList() 210 | 211 | checkGLError() 212 | } 213 | 214 | func DeinitFont() { 215 | gl.DeleteLists(oFontBase, 32+96*4) 216 | gl.DeleteLists(oFontBackground, 1) 217 | } 218 | 219 | func LoadTexture(path string) { 220 | //fmt.Printf("Trying to load texture %q: ", path) 221 | 222 | // Open the file 223 | file, err := os.Open(path) 224 | if err != nil { 225 | fmt.Println(os.Getwd()) 226 | log.Fatal(err) 227 | } 228 | defer file.Close() 229 | 230 | // Decode the image 231 | img, _, err := image.Decode(file) 232 | if err != nil { 233 | log.Fatal(err) 234 | } 235 | 236 | bounds := img.Bounds() 237 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 238 | 239 | var format int 240 | var pixPointer *uint8 241 | switch img := img.(type) { 242 | case *image.RGBA: 243 | format = gl.RGBA 244 | pixPointer = &img.Pix[0] 245 | case *image.NRGBA: 246 | format = gl.RGBA 247 | pixPointer = &img.Pix[0] 248 | case *image.Gray: 249 | format = gl.ALPHA 250 | pixPointer = &img.Pix[0] 251 | default: 252 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 253 | } 254 | 255 | var texture uint32 256 | gl.GenTextures(1, &texture) 257 | gl.BindTexture(gl.TEXTURE_2D, texture) 258 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 259 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 260 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 261 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 262 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 263 | checkGLError() 264 | } 265 | 266 | var ( 267 | nearlyWhiteColor = mgl64.Vec3{0.975, 0.975, 0.975} 268 | ) 269 | 270 | func drawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 271 | gl.Color3dv(&backgroundColor[0]) 272 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 273 | } 274 | 275 | func drawInnerSlicedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 276 | if size[0] == 0 || size[1] == 0 { 277 | return 278 | } 279 | 280 | const OuterDistance = 2.5 281 | gl.Begin(gl.POLYGON) 282 | gl.Color3dv(&borderColor[0]) 283 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]) 284 | gl.Vertex2d(pos[0], pos[1]+OuterDistance) 285 | gl.Vertex2d(pos[0], pos[1]-OuterDistance+size[1]) 286 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]+size[1]) 287 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]+size[1]) 288 | gl.Vertex2d(pos[0]+size[0], pos[1]-OuterDistance+size[1]) 289 | gl.Vertex2d(pos[0]+size[0], pos[1]+OuterDistance) 290 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]) 291 | gl.End() 292 | 293 | const InnerDistance = OuterDistance + (math.Sqrt2 - 1) 294 | gl.Begin(gl.POLYGON) 295 | gl.Color3dv(&backgroundColor[0]) 296 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]+1) 297 | gl.Vertex2d(pos[0]+1, pos[1]+InnerDistance) 298 | gl.Vertex2d(pos[0]+1, pos[1]-InnerDistance+size[1]) 299 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]-1+size[1]) 300 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]-1+size[1]) 301 | gl.Vertex2d(pos[0]-1+size[0], pos[1]-InnerDistance+size[1]) 302 | gl.Vertex2d(pos[0]-1+size[0], pos[1]+InnerDistance) 303 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]+1) 304 | gl.End() 305 | } 306 | 307 | func checkGLError() { 308 | err := gl.GetError() 309 | if err != 0 { 310 | panic(fmt.Errorf("GL error: %v", err)) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /page/terminal/main.go: -------------------------------------------------------------------------------- 1 | // terminal is a simple terminal app. 2 | package main 3 | 4 | import ( 5 | "go/build" 6 | "log" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/go-gl/gl/v2.1/gl" 12 | "github.com/go-gl/mathgl/mgl64" 13 | "github.com/goxjs/glfw" 14 | "github.com/shurcooL-legacy/Conception-go/event" 15 | ) 16 | 17 | const ( 18 | borderX, borderY = 5, 4 19 | ) 20 | 21 | func init() { 22 | runtime.LockOSThread() 23 | } 24 | 25 | const prompt = "~ $ " 26 | 27 | var text = prompt 28 | 29 | func run() error { 30 | if err := glfw.Init(nopContextWatcher{}); err != nil { 31 | return err 32 | } 33 | defer glfw.Terminate() 34 | 35 | glfw.WindowHint(glfw.Resizable, gl.FALSE) 36 | window, err := glfw.CreateWindow(80*fontWidth+borderX*2, 25*fontHeight+borderY*2, "Terminal — 80×25", nil, nil) 37 | if err != nil { 38 | return err 39 | } 40 | window.MakeContextCurrent() 41 | 42 | if err := gl.Init(); err != nil { 43 | return err 44 | } 45 | 46 | // Clear screen right away. 47 | gl.ClearColor(1, 1, 1, 1) 48 | gl.Clear(gl.COLOR_BUFFER_BIT) 49 | window.SwapBuffers() 50 | 51 | glfw.SwapInterval(1) // Vsync. 52 | 53 | InitFont() 54 | defer DeinitFont() 55 | 56 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 57 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 58 | 59 | var windowSize [2]int 60 | windowSize[0], windowSize[1] = w.GetSize() 61 | 62 | // Update the projection matrix. 63 | gl.MatrixMode(gl.PROJECTION) 64 | gl.LoadIdentity() 65 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 66 | gl.MatrixMode(gl.MODELVIEW) 67 | 68 | /*inputEvent := InputEvent{ 69 | Pointer: windowPointer, 70 | EventTypes: map[EventType]struct{}{AxisEvent: struct{}{}}, 71 | InputID: 0, 72 | Buttons: nil, 73 | Sliders: nil, 74 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 75 | } 76 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent)*/ 77 | } 78 | { 79 | var framebufferSize [2]int 80 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 81 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 82 | } 83 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 84 | 85 | var mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 86 | var keyboardPointer = &event.Pointer{VirtualCategory: event.Typing} 87 | var inputEventQueue []event.InputEvent 88 | 89 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 90 | inputEvent := event.InputEvent{ 91 | Pointer: mousePointer, 92 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 93 | InputID: 0, 94 | Buttons: nil, 95 | Sliders: []float64{xdelta, ydelta}, 96 | } 97 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 98 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 99 | inputEvent.Axes = []float64{xpos, ypos} 100 | } 101 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 102 | }) 103 | 104 | window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 105 | inputEvent := event.InputEvent{ 106 | Pointer: mousePointer, 107 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 108 | InputID: 2, 109 | Buttons: nil, 110 | Sliders: []float64{yoff, xoff}, 111 | Axes: nil, 112 | } 113 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 114 | }) 115 | 116 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 117 | inputEvent := event.InputEvent{ 118 | Pointer: mousePointer, 119 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 120 | InputID: uint16(button), 121 | Buttons: []bool{action != glfw.Release}, 122 | Sliders: nil, 123 | Axes: nil, 124 | ModifierKey: uint8(mods), 125 | } 126 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 127 | }) 128 | 129 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 130 | inputEvent := event.InputEvent{ 131 | Pointer: keyboardPointer, 132 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 133 | InputID: uint16(key), 134 | Buttons: []bool{action != glfw.Release}, 135 | Sliders: nil, 136 | Axes: nil, 137 | ModifierKey: uint8(mods), 138 | } 139 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 140 | }) 141 | 142 | window.SetCharCallback(func(w *glfw.Window, char rune) { 143 | inputEvent := event.InputEvent{ 144 | Pointer: keyboardPointer, 145 | EventTypes: map[event.EventType]struct{}{event.CharacterEvent: {}}, 146 | InputID: uint16(char), 147 | Buttons: nil, 148 | Sliders: nil, 149 | Axes: nil, 150 | } 151 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 152 | }) 153 | 154 | var framebufferSize [2]int 155 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 156 | 157 | app := &app{window: window} 158 | 159 | for !window.ShouldClose() { 160 | glfw.PollEvents() 161 | 162 | // Process Input. 163 | inputEventQueue = app.processInputEventQueue(inputEventQueue) 164 | 165 | gl.Clear(gl.COLOR_BUFFER_BIT) 166 | 167 | /*gl.Color3d(0.5, 0.5, 0.5) 168 | gl.Begin(gl.TRIANGLE_FAN) 169 | gl.Vertex2i(0, 0) 170 | gl.Vertex2i(int32(framebufferSize[0]), 0) 171 | gl.Vertex2i(int32(framebufferSize[0]), 1) 172 | gl.Vertex2i(0, 1) 173 | gl.End()*/ 174 | 175 | gl.Color3d(0, 0, 0) 176 | NewOpenGLStream(mgl64.Vec2{borderX, borderY}).PrintText(text) 177 | 178 | // Draw caret. 179 | lines := strings.Split(text, "\n") 180 | lastLine := lines[len(lines)-1] 181 | expandedCaretPosition, caretLine := len(lastLine), len(lines)-1 182 | gl.PushMatrix() 183 | gl.Translated(borderX, borderY, 0) 184 | gl.Color3d(0, 0, 0) 185 | gl.Recti(int32(expandedCaretPosition*fontWidth-1), int32(caretLine*fontHeight), int32(expandedCaretPosition*fontWidth+1), int32(caretLine*fontHeight)+fontHeight) 186 | gl.PopMatrix() 187 | 188 | mousePointer.Render() 189 | 190 | window.SwapBuffers() 191 | runtime.Gosched() 192 | } 193 | 194 | return nil 195 | } 196 | 197 | func main() { 198 | err := run() 199 | if err != nil { 200 | log.Fatalln(err) 201 | } 202 | } 203 | 204 | type app struct { 205 | window *glfw.Window 206 | } 207 | 208 | func (a *app) processInputEventQueue(inputEventQueue []event.InputEvent) []event.InputEvent { 209 | for len(inputEventQueue) > 0 { 210 | inputEvent := inputEventQueue[0] 211 | 212 | //fmt.Println(inputEvent) 213 | _ = inputEvent 214 | //spew.Dump(inputEvent) 215 | 216 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyW && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModSuper { 217 | a.window.SetShouldClose(true) 218 | } 219 | 220 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) & ^glfw.ModShift == 0 { 221 | switch glfw.Key(inputEvent.InputID) { 222 | case glfw.KeyBackspace: 223 | if len(text) > 0 { 224 | text = text[:len(text)-1] 225 | } 226 | case glfw.KeyEnter: 227 | text += "\n-bash: command not found" 228 | text += "\n" + prompt 229 | } 230 | } 231 | 232 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyL && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModControl { 233 | text = prompt 234 | } 235 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyC && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModControl { 236 | text += "^C\n" + prompt 237 | } 238 | 239 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.CharacterEvent) { 240 | text += string(inputEvent.InputID) 241 | } 242 | 243 | inputEventQueue = inputEventQueue[1:] 244 | } 245 | 246 | return inputEventQueue 247 | } 248 | 249 | type nopContextWatcher struct{} 250 | 251 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 252 | func (nopContextWatcher) OnDetach() {} 253 | 254 | // Set the working directory to the root of Conception-go package, so that its assets can be accessed. 255 | func init() { 256 | // importPathToDir resolves the absolute path from importPath. 257 | // There doesn't need to be a valid Go package inside that import path, 258 | // but the directory must exist. 259 | importPathToDir := func(importPath string) (string, error) { 260 | p, err := build.Import(importPath, "", build.FindOnly) 261 | if err != nil { 262 | return "", err 263 | } 264 | return p.Dir, nil 265 | } 266 | 267 | dir, err := importPathToDir("github.com/shurcooL-legacy/Conception-go") 268 | if err != nil { 269 | log.Fatalln("Unable to find github.com/shurcooL-legacy/Conception-go package in your GOPATH, it's needed to load assets:", err) 270 | } 271 | err = os.Chdir(dir) 272 | if err != nil { 273 | log.Fatalln("os.Chdir:", err) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /pkg/analysis/generated_detection.go: -------------------------------------------------------------------------------- 1 | // Package analysis provides a routine that determines if a file is generated or handcrafted. 2 | // 3 | // Deprecated: Use github.com/shurcooL/go/generated package instead. This implementation 4 | // was done ad-hoc before a standard was proposed. 5 | package analysis 6 | 7 | import ( 8 | "bufio" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | // IsFileGenerated returns true if the specified file is generated, or false if it's handcrafted. 16 | // rootDir is the filepath of root directory, but name is a '/'-separated path to file. 17 | // 18 | // It considers vendored files as "generated", in the sense that they are not the canonical 19 | // version of a file. This behavior would ideally be factored out into a higher level utility, 20 | // since it has nothing to do with generated comments. 21 | // 22 | // Deprecated: Use generated.ParseFile instead, which is more well defined because it 23 | // implements a specification. 24 | func IsFileGenerated(rootDir, name string) (bool, error) { 25 | // Detect from name. 26 | switch { 27 | case strings.HasPrefix(name, "vendor/") || strings.Contains(name, "/vendor/"): 28 | return true, nil 29 | case strings.HasPrefix(name, "Godeps/"): 30 | return true, nil 31 | } 32 | 33 | // Detect from file contents. 34 | f, err := os.Open(filepath.Join(rootDir, filepath.FromSlash(name))) 35 | if err != nil { 36 | return false, err 37 | } 38 | defer f.Close() 39 | r := bufio.NewReader(f) 40 | s, err := r.ReadString('\n') 41 | if err == io.EOF { 42 | // Empty file or exactly 1 line is not considered to be generated. 43 | return false, nil 44 | } else if err != nil { 45 | return false, err 46 | } 47 | if strings.Contains(s, "Code generated by") { // Consistent with https://golang.org/cl/15073. 48 | return true, nil 49 | } 50 | return (strings.Contains(s, "GENERATED") || strings.Contains(s, "generated")) && strings.Contains(s, "DO NOT EDIT"), nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/analysis/generated_detection_test.go: -------------------------------------------------------------------------------- 1 | package analysis_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/shurcooL-legacy/Conception-go/pkg/analysis" 8 | ) 9 | 10 | func ExampleIsFileGenerated() { 11 | cwd, err := os.Getwd() 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/generated_0.go.txt")) 17 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_0.go.txt")) 18 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_1.go.txt")) 19 | fmt.Println(analysis.IsFileGenerated(cwd, "vendor/github.com/shurcooL/go/analysis/file.go")) 20 | fmt.Println(analysis.IsFileGenerated(cwd, "subpkg/vendor/math/math.go")) 21 | 22 | // Output: 23 | // true