ui.go (44073B)
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 //go:build (darwin || freebsd || linux || windows) && !android && !ios 16 // +build darwin freebsd linux windows 17 // +build !android 18 // +build !ios 19 20 package glfw 21 22 import ( 23 "fmt" 24 "image" 25 "os" 26 "runtime" 27 "sync" 28 "sync/atomic" 29 "time" 30 31 "github.com/hajimehoshi/ebiten/v2/internal/devicescale" 32 "github.com/hajimehoshi/ebiten/v2/internal/driver" 33 "github.com/hajimehoshi/ebiten/v2/internal/glfw" 34 "github.com/hajimehoshi/ebiten/v2/internal/hooks" 35 "github.com/hajimehoshi/ebiten/v2/internal/thread" 36 ) 37 38 func driverCursorModeToGLFWCursorMode(mode driver.CursorMode) int { 39 switch mode { 40 case driver.CursorModeVisible: 41 return glfw.CursorNormal 42 case driver.CursorModeHidden: 43 return glfw.CursorHidden 44 case driver.CursorModeCaptured: 45 return glfw.CursorDisabled 46 default: 47 panic(fmt.Sprintf("glfw: invalid driver.CursorMode: %d", mode)) 48 } 49 } 50 51 type UserInterface struct { 52 context driver.UIContext 53 title string 54 window *glfw.Window 55 56 // windowWidth and windowHeight represents a window size. 57 // The units are device-dependent pixels. 58 windowWidth int 59 windowHeight int 60 61 // The units are device-independent pixels. 62 minWindowWidthInDP int 63 minWindowHeightInDP int 64 maxWindowWidthInDP int 65 maxWindowHeightInDP int 66 67 running uint32 68 toChangeSize bool 69 origPosX int 70 origPosY int 71 runnableOnUnfocused bool 72 fpsMode driver.FPSMode 73 iconImages []image.Image 74 cursorShape driver.CursorShape 75 windowClosingHandled bool 76 windowBeingClosed bool 77 78 // setSizeCallbackEnabled must be accessed from the main thread. 79 setSizeCallbackEnabled bool 80 81 // err must be accessed from the main thread. 82 err error 83 84 lastDeviceScaleFactor float64 85 86 // These values are not changed after initialized. 87 // TODO: the fullscreen size should be updated when the initial window position is changed? 88 initMonitor *glfw.Monitor 89 initFullscreenWidthInDP int 90 initFullscreenHeightInDP int 91 92 initTitle string 93 initFPSMode driver.FPSMode 94 initFullscreen bool 95 initCursorMode driver.CursorMode 96 initWindowDecorated bool 97 initWindowResizable bool 98 initWindowPositionXInDP int 99 initWindowPositionYInDP int 100 initWindowWidthInDP int 101 initWindowHeightInDP int 102 initWindowFloating bool 103 initWindowMaximized bool 104 initScreenTransparent bool 105 initFocused bool 106 107 fpsModeInited bool 108 109 input Input 110 iwindow window 111 112 sizeCallback glfw.SizeCallback 113 closeCallback glfw.CloseCallback 114 framebufferSizeCallback glfw.FramebufferSizeCallback 115 framebufferSizeCallbackCh chan struct{} 116 117 t thread.Thread 118 m sync.RWMutex 119 } 120 121 const ( 122 maxInt = int(^uint(0) >> 1) 123 minInt = -maxInt - 1 124 invalidPos = minInt 125 ) 126 127 var ( 128 theUI = &UserInterface{ 129 runnableOnUnfocused: true, 130 minWindowWidthInDP: glfw.DontCare, 131 minWindowHeightInDP: glfw.DontCare, 132 maxWindowWidthInDP: glfw.DontCare, 133 maxWindowHeightInDP: glfw.DontCare, 134 origPosX: invalidPos, 135 origPosY: invalidPos, 136 initFPSMode: driver.FPSModeVsyncOn, 137 initCursorMode: driver.CursorModeVisible, 138 initWindowDecorated: true, 139 initWindowPositionXInDP: invalidPos, 140 initWindowPositionYInDP: invalidPos, 141 initWindowWidthInDP: 640, 142 initWindowHeightInDP: 480, 143 initFocused: true, 144 fpsMode: driver.FPSModeVsyncOn, 145 } 146 ) 147 148 func init() { 149 theUI.input.ui = theUI 150 theUI.iwindow.ui = theUI 151 } 152 153 func Get() *UserInterface { 154 return theUI 155 } 156 157 func init() { 158 hideConsoleWindowOnWindows() 159 if err := initialize(); err != nil { 160 panic(err) 161 } 162 glfw.SetMonitorCallback(func(monitor *glfw.Monitor, event glfw.PeripheralEvent) { 163 updateMonitors() 164 }) 165 updateMonitors() 166 } 167 168 var glfwSystemCursors = map[driver.CursorShape]*glfw.Cursor{} 169 170 func initialize() error { 171 if err := glfw.Init(); err != nil { 172 return err 173 } 174 175 glfw.WindowHint(glfw.Visible, glfw.False) 176 glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) 177 178 // Create a window to set the initial monitor. 179 w, err := glfw.CreateWindow(16, 16, "", nil, nil) 180 if err != nil { 181 return err 182 } 183 if w == nil { 184 // This can happen on Windows Remote Desktop (#903). 185 panic("glfw: glfw.CreateWindow must not return nil") 186 } 187 defer w.Destroy() 188 initializeWindowAfterCreation(w) 189 190 m := currentMonitor(w) 191 theUI.initMonitor = m 192 v := m.GetVideoMode() 193 scale := videoModeScale(m) 194 theUI.initFullscreenWidthInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Width), scale)) 195 theUI.initFullscreenHeightInDP = int(theUI.fromGLFWMonitorPixel(float64(v.Height), scale)) 196 197 // Create system cursors. These cursors are destroyed at glfw.Terminate(). 198 glfwSystemCursors[driver.CursorShapeDefault] = nil 199 glfwSystemCursors[driver.CursorShapeText] = glfw.CreateStandardCursor(glfw.IBeamCursor) 200 glfwSystemCursors[driver.CursorShapeCrosshair] = glfw.CreateStandardCursor(glfw.CrosshairCursor) 201 glfwSystemCursors[driver.CursorShapePointer] = glfw.CreateStandardCursor(glfw.HandCursor) 202 glfwSystemCursors[driver.CursorShapeEWResize] = glfw.CreateStandardCursor(glfw.HResizeCursor) 203 glfwSystemCursors[driver.CursorShapeNSResize] = glfw.CreateStandardCursor(glfw.VResizeCursor) 204 205 return nil 206 } 207 208 type monitor struct { 209 m *glfw.Monitor 210 vm *glfw.VidMode 211 // Pos of monitor in virtual coords 212 x int 213 y int 214 } 215 216 // monitors is the monitor list cache for desktop glfw compile targets. 217 // populated by 'updateMonitors' which is called on init and every 218 // monitor config change event. 219 // 220 // monitors must be manipulated on the main thread. 221 var monitors []*monitor 222 223 func updateMonitors() { 224 monitors = nil 225 ms := glfw.GetMonitors() 226 for _, m := range ms { 227 x, y := m.GetPos() 228 monitors = append(monitors, &monitor{ 229 m: m, 230 vm: m.GetVideoMode(), 231 x: x, 232 y: y, 233 }) 234 } 235 clearVideoModeScaleCache() 236 devicescale.ClearCache() 237 } 238 239 func ensureMonitors() []*monitor { 240 if len(monitors) == 0 { 241 updateMonitors() 242 } 243 return monitors 244 } 245 246 // getMonitorFromPosition returns a monitor for the given window x/y, 247 // or returns nil if monitor is not found. 248 // 249 // getMonitorFromPosition must be called on the main thread. 250 func getMonitorFromPosition(wx, wy int) *monitor { 251 for _, m := range ensureMonitors() { 252 // TODO: Fix incorrectness in the cases of https://github.com/glfw/glfw/issues/1961. 253 // See also internal/devicescale/impl_desktop.go for a maybe better way of doing this. 254 if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height { 255 return m 256 } 257 } 258 return nil 259 } 260 261 func (u *UserInterface) isRunning() bool { 262 return atomic.LoadUint32(&u.running) != 0 263 } 264 265 func (u *UserInterface) setRunning(running bool) { 266 if running { 267 atomic.StoreUint32(&u.running, 1) 268 } else { 269 atomic.StoreUint32(&u.running, 0) 270 } 271 } 272 273 func (u *UserInterface) getWindowSizeLimits() (minw, minh, maxw, maxh int) { 274 u.m.RLock() 275 defer u.m.RUnlock() 276 277 minw, minh, maxw, maxh = -1, -1, -1, -1 278 if u.minWindowWidthInDP >= 0 { 279 minw = int(u.toGLFWPixel(float64(u.minWindowWidthInDP))) 280 } 281 if u.minWindowHeightInDP >= 0 { 282 minh = int(u.toGLFWPixel(float64(u.minWindowHeightInDP))) 283 } 284 if u.maxWindowWidthInDP >= 0 { 285 maxw = int(u.toGLFWPixel(float64(u.maxWindowWidthInDP))) 286 } 287 if u.maxWindowHeightInDP >= 0 { 288 maxh = int(u.toGLFWPixel(float64(u.maxWindowHeightInDP))) 289 } 290 return 291 } 292 293 func (u *UserInterface) getWindowSizeLimitsInDP() (minw, minh, maxw, maxh int) { 294 u.m.RLock() 295 defer u.m.RUnlock() 296 return u.minWindowWidthInDP, u.minWindowHeightInDP, u.maxWindowWidthInDP, u.maxWindowHeightInDP 297 } 298 299 func (u *UserInterface) setWindowSizeLimitsInDP(minw, minh, maxw, maxh int) bool { 300 u.m.RLock() 301 defer u.m.RUnlock() 302 if u.minWindowWidthInDP == minw && u.minWindowHeightInDP == minh && u.maxWindowWidthInDP == maxw && u.maxWindowHeightInDP == maxh { 303 return false 304 } 305 u.minWindowWidthInDP = minw 306 u.minWindowHeightInDP = minh 307 u.maxWindowWidthInDP = maxw 308 u.maxWindowHeightInDP = maxh 309 return true 310 } 311 312 func (u *UserInterface) getInitTitle() string { 313 u.m.RLock() 314 v := u.initTitle 315 u.m.RUnlock() 316 return v 317 } 318 319 func (u *UserInterface) setInitTitle(title string) { 320 u.m.RLock() 321 u.initTitle = title 322 u.m.RUnlock() 323 } 324 325 func (u *UserInterface) getInitFPSMode() driver.FPSMode { 326 u.m.RLock() 327 v := u.initFPSMode 328 u.m.RUnlock() 329 return v 330 } 331 332 func (u *UserInterface) isInitFullscreen() bool { 333 u.m.RLock() 334 v := u.initFullscreen 335 u.m.RUnlock() 336 return v 337 } 338 339 func (u *UserInterface) setInitFullscreen(initFullscreen bool) { 340 u.m.Lock() 341 u.initFullscreen = initFullscreen 342 u.m.Unlock() 343 } 344 345 func (u *UserInterface) getInitCursorMode() driver.CursorMode { 346 u.m.RLock() 347 v := u.initCursorMode 348 u.m.RUnlock() 349 return v 350 } 351 352 func (u *UserInterface) setInitCursorMode(mode driver.CursorMode) { 353 u.m.Lock() 354 u.initCursorMode = mode 355 u.m.Unlock() 356 } 357 358 func (u *UserInterface) getCursorShape() driver.CursorShape { 359 u.m.RLock() 360 v := u.cursorShape 361 u.m.RUnlock() 362 return v 363 } 364 365 func (u *UserInterface) setCursorShape(shape driver.CursorShape) driver.CursorShape { 366 u.m.Lock() 367 old := u.cursorShape 368 u.cursorShape = shape 369 u.m.Unlock() 370 return old 371 } 372 373 func (u *UserInterface) isInitWindowDecorated() bool { 374 u.m.RLock() 375 v := u.initWindowDecorated 376 u.m.RUnlock() 377 return v 378 } 379 380 func (u *UserInterface) setInitWindowDecorated(decorated bool) { 381 u.m.Lock() 382 u.initWindowDecorated = decorated 383 u.m.Unlock() 384 } 385 386 func (u *UserInterface) isRunnableOnUnfocused() bool { 387 u.m.RLock() 388 v := u.runnableOnUnfocused 389 u.m.RUnlock() 390 return v 391 } 392 393 func (u *UserInterface) setRunnableOnUnfocused(runnableOnUnfocused bool) { 394 u.m.Lock() 395 u.runnableOnUnfocused = runnableOnUnfocused 396 u.m.Unlock() 397 } 398 399 func (u *UserInterface) isInitWindowResizable() bool { 400 u.m.RLock() 401 v := u.initWindowResizable 402 u.m.RUnlock() 403 return v 404 } 405 406 func (u *UserInterface) setInitWindowResizable(resizable bool) { 407 u.m.Lock() 408 u.initWindowResizable = resizable 409 u.m.Unlock() 410 } 411 412 func (u *UserInterface) isInitScreenTransparent() bool { 413 u.m.RLock() 414 v := u.initScreenTransparent 415 u.m.RUnlock() 416 return v 417 } 418 419 func (u *UserInterface) setInitScreenTransparent(transparent bool) { 420 u.m.RLock() 421 u.initScreenTransparent = transparent 422 u.m.RUnlock() 423 } 424 425 func (u *UserInterface) getIconImages() []image.Image { 426 u.m.RLock() 427 i := u.iconImages 428 u.m.RUnlock() 429 return i 430 } 431 432 func (u *UserInterface) setIconImages(iconImages []image.Image) { 433 u.m.Lock() 434 u.iconImages = iconImages 435 u.m.Unlock() 436 } 437 438 func (u *UserInterface) getInitWindowPosition() (int, int) { 439 u.m.RLock() 440 defer u.m.RUnlock() 441 if u.initWindowPositionXInDP != invalidPos && u.initWindowPositionYInDP != invalidPos { 442 return u.initWindowPositionXInDP, u.initWindowPositionYInDP 443 } 444 return invalidPos, invalidPos 445 } 446 447 func (u *UserInterface) setInitWindowPosition(x, y int) { 448 u.m.Lock() 449 defer u.m.Unlock() 450 451 u.initWindowPositionXInDP = x 452 u.initWindowPositionYInDP = y 453 } 454 455 func (u *UserInterface) getInitWindowSize() (int, int) { 456 u.m.Lock() 457 w, h := u.initWindowWidthInDP, u.initWindowHeightInDP 458 u.m.Unlock() 459 return w, h 460 } 461 462 func (u *UserInterface) setInitWindowSize(width, height int) { 463 u.m.Lock() 464 u.initWindowWidthInDP, u.initWindowHeightInDP = width, height 465 u.m.Unlock() 466 } 467 468 func (u *UserInterface) isInitWindowFloating() bool { 469 u.m.Lock() 470 f := u.initWindowFloating 471 u.m.Unlock() 472 return f 473 } 474 475 func (u *UserInterface) setInitWindowFloating(floating bool) { 476 u.m.Lock() 477 u.initWindowFloating = floating 478 u.m.Unlock() 479 } 480 481 func (u *UserInterface) isInitWindowMaximized() bool { 482 u.m.Lock() 483 m := u.initWindowMaximized 484 u.m.Unlock() 485 return m 486 } 487 488 func (u *UserInterface) setInitWindowMaximized(maximized bool) { 489 u.m.Lock() 490 u.initWindowMaximized = maximized 491 u.m.Unlock() 492 } 493 494 func (u *UserInterface) isWindowClosingHandled() bool { 495 u.m.Lock() 496 v := u.windowClosingHandled 497 u.m.Unlock() 498 return v 499 } 500 501 func (u *UserInterface) setWindowClosingHandled(handled bool) { 502 u.m.Lock() 503 u.windowClosingHandled = handled 504 u.m.Unlock() 505 } 506 507 func (u *UserInterface) isWindowBeingClosed() bool { 508 u.m.Lock() 509 v := u.windowBeingClosed 510 u.m.Unlock() 511 return v 512 } 513 514 func (u *UserInterface) isInitFocused() bool { 515 u.m.Lock() 516 v := u.initFocused 517 u.m.Unlock() 518 return v 519 } 520 521 func (u *UserInterface) setInitFocused(focused bool) { 522 u.m.Lock() 523 u.initFocused = focused 524 u.m.Unlock() 525 } 526 527 func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { 528 if !u.isRunning() { 529 return u.initFullscreenWidthInDP, u.initFullscreenHeightInDP 530 } 531 532 var w, h int 533 _ = u.t.Call(func() error { 534 m := currentMonitor(u.window) 535 v := m.GetVideoMode() 536 s := videoModeScale(m) 537 w = int(u.fromGLFWMonitorPixel(float64(v.Width), s)) 538 h = int(u.fromGLFWMonitorPixel(float64(v.Height), s)) 539 return nil 540 }) 541 return w, h 542 } 543 544 // isFullscreen must be called from the main thread. 545 func (u *UserInterface) isFullscreen() bool { 546 if !u.isRunning() { 547 panic("glfw: isFullscreen can't be called before the main loop starts") 548 } 549 return u.window.GetMonitor() != nil || u.isNativeFullscreen() 550 } 551 552 func (u *UserInterface) IsFullscreen() bool { 553 if !u.isRunning() { 554 return u.isInitFullscreen() 555 } 556 b := false 557 _ = u.t.Call(func() error { 558 b = u.isFullscreen() 559 return nil 560 }) 561 return b 562 } 563 564 func (u *UserInterface) SetFullscreen(fullscreen bool) { 565 if !u.isRunning() { 566 u.setInitFullscreen(fullscreen) 567 return 568 } 569 570 var update bool 571 _ = u.t.Call(func() error { 572 update = u.isFullscreen() != fullscreen 573 return nil 574 }) 575 if !update { 576 return 577 } 578 579 _ = u.t.Call(func() error { 580 w, h := u.windowWidth, u.windowHeight 581 u.setWindowSize(w, h, fullscreen) 582 return nil 583 }) 584 } 585 586 func (u *UserInterface) IsFocused() bool { 587 if !u.isRunning() { 588 return false 589 } 590 591 var focused bool 592 _ = u.t.Call(func() error { 593 focused = u.window.GetAttrib(glfw.Focused) == glfw.True 594 return nil 595 }) 596 return focused 597 } 598 599 func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) { 600 u.setRunnableOnUnfocused(runnableOnUnfocused) 601 } 602 603 func (u *UserInterface) IsRunnableOnUnfocused() bool { 604 return u.isRunnableOnUnfocused() 605 } 606 607 func (u *UserInterface) SetFPSMode(mode driver.FPSMode) { 608 if !u.isRunning() { 609 // In general, m is used for locking init* values. 610 // m is not used for updating vsync in setWindowSize so far, but 611 // it should be OK since any goroutines can't reach here when 612 // the game already starts and setWindowSize can be called. 613 u.m.Lock() 614 u.initFPSMode = mode 615 u.m.Unlock() 616 return 617 } 618 _ = u.t.Call(func() error { 619 if !u.fpsModeInited { 620 u.m.Lock() 621 u.initFPSMode = mode 622 u.m.Unlock() 623 return nil 624 } 625 u.setFPSMode(mode) 626 u.updateVsync() 627 return nil 628 }) 629 } 630 631 func (u *UserInterface) FPSMode() driver.FPSMode { 632 if !u.isRunning() { 633 return u.getInitFPSMode() 634 } 635 var v driver.FPSMode 636 _ = u.t.Call(func() error { 637 if !u.fpsModeInited { 638 v = u.getInitFPSMode() 639 return nil 640 } 641 v = u.fpsMode 642 return nil 643 }) 644 return v 645 } 646 647 func (u *UserInterface) ScheduleFrame() { 648 if !u.isRunning() { 649 return 650 } 651 // As the main thread can be blocked, do not check the current FPS mode. 652 // PostEmptyEvent is concurrent safe. 653 glfw.PostEmptyEvent() 654 } 655 656 func (u *UserInterface) CursorMode() driver.CursorMode { 657 if !u.isRunning() { 658 return u.getInitCursorMode() 659 } 660 var v driver.CursorMode 661 _ = u.t.Call(func() error { 662 mode := u.window.GetInputMode(glfw.CursorMode) 663 switch mode { 664 case glfw.CursorNormal: 665 v = driver.CursorModeVisible 666 case glfw.CursorHidden: 667 v = driver.CursorModeHidden 668 case glfw.CursorDisabled: 669 v = driver.CursorModeCaptured 670 default: 671 panic(fmt.Sprintf("glfw: invalid GLFW cursor mode: %d", mode)) 672 } 673 return nil 674 }) 675 return v 676 } 677 678 func (u *UserInterface) SetCursorMode(mode driver.CursorMode) { 679 if !u.isRunning() { 680 u.setInitCursorMode(mode) 681 return 682 } 683 _ = u.t.Call(func() error { 684 u.window.SetInputMode(glfw.CursorMode, driverCursorModeToGLFWCursorMode(mode)) 685 return nil 686 }) 687 } 688 689 func (u *UserInterface) CursorShape() driver.CursorShape { 690 return u.getCursorShape() 691 } 692 693 func (u *UserInterface) SetCursorShape(shape driver.CursorShape) { 694 old := u.setCursorShape(shape) 695 if old == shape { 696 return 697 } 698 if !u.isRunning() { 699 return 700 } 701 _ = u.t.Call(func() error { 702 u.setNativeCursor(shape) 703 return nil 704 }) 705 } 706 707 func (u *UserInterface) DeviceScaleFactor() float64 { 708 if !u.isRunning() { 709 // TODO: Use the initWindowPosition. This requires to convert the units correctly (#1575). 710 return u.deviceScaleFactor() 711 } 712 713 f := 0.0 714 _ = u.t.Call(func() error { 715 f = u.deviceScaleFactor() 716 return nil 717 }) 718 return f 719 } 720 721 // deviceScaleFactor must be called from the main thread. 722 func (u *UserInterface) deviceScaleFactor() float64 { 723 m := u.initMonitor 724 if u.window != nil { 725 m = currentMonitor(u.window) 726 } 727 mx, my := m.GetPos() 728 return devicescale.GetAt(mx, my) 729 } 730 731 func init() { 732 // Lock the main thread. 733 runtime.LockOSThread() 734 } 735 736 func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) { 737 panic("glfw: RunWithoutMainLoop is not implemented") 738 } 739 740 // createWindow creates a GLFW window. 741 // 742 // createWindow must be called from the main thread. 743 // 744 // createWindow does not set the position or size so far. 745 func (u *UserInterface) createWindow() error { 746 if u.window != nil { 747 panic("glfw: u.window must not exist at createWindow") 748 } 749 750 // As a start, create a window with temporary size to create OpenGL context thread. 751 window, err := glfw.CreateWindow(16, 16, "", nil, nil) 752 if err != nil { 753 return err 754 } 755 initializeWindowAfterCreation(window) 756 757 u.window = window 758 759 if u.Graphics().IsGL() { 760 u.window.MakeContextCurrent() 761 } 762 763 u.window.SetInputMode(glfw.CursorMode, driverCursorModeToGLFWCursorMode(u.getInitCursorMode())) 764 u.window.SetCursor(glfwSystemCursors[u.getCursorShape()]) 765 u.window.SetTitle(u.title) 766 // TODO: Set icons 767 768 u.registerWindowSetSizeCallback() 769 u.registerWindowCloseCallback() 770 771 return nil 772 } 773 774 // registerWindowSetSizeCallback must be called from the main thread. 775 func (u *UserInterface) registerWindowSetSizeCallback() { 776 if u.sizeCallback == 0 { 777 u.sizeCallback = glfw.ToSizeCallback(func(_ *glfw.Window, width, height int) { 778 if !u.setSizeCallbackEnabled { 779 return 780 } 781 782 u.adjustViewSize() 783 784 if u.window.GetAttrib(glfw.Resizable) == glfw.False { 785 return 786 } 787 if u.isFullscreen() && !u.isNativeFullscreen() { 788 return 789 } 790 791 if err := u.runOnAnotherThreadFromMainThread(func() error { 792 // Disable Vsync temporarily. On macOS, getting a next frame can get stuck (#1740). 793 u.Graphics().SetVsyncEnabled(false) 794 795 var outsideWidth, outsideHeight float64 796 var outsideSizeChanged bool 797 798 _ = u.t.Call(func() error { 799 if width != 0 || height != 0 { 800 u.setWindowSize(width, height, u.isFullscreen()) 801 } 802 803 outsideWidth, outsideHeight, outsideSizeChanged = u.updateSize() 804 return nil 805 }) 806 if outsideSizeChanged { 807 u.context.Layout(outsideWidth, outsideHeight) 808 } 809 if err := u.context.ForceUpdateFrame(); err != nil { 810 return err 811 } 812 if u.Graphics().IsGL() { 813 _ = u.t.Call(func() error { 814 u.swapBuffers() 815 return nil 816 }) 817 } 818 return nil 819 }); err != nil { 820 u.err = err 821 } 822 }) 823 } 824 u.window.SetSizeCallback(u.sizeCallback) 825 } 826 827 // registerWindowCloseCallback must be called from the main thread. 828 func (u *UserInterface) registerWindowCloseCallback() { 829 if u.closeCallback == 0 { 830 u.closeCallback = glfw.ToCloseCallback(func(_ *glfw.Window) { 831 u.m.Lock() 832 u.windowBeingClosed = true 833 u.m.Unlock() 834 835 if !u.isWindowClosingHandled() { 836 return 837 } 838 u.window.SetShouldClose(false) 839 }) 840 } 841 u.window.SetCloseCallback(u.closeCallback) 842 } 843 844 func (u *UserInterface) init() error { 845 if u.Graphics().IsGL() { 846 glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI) 847 glfw.WindowHint(glfw.ContextVersionMajor, 2) 848 glfw.WindowHint(glfw.ContextVersionMinor, 1) 849 } else { 850 glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) 851 } 852 853 glfw.WindowHint(glfw.AutoIconify, glfw.False) 854 855 decorated := glfw.False 856 if u.isInitWindowDecorated() { 857 decorated = glfw.True 858 } 859 glfw.WindowHint(glfw.Decorated, decorated) 860 861 transparent := glfw.False 862 if u.isInitScreenTransparent() { 863 transparent = glfw.True 864 } 865 glfw.WindowHint(glfw.TransparentFramebuffer, transparent) 866 u.Graphics().SetTransparent(u.isInitScreenTransparent()) 867 868 resizable := glfw.False 869 if u.isInitWindowResizable() { 870 resizable = glfw.True 871 } 872 glfw.WindowHint(glfw.Resizable, resizable) 873 874 floating := glfw.False 875 if u.isInitWindowFloating() { 876 floating = glfw.True 877 } 878 glfw.WindowHint(glfw.Floating, floating) 879 880 focused := glfw.False 881 if u.isInitFocused() { 882 focused = glfw.True 883 } 884 glfw.WindowHint(glfw.FocusOnShow, focused) 885 886 // Set the window visible explicitly or the application freezes on Wayland (#974). 887 if os.Getenv("WAYLAND_DISPLAY") != "" { 888 glfw.WindowHint(glfw.Visible, glfw.True) 889 } 890 891 if err := u.createWindow(); err != nil { 892 return err 893 } 894 895 u.setSizeCallbackEnabled = true 896 897 setSize := func() { 898 ww, wh := u.getInitWindowSize() 899 ww = int(u.toGLFWPixel(float64(ww))) 900 wh = int(u.toGLFWPixel(float64(wh))) 901 u.setWindowSize(ww, wh, u.isFullscreen()) 902 } 903 904 // Set the window size and the window position in this order on Linux or other UNIX using X (#1118), 905 // but this should be inverted on Windows. This is very tricky, but there is no obvious way to solve 906 // this. This doesn't matter on macOS. 907 if runtime.GOOS == "windows" { 908 u.setWindowPosition(u.getInitWindowPosition()) 909 setSize() 910 } else { 911 setSize() 912 u.setWindowPosition(u.getInitWindowPosition()) 913 } 914 915 u.updateWindowSizeLimits() 916 917 // Maximizing a window requires a proper size and position. Call Maximize here (#1117). 918 if u.isInitWindowMaximized() { 919 u.window.Maximize() 920 } 921 922 u.title = u.getInitTitle() 923 u.window.SetTitle(u.title) 924 u.window.Show() 925 926 if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok { 927 g.SetWindow(u.nativeWindow()) 928 } 929 930 return nil 931 } 932 933 func (u *UserInterface) updateSize() (float64, float64, bool) { 934 ww, wh := u.windowWidth, u.windowHeight 935 u.setWindowSize(ww, wh, u.isFullscreen()) 936 937 if !u.toChangeSize { 938 return 0, 0, false 939 } 940 u.toChangeSize = false 941 942 var w, h float64 943 if u.isFullscreen() && !u.isNativeFullscreen() { 944 // On Linux, the window size is not reliable just after making the window 945 // fullscreened. Use the monitor size. 946 // On macOS's native fullscreen, the window's size returns a more precise size 947 // reflecting the adjustment of the view size (#1745). 948 m := currentMonitor(u.window) 949 v := m.GetVideoMode() 950 ww, wh := v.Width, v.Height 951 s := videoModeScale(m) 952 w = u.fromGLFWMonitorPixel(float64(ww), s) 953 h = u.fromGLFWMonitorPixel(float64(wh), s) 954 } else { 955 // Instead of u.windowWidth and u.windowHeight, use the actual window size here. 956 // On Windows, the specified size at SetSize and the actual window size might not 957 // match (#1163). 958 ww, wh := u.window.GetSize() 959 w = u.fromGLFWPixel(float64(ww)) 960 h = u.fromGLFWPixel(float64(wh)) 961 } 962 963 return w, h, true 964 } 965 966 // setFPSMode must be called from the main thread. 967 func (u *UserInterface) setFPSMode(fpsMode driver.FPSMode) { 968 u.fpsMode = fpsMode 969 u.fpsModeInited = true 970 971 sticky := glfw.True 972 if fpsMode == driver.FPSModeVsyncOffMinimum { 973 sticky = glfw.False 974 } 975 u.window.SetInputMode(glfw.StickyMouseButtonsMode, sticky) 976 u.window.SetInputMode(glfw.StickyKeysMode, sticky) 977 } 978 979 // update must be called from the main thread. 980 func (u *UserInterface) update() (float64, float64, bool, error) { 981 if u.err != nil { 982 return 0, 0, false, u.err 983 } 984 985 if u.window.ShouldClose() { 986 return 0, 0, false, driver.RegularTermination 987 } 988 989 if u.isInitFullscreen() { 990 w, h := u.window.GetSize() 991 u.setWindowSize(w, h, true) 992 u.setInitFullscreen(false) 993 } 994 995 // Initialize vsync after SetMonitor is called. See the comment in updateVsync. 996 // Calling this inside setWindowSize didn't work (#1363). 997 if !u.fpsModeInited { 998 u.setFPSMode(u.getInitFPSMode()) 999 } 1000 1001 // Call updateVsync even though fpsMode is not updated. 1002 // The vsync state might be changed in other places (e.g., the SetSizeCallback). 1003 // Also, when toggling to fullscreen, vsync state might be reset unexpectedly (#1787). 1004 u.updateVsync() 1005 1006 outsideWidth, outsideHeight, outsideSizeChanged := u.updateSize() 1007 1008 if u.fpsMode != driver.FPSModeVsyncOffMinimum { 1009 // TODO: Updating the input can be skipped when clock.Update returns 0 (#1367). 1010 glfw.PollEvents() 1011 } else { 1012 glfw.WaitEvents() 1013 } 1014 u.input.update(u.window, u.context) 1015 1016 for !u.isRunnableOnUnfocused() && u.window.GetAttrib(glfw.Focused) == 0 && !u.window.ShouldClose() { 1017 if err := hooks.SuspendAudio(); err != nil { 1018 return 0, 0, false, err 1019 } 1020 // Wait for an arbitrary period to avoid busy loop. 1021 time.Sleep(time.Second / 60) 1022 glfw.PollEvents() 1023 } 1024 if err := hooks.ResumeAudio(); err != nil { 1025 return 0, 0, false, err 1026 } 1027 1028 return outsideWidth, outsideHeight, outsideSizeChanged, nil 1029 } 1030 1031 func (u *UserInterface) loop() error { 1032 defer func() { 1033 _ = u.t.Call(func() error { 1034 glfw.Terminate() 1035 return nil 1036 }) 1037 }() 1038 1039 for { 1040 var unfocused bool 1041 1042 // On Windows, the focusing state might be always false (#987). 1043 // On Windows, even if a window is in another workspace, vsync seems to work. 1044 // Then let's assume the window is always 'focused' as a workaround. 1045 if runtime.GOOS != "windows" { 1046 unfocused = u.window.GetAttrib(glfw.Focused) == glfw.False 1047 } 1048 1049 var t1, t2 time.Time 1050 1051 if unfocused { 1052 t1 = time.Now() 1053 } 1054 1055 var outsideWidth, outsideHeight float64 1056 var outsideSizeChanged bool 1057 if err := u.t.Call(func() error { 1058 var err error 1059 outsideWidth, outsideHeight, outsideSizeChanged, err = u.update() 1060 return err 1061 }); err != nil { 1062 return err 1063 } 1064 if outsideSizeChanged { 1065 u.context.Layout(outsideWidth, outsideHeight) 1066 } 1067 1068 if err := u.context.UpdateFrame(); err != nil { 1069 return err 1070 } 1071 1072 // Create icon images in a different goroutine (#1478). 1073 // In the fullscreen mode, SetIcon fails (#1578). 1074 if imgs := u.getIconImages(); len(imgs) > 0 && !u.isFullscreen() { 1075 u.setIconImages(nil) 1076 1077 // Convert the icons in the different goroutine, as (*ebiten.Image).At cannot be invoked 1078 // from this goroutine. At works only in between BeginFrame and EndFrame. 1079 go func() { 1080 newImgs := make([]image.Image, len(imgs)) 1081 for i, img := range imgs { 1082 // TODO: If img is not *ebiten.Image, this converting is not necessary. 1083 // However, this package cannot refer *ebiten.Image due to the package 1084 // dependencies. 1085 1086 b := img.Bounds() 1087 rgba := image.NewRGBA(b) 1088 for j := b.Min.Y; j < b.Max.Y; j++ { 1089 for i := b.Min.X; i < b.Max.X; i++ { 1090 rgba.Set(i, j, img.At(i, j)) 1091 } 1092 } 1093 newImgs[i] = rgba 1094 } 1095 1096 _ = u.t.Call(func() error { 1097 // In the fullscreen mode, reset the icon images and try again later. 1098 if u.isFullscreen() { 1099 u.setIconImages(imgs) 1100 return nil 1101 } 1102 u.window.SetIcon(newImgs) 1103 return nil 1104 }) 1105 }() 1106 } 1107 1108 // swapBuffers also checks IsGL, so this condition is redundant. 1109 // However, (*thread).Call is not good for performance due to channels. 1110 // Let's avoid this whenever possible (#1367). 1111 if u.Graphics().IsGL() { 1112 _ = u.t.Call(func() error { 1113 u.swapBuffers() 1114 return nil 1115 }) 1116 } 1117 1118 if unfocused { 1119 t2 = time.Now() 1120 } 1121 1122 // When a window is not focused, SwapBuffers might return immediately and CPU might be busy. 1123 // Mitigate this by sleeping (#982). 1124 if unfocused { 1125 d := t2.Sub(t1) 1126 const wait = time.Second / 60 1127 if d < wait { 1128 time.Sleep(wait - d) 1129 } 1130 } 1131 } 1132 } 1133 1134 // swapBuffers must be called from the main thread. 1135 func (u *UserInterface) swapBuffers() { 1136 if u.Graphics().IsGL() { 1137 u.window.SwapBuffers() 1138 } 1139 } 1140 1141 // updateWindowSizeLimits must be called from the main thread. 1142 func (u *UserInterface) updateWindowSizeLimits() { 1143 minw, minh, maxw, maxh := u.getWindowSizeLimitsInDP() 1144 if minw < 0 { 1145 minw = glfw.DontCare 1146 } else { 1147 minw = int(u.toGLFWPixel(float64(minw))) 1148 } 1149 if minh < 0 { 1150 minh = glfw.DontCare 1151 } else { 1152 minh = int(u.toGLFWPixel(float64(minh))) 1153 } 1154 if maxw < 0 { 1155 maxw = glfw.DontCare 1156 } else { 1157 maxw = int(u.toGLFWPixel(float64(maxw))) 1158 } 1159 if maxh < 0 { 1160 maxh = glfw.DontCare 1161 } else { 1162 maxh = int(u.toGLFWPixel(float64(maxh))) 1163 } 1164 u.window.SetSizeLimits(minw, minh, maxw, maxh) 1165 } 1166 1167 // adjustWindowSizeBasedOnSizeLimitsInDP adjust the size based on the window size limits. 1168 // width and height are in device-dependent pixels. 1169 func (u *UserInterface) adjustWindowSizeBasedOnSizeLimits(width, height int) (int, int) { 1170 minw, minh, maxw, maxh := u.getWindowSizeLimits() 1171 if minw >= 0 && width < minw { 1172 width = minw 1173 } 1174 if minh >= 0 && height < minh { 1175 height = minh 1176 } 1177 if maxw >= 0 && width > maxw { 1178 width = maxw 1179 } 1180 if maxh >= 0 && height > maxh { 1181 height = maxh 1182 } 1183 return width, height 1184 } 1185 1186 // adjustWindowSizeBasedOnSizeLimitsInDP adjust the size based on the window size limits. 1187 // width and height are in device-independent pixels. 1188 func (u *UserInterface) adjustWindowSizeBasedOnSizeLimitsInDP(width, height int) (int, int) { 1189 minw, minh, maxw, maxh := u.getWindowSizeLimitsInDP() 1190 if minw >= 0 && width < minw { 1191 width = minw 1192 } 1193 if minh >= 0 && height < minh { 1194 height = minh 1195 } 1196 if maxw >= 0 && width > maxw { 1197 width = maxw 1198 } 1199 if maxh >= 0 && height > maxh { 1200 height = maxh 1201 } 1202 return width, height 1203 } 1204 1205 // setWindowSize must be called from the main thread. 1206 func (u *UserInterface) setWindowSize(width, height int, fullscreen bool) { 1207 width, height = u.adjustWindowSizeBasedOnSizeLimits(width, height) 1208 1209 u.Graphics().SetFullscreen(fullscreen) 1210 1211 if u.windowWidth == width && u.windowHeight == height && u.isFullscreen() == fullscreen && u.lastDeviceScaleFactor == u.deviceScaleFactor() { 1212 return 1213 } 1214 1215 if width < 1 { 1216 width = 1 1217 } 1218 if height < 1 { 1219 height = 1 1220 } 1221 1222 u.lastDeviceScaleFactor = u.deviceScaleFactor() 1223 1224 // To make sure the current existing framebuffers are rendered, 1225 // swap buffers here before SetSize is called. 1226 u.swapBuffers() 1227 1228 // Disable the callback of SetSize. This callback can be invoked by SetMonitor or SetSize. 1229 // ForceUpdateFrame is called from the callback. 1230 // While setWindowSize can be called from UpdateFrame, 1231 // calling ForceUpdateFrame inside UpdateFrame is illegal (#1505). 1232 if u.setSizeCallbackEnabled { 1233 u.setSizeCallbackEnabled = false 1234 defer func() { 1235 u.setSizeCallbackEnabled = true 1236 }() 1237 } 1238 1239 windowRecreated := u.setWindowSizeImpl(width, height, fullscreen) 1240 1241 u.adjustViewSize() 1242 1243 // As width might be updated, update windowWidth/Height here. 1244 u.windowWidth = width 1245 u.windowHeight = height 1246 1247 u.toChangeSize = true 1248 1249 if windowRecreated { 1250 if g, ok := u.Graphics().(interface{ SetWindow(uintptr) }); ok { 1251 g.SetWindow(u.nativeWindow()) 1252 } 1253 } 1254 } 1255 1256 func (u *UserInterface) setWindowSizeImpl(width, height int, fullscreen bool) bool { 1257 var windowRecreated bool 1258 1259 if fullscreen { 1260 if x, y := u.origPos(); x == invalidPos || y == invalidPos { 1261 u.setOrigPos(u.window.GetPos()) 1262 } 1263 1264 if u.isNativeFullscreenAvailable() { 1265 u.setNativeFullscreen(fullscreen) 1266 } else { 1267 m := currentMonitor(u.window) 1268 v := m.GetVideoMode() 1269 u.window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate) 1270 1271 // Swapping buffer is necesary to prevent the image lag (#1004). 1272 // TODO: This might not work when vsync is disabled. 1273 if u.Graphics().IsGL() { 1274 glfw.PollEvents() 1275 u.swapBuffers() 1276 } 1277 } 1278 } else { 1279 // On Windows, giving a too small width doesn't call a callback (#165). 1280 // To prevent hanging up, return asap if the width is too small. 1281 // 126 is an arbitrary number and I guess this is small enough. 1282 minWindowWidth := int(u.toGLFWPixel(126)) 1283 if u.window.GetAttrib(glfw.Decorated) == glfw.False { 1284 minWindowWidth = 1 1285 } 1286 if width < minWindowWidth { 1287 width = minWindowWidth 1288 } 1289 1290 if u.isNativeFullscreenAvailable() && u.isNativeFullscreen() { 1291 u.setNativeFullscreen(false) 1292 } else if !u.isNativeFullscreenAvailable() && u.window.GetMonitor() != nil { 1293 if u.Graphics().IsGL() { 1294 // When OpenGL is used, swapping buffer is enough to solve the image-lag 1295 // issue (#1004). Rather, recreating window destroys GPU resources. 1296 // TODO: This might not work when vsync is disabled. 1297 u.window.SetMonitor(nil, 0, 0, width, height, 0) 1298 glfw.PollEvents() 1299 u.swapBuffers() 1300 } else { 1301 // Recreate the window since an image lag remains after coming back from 1302 // fullscreen (#1004). 1303 if u.window != nil { 1304 u.window.Destroy() 1305 u.window = nil 1306 } 1307 if err := u.createWindow(); err != nil { 1308 // TODO: This should return an error. 1309 panic(fmt.Sprintf("glfw: failed to recreate window: %v", err)) 1310 } 1311 // Reset the size limits explicitly. 1312 u.updateWindowSizeLimits() 1313 u.window.Show() 1314 windowRecreated = true 1315 } 1316 } 1317 1318 if x, y := u.origPos(); x != invalidPos && y != invalidPos { 1319 u.window.SetPos(x, y) 1320 // Dirty hack for macOS (#703). Rendering doesn't work correctly with one SetPos, but 1321 // work with two or more SetPos. 1322 if runtime.GOOS == "darwin" { 1323 u.window.SetPos(x+1, y) 1324 u.window.SetPos(x, y) 1325 } 1326 u.setOrigPos(invalidPos, invalidPos) 1327 } 1328 1329 // Set the window size after the position. The order matters. 1330 // In the opposite order, the window size might not be correct when going back from fullscreen with multi monitors. 1331 oldW, oldH := u.window.GetSize() 1332 newW := width 1333 newH := height 1334 if oldW != newW || oldH != newH { 1335 u.framebufferSizeCallbackCh = make(chan struct{}, 1) 1336 if u.framebufferSizeCallback == 0 { 1337 u.framebufferSizeCallback = glfw.ToFramebufferSizeCallback(func(_ *glfw.Window, _, _ int) { 1338 // This callback can be invoked multiple times by one PollEvents in theory (#1618). 1339 // Allow the case when the channel is full. 1340 select { 1341 case u.framebufferSizeCallbackCh <- struct{}{}: 1342 default: 1343 } 1344 }) 1345 } 1346 u.window.SetFramebufferSizeCallback(u.framebufferSizeCallback) 1347 u.window.SetSize(newW, newH) 1348 // Just after SetSize, GetSize is not reliable especially on Linux/UNIX. 1349 // Let's wait for FramebufferSize callback in any cases. 1350 1351 // Use the timeout as FramebufferSize event might not be fired (#1618). 1352 t := time.NewTimer(time.Second) 1353 defer t.Stop() 1354 1355 event: 1356 for { 1357 glfw.PollEvents() 1358 select { 1359 case <-u.framebufferSizeCallbackCh: 1360 break event 1361 case <-t.C: 1362 break event 1363 default: 1364 time.Sleep(time.Millisecond) 1365 } 1366 } 1367 u.window.SetFramebufferSizeCallback(glfw.ToFramebufferSizeCallback(nil)) 1368 close(u.framebufferSizeCallbackCh) 1369 u.framebufferSizeCallbackCh = nil 1370 } 1371 1372 // Window title might be lost on macOS after coming back from fullscreen. 1373 u.window.SetTitle(u.title) 1374 } 1375 1376 return windowRecreated 1377 } 1378 1379 // updateVsync must be called on the main thread. 1380 func (u *UserInterface) updateVsync() { 1381 if u.Graphics().IsGL() { 1382 // SwapInterval is affected by the current monitor of the window. 1383 // This needs to be called at least after SetMonitor. 1384 // Without SwapInterval after SetMonitor, vsynch doesn't work (#375). 1385 // 1386 // TODO: (#405) If triple buffering is needed, SwapInterval(0) should be called, 1387 // but is this correct? If glfw.SwapInterval(0) and the driver doesn't support triple 1388 // buffering, what will happen? 1389 if u.fpsMode == driver.FPSModeVsyncOn { 1390 glfw.SwapInterval(1) 1391 } else { 1392 glfw.SwapInterval(0) 1393 } 1394 } 1395 u.Graphics().SetVsyncEnabled(u.fpsMode == driver.FPSModeVsyncOn) 1396 } 1397 1398 // currentMonitor returns the current active monitor. 1399 // 1400 // The given window might or might not be used to detect the monitor. 1401 // 1402 // currentMonitor must be called on the main thread. 1403 func currentMonitor(window *glfw.Window) *glfw.Monitor { 1404 // GetMonitor is available only in fullscreen. 1405 if m := window.GetMonitor(); m != nil { 1406 return m 1407 } 1408 1409 // Getting a monitor from a window position is not reliable in general (e.g., when a window is put across 1410 // multiple monitors, or, before SetWindowPosition is called.). 1411 // Get the monitor which the current window belongs to. This requires OS API. 1412 if m := currentMonitorByOS(window); m != nil { 1413 return m 1414 } 1415 1416 // As the fallback, detect the monitor from the window. 1417 if m := getMonitorFromPosition(window.GetPos()); m != nil { 1418 return m.m 1419 } 1420 return glfw.GetPrimaryMonitor() 1421 } 1422 1423 func (u *UserInterface) SetScreenTransparent(transparent bool) { 1424 if !u.isRunning() { 1425 u.setInitScreenTransparent(transparent) 1426 return 1427 } 1428 panic("glfw: SetScreenTransparent can't be called after the main loop starts") 1429 } 1430 1431 func (u *UserInterface) IsScreenTransparent() bool { 1432 if !u.isRunning() { 1433 return u.isInitScreenTransparent() 1434 } 1435 val := false 1436 _ = u.t.Call(func() error { 1437 val = u.window.GetAttrib(glfw.TransparentFramebuffer) == glfw.True 1438 return nil 1439 }) 1440 return val 1441 } 1442 1443 func (u *UserInterface) ResetForFrame() { 1444 // The offscreens must be updated every frame (#490). 1445 var w, h float64 1446 var changed bool 1447 _ = u.t.Call(func() error { 1448 w, h, changed = u.updateSize() 1449 return nil 1450 }) 1451 if changed { 1452 u.context.Layout(w, h) 1453 } 1454 u.input.resetForFrame() 1455 1456 u.m.Lock() 1457 u.windowBeingClosed = false 1458 u.m.Unlock() 1459 } 1460 1461 func (u *UserInterface) MonitorPosition() (int, int) { 1462 if !u.isRunning() { 1463 return u.monitorPosition() 1464 } 1465 var mx, my int 1466 _ = u.t.Call(func() error { 1467 mx, my = u.monitorPosition() 1468 return nil 1469 }) 1470 return mx, my 1471 } 1472 1473 func (u *UserInterface) SetInitFocused(focused bool) { 1474 if u.isRunning() { 1475 panic("ui: SetInitFocused must be called before the main loop") 1476 } 1477 u.setInitFocused(focused) 1478 } 1479 1480 func (u *UserInterface) monitorPosition() (int, int) { 1481 // TODO: fromGLFWMonitorPixel might be required. 1482 return currentMonitor(u.window).GetPos() 1483 } 1484 1485 func (u *UserInterface) Input() driver.Input { 1486 return &u.input 1487 } 1488 1489 func (u *UserInterface) Window() driver.Window { 1490 return &u.iwindow 1491 } 1492 1493 // GLFW's functions to manipulate a window can invoke the SetSize callback (#1576, #1585, #1606). 1494 // As the callback must not be called in the frame (between BeginFrame and EndFrame), 1495 // disable the callback temporarily. 1496 1497 // maximizeWindow must be called from the main thread. 1498 func (u *UserInterface) maximizeWindow() { 1499 if u.isNativeFullscreen() { 1500 return 1501 } 1502 1503 if u.setSizeCallbackEnabled { 1504 u.setSizeCallbackEnabled = false 1505 defer func() { 1506 u.setSizeCallbackEnabled = true 1507 }() 1508 } 1509 u.window.Maximize() 1510 1511 if !u.isFullscreen() { 1512 // On Linux/UNIX, maximizing might not finish even though Maximize returns. Just wait for its finish. 1513 // Do not check this in the fullscreen since apparently the condition can never be true. 1514 for u.window.GetAttrib(glfw.Maximized) != glfw.True { 1515 glfw.PollEvents() 1516 } 1517 1518 // Call setWindowSize explicitly in order to update the rendering since the callback is disabled now. 1519 // Do not call setWindowSize in the fullscreen mode since setWindowSize requires the window size 1520 // before the fullscreen, while window.GetSize() returns the desktop screen size in the fullscreen mode. 1521 w, h := u.window.GetSize() 1522 u.setWindowSize(w, h, u.isFullscreen()) 1523 } 1524 } 1525 1526 // iconifyWindow must be called from the main thread. 1527 func (u *UserInterface) iconifyWindow() { 1528 if u.isNativeFullscreen() { 1529 return 1530 } 1531 1532 if u.setSizeCallbackEnabled { 1533 u.setSizeCallbackEnabled = false 1534 defer func() { 1535 u.setSizeCallbackEnabled = true 1536 }() 1537 } 1538 u.window.Iconify() 1539 1540 // On Linux/UNIX, iconifying might not finish even though Iconify returns. Just wait for its finish. 1541 for u.window.GetAttrib(glfw.Iconified) != glfw.True { 1542 glfw.PollEvents() 1543 } 1544 1545 // After iconifiying, the window is invisible and setWindowSize doesn't have to be called. 1546 // Rather, the window size might be (0, 0) and it might be impossible to call setWindowSize (#1585). 1547 } 1548 1549 // restoreWindow must be called from the main thread. 1550 func (u *UserInterface) restoreWindow() { 1551 if u.setSizeCallbackEnabled { 1552 u.setSizeCallbackEnabled = false 1553 defer func() { 1554 u.setSizeCallbackEnabled = true 1555 }() 1556 } 1557 1558 u.window.Restore() 1559 1560 // On Linux/UNIX, restoring might not finish even though Restore returns (#1608). Just wait for its finish. 1561 // On macOS, the restoring state might be the same as the maximized state. Skip this. 1562 if runtime.GOOS != "darwin" { 1563 for u.window.GetAttrib(glfw.Maximized) == glfw.True || u.window.GetAttrib(glfw.Iconified) == glfw.True { 1564 glfw.PollEvents() 1565 } 1566 } 1567 1568 // Call setWindowSize explicitly in order to update the rendering since the callback is disabled now. 1569 // Do not call setWindowSize in the fullscreen mode since setWindowSize requires the window size 1570 // before the fullscreen, while window.GetSize() returns the desktop screen size in the fullscreen mode. 1571 if !u.isFullscreen() { 1572 w, h := u.window.GetSize() 1573 u.setWindowSize(w, h, u.isFullscreen()) 1574 } 1575 } 1576 1577 // setWindowDecorated must be called from the main thread. 1578 func (u *UserInterface) setWindowDecorated(decorated bool) { 1579 if u.setSizeCallbackEnabled { 1580 u.setSizeCallbackEnabled = false 1581 defer func() { 1582 u.setSizeCallbackEnabled = true 1583 }() 1584 } 1585 v := glfw.False 1586 if decorated { 1587 v = glfw.True 1588 } 1589 u.window.SetAttrib(glfw.Decorated, v) 1590 1591 // The title can be lost when the decoration is gone. Recover this. 1592 if decorated { 1593 u.window.SetTitle(u.title) 1594 } 1595 } 1596 1597 // setWindowFloating must be called from the main thread. 1598 func (u *UserInterface) setWindowFloating(floating bool) { 1599 if u.setSizeCallbackEnabled { 1600 u.setSizeCallbackEnabled = false 1601 defer func() { 1602 u.setSizeCallbackEnabled = true 1603 }() 1604 } 1605 v := glfw.False 1606 if floating { 1607 v = glfw.True 1608 } 1609 u.window.SetAttrib(glfw.Floating, v) 1610 } 1611 1612 // setWindowResizable must be called from the main thread. 1613 func (u *UserInterface) setWindowResizable(resizable bool) { 1614 if u.setSizeCallbackEnabled { 1615 u.setSizeCallbackEnabled = false 1616 defer func() { 1617 u.setSizeCallbackEnabled = true 1618 }() 1619 } 1620 1621 v := glfw.False 1622 if resizable { 1623 v = glfw.True 1624 } 1625 u.window.SetAttrib(glfw.Resizable, v) 1626 } 1627 1628 // setWindowPosition must be called from the main thread. 1629 func (u *UserInterface) setWindowPosition(x, y int) { 1630 if u.setSizeCallbackEnabled { 1631 u.setSizeCallbackEnabled = false 1632 defer func() { 1633 u.setSizeCallbackEnabled = true 1634 }() 1635 } 1636 1637 mx, my := currentMonitor(u.window).GetPos() 1638 xf := u.toGLFWPixel(float64(x)) 1639 yf := u.toGLFWPixel(float64(y)) 1640 if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf)); u.isFullscreen() { 1641 u.setOrigPos(x, y) 1642 } else { 1643 u.window.SetPos(x, y) 1644 } 1645 1646 // Call setWindowSize explicitly in order to update the rendering since the callback is disabled now. 1647 // 1648 // There are cases when setWindowSize should be called (#1606) and should not be called (#1609). 1649 // For the former, macOS seems enough so far. 1650 // 1651 // Do not call setWindowSize in the fullscreen mode since setWindowSize requires the window size 1652 // before the fullscreen, while window.GetSize() returns the desktop screen size in the fullscreen mode. 1653 if !u.isFullscreen() && runtime.GOOS == "darwin" { 1654 w, h := u.window.GetSize() 1655 u.setWindowSize(w, h, u.isFullscreen()) 1656 } 1657 } 1658 1659 // setWindowTitle must be called from the main thread. 1660 func (u *UserInterface) setWindowTitle(title string) { 1661 if u.setSizeCallbackEnabled { 1662 u.setSizeCallbackEnabled = false 1663 defer func() { 1664 u.setSizeCallbackEnabled = true 1665 }() 1666 } 1667 1668 u.window.SetTitle(title) 1669 } 1670 1671 func (u *UserInterface) origPos() (int, int) { 1672 // On macOS, the window can be fullscreened without calling an Ebiten function. 1673 // Then, an original position might not be available by u.window.GetPos(). 1674 // Do not rely on the window position. 1675 if u.isNativeFullscreenAvailable() { 1676 return invalidPos, invalidPos 1677 } 1678 return u.origPosX, u.origPosY 1679 } 1680 1681 func (u *UserInterface) setOrigPos(x, y int) { 1682 // TODO: The original position should be updated at a 'PosCallback'. 1683 1684 // On macOS, the window can be fullscreened without calling an Ebiten function. 1685 // Then, an original position might not be available by u.window.GetPos(). 1686 // Do not rely on the window position. 1687 if u.isNativeFullscreenAvailable() { 1688 return 1689 } 1690 u.origPosX = x 1691 u.origPosY = y 1692 }