Exemple Screen/WindowScreen with Alt-Tab and Alt-Enter

Share your advanced PureBasic knowledge/code with the community.
User avatar
thyphoon
Enthusiast
Enthusiast
Posts: 367
Joined: Sat Dec 25, 2004 2:37 pm

Exemple Screen/WindowScreen with Alt-Tab and Alt-Enter

Post by thyphoon »

Hello everyone,

I've cleaned up some code I wrote a few years ago...
I'm sharing it with you because it might be useful, especially for beginners.

If anyone has any suggestions for improvement, please feel free to share them.

Code: Select all

EnableExplicit

; -----------------------------------------------------------------------------
; Structure containing all parameters and states related to display
; -----------------------------------------------------------------------------
Structure screenStruct
  isFullScreen.b      ; 0 = windowed, 1 = fullscreen
  screenWidth.l       ; desired width
  screenHeight.l      ; desired height
  screenDepth.l       ; color depth (bits)
  screenFlags.l       ; PB flags for the screen (sync, etc.)
  screenTitle.s       ; title
  spriteMouse.i       ; sprite ID used as cursor
  mouseX.l            ; mouse X position
  mouseY.l            ; mouse Y position
EndStructure

; Global instance of the screen structure
Global Screen.screenStruct

; -----------------------------------------------------------------------------
; Initialize default values
; -----------------------------------------------------------------------------
Screen\isFullScreen  = #False
Screen\screenWidth   = 1280
Screen\screenHeight  = 720
Screen\screenDepth   = 32
Screen\screenFlags   = #PB_Screen_SmartSynchronization
Screen\screenTitle   = "Demo Alt-Tab / Alt-Enter"

; -----------------------------------------------------------------------------
; Create assets (here a 16x16 sprite to simulate a red cursor)
; -----------------------------------------------------------------------------
Procedure InitAssets()

  ; Create the sprite + start a drawing context on this sprite
  If CreateSprite(Screen\spriteMouse, 16, 16, #PB_Sprite_AlphaBlending) And StartDrawing(SpriteOutput(Screen\spriteMouse))

    ; Allows drawing with full alpha handling (RGBA channels)
    DrawingMode(#PB_2DDrawing_AllChannels)

    ; Draw a centered red circle at (8,8) with radius 8 (simple cursor)
    Circle(8, 8, 8, RGBA(255, 0, 0, 255))

    StopDrawing()
  Else
    Debug "Error Draw cursor"
  EndIf

EndProcedure

; -----------------------------------------------------------------------------
; Initialize the screen depending on the mode: fullscreen or windowed
; Returns #True if OK
; -----------------------------------------------------------------------------
Procedure.b InitScreen()

  If Screen\isFullScreen

    ; In fullscreen mode, iterate through available modes to find a mode
    ; at least equal to the requested resolution, with a 60 Hz refresh.
    ExamineScreenModes()
    While NextScreenMode()
      If ScreenModeWidth() >= Screen\screenWidth And ScreenModeHeight() >= Screen\screenHeight And ScreenModeRefreshRate() = 60
        Screen\screenWidth  = ScreenModeWidth()
        Screen\screenHeight = ScreenModeHeight()
        Break
      EndIf
    Wend

    ; Open the fullscreen screen
    If OpenScreen(Screen\screenWidth, Screen\screenHeight, Screen\screenDepth, Screen\screenTitle, #PB_Screen_WaitSynchronization)
      ProcedureReturn #True
    Else
      MessageRequester("Error", "Unable to open the screen")
      End
    EndIf

  Else
    ; Windowed mode: create a PB window then a "WindowedScreen" inside it.
    ; DesktopUnscaledX/Y avoids surprises with Windows scaling (DPI).
    If OpenWindow(0, 0, 0, DesktopUnscaledX(Screen\screenWidth), DesktopUnscaledY(Screen\screenHeight),
                  Screen\screenTitle, #PB_Window_ScreenCentered) And
       OpenWindowedScreen(WindowID(0), 0, 0, Screen\screenWidth, Screen\screenHeight, #False, 0, 0, #PB_Screen_WaitSynchronization)

      ProcedureReturn #True
    EndIf

  EndIf

EndProcedure

; -----------------------------------------------------------------------------
; Handle ALT+TAB (focus loss / inactive screen)
; Idea: if the screen is no longer active, close it and wait until activation returns.
; -----------------------------------------------------------------------------
Procedure CheckScreenFocus()

  ; IsScreenActive() = 0 => the screen is no longer active (e.g., Alt-Tab)
  If IsScreenActive() = 0

    ; Release sprites
    ;TODO FreeAssets()
    FreeSprite(Screen\spriteMouse)

    ; Create a tiny minimized/borderless window to "keep a window handle"
    ; while closing the screen. The goal is to wait for an activation event.
    OpenWindow(0, 1, 1, 1, 1, Screen\screenTitle, #PB_Window_Minimize | #PB_Window_BorderLess)

    ; Close fullscreen / windowed screen
    CloseScreen()

    Protected Event.l
    Repeat
      Event = WaitWindowEvent()

      ; If the user closes the window, quit
      If Event = #PB_Event_CloseWindow
        End
        Break
      EndIf

    ; Wait until the window receives the activation event
    Until Event = #PB_Event_ActivateWindow

    ; Once reactivated, close the small window
    CloseWindow(0)

    ; Reinitialize screen + assets
    InitScreen()
    InitAssets()

  EndIf

EndProcedure

; -----------------------------------------------------------------------------
; Toggle windowed <-> fullscreen via ALT + Enter
; -----------------------------------------------------------------------------
Procedure ToggleFullScreen()

  ; Logical XOR: invert 0/1
  Screen\isFullScreen ! 1

  ; If switching to fullscreen, also close the windowed window.
  If Screen\isFullScreen = #True
    CloseScreen()
    CloseWindow(0)
  Else
    ; If switching back to windowed, only close the screen (window will be recreated)
    CloseScreen()
  EndIf

  ; Re-open the correct mode + recreate assets (cursor)
  InitScreen()
  InitAssets()

EndProcedure

; -----------------------------------------------------------------------------
; Main loop
; -----------------------------------------------------------------------------
Procedure Main()

  Protected Event.i

  ; Initialize subsystems
  InitSprite()
  InitKeyboard()
  InitMouse()

  ; Initialize display + assets
  InitScreen()
  InitAssets()

  Repeat

    ; In windowed mode, drain window events to avoid "freeze"
    If Screen\isFullScreen = #False
      Repeat
        Event = WindowEvent()
      Until Event = 0
    EndIf

    ; Clear the screen
    ClearScreen(RGB(0, 0, 32))

    ; Update keyboard/mouse
    ExamineKeyboard()
    ExamineMouse()

    ; Read mouse position
    Screen\mouseX = MouseX()
    Screen\mouseY = MouseY()

    ; Display the cursor sprite at the mouse position
    DisplayTransparentSprite(Screen\spriteMouse, Screen\mouseX, Screen\mouseY)

    ; ALT + Enter => toggle fullscreen
    If KeyboardPushed(#PB_Key_LeftAlt) Or KeyboardPushed(#PB_Key_RightAlt)
      If KeyboardReleased(#PB_Key_Return)
        ToggleFullScreen()
      EndIf
    EndIf

    ; Swap buffers (double buffering)
    FlipBuffers()

    ; Handle ALT+TAB / activation loss
    CheckScreenFocus()

    ; Small CPU pause (1ms) to avoid monopolizing resources
    Delay(1)

  Until KeyboardPushed(#PB_Key_Escape)

  ; Cleanup
  CloseScreen()

EndProcedure

Main()

miso
Enthusiast
Enthusiast
Posts: 674
Joined: Sat Oct 21, 2023 4:06 pm
Location: Hungary

Re: Exemple Screen/WindowScreen with Alt-Tab and Alt-Enter

Post by miso »

Nice. Tough I would not use fullscreen exclusive, only windowed/windowed fullscreen. My concerns mainly goes for the dpi settings + windowed fullscreen, app resolution/desktop resolution "normalization". I still dont have a perfect solution i would be satisfied with...
Post Reply