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];
CGRect viewRect = [[self view] frame];
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height, (id<EbitenmobileviewViewRectSetter>)self);
}
- (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
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
}
- (void)didReceiveMemoryWarning {
@ -341,7 +332,6 @@ import android.util.Log;
import android.view.ViewGroup;
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
import {{.JavaPkg}}.ebitenmobileview.ViewRectSetter;
public class EbitenView extends ViewGroup {
private double getDeviceScale() {
@ -373,30 +363,16 @@ public class EbitenView extends ViewGroup {
private void initialize() {
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);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int widthInDp = (int)Math.ceil(pxToDp(right - left));
int heightInDp = (int)Math.ceil(pxToDp(bottom - top));
Ebitenmobileview.layout(widthInDp, heightInDp, new ViewRectSetter() {
@Override
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);
}
});
}
});
ebitenSurfaceView_.layout(0, 0, right - left, bottom - top);
double widthInDp = pxToDp(right - left);
double heightInDp = pxToDp(bottom - top);
Ebitenmobileview.layout(widthInDp, heightInDp);
}
// 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 {
Run(context UIContext) error
RunWithoutMainLoop(width, height int, scale float64, title string, context UIContext) <-chan error
RunWithoutMainLoop(context UIContext) <-chan error
DeviceScaleFactor() float64
CursorMode() CursorMode

View File

@ -543,7 +543,7 @@ func (u *UserInterface) Run(uicontext driver.UIContext) error {
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")
}

View File

@ -425,7 +425,7 @@ func (u *UserInterface) Run(context driver.UIContext) error {
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")
}

View File

@ -102,10 +102,8 @@ func (u *UserInterface) Update() {
}
type UserInterface struct {
// TODO: Remove these members: the driver layer should not care about the game screen size.
width int
height int
scale float64
outsideWidth float64
outsideHeight float64
sizeChanged bool
foreground bool
@ -207,11 +205,9 @@ func (u *UserInterface) SetForeground(foreground bool) {
}
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{})
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.
panic(err)
}
@ -220,12 +216,12 @@ func (u *UserInterface) Run(context driver.UIContext) error {
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)
go func() {
defer close(ch)
// title is ignored?
if err := u.run(width, height, scale, context, false); err != nil {
if err := u.run(context, false); err != nil {
ch <- err
}
}()
@ -233,7 +229,7 @@ func (u *UserInterface) RunWithoutMainLoop(width, height int, scale float64, tit
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
// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
// unfortunately.
@ -245,9 +241,6 @@ func (u *UserInterface) run(width, height int, scale float64, context driver.UIC
}()
u.m.Lock()
u.width = width
u.height = height
u.scale = scale
u.sizeChanged = true
u.context = context
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) {
var width, height float64
var outsideWidth, outsideHeight float64
u.m.Lock()
sizeChanged := u.sizeChanged
if sizeChanged {
if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
s := u.scale
width = float64(u.width) * s
height = float64(u.height) * s
outsideWidth = u.outsideWidth
outsideHeight = u.outsideHeight
} else {
// gomobile build
d := deviceScale()
width = float64(u.gbuildWidthPx) / d
height = float64(u.gbuildHeightPx) / d
outsideWidth = float64(u.gbuildWidthPx) / d
outsideHeight = float64(u.gbuildHeightPx) / d
}
}
u.sizeChanged = false
u.m.Unlock()
if sizeChanged {
// Dirty hack to set the offscreen size for gomobile-bind.
// 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)
context.Layout(outsideWidth, outsideHeight)
}
}
@ -333,14 +316,13 @@ func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
return 0, 0
}
// SetScreenSizeAndScale is called from mobile/ebitenmobileview.
func (u *UserInterface) SetScreenSizeAndScale(width, height int, scale float64) {
// SetOutsideSize is called from mobile/ebitenmobileview.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
// Called from ebitenmobileview.
u.m.Lock()
if u.width != width || u.height != height || u.scale != scale {
u.width = width
u.height = height
u.scale = scale
if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
u.outsideWidth = outsideWidth
u.outsideHeight = outsideHeight
u.sizeChanged = true
}
u.m.Unlock()
@ -358,11 +340,6 @@ func (u *UserInterface) setGBuildSize(widthPx, heightPx 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))
return int(xf), int(yf)
}

View File

@ -17,8 +17,6 @@
package mobile
import (
"math"
"github.com/hajimehoshi/ebiten"
"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,
})
// As the view layout is already determined, ignore the layout calculation at ebitenmobileview.
w := int(math.Ceil((float64(width) * scale)))
h := int(math.Ceil((float64(height) * scale)))
ebitenmobileview.Layout(w, h, nil)
w := float64(width) * scale
h := float64(height) * scale
ebitenmobileview.Layout(w, h)
return nil
}

View File

@ -26,54 +26,30 @@ package ebitenmobileview
import "C"
import (
"math"
"runtime"
"github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/internal/uidriver/mobile"
)
type ViewRectSetter interface {
SetViewRect(x, y, width, height int)
}
func Layout(viewWidth, viewHeight int, viewRectSetter ViewRectSetter) {
func Layout(viewWidth, viewHeight float64) {
theState.m.Lock()
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 {
// It is fine to override the existing function since only the last layout result matters.
theState.delayedLayout = func() {
layout(viewWidth, viewHeight, viewRectSetter)
layout(viewWidth, viewHeight)
}
return
}
// TODO: Layout must be called every frame like uiContext already did.
w, h := theState.game.Layout(int(viewWidth), int(viewHeight))
scaleX := float64(viewWidth) / float64(w)
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)
mobile.Get().SetOutsideSize(viewWidth, viewHeight)
if !theState.isRunning() {
theState.errorCh = ebiten.RunGameWithoutMainLoop(theState.game)
}
}

21
run.go
View File

@ -238,19 +238,16 @@ func runGame(game Game, scale float64) error {
return nil
}
// RunWithoutMainLoop runs the game, but don't call the loop on the main (UI) thread.
// Different from Run, RunWithoutMainLoop returns immediately.
// RunGameWithoutMainLoop runs the game, but don't call the loop on the main (UI) thread.
// 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.
func RunWithoutMainLoop(f func(*Image) error, width, height int, scale float64, title string) <-chan error {
game := &defaultGame{
update: (&imageDumper{f: f}).update,
width: width,
height: height,
}
theUIContext.set(game, scale)
return uiDriver().RunWithoutMainLoop(width, height, scale, title, theUIContext)
func RunGameWithoutMainLoop(game Game) <-chan error {
game = &imageDumperGame{game: game}
fixWindowPosition(WindowSize())
theUIContext.set(game, 0)
return uiDriver().RunWithoutMainLoop(theUIContext)
}
// ScreenSizeInFullscreen is deprecated as of 1.11.0-alpha.
@ -270,7 +267,7 @@ func SetScreenSize(width, height int) {
if width <= 0 || height <= 0 {
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.

View File

@ -118,11 +118,10 @@ func (c *uiContext) getScaleForWindow() float64 {
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
// uidriver/mobile.UserInterface.
func (c *uiContext) SetScreenSize(width, height int) {
// setScreenSize is for backward compatibility. This is called from ebiten.SetScreenSize.
func (c *uiContext) setScreenSize(width, height int) {
c.m.Lock()
defer c.m.Unlock()
@ -179,7 +178,7 @@ func (c *uiContext) updateOffscreen() {
// The window size is automatically adjusted when Run is used.
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.