From 0ec447e0d0e78ad37ab357446a632825d2c67b66 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sun, 1 Dec 2019 00:07:41 +0900 Subject: [PATCH] ui: Add SetScreenTransparent / IsScreenTransparent Fixes #1001 --- examples/windowsize/main.go | 9 +++- internal/driver/graphics.go | 1 + internal/driver/ui.go | 2 + internal/glfw/const.go | 15 +++--- internal/graphicsdriver/metal/ca/ca.go | 9 ++++ internal/graphicsdriver/metal/ca/ca.h | 2 + internal/graphicsdriver/metal/ca/ca.m | 4 ++ internal/graphicsdriver/metal/driver.go | 16 ++++-- internal/graphicsdriver/opengl/driver.go | 4 ++ internal/uidriver/glfw/ui.go | 62 +++++++++++++++++++----- internal/uidriver/js/ui.go | 29 +++++++++++ internal/uidriver/mobile/ui.go | 8 +++ run.go | 14 ++++++ 13 files changed, 150 insertions(+), 25 deletions(-) diff --git a/examples/windowsize/main.go b/examples/windowsize/main.go index 5dde17669..7391cd7df 100644 --- a/examples/windowsize/main.go +++ b/examples/windowsize/main.go @@ -37,7 +37,8 @@ import ( ) var ( - flagWindowPosition = flag.String("windowposition", "", "window position (e.g., 100,200)") + flagWindowPosition = flag.String("windowposition", "", "window position (e.g., 100,200)") + flagScreenTransparent = flag.Bool("screentransparent", false, "screen transparent") ) func init() { @@ -86,6 +87,7 @@ func update(screen *ebiten.Image) error { tps := ebiten.MaxTPS() decorated := ebiten.IsWindowDecorated() positionX, positionY := ebiten.WindowPosition() + transparent := ebiten.IsScreenTransparent() if ebiten.IsKeyPressed(ebiten.KeyShift) { if inpututil.IsKeyJustPressed(ebiten.KeyUp) { @@ -182,7 +184,9 @@ func update(screen *ebiten.Image) error { return nil } - screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff}) + if !transparent { + screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff}) + } w, h := gophersImage.Size() w2, h2 := screen.Size() op := &ebiten.DrawImageOptions{} @@ -261,6 +265,7 @@ func main() { if x, y, ok := parseWindowPosition(); ok { ebiten.SetWindowPosition(x, y) } + ebiten.SetScreenTransparent(*flagScreenTransparent) if err := ebiten.Run(update, initScreenWidth, initScreenHeight, initScreenScale, "Window Size (Ebiten Demo)"); err != nil { log.Fatal(err) diff --git a/internal/driver/graphics.go b/internal/driver/graphics.go index 378aff504..dd7288e86 100644 --- a/internal/driver/graphics.go +++ b/internal/driver/graphics.go @@ -26,6 +26,7 @@ type Graphics interface { Begin() End() SetWindow(window unsafe.Pointer) + SetTransparent(transparent bool) SetVertices(vertices []float32, indices []uint16) Flush() NewImage(width, height int) (Image, error) diff --git a/internal/driver/ui.go b/internal/driver/ui.go index 56fcdbb50..351078390 100644 --- a/internal/driver/ui.go +++ b/internal/driver/ui.go @@ -46,6 +46,7 @@ type UI interface { ScreenScale() float64 ScreenSizeInFullscreen() (int, int) WindowPosition() (int, int) + IsScreenTransparent() bool SetCursorVisible(visible bool) SetFullscreen(fullscreen bool) @@ -58,6 +59,7 @@ type UI interface { SetWindowResizable(resizable bool) SetWindowTitle(title string) SetWindowPosition(x, y int) + SetScreenTransparent(transparent bool) Input() Input } diff --git a/internal/glfw/const.go b/internal/glfw/const.go index 5e004b4a5..8640b112b 100644 --- a/internal/glfw/const.go +++ b/internal/glfw/const.go @@ -73,13 +73,14 @@ const ( ) const ( - ClientAPI = Hint(0x00022001) - ContextVersionMajor = Hint(0x00022002) - ContextVersionMinor = Hint(0x00022003) - Decorated = Hint(0x00020005) - Focused = Hint(0x00020001) - Resizable = Hint(0x00020003) - Visible = Hint(0x00020004) + ClientAPI = Hint(0x00022001) + ContextVersionMajor = Hint(0x00022002) + ContextVersionMinor = Hint(0x00022003) + Decorated = Hint(0x00020005) + Focused = Hint(0x00020001) + Resizable = Hint(0x00020003) + TransparentFramebuffer = Hint(0x0002000A) + Visible = Hint(0x00020004) ) const ( diff --git a/internal/graphicsdriver/metal/ca/ca.go b/internal/graphicsdriver/metal/ca/ca.go index 7aeb06c13..2e94fe29d 100644 --- a/internal/graphicsdriver/metal/ca/ca.go +++ b/internal/graphicsdriver/metal/ca/ca.go @@ -78,6 +78,15 @@ func (ml MetalLayer) SetDevice(device mtl.Device) { C.MetalLayer_SetDevice(ml.metalLayer, device.Device()) } +// SetOpaque a Boolean value indicating whether the layer contains completely opaque content. +func (ml MetalLayer) SetOpaque(opaque bool) { + if opaque { + C.MetalLayer_SetOpaque(ml.metalLayer, 1) + } else { + C.MetalLayer_SetOpaque(ml.metalLayer, 0) + } +} + // SetPixelFormat controls the pixel format of textures for rendering layer content. // // The pixel format for a Metal layer must be PixelFormatBGRA8UNorm, PixelFormatBGRA8UNormSRGB, diff --git a/internal/graphicsdriver/metal/ca/ca.h b/internal/graphicsdriver/metal/ca/ca.h index 5ce003069..d470eed34 100644 --- a/internal/graphicsdriver/metal/ca/ca.h +++ b/internal/graphicsdriver/metal/ca/ca.h @@ -16,12 +16,14 @@ #include +typedef signed char BOOL; typedef unsigned long uint_t; void *MakeMetalLayer(); uint16_t MetalLayer_PixelFormat(void *metalLayer); void MetalLayer_SetDevice(void *metalLayer, void *device); +void MetalLayer_SetOpaque(void *metalLayer, BOOL opaque); const char *MetalLayer_SetPixelFormat(void *metalLayer, uint16_t pixelFormat); const char *MetalLayer_SetMaximumDrawableCount(void *metalLayer, uint_t maximumDrawableCount); diff --git a/internal/graphicsdriver/metal/ca/ca.m b/internal/graphicsdriver/metal/ca/ca.m index 39f04313d..3ce535d58 100644 --- a/internal/graphicsdriver/metal/ca/ca.m +++ b/internal/graphicsdriver/metal/ca/ca.m @@ -38,6 +38,10 @@ void MetalLayer_SetDevice(void *metalLayer, void *device) { ((CAMetalLayer *)metalLayer).device = (id)device; } +void MetalLayer_SetOpaque(void *metalLayer, BOOL opaque) { + ((CAMetalLayer *)metalLayer).opaque = opaque; +} + const char *MetalLayer_SetPixelFormat(void *metalLayer, uint16_t pixelFormat) { @try { ((CAMetalLayer *)metalLayer).pixelFormat = (MTLPixelFormat)pixelFormat; diff --git a/internal/graphicsdriver/metal/driver.go b/internal/graphicsdriver/metal/driver.go index 8cfef67b4..11553f791 100644 --- a/internal/graphicsdriver/metal/driver.go +++ b/internal/graphicsdriver/metal/driver.go @@ -293,15 +293,14 @@ type Driver struct { screenDrawable ca.MetalDrawable - vb mtl.Buffer - ib mtl.Buffer - + vb mtl.Buffer + ib mtl.Buffer src *Image dst *Image - drawCalled bool - + transparent bool maxImageSize int + drawCalled bool t *thread.Thread @@ -440,6 +439,10 @@ func (d *Driver) NewScreenFramebufferImage(width, height int) (driver.Image, err }, nil } +func (d *Driver) SetTransparent(transparent bool) { + d.transparent = transparent +} + func (d *Driver) Reset() error { if err := d.t.Call(func() error { if d.cq != (mtl.CommandQueue{}) { @@ -455,6 +458,9 @@ func (d *Driver) Reset() error { if err := d.view.reset(); err != nil { return err } + if d.transparent { + d.view.ml.SetOpaque(false) + } replaces := map[string]string{ "{{.FilterNearest}}": fmt.Sprintf("%d", driver.FilterNearest), diff --git a/internal/graphicsdriver/opengl/driver.go b/internal/graphicsdriver/opengl/driver.go index b49f4a3b7..c80421e0b 100644 --- a/internal/graphicsdriver/opengl/driver.go +++ b/internal/graphicsdriver/opengl/driver.go @@ -54,6 +54,10 @@ func (d *Driver) SetWindow(window unsafe.Pointer) { // Do nothing. } +func (d *Driver) SetTransparent(transparent bool) { + // Do nothings. +} + func (d *Driver) checkSize(width, height int) { if width < 1 { panic(fmt.Sprintf("opengl: width (%d) must be equal or more than %d", width, 1)) diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 28d1b4a00..4b0929016 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -53,16 +53,17 @@ type UserInterface struct { lastActualScale float64 - initMonitor *glfw.Monitor - initFullscreenWidth int - initFullscreenHeight int - initFullscreen bool - initCursorVisible bool - initWindowDecorated bool - initWindowResizable bool - initWindowPositionX int - initWindowPositionY int - initIconImages []image.Image + initMonitor *glfw.Monitor + initFullscreenWidth int + initFullscreenHeight int + initFullscreen bool + initCursorVisible bool + initWindowDecorated bool + initWindowResizable bool + initWindowPositionX int + initWindowPositionY int + initScreenTransparent bool + initIconImages []image.Image reqWidth int reqHeight int @@ -260,6 +261,19 @@ func (u *UserInterface) setInitWindowResizable(resizable bool) { u.m.Unlock() } +func (u *UserInterface) isInitScreenTransparent() bool { + u.m.RLock() + v := u.initScreenTransparent + u.m.RUnlock() + return v +} + +func (u *UserInterface) setInitScreenTransparent(transparent bool) { + u.m.RLock() + u.initScreenTransparent = transparent + u.m.RUnlock() +} + func (u *UserInterface) getInitIconImages() []image.Image { u.m.RLock() i := u.initIconImages @@ -617,13 +631,19 @@ func (u *UserInterface) run(width, height int, scale float64, title string, cont glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI) } - // 'decorated' must be solved before creating a window (#556). decorated := glfw.False if u.isInitWindowDecorated() { decorated = glfw.True } glfw.WindowHint(glfw.Decorated, decorated) + transparent := glfw.False + if u.isInitScreenTransparent() { + transparent = glfw.True + } + glfw.WindowHint(glfw.TransparentFramebuffer, transparent) + u.graphics.SetTransparent(u.isInitScreenTransparent()) + resizable := glfw.False if u.isInitWindowResizable() { resizable = glfw.True @@ -1050,6 +1070,26 @@ func (u *UserInterface) WindowPosition() (int, int) { return x, y } +func (u *UserInterface) SetScreenTransparent(transparent bool) { + if !u.isRunning() { + u.setInitScreenTransparent(transparent) + return + } + panic("ui: SetScreenTransparent can't be called after Run.") +} + +func (u *UserInterface) IsScreenTransparent() bool { + if !u.isRunning() { + return u.isInitScreenTransparent() + } + val := false + _ = u.t.Call(func() error { + val = u.window.GetAttrib(glfw.TransparentFramebuffer) == glfw.True + return nil + }) + return val +} + func (u *UserInterface) Input() driver.Input { return &u.input } diff --git a/internal/uidriver/js/ui.go b/internal/uidriver/js/ui.go index 7c81b38ef..6c3b5ee8d 100644 --- a/internal/uidriver/js/ui.go +++ b/internal/uidriver/js/ui.go @@ -34,6 +34,7 @@ type UserInterface struct { scale float64 runnableInBackground bool vsync bool + running bool sizeChanged bool contextLost bool @@ -156,6 +157,9 @@ func (u *UserInterface) IsWindowResizable() bool { func (u *UserInterface) SetWindowResizable(decorated bool) { // Do nothing + if u.running { + panic("js: SetWindowResizable can't be called after the main loop starts") + } } func (u *UserInterface) DeviceScaleFactor() float64 { @@ -426,6 +430,7 @@ func (u *UserInterface) Run(width, height int, scale float64, title string, cont u.setScreenSize(width, height) u.pseudoScale = scale canvas.Call("focus") + u.running = true ch := u.loop(context) if runtime.GOARCH == "wasm" { return <-ch @@ -434,6 +439,9 @@ func (u *UserInterface) Run(width, height int, scale float64, title string, cont // On GopherJS, the main goroutine cannot be blocked due to the bug (gopherjs/gopherjs#826). // Return immediately. go func() { + defer func() { + u.running = false + }() if err := <-ch; err != nil { log.Fatal(err) } @@ -484,9 +492,30 @@ func (u *UserInterface) SetWindowPosition(x, y int) { } func (u *UserInterface) WindowPosition() (int, int) { + if !u.running { + panic("js: WindowPosition can't be called before the main loop starts") + } return 0, 0 } +func (u *UserInterface) SetScreenTransparent(transparent bool) { + if u.running { + panic("js: 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 *UserInterface) IsScreenTransparent() bool { + bodyStyle := document.Get("body").Get("style") + return bodyStyle.Get("backgroundColor").String() == "transparent" +} + func (u *UserInterface) Input() driver.Input { return &u.input } diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index 2ea943675..ddfaa9319 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -451,6 +451,14 @@ func (u *UserInterface) WindowPosition() (int, int) { return 0, 0 } +func (u *UserInterface) SetScreenTransparent(transparent bool) { + // Do nothing +} + +func (u *UserInterface) IsScreenTransparent() bool { + return false +} + func (u *UserInterface) Input() driver.Input { return &u.input } diff --git a/run.go b/run.go index 479411b96..bc98fbbbf 100644 --- a/run.go +++ b/run.go @@ -411,3 +411,17 @@ func SetMaxTPS(tps int) { } atomic.StoreInt32(¤tMaxTPS, int32(tps)) } + +// IsScreenTransparent reports whether the window is transparent. +func IsScreenTransparent() bool { + return uiDriver().IsScreenTransparent() +} + +// SetScreenTransparent sets the state if the window is transparent. +// +// SetScreenTransparent panics if SetScreenTransparent is called after Run. +// +// SetScreenTransparent does nothing on mobiles. +func SetScreenTransparent(transparent bool) { + uiDriver().SetScreenTransparent(transparent) +}