mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
7d56e4335e
This change introduces the new APIs RunGame, WindowSize and SetWindowSize. These new APIs hides the notion of 'scale', and is more flexible with the outside size change. This means that we can introduce a resizable window. This change also adds -legacy flag to examples/windowsize. If the flag is off, the new APIs are used. This change deprecates these functions since the notion of 'scale' is deprecated: * ScreenScale * ScreenSizeInFullscreen * SetScreenScale * SetScreenSize Fixes #943, #571 Updates #320
268 lines
6.8 KiB
Go
268 lines
6.8 KiB
Go
// Copyright 2014 Hajime Hoshi
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package ebiten
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/hajimehoshi/ebiten/internal/buffered"
|
|
"github.com/hajimehoshi/ebiten/internal/clock"
|
|
"github.com/hajimehoshi/ebiten/internal/driver"
|
|
"github.com/hajimehoshi/ebiten/internal/graphicscommand"
|
|
"github.com/hajimehoshi/ebiten/internal/hooks"
|
|
"github.com/hajimehoshi/ebiten/internal/shareable"
|
|
)
|
|
|
|
func init() {
|
|
shareable.SetGraphicsDriver(graphicsDriver())
|
|
graphicscommand.SetGraphicsDriver(graphicsDriver())
|
|
}
|
|
|
|
type defaultGame struct {
|
|
update func(screen *Image) error
|
|
width int
|
|
height int
|
|
context *uiContext
|
|
}
|
|
|
|
func (d *defaultGame) Update(screen *Image) error {
|
|
return d.update(screen)
|
|
}
|
|
|
|
func (d *defaultGame) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
|
|
// Ignore the outside size.
|
|
d.context.m.Lock()
|
|
w, h := d.width, d.height
|
|
d.context.m.Unlock()
|
|
return w, h
|
|
}
|
|
|
|
func newUIContext(game Game, scaleForWindow float64) *uiContext {
|
|
u := &uiContext{
|
|
game: game,
|
|
scaleForWindow: scaleForWindow,
|
|
}
|
|
if g, ok := game.(*defaultGame); ok {
|
|
g.context = u
|
|
}
|
|
return u
|
|
}
|
|
|
|
type uiContext struct {
|
|
game Game
|
|
offscreen *Image
|
|
screen *Image
|
|
|
|
// scaleForWindow is the scale of a window. This doesn't represent the scale on fullscreen. This value works
|
|
// only on desktops.
|
|
//
|
|
// scaleForWindow is for backward compatibility and is used to calculate the window size when SetScreenSize
|
|
// is called.
|
|
scaleForWindow float64
|
|
|
|
outsideSizeUpdated bool
|
|
outsideWidth float64
|
|
outsideHeight float64
|
|
|
|
m sync.Mutex
|
|
}
|
|
|
|
var theUIContext *uiContext
|
|
|
|
func (c *uiContext) setScaleForWindow(scale float64) {
|
|
g, ok := c.game.(*defaultGame)
|
|
if !ok {
|
|
panic("ebiten: setScaleForWindow can be called only when Run is used")
|
|
}
|
|
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
|
|
w, h := g.width, g.height
|
|
c.scaleForWindow = scale
|
|
uiDriver().SetWindowSize(int(float64(w)*scale), int(float64(h)*scale))
|
|
}
|
|
|
|
func (c *uiContext) getScaleForWindow() float64 {
|
|
if _, ok := c.game.(*defaultGame); !ok {
|
|
panic("ebiten: getScaleForWindow can be called only when Run is used")
|
|
}
|
|
|
|
c.m.Lock()
|
|
s := c.scaleForWindow
|
|
c.m.Unlock()
|
|
return s
|
|
}
|
|
|
|
// 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) {
|
|
g, ok := c.game.(*defaultGame)
|
|
if !ok {
|
|
panic("ebiten: SetScreenSize can be called only when Run is used")
|
|
}
|
|
|
|
c.m.Lock()
|
|
defer c.m.Unlock()
|
|
|
|
g.width = width
|
|
g.height = height
|
|
s := c.scaleForWindow
|
|
uiDriver().SetWindowSize(int(float64(width)*s), int(float64(height)*s))
|
|
}
|
|
|
|
func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
|
|
c.outsideSizeUpdated = true
|
|
c.outsideWidth = outsideWidth
|
|
c.outsideHeight = outsideHeight
|
|
}
|
|
|
|
func (c *uiContext) updateOffscreen() {
|
|
sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
|
|
|
|
if c.offscreen != nil && !c.outsideSizeUpdated {
|
|
if w, h := c.offscreen.Size(); w == sw && h == sh {
|
|
return
|
|
}
|
|
}
|
|
c.outsideSizeUpdated = false
|
|
|
|
if c.screen != nil {
|
|
_ = c.screen.Dispose()
|
|
c.screen = nil
|
|
}
|
|
|
|
if c.offscreen != nil {
|
|
if w, h := c.offscreen.Size(); w != sw || h != sh {
|
|
_ = c.offscreen.Dispose()
|
|
c.offscreen = nil
|
|
}
|
|
}
|
|
if c.offscreen == nil {
|
|
c.offscreen = newImage(sw, sh, FilterDefault, true)
|
|
}
|
|
|
|
// The window size is automatically adjusted when Run is used.
|
|
if _, ok := c.game.(*defaultGame); ok {
|
|
c.SetScreenSize(sw, sh)
|
|
}
|
|
|
|
// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
|
|
d := uiDriver().DeviceScaleFactor()
|
|
c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))
|
|
|
|
// Do not have to update scaleForWindow since this is used only for backward compatibility.
|
|
// Then, if a window is resizable, scaleForWindow (= ebiten.ScreenScale) might not match with the actual
|
|
// scale. This is fine since ebiten.ScreenScale will be deprecated.
|
|
}
|
|
|
|
func (c *uiContext) screenScale() float64 {
|
|
if c.offscreen == nil {
|
|
return 0
|
|
}
|
|
sw, sh := c.offscreen.Size()
|
|
d := uiDriver().DeviceScaleFactor()
|
|
scaleX := c.outsideWidth / float64(sw) * d
|
|
scaleY := c.outsideHeight / float64(sh) * d
|
|
return math.Min(scaleX, scaleY)
|
|
}
|
|
|
|
func (c *uiContext) offsets() (float64, float64) {
|
|
if c.offscreen == nil {
|
|
return 0, 0
|
|
}
|
|
sw, sh := c.offscreen.Size()
|
|
d := uiDriver().DeviceScaleFactor()
|
|
s := c.screenScale()
|
|
width := float64(sw) * s
|
|
height := float64(sh) * s
|
|
return (c.outsideWidth*d - width) / 2, (c.outsideHeight*d - height) / 2
|
|
}
|
|
|
|
func (c *uiContext) Update(afterFrameUpdate func()) error {
|
|
updateCount := clock.Update(MaxTPS())
|
|
|
|
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
|
|
|
if err := buffered.BeginFrame(); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < updateCount; i++ {
|
|
c.updateOffscreen()
|
|
|
|
// Mipmap images should be disposed by Clear.
|
|
c.offscreen.Clear()
|
|
|
|
setDrawingSkipped(i < updateCount-1)
|
|
|
|
if err := hooks.RunBeforeUpdateHooks(); err != nil {
|
|
return err
|
|
}
|
|
if err := c.game.Update(c.offscreen); err != nil {
|
|
return err
|
|
}
|
|
uiDriver().Input().ResetForFrame()
|
|
afterFrameUpdate()
|
|
}
|
|
|
|
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
|
c.screen.Clear()
|
|
|
|
op := &DrawImageOptions{}
|
|
|
|
s := c.screenScale()
|
|
switch vd := graphicsDriver().VDirection(); vd {
|
|
case driver.VDownward:
|
|
// c.screen is special: its Y axis is down to up,
|
|
// and the origin point is lower left.
|
|
op.GeoM.Scale(s, -s)
|
|
_, h := c.offscreen.Size()
|
|
op.GeoM.Translate(0, float64(h)*s)
|
|
case driver.VUpward:
|
|
op.GeoM.Scale(s, s)
|
|
default:
|
|
panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
|
|
}
|
|
|
|
op.GeoM.Translate(c.offsets())
|
|
op.CompositeMode = CompositeModeCopy
|
|
|
|
// filterScreen works with >=1 scale, but does not well with <1 scale.
|
|
// Use regular FilterLinear instead so far (#669).
|
|
if s >= 1 {
|
|
op.Filter = filterScreen
|
|
} else {
|
|
op.Filter = FilterLinear
|
|
}
|
|
_ = c.screen.DrawImage(c.offscreen, op)
|
|
|
|
if err := buffered.EndFrame(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *uiContext) AdjustPosition(x, y float64) (float64, float64) {
|
|
d := uiDriver().DeviceScaleFactor()
|
|
ox, oy := c.offsets()
|
|
s := c.screenScale()
|
|
return (x*d - ox) / s, (y*d - oy) / s
|
|
}
|