ui.go (27384B)
1 // Copyright 2015 Hajime Hoshi 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // +build darwin freebsd linux windows 16 // +build !android 17 // +build !ios 18 19 package glfw 20 21 import ( 22 "fmt" 23 "image" 24 "os" 25 "runtime" 26 "sync" 27 "time" 28 29 "github.com/hajimehoshi/ebiten/v2/internal/devicescale" 30 "github.com/hajimehoshi/ebiten/v2/internal/driver" 31 "github.com/hajimehoshi/ebiten/v2/internal/glfw" 32 "github.com/hajimehoshi/ebiten/v2/internal/hooks" 33 "github.com/hajimehoshi/ebiten/v2/internal/thread" 34 ) 35 36 type UserInterface struct { 37 context driver.UIContext 38 title string 39 window *glfw.Window 40 41 // windowWidth and windowHeight represents a window size. 42 // The unit is device-dependent pixels. 43 windowWidth int 44 windowHeight int 45 46 running bool 47 toChangeSize bool 48 origPosX int 49 origPosY int 50 runnableOnUnfocused bool 51 vsync bool 52 53 lastDeviceScaleFactor float64 54 55 initMonitor *glfw.Monitor 56 initTitle string 57 initVsync bool 58 initFullscreenWidthInDP int 59 initFullscreenHeightInDP int 60 initFullscreen bool 61 initCursorMode driver.CursorMode 62 initWindowDecorated bool 63 initWindowResizable bool 64 initWindowPositionXInDP int 65 initWindowPositionYInDP int 66 initWindowWidthInDP int 67 initWindowHeightInDP int 68 initWindowFloating bool 69 initWindowMaximized bool 70 initScreenTransparent bool 71 initIconImages []image.Image 72 initFocused bool 73 74 vsyncInited bool 75 76 reqWidth int 77 reqHeight int 78 79 input Input 80 iwindow window 81 82 t *thread.Thread 83 m sync.RWMutex 84 } 85 86 const ( 87 maxInt = int(^uint(0) >> 1) 88 minInt = -maxInt - 1 89 invalidPos = minInt 90 ) 91 92 var ( 93 theUI = &UserInterface{ 94 runnableOnUnfocused: true, 95 origPosX: invalidPos, 96 origPosY: invalidPos, 97 initVsync: true, 98 initCursorMode: driver.CursorModeVisible, 99 initWindowDecorated: true, 100 initWindowPositionXInDP: invalidPos, 101 initWindowPositionYInDP: invalidPos, 102 initWindowWidthInDP: 640, 103 initWindowHeightInDP: 480, 104 initFocused: true, 105 vsync: true, 106 } 107 ) 108 109 func init() { 110 theUI.input.ui = theUI 111 theUI.iwindow.ui = theUI 112 } 113 114 func Get() *UserInterface { 115 return theUI 116 } 117 118 func init() { 119 hideConsoleWindowOnWindows() 120 if err := initialize(); err != nil { 121 panic(err) 122 } 123 glfw.SetMonitorCallback(func(monitor *glfw.Monitor, event glfw.PeripheralEvent) { 124 cacheMonitors() 125 }) 126 cacheMonitors() 127 } 128 129 func initialize() error { 130 if err := glfw.Init(); err != nil { 131 return err 132 } 133 glfw.WindowHint(glfw.Visible, glfw.False) 134 glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) 135 136 // Create a window to set the initial monitor. 137 w, err := glfw.CreateWindow(16, 16, "", nil, nil) 138 if err != nil { 139 return err 140 } 141 if w == nil { 142 // This can happen on Windows Remote Desktop (#903). 143 panic("glfw: glfw.CreateWindow must not return nil") 144 } 145 146 // Create a window and set it: this affects fromGLFWMonitorPixel and deviceScaleFactor. 147 theUI.window = w 148 theUI.initMonitor = currentMonitor(w) 149 v := theUI.initMonitor.GetVideoMode() 150 theUI.initFullscreenWidthInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Width))) 151 theUI.initFullscreenHeightInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Height))) 152 theUI.window.Destroy() 153 theUI.window = nil 154 155 return nil 156 } 157 158 type cachedMonitor struct { 159 m *glfw.Monitor 160 vm *glfw.VidMode 161 // Pos of monitor in virtual coords 162 x int 163 y int 164 } 165 166 // monitors is the monitor list cache for desktop glfw compile targets. 167 // populated by 'cacheMonitors' which is called on init and every 168 // monitor config change event. 169 // 170 // monitors must be manipulated on the main thread. 171 var monitors []*cachedMonitor 172 173 func cacheMonitors() { 174 monitors = nil 175 ms := glfw.GetMonitors() 176 for _, m := range ms { 177 x, y := m.GetPos() 178 monitors = append(monitors, &cachedMonitor{ 179 m: m, 180 vm: m.GetVideoMode(), 181 x: x, 182 y: y, 183 }) 184 } 185 } 186 187 // getCachedMonitor returns a monitor for the given window x/y, 188 // or returns nil if monitor is not found. 189 // 190 // getCachedMonitor must be called on the main thread. 191 func getCachedMonitor(wx, wy int) *cachedMonitor { 192 for _, m := range monitors { 193 if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height { 194 return m 195 } 196 } 197 return nil 198 } 199 200 func (u *UserInterface) isRunning() bool { 201 u.m.RLock() 202 v := u.running 203 u.m.RUnlock() 204 return v 205 } 206 207 func (u *UserInterface) setRunning(running bool) { 208 u.m.Lock() 209 u.running = running 210 u.m.Unlock() 211 } 212 213 func (u *UserInterface) getInitTitle() string { 214 u.m.RLock() 215 v := u.initTitle 216 u.m.RUnlock() 217 return v 218 } 219 220 func (u *UserInterface) setInitTitle(title string) { 221 u.m.RLock() 222 u.initTitle = title 223 u.m.RUnlock() 224 } 225 226 func (u *UserInterface) isInitVsyncEnabled() bool { 227 u.m.RLock() 228 v := u.initVsync 229 u.m.RUnlock() 230 return v 231 } 232 233 func (u *UserInterface) isInitFullscreen() bool { 234 u.m.RLock() 235 v := u.initFullscreen 236 u.m.RUnlock() 237 return v 238 } 239 240 func (u *UserInterface) setInitFullscreen(initFullscreen bool) { 241 u.m.Lock() 242 u.initFullscreen = initFullscreen 243 u.m.Unlock() 244 } 245 246 func (u *UserInterface) getInitCursorMode() driver.CursorMode { 247 u.m.RLock() 248 v := u.initCursorMode 249 u.m.RUnlock() 250 return v 251 } 252 253 func (u *UserInterface) setInitCursorMode(mode driver.CursorMode) { 254 u.m.Lock() 255 u.initCursorMode = mode 256 u.m.Unlock() 257 } 258 259 func (u *UserInterface) isInitWindowDecorated() bool { 260 u.m.RLock() 261 v := u.initWindowDecorated 262 u.m.RUnlock() 263 return v 264 } 265 266 func (u *UserInterface) setInitWindowDecorated(decorated bool) { 267 u.m.Lock() 268 u.initWindowDecorated = decorated 269 u.m.Unlock() 270 } 271 272 func (u *UserInterface) isRunnableOnUnfocused() bool { 273 u.m.RLock() 274 v := u.runnableOnUnfocused 275 u.m.RUnlock() 276 return v 277 } 278 279 func (u *UserInterface) setRunnableOnUnfocused(runnableOnUnfocused bool) { 280 u.m.Lock() 281 u.runnableOnUnfocused = runnableOnUnfocused 282 u.m.Unlock() 283 } 284 285 func (u *UserInterface) isInitWindowResizable() bool { 286 u.m.RLock() 287 v := u.initWindowResizable 288 u.m.RUnlock() 289 return v 290 } 291 292 func (u *UserInterface) setInitWindowResizable(resizable bool) { 293 u.m.Lock() 294 u.initWindowResizable = resizable 295 u.m.Unlock() 296 } 297 298 func (u *UserInterface) isInitScreenTransparent() bool { 299 u.m.RLock() 300 v := u.initScreenTransparent 301 u.m.RUnlock() 302 return v 303 } 304 305 func (u *UserInterface) setInitScreenTransparent(transparent bool) { 306 u.m.RLock() 307 u.initScreenTransparent = transparent 308 u.m.RUnlock() 309 } 310 311 func (u *UserInterface) getInitIconImages() []image.Image { 312 u.m.RLock() 313 i := u.initIconImages 314 u.m.RUnlock() 315 return i 316 } 317 318 func (u *UserInterface) setInitIconImages(iconImages []image.Image) { 319 u.m.Lock() 320 u.initIconImages = iconImages 321 u.m.Unlock() 322 } 323 324 func (u *UserInterface) getInitWindowPosition() (int, int) { 325 u.m.RLock() 326 defer u.m.RUnlock() 327 if u.initWindowPositionXInDP != invalidPos && u.initWindowPositionYInDP != invalidPos { 328 return u.initWindowPositionXInDP, u.initWindowPositionYInDP 329 } 330 return invalidPos, invalidPos 331 } 332 333 func (u *UserInterface) setInitWindowPosition(x, y int) { 334 u.m.Lock() 335 defer u.m.Unlock() 336 337 u.initWindowPositionXInDP = x 338 u.initWindowPositionYInDP = y 339 } 340 341 func (u *UserInterface) getInitWindowSize() (int, int) { 342 u.m.Lock() 343 w, h := u.initWindowWidthInDP, u.initWindowHeightInDP 344 u.m.Unlock() 345 return w, h 346 } 347 348 func (u *UserInterface) setInitWindowSize(width, height int) { 349 u.m.Lock() 350 u.initWindowWidthInDP, u.initWindowHeightInDP = width, height 351 u.m.Unlock() 352 } 353 354 func (u *UserInterface) isInitWindowFloating() bool { 355 u.m.Lock() 356 f := u.initWindowFloating 357 u.m.Unlock() 358 return f 359 } 360 361 func (u *UserInterface) setInitWindowFloating(floating bool) { 362 u.m.Lock() 363 u.initWindowFloating = floating 364 u.m.Unlock() 365 } 366 367 func (u *UserInterface) isInitWindowMaximized() bool { 368 u.m.Lock() 369 f := u.initWindowMaximized 370 u.m.Unlock() 371 return f 372 } 373 374 func (u *UserInterface) setInitWindowMaximized(floating bool) { 375 u.m.Lock() 376 u.initWindowMaximized = floating 377 u.m.Unlock() 378 } 379 380 func (u *UserInterface) isInitFocused() bool { 381 u.m.Lock() 382 v := u.initFocused 383 u.m.Unlock() 384 return v 385 } 386 387 func (u *UserInterface) setInitFocused(focused bool) { 388 u.m.Lock() 389 u.initFocused = focused 390 u.m.Unlock() 391 } 392 393 func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { 394 if !u.isRunning() { 395 return u.initFullscreenWidthInDP, u.initFullscreenHeightInDP 396 } 397 398 var w, h int 399 _ = u.t.Call(func() error { 400 v := currentMonitor(u.window).GetVideoMode() 401 w = int(u.fromGLFWMonitorPixel(float64(v.Width))) 402 h = int(u.fromGLFWMonitorPixel(float64(v.Height))) 403 return nil 404 }) 405 return w, h 406 } 407 408 // isFullscreen must be called from the main thread. 409 func (u *UserInterface) isFullscreen() bool { 410 if !u.isRunning() { 411 panic("glfw: isFullscreen can't be called before the main loop starts") 412 } 413 return u.window.GetMonitor() != nil 414 } 415 416 func (u *UserInterface) IsFullscreen() bool { 417 if !u.isRunning() { 418 return u.isInitFullscreen() 419 } 420 b := false 421 _ = u.t.Call(func() error { 422 b = u.isFullscreen() 423 return nil 424 }) 425 return b 426 } 427 428 func (u *UserInterface) SetFullscreen(fullscreen bool) { 429 if !u.isRunning() { 430 u.setInitFullscreen(fullscreen) 431 return 432 } 433 434 var update bool 435 _ = u.t.Call(func() error { 436 update = u.isFullscreen() != fullscreen 437 return nil 438 }) 439 if !update { 440 return 441 } 442 443 var w, h int 444 _ = u.t.Call(func() error { 445 w, h = u.windowWidth, u.windowHeight 446 return nil 447 }) 448 u.setWindowSize(w, h, fullscreen) 449 } 450 451 func (u *UserInterface) IsFocused() bool { 452 if !u.isRunning() { 453 return false 454 } 455 456 var focused bool 457 _ = u.t.Call(func() error { 458 focused = u.window.GetAttrib(glfw.Focused) == glfw.True 459 return nil 460 }) 461 return focused 462 } 463 464 func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) { 465 u.setRunnableOnUnfocused(runnableOnUnfocused) 466 } 467 468 func (u *UserInterface) IsRunnableOnUnfocused() bool { 469 return u.isRunnableOnUnfocused() 470 } 471 472 func (u *UserInterface) SetVsyncEnabled(enabled bool) { 473 if !u.isRunning() { 474 // In general, m is used for locking init* values. 475 // m is not used for updating vsync in setWindowSize so far, but 476 // it should be OK since any goroutines can't reach here when 477 // the game already starts and setWindowSize can be called. 478 u.m.Lock() 479 u.initVsync = enabled 480 u.m.Unlock() 481 return 482 } 483 _ = u.t.Call(func() error { 484 if !u.vsyncInited { 485 u.m.Lock() 486 u.initVsync = enabled 487 u.m.Unlock() 488 return nil 489 } 490 u.vsync = enabled 491 u.updateVsync() 492 return nil 493 }) 494 } 495 496 func (u *UserInterface) IsVsyncEnabled() bool { 497 if !u.isRunning() { 498 return u.isInitVsyncEnabled() 499 } 500 var v bool 501 _ = u.t.Call(func() error { 502 if !u.vsyncInited { 503 v = u.isInitVsyncEnabled() 504 return nil 505 } 506 v = u.vsync 507 return nil 508 }) 509 return v 510 } 511 512 func (u *UserInterface) CursorMode() driver.CursorMode { 513 if !u.isRunning() { 514 return u.getInitCursorMode() 515 } 516 var v driver.CursorMode 517 _ = u.t.Call(func() error { 518 mode := u.window.GetInputMode(glfw.CursorMode) 519 switch mode { 520 case glfw.CursorNormal: 521 v = driver.CursorModeVisible 522 case glfw.CursorHidden: 523 v = driver.CursorModeHidden 524 case glfw.CursorDisabled: 525 v = driver.CursorModeCaptured 526 default: 527 panic(fmt.Sprintf("invalid cursor mode: %d", mode)) 528 } 529 return nil 530 }) 531 return v 532 } 533 534 func (u *UserInterface) SetCursorMode(mode driver.CursorMode) { 535 if !u.isRunning() { 536 u.setInitCursorMode(mode) 537 return 538 } 539 _ = u.t.Call(func() error { 540 var c int 541 switch mode { 542 case driver.CursorModeVisible: 543 c = glfw.CursorNormal 544 case driver.CursorModeHidden: 545 c = glfw.CursorHidden 546 case driver.CursorModeCaptured: 547 c = glfw.CursorDisabled 548 default: 549 panic(fmt.Sprintf("invalid cursor mode: %d", mode)) 550 } 551 u.window.SetInputMode(glfw.CursorMode, c) 552 return nil 553 }) 554 } 555 556 func (u *UserInterface) DeviceScaleFactor() float64 { 557 if !u.isRunning() { 558 return devicescale.GetAt(u.initMonitor.GetPos()) 559 } 560 561 f := 0.0 562 _ = u.t.Call(func() error { 563 f = u.deviceScaleFactor() 564 return nil 565 }) 566 return f 567 } 568 569 // deviceScaleFactor must be called from the main thread. 570 func (u *UserInterface) deviceScaleFactor() float64 { 571 // Before calling SetWindowPosition, the window's position is not reliable. 572 if u.iwindow.setPositionCalled { 573 // Avoid calling monitor.GetPos if we have the monitor position cached already. 574 if cm := getCachedMonitor(u.window.GetPos()); cm != nil { 575 return devicescale.GetAt(cm.x, cm.y) 576 } 577 } 578 return devicescale.GetAt(currentMonitor(u.window).GetPos()) 579 } 580 581 func init() { 582 // Lock the main thread. 583 runtime.LockOSThread() 584 } 585 586 func (u *UserInterface) Run(uicontext driver.UIContext) error { 587 u.context = uicontext 588 589 // Initialize the main thread first so the thread is available at u.run (#809). 590 u.t = thread.New() 591 u.Graphics().SetThread(u.t) 592 593 ch := make(chan error, 1) 594 go func() { 595 defer func() { 596 _ = u.t.Call(func() error { 597 return thread.BreakLoop 598 }) 599 }() 600 601 defer close(ch) 602 if err := u.run(); err != nil { 603 ch <- err 604 } 605 }() 606 607 u.setRunning(true) 608 u.t.Loop() 609 u.setRunning(false) 610 return <-ch 611 } 612 613 func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) { 614 panic("glfw: RunWithoutMainLoop is not implemented") 615 } 616 617 // createWindow creates a GLFW window. 618 // 619 // createWindow must be called from the main thread. 620 // 621 // createWindow does not set the position or size so far. 622 func (u *UserInterface) createWindow() error { 623 if u.window != nil { 624 panic("glfw: u.window must not exist at createWindow") 625 } 626 627 // As a start, create a window with temporary size to create OpenGL context thread. 628 window, err := glfw.CreateWindow(16, 16, "", nil, nil) 629 if err != nil { 630 return err 631 } 632 u.window = window 633 634 if u.Graphics().IsGL() { 635 u.window.MakeContextCurrent() 636 } 637 638 u.window.SetInputMode(glfw.StickyMouseButtonsMode, glfw.True) 639 u.window.SetInputMode(glfw.StickyKeysMode, glfw.True) 640 641 mode := glfw.CursorNormal 642 switch u.getInitCursorMode() { 643 case driver.CursorModeHidden: 644 mode = glfw.CursorHidden 645 case driver.CursorModeCaptured: 646 mode = glfw.CursorDisabled 647 } 648 u.window.SetInputMode(glfw.CursorMode, mode) 649 u.window.SetTitle(u.title) 650 // TODO: Set icons 651 652 u.window.SetSizeCallback(func(_ *glfw.Window, width, height int) { 653 if u.window.GetAttrib(glfw.Resizable) == glfw.False { 654 return 655 } 656 if u.isFullscreen() { 657 return 658 } 659 u.reqWidth = width 660 u.reqHeight = height 661 }) 662 663 return nil 664 } 665 666 func (u *UserInterface) run() error { 667 if err := u.t.Call(func() error { 668 if u.Graphics().IsGL() { 669 glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI) 670 glfw.WindowHint(glfw.ContextVersionMajor, 2) 671 glfw.WindowHint(glfw.ContextVersionMinor, 1) 672 } else { 673 glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) 674 } 675 676 decorated := glfw.False 677 if u.isInitWindowDecorated() { 678 decorated = glfw.True 679 } 680 glfw.WindowHint(glfw.Decorated, decorated) 681 682 transparent := glfw.False 683 if u.isInitScreenTransparent() { 684 transparent = glfw.True 685 } 686 glfw.WindowHint(glfw.TransparentFramebuffer, transparent) 687 u.Graphics().SetTransparent(u.isInitScreenTransparent()) 688 689 resizable := glfw.False 690 if u.isInitWindowResizable() { 691 resizable = glfw.True 692 } 693 glfw.WindowHint(glfw.Resizable, resizable) 694 695 floating := glfw.False 696 if u.isInitWindowFloating() { 697 floating = glfw.True 698 } 699 glfw.WindowHint(glfw.Floating, floating) 700 701 focused := glfw.False 702 if u.isInitFocused() { 703 focused = glfw.True 704 } 705 glfw.WindowHint(glfw.FocusOnShow, focused) 706 707 // Set the window visible explicitly or the application freezes on Wayland (#974). 708 if os.Getenv("WAYLAND_DISPLAY") != "" { 709 glfw.WindowHint(glfw.Visible, glfw.True) 710 } 711 712 if err := u.createWindow(); err != nil { 713 return err 714 } 715 716 if i := u.getInitIconImages(); i != nil { 717 u.window.SetIcon(i) 718 } 719 return nil 720 }); err != nil { 721 return err 722 } 723 724 setPosition := func() { 725 u.iwindow.SetPosition(u.getInitWindowPosition()) 726 } 727 setSize := func() { 728 ww, wh := u.getInitWindowSize() 729 ww = int(u.toGLFWPixel(float64(ww))) 730 wh = int(u.toGLFWPixel(float64(wh))) 731 u.setWindowSize(ww, wh, u.isFullscreen()) 732 } 733 734 // Set the window size and the window position in this order on Linux or other UNIX using X (#1118), 735 // but this should be inverted on Windows. This is very tricky, but there is no obvious way to solve this. 736 // This doesn't matter on macOS. 737 if runtime.GOOS == "windows" { 738 setPosition() 739 setSize() 740 } else { 741 setSize() 742 setPosition() 743 } 744 745 // Maximizing a window requires a proper size and position. Call Maximize here (#1117). 746 if u.isInitWindowMaximized() { 747 _ = u.t.Call(func() error { 748 u.window.Maximize() 749 return nil 750 }) 751 } 752 753 _ = u.t.Call(func() error { 754 u.title = u.getInitTitle() 755 u.window.SetTitle(u.title) 756 u.window.Show() 757 return nil 758 }) 759 760 var w uintptr 761 _ = u.t.Call(func() error { 762 w = u.nativeWindow() 763 return nil 764 }) 765 if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok { 766 g.SetWindow(w) 767 } 768 return u.loop() 769 } 770 771 func (u *UserInterface) updateSize() { 772 var w, h int 773 _ = u.t.Call(func() error { 774 w, h = u.windowWidth, u.windowHeight 775 return nil 776 }) 777 u.setWindowSize(w, h, u.isFullscreen()) 778 779 sizeChanged := false 780 _ = u.t.Call(func() error { 781 if !u.toChangeSize { 782 return nil 783 } 784 785 u.toChangeSize = false 786 sizeChanged = true 787 return nil 788 }) 789 if sizeChanged { 790 var w, h float64 791 _ = u.t.Call(func() error { 792 if u.isFullscreen() { 793 v := currentMonitor(u.window).GetVideoMode() 794 ww, wh := v.Width, v.Height 795 w = u.fromGLFWMonitorPixel(float64(ww)) 796 h = u.fromGLFWMonitorPixel(float64(wh)) 797 } else { 798 // Instead of u.windowWidth and u.windowHeight, use the actual window size here. 799 // On Windows, the specified size at SetSize and the actual window size might not 800 // match (#1163). 801 ww, wh := u.window.GetSize() 802 w = u.fromGLFWPixel(float64(ww)) 803 h = u.fromGLFWPixel(float64(wh)) 804 } 805 // On Linux/UNIX, further adjusting is required (#1307). 806 w = u.toFramebufferPixel(w) 807 h = u.toFramebufferPixel(h) 808 return nil 809 }) 810 u.context.Layout(w, h) 811 } 812 } 813 814 func (u *UserInterface) update() error { 815 shouldClose := false 816 _ = u.t.Call(func() error { 817 shouldClose = u.window.ShouldClose() 818 return nil 819 }) 820 if shouldClose { 821 return driver.RegularTermination 822 } 823 824 if u.isInitFullscreen() { 825 var w, h int 826 _ = u.t.Call(func() error { 827 w, h = u.window.GetSize() 828 return nil 829 }) 830 u.setWindowSize(w, h, true) 831 u.setInitFullscreen(false) 832 } 833 834 // Initialize vsync after SetMonitor is called. See the comment in updateVsync. 835 // Calling this inside setWindowSize didn't work (#1363). 836 _ = u.t.Call(func() error { 837 if !u.vsyncInited { 838 u.vsync = u.isInitVsyncEnabled() 839 u.updateVsync() 840 u.vsyncInited = true 841 } 842 return nil 843 }) 844 845 // This call is needed for initialization. 846 u.updateSize() 847 848 _ = u.t.Call(func() error { 849 glfw.PollEvents() 850 return nil 851 }) 852 u.input.update(u.window, u.context) 853 _ = u.t.Call(func() error { 854 defer hooks.ResumeAudio() 855 856 for !u.isRunnableOnUnfocused() && u.window.GetAttrib(glfw.Focused) == 0 { 857 hooks.SuspendAudio() 858 // Wait for an arbitrary period to avoid busy loop. 859 time.Sleep(time.Second / 60) 860 glfw.PollEvents() 861 if u.window.ShouldClose() { 862 return nil 863 } 864 } 865 return nil 866 }) 867 if err := u.context.Update(); err != nil { 868 return err 869 } 870 if err := u.context.Draw(); err != nil { 871 return err 872 } 873 874 // Update the screen size when the window is resizable. 875 var w, h int 876 _ = u.t.Call(func() error { 877 w, h = u.reqWidth, u.reqHeight 878 return nil 879 }) 880 if w != 0 || h != 0 { 881 u.setWindowSize(w, h, u.isFullscreen()) 882 } 883 _ = u.t.Call(func() error { 884 u.reqWidth = 0 885 u.reqHeight = 0 886 return nil 887 }) 888 return nil 889 } 890 891 func (u *UserInterface) loop() error { 892 defer func() { 893 _ = u.t.Call(func() error { 894 glfw.Terminate() 895 return nil 896 }) 897 }() 898 for { 899 var unfocused bool 900 901 // On Windows, the focusing state might be always false (#987). 902 // On Windows, even if a window is in another workspace, vsync seems to work. 903 // Then let's assume the window is always 'focused' as a workaround. 904 if runtime.GOOS != "windows" { 905 unfocused = u.window.GetAttrib(glfw.Focused) == glfw.False 906 } 907 908 var t1, t2 time.Time 909 910 if unfocused { 911 t1 = time.Now() 912 } 913 if err := u.update(); err != nil { 914 return err 915 } 916 917 _ = u.t.Call(func() error { 918 u.swapBuffers() 919 return nil 920 }) 921 if unfocused { 922 t2 = time.Now() 923 } 924 925 // When a window is not focused, SwapBuffers might return immediately and CPU might be busy. 926 // Mitigate this by sleeping (#982). 927 if unfocused { 928 d := t2.Sub(t1) 929 const wait = time.Second / 60 930 if d < wait { 931 time.Sleep(wait - d) 932 } 933 } 934 } 935 } 936 937 // swapBuffers must be called from the main thread. 938 func (u *UserInterface) swapBuffers() { 939 if u.Graphics().IsGL() { 940 u.window.SwapBuffers() 941 } 942 } 943 944 func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) { 945 windowRecreated := false 946 947 _ = u.t.Call(func() error { 948 if u.windowWidth == width && u.windowHeight == height && u.isFullscreen() == fullscreen && u.lastDeviceScaleFactor == u.deviceScaleFactor() { 949 return nil 950 } 951 952 if width < 1 { 953 width = 1 954 } 955 if height < 1 { 956 height = 1 957 } 958 959 u.lastDeviceScaleFactor = u.deviceScaleFactor() 960 961 // To make sure the current existing framebuffers are rendered, 962 // swap buffers here before SetSize is called. 963 u.swapBuffers() 964 965 if fullscreen { 966 if u.origPosX == invalidPos || u.origPosY == invalidPos { 967 u.origPosX, u.origPosY = u.window.GetPos() 968 } 969 m := currentMonitor(u.window) 970 v := m.GetVideoMode() 971 u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate) 972 973 // Swapping buffer is necesary to prevent the image lag (#1004). 974 // TODO: This might not work when vsync is disabled. 975 if u.Graphics().IsGL() { 976 glfw.PollEvents() 977 u.swapBuffers() 978 } 979 } else { 980 // On Windows, giving a too small width doesn't call a callback (#165). 981 // To prevent hanging up, return asap if the width is too small. 982 // 126 is an arbitrary number and I guess this is small enough. 983 minWindowWidth := int(u.toGLFWPixel(126)) 984 if u.window.GetAttrib(glfw.Decorated) == glfw.False { 985 minWindowWidth = 1 986 } 987 if width < minWindowWidth { 988 width = minWindowWidth 989 } 990 991 if u.window.GetMonitor() != nil { 992 if u.Graphics().IsGL() { 993 // When OpenGL is used, swapping buffer is enough to solve the image-lag 994 // issue (#1004). Rather, recreating window destroys GPU resources. 995 // TODO: This might not work when vsync is disabled. 996 u.window.SetMonitor(nil, 0, 0, width, height, 0) 997 glfw.PollEvents() 998 u.swapBuffers() 999 } else { 1000 // Recreate the window since an image lag remains after coming back from 1001 // fullscreen (#1004). 1002 if u.window != nil { 1003 u.window.Destroy() 1004 u.window = nil 1005 } 1006 if err := u.createWindow(); err != nil { 1007 // TODO: This should return an error. 1008 panic(fmt.Sprintf("glfw: failed to recreate window: %v", err)) 1009 } 1010 u.window.Show() 1011 windowRecreated = true 1012 } 1013 } 1014 1015 if u.origPosX != invalidPos && u.origPosY != invalidPos { 1016 x := u.origPosX 1017 y := u.origPosY 1018 u.window.SetPos(x, y) 1019 // Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but 1020 // work with two or more SetPos. 1021 if runtime.GOOS == "darwin" { 1022 u.window.SetPos(x+1, y) 1023 u.window.SetPos(x, y) 1024 } 1025 u.origPosX = invalidPos 1026 u.origPosY = invalidPos 1027 } 1028 1029 // Set the window size after the position. The order matters. 1030 // In the opposite order, the window size might not be correct when going back from fullscreen with multi monitors. 1031 oldW, oldH := u.window.GetSize() 1032 newW := width 1033 newH := height 1034 if oldW != newW || oldH != newH { 1035 ch := make(chan struct{}) 1036 u.window.SetFramebufferSizeCallback(func(_ *glfw.Window, _, _ int) { 1037 u.window.SetFramebufferSizeCallback(nil) 1038 close(ch) 1039 }) 1040 u.window.SetSize(newW, newH) 1041 event: 1042 for { 1043 glfw.PollEvents() 1044 select { 1045 case <-ch: 1046 break event 1047 default: 1048 } 1049 } 1050 } 1051 1052 // Window title might be lost on macOS after coming back from fullscreen. 1053 u.window.SetTitle(u.title) 1054 } 1055 1056 // As width might be updated, update windowWidth/Height here. 1057 u.windowWidth = width 1058 u.windowHeight = height 1059 1060 u.toChangeSize = true 1061 return nil 1062 }) 1063 1064 if windowRecreated { 1065 if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok { 1066 g.SetWindow(u.nativeWindow()) 1067 } 1068 } 1069 } 1070 1071 // updateVsync must be called on the main thread. 1072 func (u *UserInterface) updateVsync() { 1073 if u.Graphics().IsGL() { 1074 // SwapInterval is affected by the current monitor of the window. 1075 // This needs to be called at least after SetMonitor. 1076 // Without SwapInterval after SetMonitor, vsynch doesn't work (#375). 1077 // 1078 // TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called, 1079 // but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple 1080 // buffering, what will happen? 1081 if u.vsync { 1082 glfw.SwapInterval(1) 1083 } else { 1084 glfw.SwapInterval(0) 1085 } 1086 } 1087 u.Graphics().SetVsyncEnabled(u.vsync) 1088 } 1089 1090 // currentMonitor returns the current active monitor. 1091 // 1092 // The given window might or might not be used to detect the monitor. 1093 // 1094 // currentMonitor must be called on the main thread. 1095 func currentMonitor(window *glfw.Window) *glfw.Monitor { 1096 // GetMonitor is available only on fullscreen. 1097 if m := window.GetMonitor(); m != nil { 1098 return m 1099 } 1100 1101 // Getting a monitor from a window position is not reliable in general (e.g., when a window is put across 1102 // multiple monitors, or, before SetWindowPosition is called.). 1103 // Get the monitor which the current window belongs to. This requires OS API. 1104 if m := currentMonitorByOS(window); m != nil { 1105 return m 1106 } 1107 1108 // As the fallback, detect the monitor from the window. 1109 if m := getCachedMonitor(window.GetPos()); m != nil { 1110 return m.m 1111 } 1112 return glfw.GetPrimaryMonitor() 1113 } 1114 1115 func (u *UserInterface) SetScreenTransparent(transparent bool) { 1116 if !u.isRunning() { 1117 u.setInitScreenTransparent(transparent) 1118 return 1119 } 1120 panic("glfw: SetScreenTransparent can't be called after the main loop starts") 1121 } 1122 1123 func (u *UserInterface) IsScreenTransparent() bool { 1124 if !u.isRunning() { 1125 return u.isInitScreenTransparent() 1126 } 1127 val := false 1128 _ = u.t.Call(func() error { 1129 val = u.window.GetAttrib(glfw.TransparentFramebuffer) == glfw.True 1130 return nil 1131 }) 1132 return val 1133 } 1134 1135 func (u *UserInterface) ResetForFrame() { 1136 // The offscreens must be updated every frame (#490). 1137 u.updateSize() 1138 u.input.resetForFrame() 1139 } 1140 1141 func (u *UserInterface) MonitorPosition() (int, int) { 1142 if !u.isRunning() { 1143 return u.monitorPosition() 1144 } 1145 var mx, my int 1146 _ = u.t.Call(func() error { 1147 mx, my = u.monitorPosition() 1148 return nil 1149 }) 1150 return mx, my 1151 } 1152 1153 func (u *UserInterface) SetInitFocused(focused bool) { 1154 if u.isRunning() { 1155 panic("ui: SetInitFocused must be called before the main loop") 1156 } 1157 u.setInitFocused(focused) 1158 } 1159 1160 func (u *UserInterface) monitorPosition() (int, int) { 1161 // TODO: fromGLFWMonitorPixel might be required. 1162 return currentMonitor(u.window).GetPos() 1163 } 1164 1165 func (u *UserInterface) Input() driver.Input { 1166 return &u.input 1167 } 1168 1169 func (u *UserInterface) Window() driver.Window { 1170 return &u.iwindow 1171 }