From 3024e07ecc01a7f5864051150ee7934c542d7256 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 9 Dec 2022 21:23:41 +0900 Subject: [PATCH] ebiten: add RunGameOptions.ScreenTransparent Closes #2378 --- examples/windowsize/main.go | 2 +- gameforui.go | 8 ++++--- imagedumper.go | 9 ++++--- internal/ui/ui.go | 5 ++-- internal/ui/ui_glfw.go | 44 ++++++----------------------------- internal/ui/ui_js.go | 22 +++++++++--------- internal/ui/ui_mobile.go | 12 ++++------ internal/ui/ui_nintendosdk.go | 11 ++++----- run.go | 44 +++++++++++++++++++++++++++++------ run_mobile.go | 3 ++- 10 files changed, 78 insertions(+), 82 deletions(-) diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index f03c75aa2..a30e18c98 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -404,7 +404,6 @@ func main() { if x, y, ok := parseWindowPosition(); ok { ebiten.SetWindowPosition(x, y) } - ebiten.SetScreenTransparent(*flagTransparent) g := &game{ width: initScreenWidth, @@ -464,6 +463,7 @@ func main() { log.Fatalf("unexpected graphics library: %s", *flagGraphicsLibrary) } op.InitUnfocused = !*flagInitFocused + op.ScreenTransparent = *flagTransparent const title = "Window Size (Ebitengine Demo)" ww := int(float64(g.width) * initScreenScale) diff --git a/gameforui.go b/gameforui.go index 48431b204..942680357 100644 --- a/gameforui.go +++ b/gameforui.go @@ -80,11 +80,13 @@ type gameForUI struct { screen *Image screenShader *Shader imageDumper imageDumper + transparent bool } -func newGameForUI(game Game) *gameForUI { +func newGameForUI(game Game, transparent bool) *gameForUI { g := &gameForUI{ - game: game, + game: game, + transparent: transparent, } s, err := NewShader([]byte(screenShaderSrc)) @@ -157,7 +159,7 @@ func (g *gameForUI) Update() error { func (g *gameForUI) DrawOffscreen() error { g.game.Draw(g.offscreen) - if err := g.imageDumper.dump(g.offscreen); err != nil { + if err := g.imageDumper.dump(g.offscreen, g.transparent); err != nil { return err } return nil diff --git a/imagedumper.go b/imagedumper.go index 6b241aa89..b081471bf 100644 --- a/imagedumper.go +++ b/imagedumper.go @@ -31,7 +31,7 @@ func datetimeForFilename() string { return now.Format(datetimeFormat) } -func takeScreenshot(screen *Image) error { +func takeScreenshot(screen *Image, transparent bool) error { name := "screenshot_" + datetimeForFilename() + ".png" // Use the home directory for mobiles as a provisional implementation. if runtime.GOOS == "android" || runtime.GOOS == "ios" { @@ -41,8 +41,7 @@ func takeScreenshot(screen *Image) error { } name = filepath.Join(home, name) } - blackbg := !IsScreenTransparent() - dumpedName, err := screen.image.DumpScreenshot(name, blackbg) + dumpedName, err := screen.image.DumpScreenshot(name, !transparent) if err != nil { return err } @@ -155,10 +154,10 @@ func (i *imageDumper) update() error { return nil } -func (i *imageDumper) dump(screen *Image) error { +func (i *imageDumper) dump(screen *Image, transparent bool) error { if i.toTakeScreenshot { i.toTakeScreenshot = false - if err := takeScreenshot(screen); err != nil { + if err := takeScreenshot(screen, transparent); err != nil { return err } } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 745c3ebd7..753b72f7e 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -97,6 +97,7 @@ func (u *UserInterface) dumpImages(dir string) (string, error) { } type RunOptions struct { - GraphicsLibrary GraphicsLibrary - InitUnfocused bool + GraphicsLibrary GraphicsLibrary + InitUnfocused bool + ScreenTransparent bool } diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 6b4911fc0..ab656c193 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -92,7 +92,6 @@ type userInterfaceImpl struct { initWindowHeightInDIP int initWindowFloating bool initWindowMaximized bool - initScreenTransparent bool origWindowPosX int origWindowPosY int @@ -360,19 +359,6 @@ func (u *userInterfaceImpl) setRunnableOnUnfocused(runnableOnUnfocused bool) { u.m.Unlock() } -func (u *userInterfaceImpl) isInitScreenTransparent() bool { - u.m.RLock() - v := u.initScreenTransparent - u.m.RUnlock() - return v -} - -func (u *userInterfaceImpl) setInitScreenTransparent(transparent bool) { - u.m.Lock() - u.initScreenTransparent = transparent - u.m.Unlock() -} - func (u *userInterfaceImpl) getIconImages() []image.Image { u.m.RLock() i := u.iconImages @@ -874,21 +860,20 @@ func (u *userInterfaceImpl) init(options *RunOptions) error { } glfw.WindowHint(glfw.Decorated, decorated) - transparent := u.isInitScreenTransparent() glfwTransparent := glfw.False - if transparent { + if options.ScreenTransparent { glfwTransparent = glfw.True } glfw.WindowHint(glfw.TransparentFramebuffer, glfwTransparent) g, err := newGraphicsDriver(&graphicsDriverCreatorImpl{ - transparent: transparent, + transparent: options.ScreenTransparent, }, options.GraphicsLibrary) if err != nil { return err } u.graphicsDriver = g - u.graphicsDriver.SetTransparent(u.isInitScreenTransparent()) + u.graphicsDriver.SetTransparent(options.ScreenTransparent) if u.graphicsDriver.IsGL() { u.graphicsDriver.(interface{ SetGLFWClientAPI() }).SetGLFWClientAPI() @@ -1417,25 +1402,6 @@ func monitorFromWindow(window *glfw.Window) *glfw.Monitor { return nil } -func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) { - if !u.isRunning() { - u.setInitScreenTransparent(transparent) - return - } - panic("ui: SetScreenTransparent can't be called after the main loop starts") -} - -func (u *userInterfaceImpl) IsScreenTransparent() bool { - if !u.isRunning() { - return u.isInitScreenTransparent() - } - val := false - u.t.Call(func() { - val = u.window.GetAttrib(glfw.TransparentFramebuffer) == glfw.True - }) - return val -} - func (u *userInterfaceImpl) resetForTick() { u.input.resetForTick() @@ -1617,3 +1583,7 @@ func (u *userInterfaceImpl) setOrigWindowPos(x, y int) { u.origWindowPosX = x u.origWindowPosY = y } + +func IsScreenTransparentAvailable() bool { + return true +} diff --git a/internal/ui/ui_js.go b/internal/ui/ui_js.go index e327c0daf..490aebd72 100644 --- a/internal/ui/ui_js.go +++ b/internal/ui/ui_js.go @@ -652,6 +652,13 @@ func (u *userInterfaceImpl) Run(game Game, options *RunOptions) error { return err } u.graphicsDriver = g + + if bodyStyle := document.Get("body").Get("style"); options.ScreenTransparent { + bodyStyle.Set("backgroundColor", "transparent") + } else { + bodyStyle.Set("backgroundColor", "#000") + } + return <-u.loop(game) } @@ -670,17 +677,6 @@ func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) { panic("ui: SetScreenTransparent can't be called after the main loop starts") } - bodyStyle := document.Get("body").Get("style") - if transparent { - bodyStyle.Set("backgroundColor", "transparent") - } else { - bodyStyle.Set("backgroundColor", "#000") - } -} - -func (u *userInterfaceImpl) IsScreenTransparent() bool { - bodyStyle := document.Get("body").Get("style") - return bodyStyle.Get("backgroundColor").Equal(stringTransparent) } func (u *userInterfaceImpl) resetForTick() { @@ -700,3 +696,7 @@ func (u *userInterfaceImpl) beginFrame() { func (u *userInterfaceImpl) endFrame() { } + +func IsScreenTransparentAvailable() bool { + return true +} diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index 7fc266b1f..7e5d52d2f 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -421,14 +421,6 @@ func (u *userInterfaceImpl) DeviceScaleFactor() float64 { return deviceScale() } -func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) { - // Do nothing -} - -func (u *userInterfaceImpl) IsScreenTransparent() bool { - return false -} - func (u *userInterfaceImpl) resetForTick() { u.input.resetForTick() } @@ -475,3 +467,7 @@ func (u *userInterfaceImpl) beginFrame() { func (u *userInterfaceImpl) endFrame() { } + +func IsScreenTransparentAvailable() bool { + return false +} diff --git a/internal/ui/ui_nintendosdk.go b/internal/ui/ui_nintendosdk.go index 208b5e902..7eeb4c342 100644 --- a/internal/ui/ui_nintendosdk.go +++ b/internal/ui/ui_nintendosdk.go @@ -126,13 +126,6 @@ func (*userInterfaceImpl) SetFPSMode(mode FPSModeType) { func (*userInterfaceImpl) ScheduleFrame() { } -func (*userInterfaceImpl) IsScreenTransparent() bool { - return false -} - -func (*userInterfaceImpl) SetScreenTransparent(transparent bool) { -} - func (*userInterfaceImpl) Input() *Input { return &theUI.input } @@ -146,3 +139,7 @@ func (u *userInterfaceImpl) beginFrame() { func (u *userInterfaceImpl) endFrame() { } + +func IsScreenTransparentAvailable() bool { + return false +} diff --git a/run.go b/run.go index efd46dd96..5a63bc3fd 100644 --- a/run.go +++ b/run.go @@ -241,6 +241,12 @@ type RunGameOptions struct { // // The default (zero) value is false, which means that the window is focused. InitUnfocused bool + + // ScreenTransparent represents whether the window is transparent or not. + // ScreenTransparent is valid on desktops and browsers. + // + // The default (zero) value is false, which means that the window is not transparent. + ScreenTransparent bool } // RunGameWithOptions starts the main loop and runs the game with the specified options. @@ -280,8 +286,17 @@ func RunGameWithOptions(game Game, options *RunGameOptions) error { defer atomic.StoreInt32(&isRunGameEnded_, 1) initializeWindowPositionIfNeeded(WindowSize()) - g := newGameForUI(game) - if err := ui.Get().Run(g, toUIRunOptions(options)); err != nil { + + op := toUIRunOptions(options) + // This is necessary to change the result of IsScreenTransparent. + if op.ScreenTransparent { + atomic.StoreInt32(&screenTransparent, 1) + } else { + atomic.StoreInt32(&screenTransparent, 0) + } + g := newGameForUI(game, op.ScreenTransparent) + + if err := ui.Get().Run(g, op); err != nil { if errors.Is(err, Termination) { return nil } @@ -582,8 +597,13 @@ func SetMaxTPS(tps int) { // IsScreenTransparent reports whether the window is transparent. // // IsScreenTransparent is concurrent-safe. +// +// Deprecated: as of v2.5. func IsScreenTransparent() bool { - return ui.Get().IsScreenTransparent() + if !ui.IsScreenTransparentAvailable() { + return false + } + return atomic.LoadInt32(&screenTransparent) != 0 } // SetScreenTransparent sets the state if the window is transparent. @@ -593,10 +613,18 @@ func IsScreenTransparent() bool { // SetScreenTransparent does nothing on mobiles. // // SetScreenTransparent is concurrent-safe. +// +// Deprecated: as of v2.5. Use RunGameWithOptions instead. func SetScreenTransparent(transparent bool) { - ui.Get().SetScreenTransparent(transparent) + if transparent { + atomic.StoreInt32(&screenTransparent, 1) + } else { + atomic.StoreInt32(&screenTransparent, 0) + } } +var screenTransparent int32 = 0 + // SetInitFocused sets whether the application is focused on show. // The default value is true, i.e., the application is focused. // @@ -620,11 +648,13 @@ var initUnfocused int32 = 0 func toUIRunOptions(options *RunGameOptions) *ui.RunOptions { if options == nil { return &ui.RunOptions{ - InitUnfocused: atomic.LoadInt32(&initUnfocused) != 0, + InitUnfocused: atomic.LoadInt32(&initUnfocused) != 0, + ScreenTransparent: atomic.LoadInt32(&screenTransparent) != 0, } } return &ui.RunOptions{ - GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary), - InitUnfocused: options.InitUnfocused, + GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary), + InitUnfocused: options.InitUnfocused, + ScreenTransparent: options.ScreenTransparent, } } diff --git a/run_mobile.go b/run_mobile.go index 6bed87d6e..fd3e5745f 100644 --- a/run_mobile.go +++ b/run_mobile.go @@ -28,5 +28,6 @@ import ( // // TODO: Remove this. In order to remove this, the gameForUI should be in another package. func RunGameWithoutMainLoop(game Game, options *RunGameOptions) { - ui.RunWithoutMainLoop(newGameForUI(game), toUIRunOptions(options)) + op := toUIRunOptions(options) + ui.RunWithoutMainLoop(newGameForUI(game, op.ScreenTransparent), op) }