mobile/ebitenmobileview: Use the common uiContext for layouting

This means that the whole offscreen is cleared correctly.

This change is a little breaking change: SetScreenSize or other
functions no longer works on ebitenmobile. Use Layout instead.

Fixes #1019
This commit is contained in:
Hajime Hoshi 2020-02-11 17:53:50 +09:00
parent 1b4c9f4e4d
commit c927d33457
10 changed files with 50 additions and 127 deletions

View File

@ -220,16 +220,7 @@ const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
[super viewDidLayoutSubviews]; [super viewDidLayoutSubviews];
CGRect viewRect = [[self view] frame]; CGRect viewRect = [[self view] frame];
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height, (id<EbitenmobileviewViewRectSetter>)self); EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
}
- (void)setViewRect:(long)x y:(long)y width:(long)width height:(long)height {
CGRect viewRect = CGRectMake(x, y, width, height);
#if EBITEN_METAL
[[self metalView] setFrame:viewRect];
#else
[[self glkView] setFrame:viewRect];
#endif
} }
- (void)didReceiveMemoryWarning { - (void)didReceiveMemoryWarning {
@ -341,7 +332,6 @@ import android.util.Log;
import android.view.ViewGroup; import android.view.ViewGroup;
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview; import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
import {{.JavaPkg}}.ebitenmobileview.ViewRectSetter;
public class EbitenView extends ViewGroup { public class EbitenView extends ViewGroup {
private double getDeviceScale() { private double getDeviceScale() {
@ -373,30 +363,16 @@ public class EbitenView extends ViewGroup {
private void initialize() { private void initialize() {
ebitenSurfaceView_ = new EbitenSurfaceView(getContext()); ebitenSurfaceView_ = new EbitenSurfaceView(getContext());
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(ebitenSurfaceView_, params); addView(ebitenSurfaceView_, params);
} }
@Override @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int widthInDp = (int)Math.ceil(pxToDp(right - left)); ebitenSurfaceView_.layout(0, 0, right - left, bottom - top);
int heightInDp = (int)Math.ceil(pxToDp(bottom - top)); double widthInDp = pxToDp(right - left);
Ebitenmobileview.layout(widthInDp, heightInDp, new ViewRectSetter() { double heightInDp = pxToDp(bottom - top);
@Override Ebitenmobileview.layout(widthInDp, heightInDp);
public void setViewRect(long xInDp, long yInDp, long widthInDp, long heightInDp) {
// Use Math.floor to use smaller and safer values, or glitches can appear (#956).
final int widthInPx = (int)Math.floor(dpToPx(widthInDp));
final int heightInPx = (int)Math.floor(dpToPx(heightInDp));
final int xInPx = (int)Math.floor(dpToPx(xInDp));
final int yInPx = (int)Math.floor(dpToPx(yInDp));
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
ebitenSurfaceView_.layout(xInPx, yInPx, xInPx + widthInPx, yInPx + heightInPx);
}
});
}
});
} }
// suspendGame suspends the game. // suspendGame suspends the game.

File diff suppressed because one or more lines are too long

View File

@ -32,7 +32,7 @@ var RegularTermination = errors.New("regular termination")
type UI interface { type UI interface {
Run(context UIContext) error Run(context UIContext) error
RunWithoutMainLoop(width, height int, scale float64, title string, context UIContext) <-chan error RunWithoutMainLoop(context UIContext) <-chan error
DeviceScaleFactor() float64 DeviceScaleFactor() float64
CursorMode() CursorMode CursorMode() CursorMode

View File

@ -543,7 +543,7 @@ func (u *UserInterface) Run(uicontext driver.UIContext) error {
return <-ch return <-ch
} }
func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, title string, context driver.UIContext) <-chan error { func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) <-chan error {
panic("glfw: RunWithoutMainLoop is not implemented") panic("glfw: RunWithoutMainLoop is not implemented")
} }

View File

@ -425,7 +425,7 @@ func (u *UserInterface) Run(context driver.UIContext) error {
return nil return nil
} }
func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, title string, context driver.UIContext) <-chan error { func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) <-chan error {
panic("js: RunWithoutMainLoop is not implemented") panic("js: RunWithoutMainLoop is not implemented")
} }

View File

@ -102,10 +102,8 @@ func (u *UserInterface) Update() {
} }
type UserInterface struct { type UserInterface struct {
// TODO: Remove these members: the driver layer should not care about the game screen size. outsideWidth float64
width int outsideHeight float64
height int
scale float64
sizeChanged bool sizeChanged bool
foreground bool foreground bool
@ -207,11 +205,9 @@ func (u *UserInterface) SetForeground(foreground bool) {
} }
func (u *UserInterface) Run(context driver.UIContext) error { func (u *UserInterface) Run(context driver.UIContext) error {
// TODO: Remove width/height/scale arguments. They are not used from gomobile-build.
u.setGBuildSizeCh = make(chan struct{}) u.setGBuildSizeCh = make(chan struct{})
go func() { go func() {
if err := u.run(16, 16, 1, context, true); err != nil { if err := u.run(context, true); err != nil {
// As mobile apps never ends, Loop can't return. Just panic here. // As mobile apps never ends, Loop can't return. Just panic here.
panic(err) panic(err)
} }
@ -220,12 +216,12 @@ func (u *UserInterface) Run(context driver.UIContext) error {
return nil return nil
} }
func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, title string, context driver.UIContext) <-chan error { func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) <-chan error {
ch := make(chan error) ch := make(chan error)
go func() { go func() {
defer close(ch) defer close(ch)
// title is ignored? // title is ignored?
if err := u.run(width, height, scale, context, false); err != nil { if err := u.run(context, false); err != nil {
ch <- err ch <- err
} }
}() }()
@ -233,7 +229,7 @@ func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, tit
return ch return ch
} }
func (u *UserInterface) run(width, height int, scale float64, context driver.UIContext, mainloop bool) (err error) { func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for // Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer // Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
// unfortunately. // unfortunately.
@ -245,9 +241,6 @@ func (u *UserInterface) run(width, height int, scale float64, context driver.UIC
}() }()
u.m.Lock() u.m.Lock()
u.width = width
u.height = height
u.scale = scale
u.sizeChanged = true u.sizeChanged = true
u.context = context u.context = context
u.m.Unlock() u.m.Unlock()
@ -280,36 +273,26 @@ func (u *UserInterface) run(width, height int, scale float64, context driver.UIC
} }
func (u *UserInterface) updateSize(context driver.UIContext) { func (u *UserInterface) updateSize(context driver.UIContext) {
var width, height float64 var outsideWidth, outsideHeight float64
u.m.Lock() u.m.Lock()
sizeChanged := u.sizeChanged sizeChanged := u.sizeChanged
if sizeChanged { if sizeChanged {
if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 { if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
s := u.scale outsideWidth = u.outsideWidth
width = float64(u.width) * s outsideHeight = u.outsideHeight
height = float64(u.height) * s
} else { } else {
// gomobile build // gomobile build
d := deviceScale() d := deviceScale()
width = float64(u.gbuildWidthPx) / d outsideWidth = float64(u.gbuildWidthPx) / d
height = float64(u.gbuildHeightPx) / d outsideHeight = float64(u.gbuildHeightPx) / d
} }
} }
u.sizeChanged = false u.sizeChanged = false
u.m.Unlock() u.m.Unlock()
if sizeChanged { if sizeChanged {
// Dirty hack to set the offscreen size for gomobile-bind. context.Layout(outsideWidth, outsideHeight)
// TODO: Remove this. The layouting logic must be in the package ebiten, not here.
if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
context.(interface {
SetScreenSize(width, height int)
}).SetScreenSize(u.width, u.height)
}
// Sizing also calls GL functions
context.Layout(width, height)
} }
} }
@ -333,14 +316,13 @@ func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
return 0, 0 return 0, 0
} }
// SetScreenSizeAndScale is called from mobile/ebitenmobileview. // SetOutsideSize is called from mobile/ebitenmobileview.
func (u *UserInterface) SetScreenSizeAndScale(width, height int, scale float64) { func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
// Called from ebitenmobileview. // Called from ebitenmobileview.
u.m.Lock() u.m.Lock()
if u.width != width || u.height != height || u.scale != scale { if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
u.width = width u.outsideWidth = outsideWidth
u.height = height u.outsideHeight = outsideHeight
u.scale = scale
u.sizeChanged = true u.sizeChanged = true
} }
u.m.Unlock() u.m.Unlock()
@ -358,11 +340,6 @@ func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
} }
func (u *UserInterface) adjustPosition(x, y int) (int, int) { func (u *UserInterface) adjustPosition(x, y int) (int, int) {
// This function's caller already protects this function by the mutex.
if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
s := u.scale
return int(float64(x) / s), int(float64(y) / s)
}
xf, yf := u.context.AdjustPosition(float64(x), float64(y)) xf, yf := u.context.AdjustPosition(float64(x), float64(y))
return int(xf), int(yf) return int(xf), int(yf)
} }

View File

@ -17,8 +17,6 @@
package mobile package mobile
import ( import (
"math"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/mobile/ebitenmobileview" "github.com/hajimehoshi/ebiten/mobile/ebitenmobileview"
) )
@ -57,9 +55,9 @@ func Start(f func(*ebiten.Image) error, width, height int, scale float64, title
height: height, height: height,
}) })
// As the view layout is already determined, ignore the layout calculation at ebitenmobileview. // As the view layout is already determined, ignore the layout calculation at ebitenmobileview.
w := int(math.Ceil((float64(width) * scale))) w := float64(width) * scale
h := int(math.Ceil((float64(height) * scale))) h := float64(height) * scale
ebitenmobileview.Layout(w, h, nil) ebitenmobileview.Layout(w, h)
return nil return nil
} }

View File

@ -26,54 +26,30 @@ package ebitenmobileview
import "C" import "C"
import ( import (
"math"
"runtime" "runtime"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/internal/uidriver/mobile" "github.com/hajimehoshi/ebiten/internal/uidriver/mobile"
) )
type ViewRectSetter interface { func Layout(viewWidth, viewHeight float64) {
SetViewRect(x, y, width, height int)
}
func Layout(viewWidth, viewHeight int, viewRectSetter ViewRectSetter) {
theState.m.Lock() theState.m.Lock()
defer theState.m.Unlock() defer theState.m.Unlock()
layout(viewWidth, viewHeight, viewRectSetter) layout(viewWidth, viewHeight)
} }
func layout(viewWidth, viewHeight int, viewRectSetter ViewRectSetter) { func layout(viewWidth, viewHeight float64) {
if theState.game == nil { if theState.game == nil {
// It is fine to override the existing function since only the last layout result matters. // It is fine to override the existing function since only the last layout result matters.
theState.delayedLayout = func() { theState.delayedLayout = func() {
layout(viewWidth, viewHeight, viewRectSetter) layout(viewWidth, viewHeight)
} }
return return
} }
// TODO: Layout must be called every frame like uiContext already did. mobile.Get().SetOutsideSize(viewWidth, viewHeight)
w, h := theState.game.Layout(int(viewWidth), int(viewHeight)) if !theState.isRunning() {
scaleX := float64(viewWidth) / float64(w) theState.errorCh = ebiten.RunGameWithoutMainLoop(theState.game)
scaleY := float64(viewHeight) / float64(h)
scale := math.Min(scaleX, scaleY)
// To convert a logical offscreen size to the actual screen size, use Math.floor to use smaller and safer
// values, or glitches can appear (#956).
width := int(math.Floor(float64(w) * scale))
height := int(math.Floor(float64(h) * scale))
x := (viewWidth - width) / 2
y := (viewHeight - height) / 2
if theState.isRunning() {
mobile.Get().SetScreenSizeAndScale(w, h, scale)
} else {
// The last argument 'title' is not used on mobile platforms, so just pass an empty string.
theState.errorCh = ebiten.RunWithoutMainLoop(theState.game.Update, w, h, scale, "")
}
if viewRectSetter != nil {
viewRectSetter.SetViewRect(x, y, width, height)
} }
} }

21
run.go
View File

@ -238,19 +238,16 @@ func runGame(game Game, scale float64) error {
return nil return nil
} }
// RunWithoutMainLoop runs the game, but don't call the loop on the main (UI) thread. // RunGameWithoutMainLoop runs the game, but don't call the loop on the main (UI) thread.
// Different from Run, RunWithoutMainLoop returns immediately. // Different from Run, RunGameWithoutMainLoop returns immediately.
// //
// Ebiten users should NOT call RunWithoutMainLoop. // Ebiten users should NOT call RunGameWithoutMainLoop.
// Instead, functions in github.com/hajimehoshi/ebiten/mobile package calls this. // Instead, functions in github.com/hajimehoshi/ebiten/mobile package calls this.
func RunWithoutMainLoop(f func(*Image) error, width, height int, scale float64, title string) <-chan error { func RunGameWithoutMainLoop(game Game) <-chan error {
game := &defaultGame{ game = &imageDumperGame{game: game}
update: (&imageDumper{f: f}).update, fixWindowPosition(WindowSize())
width: width, theUIContext.set(game, 0)
height: height, return uiDriver().RunWithoutMainLoop(theUIContext)
}
theUIContext.set(game, scale)
return uiDriver().RunWithoutMainLoop(width, height, scale, title, theUIContext)
} }
// ScreenSizeInFullscreen is deprecated as of 1.11.0-alpha. // ScreenSizeInFullscreen is deprecated as of 1.11.0-alpha.
@ -270,7 +267,7 @@ func SetScreenSize(width, height int) {
if width <= 0 || height <= 0 { if width <= 0 || height <= 0 {
panic("ebiten: width and height must be positive") panic("ebiten: width and height must be positive")
} }
theUIContext.SetScreenSize(width, height) theUIContext.setScreenSize(width, height)
} }
// SetScreenScale is deprecated as of 1.11.0-alpha. Use SetWindowSize instead. // SetScreenScale is deprecated as of 1.11.0-alpha. Use SetWindowSize instead.

View File

@ -118,11 +118,10 @@ func (c *uiContext) getScaleForWindow() float64 {
return s return s
} }
// SetScreenSize sets the (logical) screen size and adjusts the window size. // setScreenSize sets the (logical) screen size and adjusts the window size.
// //
// SetScreenSize is for backward compatibility. This is called from ebiten.SetScreenSize and // setScreenSize is for backward compatibility. This is called from ebiten.SetScreenSize.
// uidriver/mobile.UserInterface. func (c *uiContext) setScreenSize(width, height int) {
func (c *uiContext) SetScreenSize(width, height int) {
c.m.Lock() c.m.Lock()
defer c.m.Unlock() defer c.m.Unlock()
@ -179,7 +178,7 @@ func (c *uiContext) updateOffscreen() {
// The window size is automatically adjusted when Run is used. // The window size is automatically adjusted when Run is used.
if _, ok := c.game.(*defaultGame); ok { if _, ok := c.game.(*defaultGame); ok {
c.SetScreenSize(sw, sh) c.setScreenSize(sw, sh)
} }
// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this. // TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.