Jump to content

🧠 “My Window Won't Center (and Other Fullscreen Mode Traumas)”


A real-life story about how graphics engines can mess up your desktop without asking permission.
image.thumb.png.fa76ab9e22e4c37589d74eeb41bef33c.png

🧩 Technical Context

You're developing your game in Leadwerks 5, you create a windowed mode with beautiful dimensions like 1280 x 720, and you decide to center it stylishly using:

 

local displaySize = display:GetSize()
local posX = displaySize.x / 2 - width / 2
local posY = displaySize.y / 2 - height / 2

So far, so good...
BUT...

🧨 When you decide to switch to fullscreen mode, everything goes haywire.
 

💥 What Happened Here?

When you activate fullscreen, you're not just removing the window border and maximizing the visible area.
No, sir.

In many engines (and Leadwerks is no exception), the operating system completely changes the monitor's resolution to match that of the game window.

Yes, you read that right:

“The entire system lowers its resolution to accommodate your game.”

For example:
If your desktop was 1920x1080 and your window is 1280x720, then upon going fullscreen, your entire operating system is now at 1280x720.

🕳️ What Does This Imply?

Upon returning to windowed mode, if you call display:GetSize() again, what you get is no longer the real desktop resolution, but the last forced monitor size.

Result:
🔄 Your window is created mispositioned, miscentered, everything's wrong.
 

🧪 Is This a Bug?

No, it's “expected behavior with dark side effects.”
Engines like SDL, Unity, or Unreal Engine also have to deal with this.

  • SDL recommends using SDL_GetDisplayBounds() to get the real resolution.

  • Unity has separate APIs for “render resolution” and “display resolution”.

  • Unreal uses GetDesktopResolution() only at startup, never after fullscreen.


The Solution: Cache It Like It's Gold!

We created a system that:

  1. Detects the real desktop resolution the first time the game starts in windowed mode.

  2. Saves it in the configuration file as:

     

    desktop_resolution=1920x1080

    3. Uses that value whenever we need to center the window, and never touches it again.

    Additionally, we made a small local cache in Lua to avoid reading the file all the time.


    💾 BONUS: Temporary Backup in Case Everything Explodes

    We also created a system to save the current resolution in a temporary file before applying any screen changes.
    So, if the player presses "CANCEL", we simply restore from the backup, and the universe is back in balance.


    💙 Conclusion

  3. ⚠️ Don't trust display:GetSize() after fullscreen.

  4. 🛡️ Save the real desktop resolution once, and protect it like it's your HUD's lifeline.

  5. 🤓 Understanding how graphics engines work at a low level saves you hours of yelling at the monitor.

  6. 😂 And remember: sometimes, the real bug... is the implicit logic the system didn't tell you about

     

    -- Last Update 5 de Abril 2025
    
    Window = {}
    Window.__index = Window
    
    -- Variable local para almacenar la resolución de escritorio de forma persistente
    local cachedDesktopResolution = nil
    
    function Window:New(title, x, y, width, height, display, style)
        local this = setmetatable({}, Window)
    
        local title = title or "Ventana"
        local x = x or 0
        local y = y or 0
        local display = display or GetDisplays()[1]
    
        -- Cargar configuración
        local settingsManager = SettingsManager:New()
        local settings = settingsManager:Load()
    
        -- Guardar backup temporal de resolución y fullscreen antes de crear la nueva ventana
        local tempSettings = {
            resolution = tostring(width) .. " x " .. tostring(height),
            fullscreen = settings["fullscreen"] or "0"
        }
        settingsManager:SaveTemporary(tempSettings)
        Print("📝 Backup temporal guardado: " .. tempSettings.resolution)
    
        -- Inicializar la resolución de escritorio solo una vez y guardarla en un caché local
        if not cachedDesktopResolution then
            local storedDesktopRes = settings["desktop_resolution"]
            if storedDesktopRes and storedDesktopRes:match("^(%d+)x(%d+)$") then
                local w, h = storedDesktopRes:match("(%d+)x(%d+)")
                cachedDesktopResolution = { x = tonumber(w), y = tonumber(h) }
                Print("🖥️ Resolución de escritorio cargada desde config: " .. storedDesktopRes)
            else
                local size = display:GetSize()
                cachedDesktopResolution = { x = size.x, y = size.y }
                settings["desktop_resolution"] = tostring(size.x) .. "x" .. tostring(size.y)
                settingsManager:Save(settings)
                Print("🖥️ Resolución de escritorio guardada por primera vez: " .. settings["desktop_resolution"])
            end
        else
            Print("🖥️ Usando resolución de escritorio en caché: " .. tostring(cachedDesktopResolution.x) .. "x" .. tostring(cachedDesktopResolution.y))
        end
    
        local savedDesktopW = cachedDesktopResolution.x
        local savedDesktopH = cachedDesktopResolution.y
    
        -- Cargar o asignar resolución de ventana
        if settings["resolution"] then
            local w, h = settings["resolution"]:match("(%d+)%s*x%s*(%d+)")
            if w and h then
                width = tonumber(w)
                height = tonumber(h)
                Print("📐 Resolución de ventana cargada desde config: " .. width .. "x" .. height)
            else
                width = width or 800
                height = height or 600
            end
        else
            width = width or 800
            height = height or 600
        end
    
        -- Determinar modo ventana o fullscreen
        local fullscreen = false
        local fs = settings["fullscreen"]
        if fs then
            fs = fs:lower()
            if fs == "true" or fs == "1" or fs == "yes" then
                fullscreen = true
            end
        end
    
        if fullscreen then
            style = WINDOW_FULLSCREEN 
            Print("🖥️ Modo pantalla completa activado.")
        else
            style = WINDOW_TITLEBAR
            Print("🪟 Modo ventana activado.")
        end
    
        -- Calcular posición centrada
        local posX = savedDesktopW / 2 - (width / 2)
        local posY = savedDesktopH / 2 - (height / 2)
    
        local window = CreateWindow(title, posX, posY, width, height, display, style)
        local framebuffer = CreateFramebuffer(window)
    
        Print("[Window] Ventana creada con éxito: " .. title)
    
        -- Métodos públicos
        function this:Closed() return window:Closed() end
        function this:GetWindow() return window end
        function this:GetFramebuffer() return framebuffer end
        function this:KeyDown(key) return window:KeyDown(key) end
        function this:GetWidth() return width end
        function this:GetHeight() return height end
        function this:GetTitle() return title end
        function this:SetTitle(newTitle)
            title = newTitle
            window:SetTitle(newTitle)
        end
        function this:Destroy()
            window = nil
            framebuffer = nil
            if self.settings then
                self.settings:Destroy()
                self.settings = nil
            end
            Print("[Window] 🧹 Recursos liberados correctamente.")
        end
    
        return this
    end
    
    -- 💾 Método estático para restaurar resolución desde el archivo temporal
    function Window.RestoreFromTemp()
        local sm = SettingsManager:New()
        local temp = sm:LoadTemporary()
    
        if temp and temp["resolution"] then
            local w, h = temp["resolution"]:match("(%d+)%s*x%s*(%d+)")
            if w and h then
                local fs = temp["fullscreen"]
                if fs == "1" or fs == "true" or fs == "yes" then
                    fs = true
                else
                    fs = false
                end
                Print("🔁 Restaurando resolución temporal: " .. w .. "x" .. h .. " | fullscreen: " .. tostring(fs))
                return tonumber(w), tonumber(h), fs
            end
        end
    
        Print("⚠️ No se pudo restaurar resolución: archivo temporal no encontrado o malformado.")
        return nil
    end
    
    return Window

     

  • Like 2

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...